Minha primeira tentativa de artigo técnico
Há um bom tempo atrás comecei a escrever um post sobre JNI que acabou virando um esboço de um artigo que não saiu do 'papel', deixei ele perdido no meu computador e só agora quase 4 meses depois resolvi colocá-lo aqui mesmo no blog, o artigo é enorme então vou postar um pedacinho a cada dia, assim tenho tempo para revisá-lo e de bem, se alguem mais ler posso saber se consegui me fazer entender.
JNI - Rompendo as barreiras do acesso às aplicações nativas I
Há pouco tempo precisei desenvolver uma aplicação Java integrada com funções nativas e me vi perdida em meio de uma série de complicações na hora de utilizar JNI. Infelizmente, na época, não encontrei nenhuma documentação que se aprofundasse no assunto. Principalmente quando este era envio/manipulação de estrutura/objetos, conversei com outros desenvolvedores na empresa em que trabalhava e todos me aconselharam a fazer algum ‘ajuste técnico’ no lugar de trabalhar com o envio de objetos. Apesar de esta sugestão ser a saída mais fácil, não me pareceu nada elegante retornar um string concatenando todos os dados da estrutura, ou criar métodos auxiliares que me retornariam cada atributo da estrutura em C separadamente.
Depois de quebrar a cabeça para entender melhor o funcionamento do JNI finalmente consegui trabalhar com objetos Java dentro do C, e é isso que vou tentar explicar abaixo. Mas antes foi explicar um pouco sobre o que é e como funciona o JNI sem no entendo me estender muito, pois estas explicações se acham facilmente digitando JNI no Google, vou tentar explicar como enviar e trabalhar com os objetos Java em C.
Introdução
Em Java para nos comunicarmos com bibliotecas nativas utilizamos o JNI uma interface de comunicação desenvolvida pela Sun que permite de forma bidirecional que os códigos executados pela Java Virtual Machine (JVM) acessem aplicações e bibliotecas desenvolvidas em outras linguagens, como C, C++ e Delphi como se fossem métodos Java, facilitando, por exemplo, a integração de sistemas legados.
Porem existe algumas premissas para a utilização de JNI são elas:
1. Implementação de funcionalidades que as API's Java não fornecem
2. Problemas de desempenho.
ex.
Um algoritmo que em Java fique muito lento pode ser implementado em C ou Assembly e acessado pelo Java.
3. Ou ainda, quando o código nativo deseja ter acesso à API Java.
O desenvolvimento de um programa Java que chama funções nativas pode ser dividido em seis passos:
1. O primeiro passo é escrever o código Java que execute três tarefas;
Declaração do método nativo que estaremos chamando;
Carga da biblioteca compartilhada que contém o código nativo;
E que execute uma chamada ao método nativo.
Como exemplo, criaremos uma aplicação Java que execute um método nativo que imprima a mensagem “Olá Mundo!”.
Listagem 1 : OlaMundo.java
Como podem notar na listagem 1, os métodos nativos carregam o modificador native, e sua função nativa correspondente será implementada na forma de uma biblioteca dinâmica conforme veremos em breve.
Notamos ainda a existência do método:
void <system>.loadLibrary(String libraryName)
Observe que ele recebe como parâmetro o nome da biblioteca dinâmica que iremos utilizar. A biblioteca deve ser carregada antes da chamada de qualquer método nativo.
2. Compilar o código Java;
javac OlaMundo.java
3. Criar o arquivo de header C/C++. O arquivo de header C/C++ declarará a assinatura da função nativa que queremos chamar. Este header será usado durante a implementação da função C/C++ (veja Passo 4) onde criaremos a nossa biblioteca dinâmica.
javah OlaMundo
Lembre-se que OlaMundo é o nome da sua classe Java já compilada a partir do qual geraremos o arquivo OlaMundo.h, como podem ver na listagem 2.
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class OlaMundo */
-
- #ifndef _Included_OlaMundo
- #define _Included_OlaMundo
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: OlaMundo
- * Method: saudacoes
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_OlaMundo_saudacoes
- (JNIEnv *, jobject);
-
- #ifdef __cplusplus
- }
- #endif
- #endif
-
Listagem 2: OlaMundo.h
4. Escrever o código nativo. Este passo consiste em implementar a função desejada em C/C++, notem que no código da listagem 3 incluímos o arquivo de header OlaMundo.h criado no passo anterior.
- /*OlaMundo.c*/
-
- #include <jni.h>
- #include
- <olaMundo.h>
-
- JNIEXPORT void JNICALL Java_OlaMundo_saudacoes
- (JNIEnv *, jobject){
- return;
- }
-
Listagem 3 : OlaMundo.c
5. Criar o arquivo de biblioteca dinâmica.
Agora que todo o código necessário foi escrito, podemos compilar a biblioteca nativa. Ela será invocada dinamicamente quando a classe OlaMundo tiver o método saudacoes() invocado. Para efetuar a compilação devemos considerar o sistema operacional e o compilador utililizado.
Se estivermos utilizando o Windows e o Microsoft Visual Studio, utilizaremos o comando abaixo:
cl -Ic:\jdk1.5.0_07\include -Ic:\jdk1.5.0_07\include\win32 -MD -LD OlaMundo.c OlaMundo.dll
Já em Solaris poderíamos utilizar:
cc -G -I/java/include -I/java/include/solaris OlaMundo.c -o libOlaMundo.so
E em Linux o comando correspondente seria:
gcc -o libOlaMundo.so -shared -Wl,-soname, libOlaMundo.so -I/export/home/java/include -I/export/home/java/include/linux OlaMundo.c -static –lc
Concluído o passo 5 teremos uma aplicação JNI pronta esperando para ser executada.
6. Executar o programa.
Neste ponto, têm-se dois componentes prontos para serem executados. O arquivo “.class” (OlaMundo.class) chama o método nativo e a biblioteca nativa (olamundo.dll ou libOlaMundo.so) implementa o método nativo. Como a classe OlaMundo contém sua própria função main, ela pode ser executada da seguinte maneira:
> java OlaMundo
Durante a execução do programa podemos obter algumas mensagens de erro, sendo que as três mais comuns são:
1. Uma ligação dinâmica (dynamic link) não pode ser encontrada. Isto resulta na mensagem de erro: java.lang.UnsatisfiedLinkError. Normalmente, esta mensagem significa que ou o arquivo de biblioteca dinâmica, ou um método nativo específico dentro da biblioteca dinâmica não pode ser encontrado.
2. O arquivo de biblioteca dinâmica não pode ser encontrado. Isso acontece quando a JVM não consegue localizar o arquivo de biblioteca indicado no loadLibrary(), caso isto ocorra devemos verificar se o nome do arquivo esta escrito corretamente e que não se tenha especificado a extensão. Devemos, também, verificar se o arquivo de biblioteca encontra-se acessível à JVM. Caso desejemos utilizar outros caminhos, que não os padrões, existem duas técnicas. A primeira consiste em setar a variável de ambiente PATH, no Windows, ou LD_LIBRARY_PATH, para Linux e Solaris, com o caminho desejado. E a outra é setar este caminho através de um parâmetro, durante a chamada do programa java:
java -Djava.library.path=. OlaMundo
Ola mundo!
No caso o parâmetro –D informa à máquina virtual que existe uma variável de ambiente do Java chamada java.library.path e ela será analisada pela máquina virtual como outro caminho alternativo aonde ir buscar as bibliotecas dinâmicas e até mesmo outras classes utilizadas no projeto.
No exemplo acima estamos informando que o arquivo de biblioteca dinâmica encontra-se no diretório atual.
3. Um método com a assinatura especificada não pode ser encontrado. A implementação da função C/C++ deve possuir uma assinatura idêntica à assinatura da função no arquivo de header. Se quiser saber um pouco mais sobre assinatura/descritor de argumento é só ler o próximo capítulo e este não for o seu caso vá direto para o capitulo “Acessando os atributos de um objeto”.
Agora que já relembramos os passos para a criação de uma aplicação JNI, podemos nos aprofundar um pouco mais sobre seu funcionamento.
O próximo post será sobre assinatura ou descritores de argumento.
6 comments
6 Comments so far






Se eu entendesse o que está escrito, deixava um comentário.
Mas… deve ser bom. Ou estar bom. Ah, vc entendeu.
Boa sua análise.
O resto do artigo? jejeje
uma pergunta:
quando for desenvolver o codigo nativo tem que estar adequando a sintaxe?
ex.:
//código em C puro
#include
#include
void saudacoes{
printf(“Olá mundo!\n”);
return;
}
//Código adequado para que a biblioteca possa ser //chamada via JNI.
#include
#include
JNIEXPORT void JNICALL Java_OlaMundo_saudacoes
(JNIEnv *, jobject){
printf(“Olá Mundo!\n”);
return;
}
Ola Frank,
Desculpe, pela demora, eu ando um pouco afastada do meu blog ;/
Mas a estrutura é esta mesmo. No inicio a gente estranha… mas depois você vai ver que fica tudo tranquilo.
Olá..Estou incluido num projecto que necessito de efectuar chamadas a código nativo..Consegui seguir todo o seu artigo excepto na parte de criação da .dll! Eu estou em windows e utilizei MVS e ao correr o comando fornecido retorna-me este erro “too many input files”.Tem alguma idea do que posso estar a fazer de errado?
Cumprimentos.
Putz! Foi mal mas o e-mail avisando do post caiu no SPAM list do gmail e eu não tenho visitado nem escrito nada nos últimos tempos… foi mal…. e a esta hora você já resolveu o seu problema… mas se precisar de alguma coisa me manda um e-mail…
andreia [dot] bustamante [at] gmail [dot] com
Olá Andréia,
Estou desenvolvendo um acesso a hardware com JNI pelo Linux através de um applet java. O erro que me retorna é sempre que a biblioteca dinâmica não pode ser encontrada na java.library.path. Onde eu devo colocar minha biblioteca para que a JVM possa encontrá-la?
Obrigado por sua atenção.
Abraço