jna_ Simplificando o acesso a código nativo com JNA Descubra como é simples integrar suas aplicações Java com bibliotecas nativas do Sistema Operacional H istoricamente, acessar métodos de uma biblioteca nativa do sistema operacional com Java sempre foi uma tarefa tediosa, afinal era necessário a escrita de muito ‘glue code’ para alcançar tal integração. A JNA (Java Native Interface) se propõe a facilitar essa tarefa ao permitir fácil acesso às bibliotecas nativas usando apenas código Java para realizar essa integração. A ideia deste artigo surgiu quando, recentemente, tivemos a necessidade de realizar a integração de um aplicativo Java desenvolvido em Swing com uma DLL proprietária para emissão de cupom fiscal. No entanto, utilizando a abordagem mostrada neste artigo o leitor conseguirá realizar essa integração de forma simples e eficiente em todos os contextos onde essa necessidade se faça presente. Nossos exemplos serão baseados na integração da Linguagem C com a plataforma Java. Os códigos escritos em C serão compilados para bibliotecas nativas e mostraremos como um aplicativo desenvolvido em Java poderá enviar e consumir informações da biblioteca nativa usando a JNA como ‘ponte’ entre as duas linguagens. Configuração do ambiente A única dependência necessária para a execução dos exemplos mostrados neste artigo é o jar da JNA, que pode ser baixado diretamente do Github do projeto (https://github.com/twall/jna). Efetue o download do arquivo jna.jar e insira-o no seu classpath. / 38 Conhecendo a JNA através de um exemplo básico Começaremos nosso estudo em JNA executando um exemplo simples, onde acessaremos uma DLL nativa do Windows para bloquear a estação de trabalho (atalho Logo+L). A interação com uma biblioteca nativa requer a criação de uma interface que servirá de ‘fachada’ para o acesso à biblioteca, contendo os métodos que serão acessados pela nossa aplicação. A figura 1 demonstra a interação entre os componentes do nosso exemplo. Após o entendimento do fluxo, crie a interface de fachada conforme a Listagem 1. Essa interface deverá estender a interface com.sun.jna.Library e irá conter a declaração de todos os métodos que nossa aplicação irá acessar da biblioteca nativa. Listagem 1. Interface de fachada para comunicação com a biblioteca nativa. import com.sun.jna.Library; public interface User32 extends Library { boolean lockWorkStation(); } Após a criação da interface, podemos implementar a classe de acesso à biblioteca nativa, conforme a Listagem 2. Rafael Roque Viana | [email protected] é graduando em Análise e Desenvolvimento de Sistemas pela FIC e atua como Analista de Sistemas na STDS (Secretaria do Trabalho e Desenvolvimento Social do Estado do Ceará) com foco no desenvolvimento de aplicações Java para Web. Possui 7 anos de experiência na plataforma Java e é certificado SCJP, SCWCD, SCEA(I) e ITIL V2. João Paulo de Oliveira Franco | [email protected] é graduando em Análise e Desenvolvimento de Sistemas pela FLF e atua como Programador Java na STDS (Secretaria do Trabalho e Desenvolvimento Social do Estado do Ceará) com foco no desenvolvimento de aplicações Java para Web. Sabemos que a Linguagem Java foi projetada desde os seus primórdios para ser independente de sistema operacional. No entanto, em algum momento pode ser necessário integrar suas aplicações com as bibliotecas nativas do sistema operacional. Neste artigo, veremos como alcançar essa integração de forma simples usando a JNA (Java Native Interface). Listagem 2. Classe de acesso aos métodos nativos. import com.sun.jna.Native; public class LockWorkStation{ public static void main(String[]args){ User32 user32 = (User32) Native.loadLibrary (“user32”,User32.class); user32.lockWorkStation(); } } Figura 2. Padrão de nomenclatura de bibliotecas de acordo com o Sistema Operacional. Parâmetros de entrada e valores de retorno A String passada como argumento para o método loadLibrary(String,Class) representa o nome da biblioteca nativa a ser acessada pelo proxy. A figura 2 apresenta o padrão de nomenclatura de bibliotecas nas plataformas mais conhecidas. Esse método lança uma java.lang.UnsatisfiedLinkError caso a biblioteca não seja encontrada. Código da Aplicação Nosso próximo exemplo mostra-se a chamada de um método recebendo parâmetros de entrada e retornando um valor do tipo int. O método recebe dois parâmetros do tipo int e retorna a soma desses valores, conforme a Listagem 3. com.sun.jna Native user32.dll loadLibrary () <<create>> DLL Proxy Java JNA LockWorkStation () LockWorkStation () Código Nativo Figura 1. Fluxo de interação. 39 \ Criando uma biblioteca nativa Embora uma biblioteca nativa possa conter uma O processo de criação de uma biblioteca nativa é quantidade potencialmente grande de métodos, semelhante à compilação de um arquivo .java em um na interface só é necessário declarar os métoexecutável .class. A única diferença é que, enquanto dos que serão efetivamente acessados pela sua o bytecode Java pode ser executado em diferentes aplicação. plataformas, a biblioteca nativa está vinculada ao sistema operacional no qual foi compilada. Para compilar o código-fonte escrito na linguagem C numa dll para Windows, foi usada a ferramenta MingW (Minimalist GNU for Windows), disponível para download em sourceforge.net/projects/mingw/ Listagem 5. Interface de fachada. files/MingW. Após efetuar a instalação da ferramenta, cole import com.sun.jna.Library; public interface Soma extends Library { o código mostrado na Listagem 3 no seu editor de int soma(int x,int y); textos preferido e salve com o nome “soma.c”. Feito } isso, use a linha de comando para navegar até o diretório onde o arquivo foi salvo e execute os comanListagem 6. Classe para execução do teste. dos mostrados na Listagem 4 para criar a biblioteca nativa. O comando “gcc -c” compila o código C e gera import util.BibliotecaUtil; um executável, enquanto o comando ‘gcc -shared’ public class Calculadora { efetua a criação da biblioteca propriamente dita. Listagem 3. Método com recebimento de argumen- tos e retorno de valor. int soma (int x, int y) { return x + y; } Listagem 4. Comandos para criação da dll. gcc -c soma.c gcc -shared -o soma.dll soma.o O projeto completo, que poderá ser baixado diretamente do site da revista, está estruturado conforme mostrado na figura 3. Semelhante ao primeiro exemplo, precisamos criar a interface para acesso a biblioteca (Listagem 5) e uma classe para testarmos a execução (Listagem 6). A classe BibliotecaUtil mostrada na Listagem 7 foi criada para facilitar o carregamento das bibliotecas nativas. } public static void main(String[] args) throws ClassNotFoundException { Soma soma = (Soma) BibliotecaUtil. carregaBiblioteca(“Soma”, “soma.dll”); System.out.println(soma.soma(40, 55)); } Listagem 7. Classe utilitária para carregamento das bibliotecas. package util; import com.sun.jna.Native; public class BibliotecaUtil { public static Object carregaBiblioteca(String classe, String nomeArquivo) throws ClassNotFoundException{ String raizApp = System.getProperty(“user.dir”); String separador = System.getProperty( “file.separator”); String diretorioLib = “native”; String caminho = raizApp +separador+ diretorioLib+separador+nomeArquivo; System.load(caminho); Class clazz = Class.forName(classe); return Native.loadLibrary(clazz); } Figura 3. Estrutura do Projeto. / 40 } Trabalhando com tipos complexos Avançando nosso exemplo, podemos fazer o mapeamento de tipos complexos entre a linguagem C e Java. Em C, o conceito de classe é explicitado através de structs, que nada mais são do que um conjunto de atributos correlacionados. O exemplo abaixo demonstra essa integração através da criação de uma Struct em C e a classe correspondente em Java para ler um objeto instanciado pela biblioteca nativa. Observe na Listagem 9 a criação da classe estática ‘Pessoa’. Essa classe serve para mapear a estrutura da struct mostrada na Listagem 8 e irá receber o objeto de retorno que será buscado da struct. Não necessariamente essa classe deve ser interna à interface que representa a biblioteca, no entanto optamos por essa abordagem por questões de simplicidade. A classe que mapeia a struct deve estender a classe com. sun.jna.Structure. Note a criação da classe ByValue’ na classe Pessoa. O que ocorre é que, por padrão, uma classe Java que estenda de Struct é interpretada como ponteiro pelo lado nativo quando passada como parâmetro. Para ‘forçar’ a passagem por valor, é necessário a criação de uma classe que estenda de Structure.ByValue. Note que essa classe também é usada para pegar o retorno da função nativa. A Listagem 10 ilustra a instanciação de um objeto do tipo Pessoa, cujos atributos serão recuperados da biblioteca nativa. Listagem 8. Tipo complexo representado através de uma struct. typedef struct{ const char *nome; int idade; } Pessoa; Pessoa retorno(){ Pessoa p ={“rafael”,29}; } } public int idade; Pessoa.ByValue retorno(); Listagem 10. Classe principal para execução do exemplo. import util.BibliotecaUtil; public class TesteClasse { } public static void main(String[] args) throws ClassNotFoundException { IClasse iclasse = (IClasse)BibliotecaUtil. carregaBiblioteca(“IClasse”, “classe.dll”); IClasse.Pessoa p = iclasse.retorno(); System.out.println(“Nome:”+p.nome); System.out.println(“Idade:”+p.idade); } Considerações Finais Apesar de não ser uma necessidade recorrente na maioria dos projetos, conhecer a integração Java com código nativo é um item importante no ‘cinto de ferramentas’ de qualquer desenvolvedor. Afinal, nunca se sabe quando será necessário reutilizar componentes de terceiros para resolver nossas tarefas do dia-a-dia. Este artigo se propôs a ser uma introdução à integração da linguagem Java com código nativo. Esperamos que os exemplos aqui mostrados tenham despertado o interesse do leitor nessa ainda pouco conhecida API da Linguagem Java. return p; } Listagem 9. Mapeamento do tipo complexo em Java para uma struct. import com.sun.jna.Library; import com.sun.jna.Structure; public interface IClasse extends Library { public static class Pessoa extends Structure{ public static class ByValue extends Pessoa implements Structure.ByValue{} /referências > Github do projeto: https://github.com/twall/jna > Documentação Oficial da JNA: http://jna.java.net/ javadoc/overview-summary.html > Artigo:http://today.java.net/article/2009/11/11/simplifynative-code-access-jna public String nome; 41 \