Interface Nativa Java
Muito se fala do Java, principalmente do fato dele ser multiplataforma, talvez essa característica em especial, de se compilar uma vez e poder rodar em diversas plataformas, tornou o Java a linguagem e plataforma mais popular no mundo. Muito se tem explorado já que toda a complexidade é feita pela JVM, mas surge a seguinte dúvida: Como a JVM faz isso? Como ela conseguir abstrair as outras plataformas para min? Obter esse conhecimento é muito importante uma vez que pode ser necessário utilizar um recurso específico da máquina e fazer com esse recurso converse diretamente com a JVM.
A comunicação entre a JVM e o código nativo quase em sua grande maioria é feito na linguagem C/C++
uma vez que elas possuem o padrão ANSI e que o mesmo é compatível com a grande maioria das plataformas, assim é possível um grande aproveitamento de código, mas em alguns casos é necessário que seja feita uma implementação específica para cada plataforma ou que existe compatibilidade com um sistema legado que foi feito em C
, por exemplo. A porta entre o código nativo e o Java é conhecido como JNI (Java Native Interface). Esse recurso é muito interessante também para fazer a ponte para plataformas em que o Java ainda não atingiu. Dentro da JVM esse recurso é usado para alguns na plataforma JSE, por exemplo, Java I/O, alguns métodos da classe java.lang.System
, JavaSound, a comunicação entre a classe, o arquivo .class
, e a sua representação dentro da JVM, a implementação da interface java.lang.Class
, as implementações do Garbage Colector dentre outros recursos. Com o JNI é possível chamar método do objeto, instanciar classes, verificar estado do objeto criado dentro da JVM, dentre outros recursos.
No entanto, usar o requer algumas responsabilidades, vale lembrar que usar o JNI perde a portabilidade, um erro nativo não é controlado pela JVM (Vale lembrar na parte em que se falou de registrados, a pilha nativa e o PC quando aponta para um processo nativo, não se sabe o seu valor preciso), não é possível debugar o código nativo através da plataforma Java, caso acontece um erro nativo pode quebrar a execução da JVM, ele não prover um Garbage Collector automático ou qualquer gerenciamento por parte da JVM. Assim é muito importante saber o momento em que se usará o JNI.
Os objetos em Java podem ser mapeados para objetos nativos e virse-versa, para garantir a comunicação de duas mãos, assim é possível estar passando um objeto Java para o lado nativo ver o seu valor.
Tipo em Java | Tipo Nativo |
---|---|
boolean |
jboolean |
byte |
jbyte |
char |
jchar |
double |
jdouble |
float |
jfloat |
int |
jint |
long |
jlong |
short |
jshort |
void |
void |
Com o objetivo de dar um pequeno exemplo com o JNI será mostrado dois simples exemplos, o primeiro será o “olá mundo” com o JNI e o segundo será um método estático que calcula o dobro do resultado passado, para isso é necessário que se tenha instalado o GCC e o JDK. O código será bem simples, no primeiro caso será enviado o nome por parâmetro e essa String será passada para o valor nativo, uma vez no nativo será concatenado o “Hello world” com o nome digitado, no segundo exemplo, o segundo parâmetro seria calculado o seu dobro.
Primeiramente será criado a classe HelloWorld.java.
public class HelloWorld {
private native void chamarMensagem(String nome);
public native static int dobrar(int valor);
public static void main(String[] args) {
String nome=args[0]==null?"nome":args[0];
int valor=args[1]==null?2:Integer.valueOf(args[1]);
HelloWorld helloWorld=new HelloWorld();
helloWorld.chamarMensagem(nome);
int resultado=HelloWorld.dobrar(valor);
System.out.println("O dobro de "+valor+" é: "+ resultado);
}
static {System.loadLibrary("HelloWorld");}
}
Em seguida compilamos com o seguinte comando:
javac HelloWorld.java
Uma vez o arquivo compilado, será necessário gerar a interface JNI como seguinte comando:
javah -jni HelloWorld
Com o comando será gerado um arquivo HelloWorld.h
.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: chamarMensagem
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem
(JNIEnv *, jobject, jstring);
/*
* Class: HelloWorld
* Method: dobro
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_HelloWorld_dobrar
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Repare que na interface o método possui o seguinte formato:
Java_NomeClasse_nomeMetodo. Em relação aos parâmetros o primeiro elemento, o JNIEnv, ele é um ponteiro que aponta para um vetor no qual possui todas as funções do JNI, o segundo depende se é método da classe ou da instância. Caso seja estático, ou seja o método possua a palavra-chave static
, o próximo parâmetro será o jclass que conterá as informações da classe, caso seja da instância o próximo parâmetro será o jobject que conterá as informações da instância.
O próximo passo é a criação do arquivo que “implemente” a interface do HelloWorld.h
, assim será criado o HelloWorld.c que implemente tal interface.
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_chamarMensagem(JNIEnv * env, jobject obj,jstring nome){
const char * nomeNativo=(*env)->GetStringUTFChars(env, nome, NULL);
printf("Hello World!!!! %s\n", nomeNativo);
return;
}
JNIEXPORT jint JNICALL Java_HelloWorld_dobrar(JNIEnv * env, jclass classe, jint valor){
return 2*valor;
}
Com o arquivo criado o próximo passo é a compilação, levando em consideração as devidas importações, como se trata de libs nativas as pastas variam de acordo com a plataforma. No caso do linux para compilar será necessário o seguinte comando:
gcc -o libHelloWorld.so -shared -I$JAVA_HOME/include -I$JAVA_HOME/linux HelloWorld.c
Uma vez compilado o código-fonte e o transformado em uma lib, no caso do linux o arquivo com extensão .so
de Shared Object. O próximo passo será “linkar” o arquivo nativo com o projeto, o primeiro passo é carregar a biblioteca dentro do código java (para isso será utilizado o comando System.loadLibrary("NomedaLib");
).
O próximo passo é colocar a lib nativa no classpath no sistema operacional ou definir o seu caminho pelo parâmetro java.library.path ao executar o projeto java. Nesse exemplo será utilizado a segunda opção juntamente o parâmetro que será o nome da que será impresso no console, assim o comando ficará:
java -Djava.library.path=. HelloWorld Otávio 4
A saída será:
Hello World!!!! Otávio
- `O dobro de 5 é: 10
Com isso se apresentou o recurso do JNI, a interface que se comunica o JVM para linguagens nativa como C
e C++
e da sua importância para a JVM como a implementação do Garbage Collector, sua existência em algumas APIs como o JavaSound além de se integrar com código legado e com plataformas cujo a JVM até o momento não atingiu. No entanto, vale salientar que se perde o fator multiplataforma e não será mais gerenciado pela JVM usando este recurso. Aprender sobre JNI é muito importante para compreender o código da máquina virtual Java, mas é necessário um conhecimento na linguagem C
e C++
.`