JNI – Java Native Interface

Java Native Interface (JNI) é um framework de programação que permite o código executado pela Java Virtual Machine chamar e ser chamado por aplicações nativas (aplicações específicas para um hardware e sistema operativo) e por bibliotecas escritas em outras linguagens, tais como C, C++, Delphi e Assembly.

Como todos os frameworks JNI também tem as suas implicações, o uso do JNI coloca em risco dois benefícios da plataforma Java. Primeiro as aplicações Java que utilizam JNI ficam dependentes do ambiente de execução, contrário de Java puro que pode ser executado em múltiplos ambientes com JNI a parte nativa tem de ser recompilada em cada um dos ambientes de execução, perdendo a portabilidade do Java. Segundo a linguagem de programação Java é uma linguagem type-safe e segura, ao contrario das linguagens nativas como C ou C++ que não são seguras.

Por estas razões o uso de JNI deve ser feito de forma cuidada e apenas caso não seja possível implementar apenas com a linguagem Java.
Depois desta breve introdução vamos passar então à prática, vamos ver quais os passos necessários para a criação de um programa em Java com JNI.

Depois de vermos o percurso a percorrer vamos começar com a implementação da classe HelloWorld.java

1 public class HelloWorld{
2
3	private native void print();
4
5	public static void main(String args[]){
6      		new HelloWorld().print();
7	}
8
9	static{
10     		System.loadLibrary("HelloWorld");
11 	}
12 }

Na linha 3 foi declarado o método nativo print e nas linhas 9 e 10 é dito ao Java para carregar o biblioteca “HelloWorld”, que vamos criar depois de realizar mais alguns passos. Em seguida vamos compilar a classe criada : javac HelloWorld.java.
Depois de obtermos o ficheiro HelloWorld.class vamos usar a ferramenta javah do Java para criar o ficheiros header (HelloWorld.h), com o comando javah -jni HelloWorld vamos obter o ficheiro HelloWorld.h com o seguinte conteúdo.

1 /* DO NOT EDIT THIS FILE - it is machine generated */
2	#include <jni.h>
3	/* Header for class HelloWorld */
4
5	#ifndef _Included_HelloWorld
6	#define _Included_HelloWorld
7	#ifdef __cplusplus
8	extern "C" {
9	#endif
10	/*
11	* Class:     HelloWorld
12 	* Method:    print
13 	* Signature: ()V
14 	*/
15	JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject);
16
17	#ifdef __cplusplus
18	}
19	#endif
20	#endif

Como podemos ver na linha 15 é declarado o header para o método nativo que iremos implementar, vamos usar essa header para criar o ficheiro de código nativo (HelloWorld.c), como podemos ver em seguida.

Na linha 3 foi declarado o método nativo print e nas linhas 9 e 10 é dito ao Java para carregar o biblioteca “HelloWorld”, que vamos criar depois de realizar mais alguns passos. Em seguida vamos compilar a classe criada : javac HelloWorld.java.
Depois de obtermos o ficheiro HelloWorld.class vamos usar a ferramenta javah do Java para criar o ficheiros header (HelloWorld.h), com o comando javah -jni HelloWorld vamos obter o ficheiro HelloWorld.h com o seguinte conteúdo.

1 /* DO NOT EDIT THIS FILE - it is machine generated */
2	#include <jni.h>
3	/* Header for class HelloWorld */
4
5	#ifndef _Included_HelloWorld
6	#define _Included_HelloWorld
7	#ifdef __cplusplus
8	extern "C" {
9	#endif
10	/*
11	* Class:     HelloWorld
12 	* Method:    print
13 	* Signature: ()V
14 	*/
15	JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject);
16
17	#ifdef __cplusplus
18	}
19	#endif
20	#endif

Como podemos ver na linha 15 é declarado o header para o método nativo que iremos implementar, vamos usar essa header para criar o ficheiro de código nativo (HelloWorld.c), como podemos ver em seguida.

1 include <jni.h>
2	#include <stdio.h>
3	#include "HelloWorld.h"
4
5	JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj){
6        		printf("Hello World!\n");
7        		return;
8	}

Se observar-mos vamos ver que a linha 15 do ficheiro HelloWorld.h é praticamente igual ao código na linha 5 do ficheiro HelloWorld.c.
Agora que já dispomos de todos os ficheiros necessários “HelloWorld.java”, “HelloWorld.class”, “HelloWorld.h” e “HelloWorld.c” vamos criar a nossa biblioteca nativa. Vamos ver como o fazer em diferentes sistemas operativos.

Linux com GCC

gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libHelloNative.so HelloNative.c

Solaris com Sun Compiler

cc -G -I jdk/include -I jdk/include/solaris -o libHelloNative.so HelloNative.c

Windows com MS C++ Compiler

cl -I jdk\include -I jdk\include\win32 -LD HelloNative.c -FeHelloNative.dll

Onde jdk é o caminho para a pasta de instalação do Java.

Vamos agora ver como realizar a passagem de dados entre Java e C através do JNI. Para isso criamos um pequeno programa em que vamos imprimir uma pergunta, usar a função scanf do C para recolher o input e retorná-lo para o Java como um Objecto String. Para isso vamos começar claro pela criação do ficheiro Prompt.java.

1 public class Prompt{
2	private native String getLine(String prompt);
3
4	public static void main(String args[]){
5		Prompt p = new Prompt();
6		String input = p.getLine("Qual o seu nome ? ");
7		System.out.println("Escreveu : "+input);
8	}
9
10	static{
11		System.loadLibrary("Prompt");
12	}
13 }

Em seguida vamos gerar o ficheiro Prompt.h que irá conter algo semelhante a este:

1 /* DO NOT EDIT THIS FILE - it is machine generated */
2	#include <jni.h>
3	/* Header for class Prompt */
4
5	#ifndef _Included_Prompt
6	#define _Included_Prompt
7	#ifdef __cplusplus
8	extern "C" {
9	#endif
10	/*
11	* Class:     Prompt
12	* Method:    getLine
13	* Signature: (Ljava/lang/String;)Ljava/lang/String;
14	*/
15	JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *, jobject, jstring);
16
17	#ifdef __cplusplus
18	}
19	#endif
20	#endif

Até aqui não há nada de novo relativamente ao HelloWorld implementado anteriormente, as novidades então agora na implementação do ficheiro Prompt.c que vai conter algumas novidade ao nível de JNI e no tratamento de Strings em JNI-C. Vamos então implementar o seguinte código.

1 #include <stdio.h>
2 #include <jni.h>
3 #include "Prompt.h"
4
5 JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt){
6
7	char buf[128];
8	const jbyte *str;
9
10	str = (*env)->GetStringUTFChars(env,prompt,NULL);
11
12	if(str == NULL){
13		return NULL;
14	}
15
16	printf("%s",str);
17	(*env)->ReleaseStringUTFChars(env,prompt,str);
18	scanf("%s",buf);
19	return (*env)->NewStringUTF(env,buf);
20 }

Como podemos ver na linha 5 nos argumentos do método para além das variáveis de ambiente que por defeito são colocadas, neste caso foi adicionada mais uma a variável prompt do tipo jstring que corresponde à String dada como argumento da classe Java. Como o C não tem o Objecto String como o Java vamos ter de passar a String para outra forma trabalhável em JNI-C, para isso é criada uma variável apontador do tipo jbyte, que de uma forma básica é o byte do JNI da mesma forma que o jstring é a String do JNI. O passo final para fazer a conversão é usar a função do JNI GetStringUTFChar que retorna uma cópia da String dada no segundo argumento (linha 10) e assim a variável str vai ficar com o valor da String dado como argumento ao método getLine.
Depois deste procedimento basta utilizar a função printf do C para imprimir a String e para terminar o processamento da String vamos usar a função ReleaseStringUTFChars (linha 17) que vai libertar a memória usada para guardar a String, atenção que este método apenas deve ser utilizado depois de terem terminado todas as operações na String.
Vamos agora para finalizar o nosso programa capturar o input do utilizador através da função scanf (linha 18) e guardar o input sob a forma de String no array de char definido na linha 7, em seguida basta converter o array de char para um Objecto String e retorná-lo para o Java, para isso vamos usar a função NewStringUTF como podemos ver na linha 19. Ao executar este exemplo o que vai acontecer será um intercâmbio de dados bastante simples, o Java envia uma String que é impressa pelo C, em seguida a função scanf vai capturar o input do utilizador e irá retornar esse input para o Java que o irá imprimir com o método println().

Passamos agora ao tratamento de arrays de Java para C, para isso vamos implementar o seguinte código.

1 public class IntArray{
2
3	private native int sumArray(int [] arr);
4
5	public static void main(String args []){
6		IntArray p = new IntArray();
7		int arr[] = new int[10];
8		for(int i = 0; i < 10; i++){
9			arr[i] = i;
10		}
11		int sum = p.sumArray(arr);
12		System.out.println("sum = "+ sum);
13	}
14
15	static{
16		System.loadLibrary("IntArray");
17	}
18 }

Como podemos ver o código implementado vai criar um array com 10 posições e vai preenche las com os números de 0 a 9, em seguida vai executar o método nativo sumArray que vai somar todos os valores presentes no array e vai retornar o total da soma. Vamos agora passar à implementação do método nativo e para isso vamos começar como sempre por gerar o ficheiro IntArray.h.

1 /* DO NOT EDIT THIS FILE - it is machine generated */
2	#include <jni.h>
3	/* Header for class IntArray */
4
5	#ifndef _Included_IntArray
6	#define _Included_IntArray
7	#ifdef __cplusplus
8	extern "C" {
9	#endif
10	/*
11	* Class:     IntArray
12 	* Method:    sumArray
13 	* Signature: ([I)I
14 	*/
15	JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *, jobject, jintArray);
16
17	#ifdef __cplusplus
18	}
19	#endif
20	#endif

Ao vermos os argumentos do método podemos verificar que está agora presente um jintArray que corresponde ao array de inteiros do Java que é dado como argumento na chamada do método. Agora o código nativo do nosso método.

1 #include <jni.h>
2 #include <stdio.h>
3 #include "IntArray.h"
4
5 JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr){
6
7	jint *carr;
8	jint i = 0;
9	jint sum = 0;
10     carr = (*env)->GetIntArrayElements(env,arr,NULL);
11		if(carr == NULL){
12		return 0;
13	}
14
15     for(i = 0; i < 10; i++){
16		sum += carr[i];
17	}
18
19     (*env)->ReleaseIntArrayElements(env,arr,carr,0);
20
21     return sum;
22 }

Analisando o código podemos ver que na linha 7 é criado um apontador do tipo jint que vai ser usado para guardar o array dado como argumento ao método, para isso usamos a função Get<Tipo>ArrayElements em que o <Tipo> é substituto neste caso por Int, esta função vai retornar um apontador para o array dado como argumento como se pode ver na linha 10.
Nas linhas 15 a 17 é executado um ciclo for(), que faz a soma de todo o array e guarda o valor na variável sum previamente criada. Depois disto basta usar a função Release<Tipo>ArrayElements para libertar o apontador que contém o array (linha 19) e por fim na linha 21 vamos retornar a soma.
O JNI tem entre outras uma função bastante importante para o tratamento de arrays, trata-se da função GetArrayLength que como o próprio nome indica retorna o tamanho de um array, esta função pode ser usada da seguinte forma :

jsize len = (*env)->GetArrayLength(env, arr);

E com isto encerramos este artigo sobre Java Native Interface, como podem ver é framework muito poderoso mas mas que deve ser apenas usado como recurso há falta de algum recurso por parte do Java ou no caso de criação de novas bibliotecas. Em todo o caso deve ser utilizado com bastante cuidado visto tratar-se de programação não segura.

Este artigo foi originalmente escrito por Fábio Correira (magician) para a 8ª Edição da Revista PROGRAMAR

Anúncios

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google photo

Está a comentar usando a sua conta Google Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s