terça-feira, 5 de fevereiro de 2008

Acessando bibliotecas nativas com JNA (Parte 2)

ir para a Parte 1

Como prometido, essa é a segunda parte do artigo sobre JNA. Na primeira parte fizemos o acesso a uma biblioteca nativa de sistemas operacionais Linux. Nesse artigo faremos a comunicação com uma DLL e para isso utilizaremos o mesmo exemplo do site do projeto JNA.

Diferentemente do artigo anterior não vamos criar aqui nossa própria DLL para acesso, mas sim utilizar uma DLL já existente nos sistemas operacionais Windows: KERNEL32.DLL. Mesmo porque grande parte (senão todos) os software que utilizariamos para essa finalidade são pagos e não queremos isso para um simples exemplo. A versão do sistema operacional utilizado para esse exemplo é o Windows XP e não tenho a mínima idéia se em outras versões também funcionará.

Como as explicações sobre o que é JNA, por que utilizar, como e onde obter já foram discutidas na primeira parte desse artigo, vamos então direto para a parte de codificação pois o cenário é o mesmo e o JAR utilizado também será o mesmo.

O objetivo aqui será obter a data e a hora do sistema através de uma biblioteca nativa do sistema operacional em questão. Obviamente que para esse objetivo existem maneiras bem mais simples utilizando diretamente a linguagem Java, mas como o exemplo é apenas para fins didáticos, vamos obter essas informações através da DLL mesmo.

Como dito anteriormente, vamos utilizar uma biblioteca já existente chamada KERNEL32.DLL e que fica localizada no diretório de instalação do Windows e na subpasta SYSTEM32. Na maioria dos casos a instalação padrão fica no diretório \WINDOWS porém se você não tem certeza de qual a unidade e o seu diretório de instalação você pode via prompt de comando utilizar o seguinte comando:

echo %SystemRoot%

Bom, na realidade isso vale apenas como curiosidade pois a DLL já é acessível de qualquer parte do sistema operacional e não precisamos nos preocupar com isso. Portanto chega de papo furado e vamos criar nossa interface que é a única parte burocrática na utilização do JNA:
import com.sun.jna.win32.StdCallLibrary;

public interface IKernel32 extends StdCallLibrary {
void GetSystemTime(DataHoraSistema result);
}

Podemos notar que existe uma diferença aqui nessa interface em relação ao exemplo do primeiro artigo. Aqui a interface não é herança de Library, mas sim de StdCallLibrary. Isso porque essa DLL usa a convenção __stdcall de chamada de métodos, porém na maioria dos casos as bibliotecas serão herança de Library mesmo.

Definimos no código acima a interface de acesso ao método GetSystemTime que recebe um parâmetro do tipo Structure pois o método nativo recebe como parâmetro uma estrutura nativa e que será populada com informações de data e hora do sistema. Para isso teremos que definir uma classe Java que será herança da classe Structure. Isso só é necessário quando métodos nativos recebem ou devolvem parâmetros do tipo de estruturas nativas. Lembrando que a DLL em questão possui vários outros métodos, mas como utilizaremos apenas um, definimos apenas ele. Salve esse arquivo como IKernel32.java e vamos então mapear nossa estrutura Java para trocarmos informações com a biblioteca nativa através dessa estrutura:
import com.sun.jna.Structure;
public class DataHoraSistema extends Structure {
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}

Criamos aqui um tipo que é herança de Structure e mapeamos os tipos para short que é o equivalente ao tipo WORD do Windows. Para isso utilizamos a tabela de mapeamento que encontramos aqui como citado no primeiro artigo. Salve como DataHoraSistema.java e agora resta a implementação da interface IKernel32:
import com.sun.jna.Native;

public class Kernel32JNA {
public static void main(String args[]) {
IKernel32 lib =
(IKernel32) Native.loadLibrary("kernel32", IKernel32.class);
DataHoraSistema time = new DataHoraSistema();
lib.GetSystemTime(time);
System.out.println("Data: " + time.wDay +
"/" + time.wMonth +
"/" + time.wYear);
System.out.println("Hora: " +
time.wHour +
":" +
time.wMinute);
}
}

No código acima apenas obtemos uma instância da biblioteca KERNEL32.DLL através do método loadLibrary do JNA que é quem faz toda a mágica e, a partir daí, trabalhamos com os tipos e métodos fornecidos pela própria biblioteca nativa e que mapeamos na classe DataHoraSistema do Java.

A partir daí é só a gente compilar. Como não estamos trabalhando com packages devemos ter no mesmo diretório os três arquivos criados mais a biblioteca JNA (jna.jar):
javac -cp .;jna.jar Kernel32JNA.java

E executar:
java -cp .;jna.jar Kernel32JNA

No meu caso o resultado foi:
Data: 5/2/2008
Hora: 13:26

Uma vez que o relógio do meu sistema operacional marcava 10:26 eu imagino que o retorno seja o horário atual sem a aplicação do fuso-horário do Brasil, já que estamos atrasados 3 horas em relação ao Meridiano de Greenwich.

É isso aí, espero que tenha ficado claro e que tenham conseguido executar sem erros. Fiquem à vontade para perguntar, comentar ou criticar.

Para os curiosos de plantão, existe um exemplo bem legal utilizando o JNA que descobre dinamicamente qual o sistema operacional hospedeiro da JVM e cria um relógio em uma janela não-retangular (circular). O exemplo pode ser executado via Java Web Start aqui e o código-fonte para análise pode ser obtido aqui.

Shaped Window Demo sendo executado:

12 comentários:

Anônimo disse...

Boa tarde Danilo.
Estou deixando este comentário somente para apontar um possível erro na classe DataHoraSistema, vc fez a assinatura 2 vezes (uma como class e outra como static class).

Eu retirei a que era static class e o exemplo funcionou tranquilo.

Estou partindo neste momento para o uso deste exemplo com a dll aqui da empresa. Já tentei algumas outras formas, mas esta ainda não.

Abraços e qquer dúvida postarei por aqui.

Danilo G. Magrini disse...

Blz Eduardo!? Obrigado por visitar o site e obrigado pela sugestão de correção. Realmente era uma falha sim, acho que foi problema de CTRL+C/CTRL+V do código que eu estava testando aqui. Já está corrigido.
Boa sorte aí com a implementação na DLL da empresa, qualquer dúvida é só postar!

abraço.

Anônimo disse...

olá

Anônimo disse...

Danilo, não há possibilidade de vc demonstrar um código que em vez de tomar os parâmetros, envie os parâmetros para a dll? (em vez do get o set)?
Claro, se é que há possibilidade disto.
Estou tentando acessar 2 métodos da dll aqui da empresa (mercado de relógios ponto e catracas). O primeiro método COMUNICA o relógio com o pc, o segundo Envia um beep para o aparelho. As assinaturas dos 2 métodos estão aqui:

AdicionaCardTcpip(pIp, pMac : WideString; pPorta : Integer; pCatraca :
WordBool; pModoComunicacao : SModoComunicacao) : Integer;

EnviaBeep(pThreadIndex: Integer; pBeep: SBeep): WordBool;



Vou postar abaixo minha tentativa e gostaria de uma ajuda sua Danilo e/ou dos participantes do blog para saber onde estou errando.

Me desculpem ficar postando o código fonte assim, mas creio q é um dos únicos jeitos de podermos identificar os erros rapidamente.

Segue o código abaixo:

Anônimo disse...

Lembrando que foram escritos em delphi.

Interface

import com.sun.jna.win32.StdCallLibrary;

interface InterfaceBip extends StdCallLibrary{
public int setAdicionaCardTcpip(AdicionaCardTcpip result);
public String setEnviaBeep(EnviaBeep resultado);
}
-----------------------------------

Classe que faz a comunicação
import com.sun.jna.Structure;

public class AdicionaCardTcpip extends Structure {
public String pIp;
public String pMac;
public int pPorta;
public String pCatraca;
public String pModoComunicacao;
}

-----------------------------------

Classe que envia o beep
import com.sun.jna.Structure;

public class EnviaBeep extends Structure {
public int pThreadIndex;
public String pBeep;
}
-----------------------------------

Classe main

import com.sun.jna.Native;

public class ClasseBip {

public static void main(String args[]){

InterfaceBip bip = (InterfaceBip) Native.loadLibrary("Kernel7x", InterfaceBip.class);

AdicionaCardTcpip tcpip = new AdicionaCardTcpip();
EnviaBeep beep = new EnviaBeep();

tcpip.pIp = "192.168.0.226";
tcpip.pMac = "";
tcpip.pPorta = 3000;
tcpip.pCatraca = "";
tcpip.pModoComunicacao = "cmcOffline";

beep.pThreadIndex = 00;
beep.pBeep = "cbliberado";

}

Anônimo disse...

Quando eu rodo... não acontece nada e não aparece nenhum erro...

Não estou entendendo o pq disto.

Grande abraço a todos.

Danilo G. Magrini disse...

Eduardo, você está confundindo algumas coisas. Não é sempre que você utiliza Structure. Você tem que mapear os tipos conforme essa tabela:

https://jna.dev.java.net/javadoc/overview-summary.html#marshalling

Ricardo disse...

Não estava conseguindo postar comentários.

Ricardo disse...

Bom dia Danilo... fiz o teste com o modelo que vc me mandou e ele gerou o seguinte erro em tempo de execução:

Exception in thread "main" java.lang.UnsatisfiedLinkError: Error looking up function 'setAdicionaCardTcpip': Não foi possível encontrar o procedimento especificado.

at com.sun.jna.Function.init(Function.java:126)
at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:219)
at com.sun.jna.Library$Handler.invoke(Library.java:191)
at $Proxy0.setAdicionaCardTcpip(Unknown Source)
at ClasseBip.main(ClasseBip.java:6)

Ricardo disse...

Esqueci de dizer... o Ricardo ae sou eu com a conta google do meu irmão Danilo.

Abraços.

Thiago Roberto disse...

Blz cara valeu!

sigma disse...

Uma dica de ferrementa free para desenvolvimento em windows: dev-c++.
Usa um bom compilador (mingw, espécie de gcc para windows), permite compilar para modo texto, gráfico, dll e outras.

Valeu pelos artigos, continue postando.