domingo, 17 de fevereiro de 2008

Métodos Nativos em Java

UTILIZAÇÃO DE MÉTODOS NATIVOS 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.


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.