Buscar
Social
Ofertas laborales ES
« JasperForge.org, portal dedicado a la Business Intelligence | Main | ¿Ha terminado el boom del outsourcing ? »
martes
jul182006

Introducción a JNIEasy



javaHispano. Tutorial JNIEasy: creación de una ventana Win32 con Java















Introducción a JNIEasy : Creación de una Ventana Win32 desde Java



Fecha de creación: 19.05.2006


Jose María Arranz Santamaría

jmarranz AT innowhere DOT com

Innowhere Software Services S.L.

















Copyright (c) 2006, Jose María Arranz Santamaría.
Este documento puede ser distribuido solo
bajo los términos y condiciones de la licencia de Documentación de
javaHispano v1.0 o posterior (la última versión se encuentra en
/licencias/).
























¿Qué es JNIEasy?
Introducción
Dónde empezar
Vista general de los pasos a seguir para crear la ventana
Registro de la clase de la ventana
La clase estructura WndClassEx
La clase callback WindowProc
Registro de la clase de la ventana (cont.)
Generación de código "proxy" (clase User32)
Verificación del registro de la clase de la ventana
Creación de la ventana
Proceso de mensajes
Desregistro de la clase de la ventana
Conclusión








¿Qué es JNIEasy?


JNIEasy es un substituto del Java Native Interface (JNI) para integrar Java,
librerías C/C++ y DLLs usando únicamente Java y POJOs (Plain Old Java Objects).
JNIEasy consigue que una clase Java normal (un POJO) se corresponda en el lado
nativo con la clase/estructura C/C++ simétrica (incluyendo métodos) y vicersa. De esta manera podemos
programar en Java lo que haríamos en C/C++ con muy pocas diferencias, pero
aprovechando todas las ventajas de robustez, gestión automática de la memoria,
flexibilidad del lenguaje, gestión sencilla de hilos ... y toda la inmensa inversión en librerías y herramientas
realizada en Java durante esta última década, todo ello sin necesidad de establecer una infraestructura
de programación en C/C++ como exige el complejo JNI.



JNIEasy ayuda a romper de una forma sencilla la barrera que supone el acceso a
librerías nativas hechas en C/C++ con Java, llevando a Java el enorme legado
del mundo C/C++. La programación nativa realizada en Java se traduce automáticamente
en acciones en el lado nativo, esta transparencia es similar a la persistencia
transparente proporcionada por tecnologías tal y como JDO, Hibernate o EJB3:
al igual que existen objetos persistentes que automáticamente se
identifican con sus correspondientes registros en una base de datos,
en JNIEasy existen los objetos nativos que se corresponden con su correspondiente
"memoria nativa" en lado C/C++, de ahí que JNIEasy pueda considerarse como la
primera herramienta que implementa esta técnica que podemos llamar
como Java Native Objects.









Introducción



En este tutorial veremos como crear una ventana Win32 con Java.
El proceso para crear una ventana en Java con JNIEasy no es muy diferente
a crear la misma en un entorno C/C++, básicamente los elementos Win32 en C necesitados
(estructuras, constantes, cabeceras de métodos) serán copiados en el lado Java
y "Javatizados" (quitando el * de los punteros, cambios en los tipos de datos etc).
Los programadores familiarizados con la programación con Win32 comprobarán que el código
es absolutamente familiar.



El código fuente de este ejemplo es incluido en la distribución de JNIEasy
y preparado para ser ejecutado con Ant con dos cambios menores en dos directorios
declarados en el archivo /conf/conf.properties : propiedades "basePath" and "JAVA_HOME".
Si tienes NetBeans 5.0 el directorio de la distribución está preparado para
ser abierto como un proyecto, si tienes problemas con versiones previas, elimina
el directorio generado /nbproject. Para poder ejecutar los ejemplos es necesario descargar
una licencia de evaluación temporal renovable en la web oficial de JNIEasy , el archivo JNIEasy.lic obtenido debe ponerse en el directorio /bin de la distribución.



Este tutorial muestra muchas características de JNIEasy y la forma más transparente de
programar de forma nativa con el framework, pero no es un ejemplo con todas las características
del framework (no hay clases C++ por ejemplo).
El framework ofrece además un rico modelo de clases para tener un control más fino de la memoria
nativa. Es conveniente consultar la documentación oficial, disponible online y en la distribución del
framework, y los ejemplos restantes incluidos en la distribución también.








Dónde empezar



La ejecución del ejemplo comienza con el método main de la clase
RunExamples.




package examples;
import com.innowhere.jnieasy.core.JNIEasy;
import examples.manual.ManualExamples;
import examples.win32exam.RunWin32Examples;

public class RunExamples
{
public static void main(String[] args) throws Exception
{
JNIEasy.get().load();

ManualExamples.runAll();
RunWin32Examples.createWin32Window();
}
}

La llamada JNIEasy.get().load()
inicializa el framework, esto debe ser realizado antes de
cualquier uso de las clases de JNIEasy. El método JNIEasy.get()
obtiene el objeto JNIEasy "singleton", éste es la raíz para obtener la mayor parte de los objetos del framework.


La ejecución del ejemplo Win32 comienza en la llamada RunWin32Examples.createWin32Window().








Vista general de los pasos a seguir para crear la ventana



La clase RunWin32Examples:




package examples.win32exam;

import com.innowhere.jnieasy.core.util.NativeCapableUtil;
import examples.win32exam.win32.user.User32;
import examples.win32exam.win32.user.WndClassEx;

public class RunWin32Examples
{
public RunWin32Examples()
{
}

public static void createWin32Window()
{
short winClassAtom = WindowExample.registerWindowClass();

testWindowClassRegistration(); // optional

WindowExample window = WindowExample.createWindow();
window.show();

WindowExample.waitMessages();

WindowExample.unregisterWindowClass();
}

public static void testWindowClassRegistration()
{
WndClassEx wndClass2 = new WndClassEx();
int hInst = WindowExample.getModuleInstance();
int res = User32.GetClassInfoEx(hInst,WindowExample.className,wndClass2);
System.out.println("Must be true: " + (res != 0));
System.out.println("Must be true: " + WindowExample.className.equals(wndClass2.getClassName()));
System.out.println("Must be true: " + (WindowExample.wndProc == wndClass2.getWndProc()));
System.out.println("Must be true: " + (NativeCapableUtil.sizeOf(wndClass2) == 48));
}
}


El método createWin32Window() realiza la creación de la ventana
y la destrucción.


Primero la clase de la ventana que se va a ser usada is registrada llamando al método
WindowExample.registerWindowClass().


Después este registro es verificado usando un nuevo objeto WndClassEx,
esta clase es la versión Java de la estructura nativa Win32 WNDCLASSEX.


La llamada WindowExample.createWindow() crea
la nueva ventana, esta ventana no es visible por defecto. Para hacerla visible
el método show() es llamado.


En este momento la ventana se muestra y está preparada para recibir eventos,
la llamada WindowExample.waitMessages()
mete el actual hilo en un bucle para recibir y procesar los eventos recibidos;
este bucle termina y retorna cuando la ventana es cerrada por el usuario (o por Windows).


Finalmente la llamada WindowExample.unregisterWindowClass()
desregistra la clase de la ventana.









Registro de la clase de la ventana



El registro de la clase de la ventana es realizado por el método
WindowExample.registerWindowClass(), este es
su código:




package examples.win32exam;

import com.innowhere.jnieasy.core.JNIEasy;
import com.innowhere.jnieasy.core.util.NativeCapableUtil;
import examples.win32exam.win32.gdi.*;
import examples.win32exam.win32.user.*;

public class WindowExample
{
public static final WindowProc wndProc = new WindowProcConcrete();
public static final String className = "JNIEasy Demo Class";

protected int hWnd;

public WindowExample(int hWnd)
{
this.hWnd = hWnd;
}

public static int getModuleInstance()
{
return (int)JNIEasy.get().getJNIEasyLib().getHandle();
}

public static short registerWindowClass()
{
int hInst = getModuleInstance();

WndClassEx wndClass = new WndClassEx();
JNIEasy.get().getNativeManager().makeNative(wndClass);
wndClass.setCBSize((int)NativeCapableUtil.sizeOf(wndClass));
wndClass.setStyle( CSConst.CS_HREDRAW | CSConst.CS_VREDRAW );
wndClass.setWndProc( wndProc );
wndClass.setCBClsExtra( 0 );
wndClass.setCBWndExtra ( 0 );
wndClass.setHInstance(hInst);
wndClass.setHIcon(User32.LoadIcon(0,IDIConst.IDI_APPLICATION));
wndClass.setHCursor(User32.LoadCursor(0,IDCConst.IDC_ARROW));
wndClass.setHbrBackground(GDI32.GetStockObject(GDIStockConst.WHITE_BRUSH));
wndClass.setMenuName(null);
wndClass.setClassName(className);
wndClass.setHIconSm(User32.LoadIcon(0,IDIConst.IDI_APPLICATION));

short winClassAtom = User32.RegisterClassEx(wndClass);
return winClassAtom;
}

. . .
}


La llamada getModuleInstance() obtiene el
"handle" de la DLL donde la callback WindowProc reside, esta callback está desarrollada por
Java pero desde el lado nativo es visto como una función compilada dentro de JNIEasy.dll
(por supuesto esto no es realmente verdad, pero la memoria es "propiedad" de JNIEasy.dll).
La implementación de getModuleInstance() es:




public static int getModuleInstance()
{
return (int)JNIEasy.get().getJNIEasyLib().getHandle();
}


donde la llamada getJNIEasyLib()
obtiene un objecto DynamicLibrary describiendo la DLL del framework.
El "handle" de la DLL es obtenida con getHandle().









La clase estructura WndClassEx



Para registrar la clase ventana es necesario rellenar una estructura Win32 WNDCLASSEX structure, esto es
reflejado en Java con la clase WndClassEx,
esta clase es absolutamente simétrica a la estructura nativa:




/*
typedef struct _WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
*/

public class WndClassEx
{
protected int cbSize; // UINT cbSize
protected int style; // UINT style
protected WindowProc lpfnWndProc; // WNDPROC lpfnWndProc
protected int cbClsExtra; // int cbClsExtra
protected int cbWndExtra; // int cbWndExtra
protected int hInstance; // HANDLE hInstance
protected int hIcon; // HICON hIcon
protected int hCursor; // HCURSOR hCursor
protected int hbrBackground; // HBRUSH hbrBackground
protected String lpszMenuName; // LPCTSTR lpszMenuName
protected String lpszClassName; // LPCTSTR lpszClassName
protected int hIconSm; // HICON hIconSm

public WndClassEx()
{
}
... /* Get and Set methods */
}


La macro UINT es resuelta en Win32 como unsigned int,
el tipo Java int es el más simétrico tipo (mismo tamaño),
JNIEasy copia el entero sin signo nativo al entero Java con signo sin ningún cambio de bits,
para obtener el valor real sin signo se puede usar el método NativePrimitiveUtil.toUnsigned(int).
Macros como HICON son resueltos en Win32 como punteros a estructuras no documentadas, en Win32
son normalmente tratados como datos enteros (la dirección de memoria), como un número de identidad,
porque el estructura apuntada es oculta y es creada/destruida por los métodos Win32,
el más apropiado tipo de dato en Java es int porque
en Win32 los punteros son números de 32 bits.



La macro LPCTSTR es resuelta en Win32 como un const char* pointer,
en un atributo gestionado por JNIEasy una referencia String es equivalente a un puntero char* si
es declarado como ANSI en el archivo XML descriptor enhancer
or wchar_t* si declarado como UNICODE, por defecto es ANSI.



La macro WNDPROC es un puntero a método, es reflejado en JNIEasy como una referencia a
objetos WindowProc, en nuestro ejemplo la clase Java WindowProc contiene un método exportado
como una callback llamable desde C/C++ como un método C con una signatura simétrica a la definida en WNDPROC,
una referencia WindowProc es vista como puntero a método nativo a este simulado método C.



La clase WndClassEx es una clase definida por el usuario como capaz de ser nativa, necesita ser
enriquecida (enhanced) usando el enhancer de JNIEasy antes de poder usarse (una específica tarea Ant
es incluida en los ejemplos de la distribución de JNIEasy), o puede ser enriquecida
usando un enhancer bajo demanda o en carga (no usado en este ejemplo). El descriptor XML enhancer
informa a JNIEasy como la clase Java es reflejada en lado C/C++.



El archivo WndClassEx.jnieasy.enh.xml declara la clase WndClassEx como una estructura nativa
y describe qué atributos son nativos (reflejados en el lado nativo) y como son
vistos en el lado nativo (su representación en la memoria nativa), por defecto todos los atributos no estáticos
y no "transient" son declarados como nativos, los tipos primitivos se describen así mismos respecto a la
memoria nativa y la signatura del atributo de tipo WindowProc es descrita en su propio archivo XML descriptor enhancer.
Por eso el archivo WndClassEx.jnieasy.enh.xml apenas necesita declarar la clase
WndClassEx como una estructura nativa:



<?xml version="1.0" encoding="UTF-8"?>

<jniEasyEnhancer version="1.0"
xmlns="http://www.innowhere.com/jnieasy/enhancer"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.innowhere.com/jnieasy/enhancer
../../../../../schemas/JNIEasy.enh.xsd">

<package name="examples.win32exam.win32.user">
<imports />
<class name="WndClassEx" type="structure" >
</class>
</package>
</jniEasyEnhancer>


Los atributos xmlns,
xmlns:xsi
xsi:schemaLocation
son declaraciones opcionales XMLSchema y pueden ser usados en tiempo de desarrollo
para validar el esquema usando una herramienta XML (los IDEs Java normalmente incluyen
ya este tipo de validación), en ejecución JNIEasy no hace una validación del esquema
XML (para ganar en rendimient), únicamente busca aquellos elementos y atributos
que espera encontrar. El XMLSchema no garantiza que el archivo XML enhancer es funcionalmente
válido.










La clase callback WindowProc



En nuestro ejemplo la clase WindowProc está diseñada para ser la clase base
de clases Java concretas.




package examples.win32exam.win32.user;

public class WindowProc
{
public WindowProc()
{
}

/*
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
*/

public int onCall(int hwnd, int uMsg, int wParam, int lParam)
{
throw new RuntimeException("Overload this method");
}
}


Una instancia (nativa) WindowProc de una clase derivada, compila "al vuelo"
un nuevo método C, la referencia a este objeto es un puntero a método para esta concreta
callback, dos instancias son dos métodos con dos diferentes direcciones.



La clase WindowProc es definida como clase definida por el usuario con
"capacidad" de ser nativa, y necesita ser enriquecida (enhanced).
El archivo WindowProc.jnieasy.enh.xml declara la clase WindowProc como
una clase nativa, localiza el método de clase WindowProc para ser llamado desde C/C++ y explica como los
parámetros y el tipo de retorno son vistos en el lado C/C++, en este caso la
representación nativa de los tipos primitivos es auto descrita, ninguna más "información nativa" es necesitada,
y el tipo retorno es obtenido del método de la clase.




<?xml version="1.0" encoding="UTF-8"?>

<jniEasyEnhancer version="1.0"
xmlns="http://www.innowhere.com/jnieasy/enhancer"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.innowhere.com/jnieasy/enhancer
../../../../../schemas/JNIEasy.enh.xsd">

<package name="examples.win32exam.win32.user">
<imports />
<class name="WindowProc" type="callback" >
<method name="onCall" callConv="std_call">
<return/>
<params>
<param class="int" />
<param class="int" />
<param class="int" />
<param class="int" />
</params>
</method>
</class>
</package>

</jniEasyEnhancer>


El atributo callConv="std_call"
declara el convencionalismo de llamada al método como __stdcall,
el estándar en métodos Win32; JNIEasy permite también la convención "c_call"
(la convención usada por los métodos estándar ISO C).









Registro de la clase de la ventana (cont.)



Volviendo al método WindowExample.registerWindowClass(),
el fragmento de código:



WndClassEx wndClass = new WndClassEx();
JNIEasy.get().getNativeManager().makeNative(wndClass);


convierte en nativa la nueva instancia WndClassEx, antes de la llamada a makeNative(wndClass)
el objeto WndClassEx funciona como un objeto normal Java, después la instancia se convierte en nativa, una nativa
instancia tiene un trozo de memoria nativa asociada, cualquier modificación de cualquier atributo nativo
modifica el "atributo nativo" también. En nuestro ejemplo la llamada makeNative(Object)
no es estrictamente necesaria debido a que la instanca WndClassEx es pasada como un parámetro al método nativo
User32.GetClassInfoEx(), antes de pasar el parámetro al
lado nativo el framework asegura que el objeto es nativo.


Como la instancia WndClassEx is nativa inmediatamente después de su creación, el framework
asegura que cualquier objeto capaz de ser nativo referenciado desde un atributo nativo de una instancia nativa
es hecho nativo, este es el caso de la llamada al método:




wndClass.setWndProc( wndProc );


El framework hace nativo el objeto apuntado por wndProc (un objeto WindowProcConcrete)
antes de asignar al atributo nativo WndClassEx.wndProc, el lado nativo del atributo (memoria nativa) es también definido, en este
ejemplo, con la dirección nativa (la dirección del método) del objeto-callback nativo WindowProcConcrete.



Todo objeto nativo libera su memoria nativa automáticamente cuando es finalizado
por el "garbage collector", en nuestro ejemplo, la memoria nativa de la instancia WndClassEx es
liberada automáticamente cuando la referencia wndClass sale fuera del ámbito evitando
fugas de memoria nativa y evitando llamar a sentencias delete/free o similares,
El atributo wndProc de WindowExample, es un una referencia de tipo WindowProc final y estática, la
instancia apuntada, un concreto objeto-callback WindowProcConcrete, es global y nunca queda sin referenciar,
esto no es diferente a un concreto, normal, método C (por supuesto en Java); en nuestro ejemplo la la clase de la ventana
usa esta callback WindowProc, toda ventana creada usará este método callback
para procesar los mensajes recibidos.



Opcionalmente la instancia nativa puede ser liberada explícitamente con el método:
NativeManager.free(Object), después la
liberada instancia es de nuevo un objeto no nativo.



Después de definir las deseadas propiedades de la estructura WndClassEx,
el objeto es pasado como parámetro en la llamada User32.RegisterClassEx(wndClass).
Este método es un proxy Java del método Win32 (definido dentro de User32.dll):




ATOM RegisterClassEx(
CONST WNDCLASSEX *lpwcx // address of structure with class data
);








Generación de código "proxy" (clase User32)



La clase User32 se crea por generación de código, es un proxy de la librería User32.dll (en nuestro ejemplo sólo un muy pequeño grupo
de métodos están definidos). La generación de código para construir métodos proxy en Java es una alternativa al enriquecimiento de métodos
(métodos Java vacíos puede ser enriquecidos para ser "proxys" de métodos nativos, C y C++), la generación de código
es más apropiada que el enriquecimiento en el caso de muy grandes DLLs (DLLs con muchos métodos C exportados), porque
todo método generado es inicializado/registrado en el framework cuando es llamado por vez primera,
(una típica aplicación Win32 usa una pequeña parte de la API Win32), in la versión enriquecida
el método es inicializado/registrado cuando la clase container es cargada; el
enriquecimiento es más apropiado para métodos declarados en clases C++, estructuras o uniones.









Verificación del registro de la clase de la ventana


El método RunWin32Examples.testWindowClassRegistration()
ilustra como el método User32.GetClassInfoEx() rellena
la memoria nativa asociada al objeto apuntado por la referencia wndClass2 (este
objeto es hecho nativo antes de pasarse al método "real" en User32.dll,
cuando los métodos get son llamados (atributos leídos exactamente) el framework automaticamente define los atributos Java
con los actuales valores de la memoria nativa.



Es especialmente interesante la llamada:
wndClass2.getWndProc(),
una simple lectura del atributo WndClassEx.wndProc, esta invocación
devuelve una referencia al objeto original WindowProcConcrete registrado.
¿Por qué? porque Windows define la dirección del método WindowProc que fue registrado en el
correspondiente atributo de la estructura WNDCLASSEX, JNIEasy guarda en un registro "débil" (weak) los
objetos nativos propietarios de su asociada memoria nativa (la dirección es guardada junto al objeto),
un objeto nativo no propietario de la asociada memoria nativa está "vinculada" (attached), varios objetos nativos
pueden ser vinculados a la misma zona de memoria/dirección, pero JNIEasy busca primero
en el registro si existe un objeto propietario de la dirección buscada, si existe,
antes de crear un nuevo objeto y vincularlo a la dirección nativa requerida.











Creación de la ventana


El método WindowExample.createWindow()
crea la ventana:



public class WindowExample
{
...
public static WindowExample createWindow()
{
int hInst = getModuleInstance();

int hWnd = User32.CreateWindowEx(0, // extended window style
className, // window class name
"Hello World", // window caption
WSConst.WS_OVERLAPPEDWINDOW, // window style
CWConst.CW_USEDEFAULT, // initial x position
CWConst.CW_USEDEFAULT, // initial y position
CWConst.CW_USEDEFAULT, // initial x size
CWConst.CW_USEDEFAULT, // initial y size
0, // parent window handle
0, // window menu handle
hInst, // program instance handle
0); // creation parameters

return new WindowExample(hWnd);
}

public void show()
{
User32.ShowWindow(hWnd,SWConst.SW_SHOW);
User32.UpdateWindow(hWnd);
}
...
}


El método User32.CreateWindowEx() crea la
ventana Win32 devolviendo un identificador (handle) de la ventana. Para ordenar a Windows mostrar la ventana, debe llamarse al método
WindowExample.show().



En este momento la ventana no está procesando mensajes, debido a que los mensajes de Windows
son enviados a la pila de mensajes del hilo creador de la ventana, este hilo es el responsable de
sacar los mensajes de la pila y procesarlos. Para hacer esto es llamado el método
WindowExample.waitMessages():




public static void waitMessages()
{
Msg msg = new Msg();
while (User32.GetMessage(msg,0, 0, 0) != 0)
{
User32.TranslateMessage(msg);
User32.DispatchMessage(msg);
}
}


El bucle termina cuando el hilo recibe el mensaje WM_QUIT
(llamando a User32.PostQuitMessage(0)).



La llamada User32.DispatchMessage(msg)
instruye a Windows a enviar el mensaje a la callback WindowProc asociada a ventana objetivo definida en el mensaje.



Por supuesto la clase Msg es definida por el usuario, capaz de ser nativa y previamente enriquecida:




package examples.win32exam.win32.user;

import examples.win32exam.win32.def.Point;

/*
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
*/

public class Msg
{
protected int hwnd;
protected int message;
protected int wParam;
protected int lParam;
protected int time;
protected Point pt = new Point(); // is embedded (by value)

public Msg()
{
}
... /* Gets and sets */


Es especialmente interesante la referencia "Point pt" ; JNIEasy tiene
dos modos de atributos de tipo referencia:


  • Por valor (by value): el objeto apuntado es "embebido" dentro de la memoria de la clase/estructura/union.

  • Por puntero (by pointer): la referencia es un puntero en la memoria nativa



Por defecto es "por puntero", en nuestro ejemplo el atributo C POINT pt es
embebido, por tanto el atributo Java reflejado debe ser declarado "por valor"
(la otra opción es que hubiera sido declarado como POINT* pt), el modo de referencia usado
es declarado en el archivo XML descriptor enhancer user/enhancer.xml,
en este ejemplo la declaración de Msg es compartida con otras
estructuras Win32 (si el enhancer en carga (on-load) fuera usado es altamente recomendado usar un archivo por clase):




<?xml version="1.0" encoding="UTF-8"?>

<jniEasyEnhancer version="1.0"
xmlns="http://www.innowhere.com/jnieasy/enhancer"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.innowhere.com/jnieasy/enhancer
../../../../../schemas/JNIEasy.enh.xsd">

<include file="WindowProc.jnieasy.enh.xml" />
<include file="WndClassEx.jnieasy.enh.xml" />

<package name="examples.win32exam.win32.user">
<imports />
<class name="Msg" type="structure" >
<field name="pt" varConv="byValue" />
</class>
<class name="PaintStruct" type="structure" >
<field name="rcPaint" varConv="byValue" />
<field name="rgbReserved" varConv="byValue" length="32" />
</class>
</package>
</jniEasyEnhancer>


El atributo varConv="byValue"
declara la referencia como "por valor" (by value). Este atributo es también usado para declarar "por valor"
el paso de un parámetro en un método.








Proceso de mensajes


Todo mensaje es enviado a la instancia registrada WindowProcConcrete
llamando al método onCall de la misma:




package examples.win32exam;

import examples.win32exam.win32.def.*;
import examples.win32exam.win32.user.*;

public class WindowProcConcrete extends WindowProc
{
public WindowProcConcrete()
{
}

public int onCall(int hWnd, int msg, int wParam, int lParam)
{
switch (msg)
{
case WMConst.WM_CREATE:
System.out.println("Window created: " + hWnd);
return 0;
case WMConst.WM_PAINT:
{
PaintStruct ps = new PaintStruct();
int hDC = User32.BeginPaint(hWnd,ps);

Rect rect = new Rect();
User32.GetClientRect(hWnd, rect);
User32.DrawText(hDC, "Hello World", -1, rect,
DTConst.DT_SINGLELINE | DTConst.DT_CENTER | DTConst.DT_VCENTER);

User32.EndPaint(hWnd, ps);
return 0;
}

case WMConst.WM_CLOSE:
{
User32.DestroyWindow(hWnd);
return 0;
}
case WMConst.WM_DESTROY:
{
User32.PostQuitMessage(0); // Ends the thread because GetMessage() returns 0
return 0;
}
case WMConst.WM_MOUSEMOVE:
{
int xPos = WinDefUtil.getXLParam(lParam);
int yPos = WinDefUtil.getYLParam(lParam);
System.out.println("(x,y)=(" + xPos +","+ yPos + ")");
return 0;
}
}
return User32.DefWindowProc(hWnd, msg, wParam, lParam);
}
}

El código es absolutamente simétrico al código C correspondiente. La ventana se mantiene creada hasta que
el usuario la cierra manualmente.



Especialmente interesante es la estructura PaintStruct:




package examples.win32exam.win32.user;
import examples.win32exam.win32.def.Rect;

/*
typedef struct tagPAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT;
*/

public class PaintStruct
{
protected int hdc; // HDC
protected int fErase; // BOOL in Win32 is an int
protected Rect rcPaint = new Rect(); // is embedded (by value)
protected int fRestore; // BOOL
protected int fIncUpdate; // BOOL
protected byte[] rgbReserved = new byte[32]; // is embedded (by value)

public PaintStruct()
{
}

... /* Gets and sets methods */
}


El atributo C BYTE rgbReserved[32] es un array embebido (de otra manera sería BYTE*),
en Java el tipo de datos más apropiado es un array byte[] "por valor" con 32 elementos,
esto es declarado en el archivo XML descriptor enhancer.




...
<class name="PaintStruct" type="structure" >
<field name="rcPaint" varConv="byValue" />
<field name="rgbReserved" varConv="byValue" length="32" />
</class>
...

El atributo length
es obligatorio si el atributo array es declarado por valor (para conocer el tamaño de la memoria que ocupa).










Desregistro de la clase de la ventana



Para finalizar el ejemplo el método
WindowExample.unregisterWindowClass()
es llamado desregistrando la clase de la ventana.




public static int unregisterWindowClass()
{
int hInst = getModuleInstance();
return User32.UnregisterClass(className,hInst);
}








Conclusión


En el presente tutorial hemos podido construir una ventana Win32 con código 100% en Java,
las estructuras y métodos empleados y su modo de uso han sido casi totalmente simétricos
al correspondiente código en C pero aprovechando las ventajas de Java, la más
evidente ha sido la total ausencia de algún tipo de "delete" o "free()", la gestión
de la memoria nativa ha corrido a cargo del garbage collector de Java, otra ventaja
no tan visible es que los posibles errores se convierten en excepciones Java
evitando los temidos crashes del mundo nativo.


Aunque existen problemas de integración del mundo Java con Windows que exigen
crear ventanas Win32 y procesar los mensajes recibidos en Java,
El ejemplo elegido no es más que un ejemplo, cada usuario podrá encontrar
otros ejemplos más cercanos a sus necesidades de integración más allá incluso
del ámbito propio de la API estándar Windows.









Acerca del autor


Jose María Arranz Santamaría es el creador de JNIEasy, fundador
de Innowhere Software Services S.L. y profesor asociado en el área
de ciencias de la computación en la Universidad Politécnica de Madrid.









Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.
Comentarios deshabilitados
Comentarios deshabilitados en esta noticia.