Introducción a JNIEasy : Creación de una Ventana Win32 desde Java
lunes, agosto 1, 2011 at 8:00AM
Abraham

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 http://www.javahispano.org/licencias/).

¿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 xmlnsxmlns: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 llamadamakeNative(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 originalWindowProcConcrete 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 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.

Article originally appeared on javaHispano (http://www.javahispano.org/).
See website for complete article licensing information.