JNI, Java Native Interface, é uma suíte de programação que permite código em Java rodando sob uma máquina virtual Java (JVM) chamar e ser chamada por aplicações nativas. Aplicações nativas são programas escritos para plataformas de hardware e sistema operacional específicos. É possível fazer chamadas para C, C++ e Assembly.

Duke tocando guitarra
JNI – Usando coisas que estão fora do Java

1. Porque?

  • Aplicações legadas que não podem ser reimplementadas
  • Bibliotecas nativas específicas que não possuem binding ou implementação em para Java
  • Trechos críticos da aplicação onde a performance do Java é inferior a uma implementação nativa.

2. Como Fazer?

Vamos fazer um olá mundo do JNI, uma aplicação Java vai chamar um programa em C que imprime “Ola Mundo”.

Essa vai ser a classe OlaMundo:

class OlaMundo {
	public native void diga ();

	static {
		System.loadLibrary("olanativo");
	}

	public static void main (String[] args) {
		OlaMundo ola = new OlaMundo ();
		ola.diga();
	}
}

Primeiro nós declaramos uma classe chamada OlaMundo. Essa classe possui um método chamado diga que recebe nenhum parâmetro e não retorna nada. Através da palavra reservada native nos declaramos que esse método é uma implementação nativa e portanto sua implementação não feita aqui.

Em seguida nos declaramos um bloco estático e chamamos o método loadLibrary do System. Nós pedimos para ele abrir a biblioteca olanativo (isso será explicado melhor mais adiante). É importante que o nome da biblioteca esteja todo em minúsculo.

No método main nós instanciamos o objeto OlaMundo e chamamos seu método diga.

Compilamos isso:

$ javac OlaMundo.java

Se você escreveu tudo certo a compilação não irá dar nenhum problema.

Agora vamos tentar rodar:

$ java OlaMundo
Exception in thread “main” java.lang.UnsatisfiedLinkError: no olanativo in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1682)
at java.lang.Runtime.loadLibrary0(Runtime.java:822)
at java.lang.System.loadLibrary(System.java:992)
at OlaMundo.(OlaMundo.java:5)

Não deu certo. Não foi possível linkar o programa com a biblioteca olanativo. Tudo bem, nós não escrevemos ela ainda. Isso não serviu pra nada, eu só mostrei isso para sublinhar que os nossos erros serão geralmente em tempo de execução e não em tempo de compilação.

Agora vamos gerar os cabeçalhos (headers) para fazermos nossa implementação nativa. A ferramenta javah faz isso pra nós, basta chamar javah seguido do nome da classe que vai chamar uma implementação nativa.

$ javah OlaMundo

Esse comando vai gerar esse OlaMundo.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class OlaMundo */

#ifndef _Included_OlaMundo
#define _Included_OlaMundo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     OlaMundo
 * Method:    diga
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_OlaMundo_diga
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Não é importante você entender esse código acima. Nós vamos incluir ele com um include na nossa implementação. Ele inclui o jni.h onde estão declarados toda a parte de conversão de tipo do Java/C e vai declarar o cabeçalho da função diga, que agora passa a se chamar Java_OlaMundo_diga.

Agora vamos, finalmente, a nossa implementação nativa.

#include 
#include "OlaMundo.h"

JNIEXPORT void JNICALL Java_OlaMundo_diga (JNIEnv * env, jobject jobj){
   printf("ola mundo\n");
}

Nós incluímos o OlaMundo.h e escrevemos a função Java_OlaMundo_diga, que simplesmente imprime um “ola mundo” e pula a linha.

Agora vamos compilar esse código, mas primeiro temos que descobrir onde está o jni.h. Ele vai estar dentro da pasta onde seu java está instalado e isso vai variar muito de distribuição para distribuição. Alguns comandos podem te dar pistas:

$ whereis java

Isso vai te dar vários lugares, veja se em algum desses lugares tem uma pasta chamada include e lá dentro o jni.h. No Ubuntu que eu estou usando o java está em /opt/jdk1.6.0/include/.

Nos vamos compilar nosso código em uma biblioteca. Essa bibliteca vai se chamar libolanativo.so. Esse nome não foi escolhido arbitrariamente, ele é formado pela string que nós citamos no primeiro programa, na linha 5, precedida por “lib” e sucedida por “.so”. Se estivermos usando o Windows, seria libolanativo.dll.

É necessária uma longa linha de comando para compilar isso com o gcc, vamos a ela:

$ gcc -o libolanativo.so -shared -Wl,-soname,libolanativo.so -I/opt/jdk1.6.0/include -I/opt/jdk1.6.0/include/linux OlaMundo.c -static -lc

Lembrando mais uma vez que você vai trocar os dois /opt/jdk1.6.0/include pelo diretório do Java onde está o jni.h.

Está quase tudo pronto, mas se você tentar executar o OlaMundo agora vai dar aquele primeiro erro. A linkagem com a libnativo.so não poderá ser feita porque o java não consegue encontrar ela. Vamos setar uma variável de ambiente que diz onde se deve procurar as bibliotecas para serem linkadas, vamos dizer . (ponto) que quer dizer o diretório corrente.

No terminal:

$ LD_LIBRARY_PATH=.
$ export LD_LIBRARY_PATH

Rufem os tambores, já está tudo pronto para executar a classe OlaMundo. No terminal:

$ java OlaMundo
ola mundo

3. Desvantagens de se usar JNI

  • Perda da portabilidade. Uma aplicação que usa uma implementação nativa só funcionará para os binários compilados disponíveis.
  • Complicação. Muitos conceitos estão envolvidos para fazer uma aplicação JNI funcionar. É necessário conhecimento em mais de uma linguagem.

4. Links Úteis: