ir para a Parte2
Tempos atrás eu me ralei para integrar periféricos com minha aplicação Java. Tive que fazer integrações com emissor de cupons fiscais (ECF), com balança, com leitor ótico e por aí afora. É certo que a maioria deles fornecem bibliotecas de comunicação que facilitam a vida do desenvolvedor, mas como nem tudo é um mar-de-rosas me deparei com 2 problemas:
1) A maioria das empresas desenvolvem bibliotecas nativas para o sistema operacional Windows (DLLs) e simplesmente ignoram outros sistemas operacionais, matando assim o sonho da multiplataforma que o Java proporciona;
2) São poucos que desenvolvem bibliotecas nativas para 2 ou mais sistemas operacionais e muito menos os que disponibilizam algum JAR para facilitar nossa vida. Nesse caso a solução é utilizar o JNI (Java Native Interface) que por sinal é bem trabalhoso.
Infelizmente temos que conviver, AINDA, com o problema número 1. O problema 2, porém, foi amenizado pois hoje podemos contar com um projeto maduro e muito mais prático: o JNA (Java Native Access) cujo primeiro release é datado de 30 de novembro de 2006. Na data em que escrevi esse artigo, o JNA se encontra na versão 3.0
O objetivo desse artigo é mostrar um exemplo simples porém prático de como utilizar o JNA e integrá-lo com uma biblioteca nativa. Para tal vamos dividir este em duas partes, onde a primeira estaremos integrando com um Shared Object ou Shared Library (.so) que é uma biblioteca compartilhada de sistemas operacionais Linux similar as DLL's nos sistemas operacionais Windows. E a segunda parte estaremos integrando com uma DLL.
Como vamos criar nosso próprio programa em C e gerar uma biblioteca dinâmica, é necessário ter instalado em seu SO Linux um ambiente de desenvolvimento, com as bibliotecas padrão e o compilador gcc. No meu caso como eu uso o Ubuntu Linux bastou que eu instalasse o pacote build-essential(Também vou levar em consideração que você tem um ambiente Java corretamente configurado):
Tempos atrás eu me ralei para integrar periféricos com minha aplicação Java. Tive que fazer integrações com emissor de cupons fiscais (ECF), com balança, com leitor ótico e por aí afora. É certo que a maioria deles fornecem bibliotecas de comunicação que facilitam a vida do desenvolvedor, mas como nem tudo é um mar-de-rosas me deparei com 2 problemas:
1) A maioria das empresas desenvolvem bibliotecas nativas para o sistema operacional Windows (DLLs) e simplesmente ignoram outros sistemas operacionais, matando assim o sonho da multiplataforma que o Java proporciona;
2) São poucos que desenvolvem bibliotecas nativas para 2 ou mais sistemas operacionais e muito menos os que disponibilizam algum JAR para facilitar nossa vida. Nesse caso a solução é utilizar o JNI (Java Native Interface) que por sinal é bem trabalhoso.
Infelizmente temos que conviver, AINDA, com o problema número 1. O problema 2, porém, foi amenizado pois hoje podemos contar com um projeto maduro e muito mais prático: o JNA (Java Native Access) cujo primeiro release é datado de 30 de novembro de 2006. Na data em que escrevi esse artigo, o JNA se encontra na versão 3.0
O objetivo desse artigo é mostrar um exemplo simples porém prático de como utilizar o JNA e integrá-lo com uma biblioteca nativa. Para tal vamos dividir este em duas partes, onde a primeira estaremos integrando com um Shared Object ou Shared Library (.so) que é uma biblioteca compartilhada de sistemas operacionais Linux similar as DLL's nos sistemas operacionais Windows. E a segunda parte estaremos integrando com uma DLL.
Como vamos criar nosso próprio programa em C e gerar uma biblioteca dinâmica, é necessário ter instalado em seu SO Linux um ambiente de desenvolvimento, com as bibliotecas padrão e o compilador gcc. No meu caso como eu uso o Ubuntu Linux bastou que eu instalasse o pacote build-essential(Também vou levar em consideração que você tem um ambiente Java corretamente configurado):
sudo apt-get install build-essential
Precisamos também fazer o download do JNA na seção de downloads do site oficial ou diretamente aqui. Salve esse arquivo no mesmo diretório que você vai trabalhar ou se você conhece sobre CLASSPATH no Java pode salvar onde quiser de forma que deveremos utilizá-la mais tarde.
Feito isso podemos começar a codificar. Vamos criar um arquivo simples em C que realiza uma soma ou uma subtração de acordo com um parâmetro recebido. O parâmetro recebido é propositalmente do tipo char* para demonstrar a compatibilidade com o tipo String existente no Java, assim como os outros parâmetros. Segue portanto o código do arquivo calcjna.c :
Feito isso podemos começar a codificar. Vamos criar um arquivo simples em C que realiza uma soma ou uma subtração de acordo com um parâmetro recebido. O parâmetro recebido é propositalmente do tipo char* para demonstrar a compatibilidade com o tipo String existente no Java, assim como os outros parâmetros. Segue portanto o código do arquivo calcjna.c :
#include "stdio.h"
#include "strings.h"
#include "calcjna.h"
int calcular(char* op, int arg1, int arg2) {
int calc = -1;
if(strcmp(op,"soma") == 0) {
calc = arg1 + arg2;
} else if(strcmp(op,"subtracao") == 0) {
calc = arg1 - arg2;
} else {
printf("Operacao %s invalida\n", op);
return calc;
}
printf("Operacao de %s. Executada com sucesso!\n", op);
return calc;
}
* a função strcmp acima retorna 0 (zero) caso o valor entre as Strings sejam o mesmo.
Não podemos esquecer de criar o arquivo Header. Segue o código do arquivo calcjna.h :
int calcular(char* op, int arg1, int arg2);
Exatamente! Só isso mesmo. Agora para gerarmos a biblioteca compartilhada devemos executar o seguinte comando:
gcc calcjna.c -shared -I. -o libcalcjna.so
Pronto, criamos nossa biblioteca nativa. Certifique-se de que o nome do objeto criado inicia com lib, pois essa é uma exigência que devemos seguir. Agora vem a parte interessante que é a comunicação da linguagem Java com o código nativo via JNA. Para fazer isso devemos criar uma interface em Java que fará o mapeamento dos tipos. Lembrando que diferentemente do JNI essa é a única exigência do JNA. O mapeamento de tipos compatíveis pode ser encontrado aqui sob a sigla "Default Type Mappings". Segue o código de nossa interface ICalcJNA.java :
import com.sun.jna.Library;
public interface ICalcJNA extends Library {
public int calcular(String operacao, int elemento1, int elemento2);
}
Veja que mapeamos o tipo char* para o tipo String do Java, bem como o tipo int que é, coicidentemente (ou não) o mesmo em ambas linguagens. Estamos quase no fim, só falta criar o arquivo responsável pela carga da biblioteca, que terá o nome de CalcJNA.java :
import com.sun.jna.Native;
public class CalcJNA {
public static void main(String args[]) {
ICalcJNA calc =
(ICalcJNA) Native.loadLibrary("calcjna", ICalcJNA.class);
int i = calc.calcular("soma", 10, 5);
System.out.println(i);
i = calc.calcular("subtracao", 10, 5);
System.out.println(i);
i = calc.calcular("divisao", 10, 5);
System.out.println(i);
}
}
Veja que em apenas uma linha de código carregamos a biblioteca através do método estático loadLibrary da classe Native. Repare também que devemos (no caso do Linux) informar somente o nome da biblioteca sem o inicio lib e sem o final .so. A partir de agora é só compilar e executar o código, no entanto cabe antes algumas considerações sobre bibliotecas compartilhadas no Linux.
Existem alguns diretórios padrão onde devemos colocar nossa biblioteca compartilhada, entre eles o /usr/lib que simplesmente exigiria que criássemos um link simbólico para nosso arquivo real:
Existem alguns diretórios padrão onde devemos colocar nossa biblioteca compartilhada, entre eles o /usr/lib que simplesmente exigiria que criássemos um link simbólico para nosso arquivo real:
sudo ln -s calcjna.so /usr/lib/libcalcjna.so
Note que precisamos de privilégios de super-usuário para isso. Outra saída seria configurar dentro do arquivo /etc/ld.so.conf o diretório em que está nosso arquivo calcjna.so e muito provavelmente executar um ldconfig. Isso tudo também como super-usuário. Caso você não tenha essa permissão ou não queira por algum motivo utilizá-la você pode configurar uma variável de ambiente que indicará ao sistema operacional, qual o diretório que ele pode encontrar as bibliotecas compartilhadas. O comando para isso é :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/diretorio
Ufa! Agora sim vamos voltar para o Java! Caso você não tenha gravado o arquivo jna.jar em algum diretório que o Java o encontre, deveremos informar explicitamente ao compilador onde ele deve buscar as classes que utilizamos, no meu caso o arquivo JAR está no mesmo diretório que estão todos os outros arquivos que criamos aqui. Portanto, vamos compilar:
javac -cp .:./jna.jar CalcJNA.java
Se nenhuma mensagem de erro apareceu, resta apenas executar e ver a mágica acontecer. Para isso:
java -cp .:./jna.jar CalcJNA
O resultado deveria ser esse:
Operacao de soma. Executada com sucesso!
15
Operacao de subtracao. Executada com sucesso!
5
Operacao divisao invalida
-1
Se isso aconteceu PARABÉNS, você acessou de forma simples um código nativo através do Java, graças a JNA. Agora em caso de erro, verifique as possíveis causas e soluções abaixo.
ERRO: Unable to load library calcjna: libcalcjna.so: cannot open shared object file: No such file or directory
CAUSA: o sistema operacional não localizou a biblioteca que você está solicitando.
SOLUÇÔES:
- o nome da biblioteca gerada realmente é igual ao que está sendo exibido pela mensagem de erro, no caso: libcalcjna.so
- você configurou corretamente a variável de ambiente LD_LIBRARY_PATH
- você tem permissões de super-usuário e não utilizou a configuração de uma variável de ambiente, verifique se criou o link simbólico em um diretório que é buscado pelo comando ldconfig e certifique-se de ter executado o comando ldconfig
- você configurou corretamente a variável de ambiente LD_LIBRARY_PATH
- você tem permissões de super-usuário e não utilizou a configuração de uma variável de ambiente, verifique se criou o link simbólico em um diretório que é buscado pelo comando ldconfig e certifique-se de ter executado o comando ldconfig
CAUSA: o compilador ou interpretador Java não encontrou o arquivo jna.jar.
SOLUÇÃO: configurar corretamente o CLASSPATH conforme abordado no artigo.
ERRO: Exception in thread "main" java.lang.NoClassDefFoundError: CalcJNA
ERRO: Exception in thread "main" java.lang.NoClassDefFoundError: ICalcJNA
CAUSA: o compilador ou interpretador Java não encontrou os arquivos bytecodes necessários.
SOLUÇÃO: configurar corretamente o CLASSPATH conforme abordado no artigo.
Concluímos que a biblioteca JNA é muito mais fácil e produtiva do que a JNI. Como ainda não utilizei em produção não sei opinar sobre sua performance ou estabilidade, mas tudo indica que é um projeto maduro. Nesse primeiro artigo vimos como integrar uma aplicação Java com uma biblioteca nativa, utilizamos o sistema operacional Linux e no próximo artigo mostrarei um exemplo acessando uma DLL do sistema operacional Windows.
Gostaria de agradecer e dedicar esse primeiro artigo ao Daniel Fernandes Martins do batteries not included que me incentivou a criar o blog e ao Marcos Antônio Assis da Onclick Sistemas que ajudou nos códigos C.
6 comentários:
Eu sabia que JNA era mais fácil de usar do que JNI, mas não sabia que era tanto!
Parabéns pelo artigo e bem-vindo ao mundo dos blogs! :)
[]s
Simples e Claro!
Parabéns Danilo
Muito bem detalhado e objetivo!
Parabéns pelo artigo!
PS: se depender da minha ajuda, serão mtos artigos :)
[]s
Muito bom esse artigo.Sou estudante de Analise e Desenvolvimento de Sistema do IFES(Instiyuto Federal do Espirito Santo), estou tentando acessar uma arquivo .ocx, mais não está funcionando. Você tem outro exemplo de acesso a uma ocx que é fornecido por terceiro.
Muito obrigado...
Danilo,
Show de bola seu artigo !
Uma opção para acessar a lib sem ter que configurar variáveis de ambiente é configurar pelo código o local onde buscá-las:
System.setProperty("jna.library.path", System.getProperty("user.dir"));
Este comando indica para procurar a lib na pasta em que o aplicativo java estiver.
Postar um comentário