Simplificando o acesso a código nativo com JNA

Propaganda
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 \
Download