UTILIZAÇÃO DE MÉTODOS NATIVOS EM JAVA
Entenda como escrever métodos nativos em C e utiliza-los em Java
Entenda como escrever métodos nativos em C e utiliza-los em Java
Por Ricardo Lima Caratti
Introdução
Durante o desenvolvimento de uma aplicação em Java, é possível que se tenha a necessidade de executar um código escrito em outra linguagem. Isso pode ocorrer por vários motivos, dentre eles destacam-se: aproveitamento de código previamente escrito, utilização de funcionalidades do sistema operacional, acesso ao hardware e até mesmo melhoria de performance. Java possui várias formas de fazer isso. Por exemplo, execução dos métodos exec() da classe Java.lang.Runtime, utilização de CORBA ou RMI. O objetivo deste artigo é mostrar uma outra forma, além das descritas acima, para fazer comunicação de uma aplicação Java com código escrito em linguagem C.
A linguagem Java possui a palavra chave native, que é um modificador utilizado somente para métodos. O modificador native indica que o corpo do método encontra-se fora do Java Virtual Machine (JVM). Um código nativo é escrito, geralmente, em C ou C++ e compilado para um tipo de hardware específico. O exemplo a seguir mostra como utilizar código nativo em Java.
class ChamadaCodigoNativo {
static {
System.loadLibrary("CodigoNativo");
}
public native int Soma(int a, int b);
}
Arquivo 1: ChamadaCodigoNativo.java
class Exemplo1 {
public static void main(String[] args) {
int a, b, c;
ChamadaCodigoNativo nativo =
new ChamadaCodigoNativo();
a = 10;
b = 11;
c = nativo.Soma(a,b);
System.out.println("Resultado: "+c);
}
}
Arquivo 2: Exemplo1.java
Note na implementação da classe ChamadaCodigoNativo, o trecho do código estático. Ele será executado uma única vez no momento em que a classe for instanciada.
A chamada System.loadLibrary(“CodigoNativo”) tenta carregar a biblioteca de ligação dinâmica CodigoNativo.dll no caso do ambiente Windows ou CodigoNativo.so no caso do ambiente UNIX. Para tanto, é claro, ele deve estar implementada.
A chamada System.loadLibrary(“CodigoNativo”) tenta carregar a biblioteca de ligação dinâmica CodigoNativo.dll no caso do ambiente Windows ou CodigoNativo.so no caso do ambiente UNIX. Para tanto, é claro, ele deve estar implementada.
Note também que a chamada do método Soma não difere de um método normal em Java.
Vantagens e desvantagens da utilização de código nativo.
Vantagens
Aproveitamento de código legado: Talvez seja a principal razão para utilização de código nativo. Em muitas situações é melhor utilizar um código já testado e funcionando que recodifica-lo para Java.
Eficiência: Embora a tecnologia utilizada nos compiladores Java esteja, a cada dia, melhorando em favor da eficiência, códigos nativos, em geral, executam mais rápidos que códigos Java.
Desvantagens:
Programa dependente de plataforma: Essa é a principal desvantagem em usar código nativo, pois, sabe-se que uma das principais características do Java é a portabilidade. Nesse caso, essa é a maior razão para evitar código nativo.
Problema de robustez: O JVM não protege a aplicação de código nativo mal escrito, isto é, uso inadequado de ponteiros, operações de I/O etc.
Coletor de lixo (garbage collection): O uso de alocação dinâmica de memória no método nativo, não trabalha em conjunto com o garbage collection do JVM. Exemplo: utilizar a função malloc() do C no método nativo.
O Java Native Interface (JNI)
Considerando uma real necessidade de utilização de código nativo, o Java disponibiliza o JNI como parte do JDK. O JNI possibilita uma interface segura entre o Java e outra linguagem. A figura 1 mostra como isso é feito.
Figura 1 - Utilização do JNI como interface do Java com código nativo
Por meio do JNI, é possível utilizar métodos nativos para:
*
Ter acesso a variáveis membros de classes em Java;
*
Criar e manipular objetos em Java. Isso inclui arrays e strings;
*
Manipular exceções;
*
Executar cheque de tipo em tempo de execução;
*
Utilizar threads.
Tratamento de tipos de dados primitivos entre Java e métodos nativos
Como foi mostrado na figura 1, o JNI é o elo entre o programa Java e os métodos nativos. Para tanto, ele precisa mapear os tipos de dados Java para os tipos de dados nativos. A tabela 1 mostra o mapeamento dos tipos primitivos e os tipos nativos equivalentes.
Tipo em Java
Tipo em C
Tamanho em bits
Void
void
-
Byte
jbyte
8
Char
jchar
16
short
jshort
16
Int
jint
32
Long
jlong
64
Float
jfloat
32
double
jdouble
64
Tabela 1 – Integração de tipos primitivos Java para tipos nativos equivalentes.
Os tipos: jbyte, jchar, jshort, jint, jlong, jfloat, jdouble e outros, estão definidos em jni.h.
Para manipulação de array o JNI usa jarray para representar uma referência a arrays em Java. Seguindo a mesma idéia, existe o jstring para manipulação de string. A manipulação de array e strings em métodos nativos requer procedimentos extras e não serão tratados aqui. Para mais informações consulte as referências indicadas neste artigo.
Os seis passos necessários
Passo 1: Implementar o código em Java;
Considere as classes ChamadaCodigoNativo.java e Exemplo1.java implementadas no início deste artigo.
Passo 2: Compilar código Java
Compilado as classes
javac ChamadaCodigoNativo.java
javac Exemplo1.java
Note que até o momento não foi feito nada além do de costume.
Passo 3: Utilizar o utilitário javah
javah –jni ChamadaCodigoNativo
O comando acima criará um arquivo chamado ChamadaCodigoNativo.h. Esse arquivo será utilizado mais adiante na implementação do código em C. A listagem a seguir mostra o conteúdo desse arquivo.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class ChamadaCodigoNativo */
#ifndef _Included_ChamadaCodigoNativo
#define _Included_ChamadaCodigoNativo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ChamadaCodigoNativo
* Method: Soma
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_ChamadaCodigoNativo_Soma
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
Arquivo 3: ChamadaCodigoNativo.h
Note o cometário da primeira linha indicado que o arquivo ChamadaCodigoNativo.h não deve ser alterado.
Note também a denominação data para o método Soma no código nativo. Isto é, a composição do nome da função é dada pelo literal Java, o nome do pacote (se for o caso), o nome da classe e o nome do método. Portanto, a função Soma em C será implementada com o nome Java_ChamadaCodigoNativo_Soma. Os dois primeiros argumento, JNIEnv * e jobject, devem sempre ser declarados, mesmo que não sejam utilizados. Veja o passo a seguir para ver como isso funciona.
Passo 4: Implementar o método nativo
Para implementar o método Soma em C, deve-se usar a mesma nomenclatura prototipada no arquivo ChamadaCodigoNativo.h. A listagem a seguir mostra o código em C (arquivo CodigoNativo.c).
#include
#include "ChamadaCodigoNativo.h"
JNIEXPORT jint JNICALL Java_ChamadaCodigoNativo_Soma(JNIEnv *env, jobject this, jint a, jint b)
{
return a + b;
}
Arquivo 4: CodigoNativo.c
Passo 5: Criando uma biblioteca de ligação dinâmica
Na codificação da classe ChamaCodigoNativo, foi utilizado bloco estático como mostrado abaixo.
static {
System.loadLibrary("CodigoNativo");
}
Como dito anteriormente, este código será executado no momento em que a classe for instanciada. A chamada a System.loadLibrary() é responsável pela carga da biblioteca de ligação dinâmica ou biblioteca compartilhada (shared library).
Para construir uma biblioteca de ligação dinâmica você deverá instruir o compilador C.
O exemplo a seguir mostra a criação de uma biblioteca de ligação dinâmica (DLL) no ambiente Microsoft Windows utilizando o código em C implementado no Passo 4. O compilador utilizado é Microsoft Visual C++ 6.0.
cl -Ic:\java\include -Ic:\java\include\win32
-LD CodigoNativo.c -FeCodigoNativo.dll
Observações sobre as chaves de compilação:
-Ic indica o caminho onde serão encontrados os arquivos de cabeçalho do JNI;
-LD indica que o código gerado será uma (DLL);
-Fe indica o nome do arquivo a ser gerado.
Exemplo utilizando o Sistema Operacional UNIX Solaris.
cc -G -I/usr/local/java/include -I/usr/local/java/include/solaris \
CodigoNativo.c -o CodigoNativo.so
Exemplo utilizando o compilador C da GNU no ambiente Linux.
gcc –fPIC –c CodigoNativo.c -I/usr/local/java/include -I/usr/local/java/include/solaris
gcc –shared –Wl, -soname, libCodigoNativo.so.1 –o libCodigoNativo.so.1.0 CodigoNativo.o
Para os dois últimos exemplos, é assumido que o java está instalado no caminho /usr/local/java.
Para mais detalhes sobre a forma de compilar e criar bibliotecas compartilhadas consulte o manual do compilador disponível em cada plataforma.
Passo 6: Execução do Programa
A execução do programa é feita normalmente, ou seja, como um programa Java comum.
java Exemplo1
A saída do programa será:
Resultado: 21
Resumo
É possível fazer aplicações Java se comunicar com código escrito em outra linguagem. As principais técnicas são:
*
Interprocess communication (IPC);
*
Objetos distribuídos (CORBA);
*
Java Native Interface (JNI).
Você deve usar métodos nativos em Java quando:
*
Você já possui uma biblioteca escrita em uma outra linguagem (código legado) e deseja utiliza-la com suas aplicações em Java;
*
Você deseja utilizar uma funcionalidade dependente de plataforma que a biblioteca padrão do Java não possui;
*
Você precisa implementar uma funcionalidade onde o tempo é um fator crítico.
As principais implicações do uso de métodos nativos são:
*
Problemas quanto a portabilidade;
*
Problema em toda aplicação no caso de código nativo mal escrito.
Referências
Wall, Watson, and Whitis. Linux Programminig, Sams, 1999.
Avenue, Garcia, Java Native Interface Specification Release 1.1, Java Software, MountainView, CA, USA, 1997
Caratti, Ricardo Lima, Revista do Linux, Ano I, Nº 5, Maio 2000, Artigo: Bibliotecas Compartilhadas.
Darwin, Ian, Java Cookbook, O’Reilly, 2001
http://java.sun.com/docs/books/tutorial/native1.1/
http://java.sun.com/docs/books/jni/html/intro.html#1811
MSDN Library Visual Studio6.0, Compiler Reference.