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: