METODOS NATIVOS
Un método nativo es un método Java (una instancia de un objeto o una clase) cuya implementación se ha realizado en otro lenguaje de programación, por ejemplo, C. Vamos a ver cómo se integran métodos nativos en clases Java. Actualmente, el lenguaje Java solamente proporciona mecanismos para integrar código C en programas Java.
Veamos pues los pasos necesarios para mezclar código nativo C y programas Java. Recurriremos (¡Cómo no!) a nuestro saludo; en este caso, el programa HolaMundo tiene dos clases Java: la primera implementa el método main() y la segunda, HolaMundo, tiene un método nativo que presenta el mensaje de saludo. La implementación de este segundo método la realizaremos en C.
10.1 Escribir Código Java
En primer lugar, debemos crear una clase Java, HolaMundo, que declare un método nativo. También debemos crear el programa principal que cree el objeto HolaMundo y llame al método nativo.
Las siguientes líneas de código definen la clase HolaMundo, que consta de un método y un segmento estático de código:
class HolaMundo {
public native void presentaSaludo();
static {
System.loadLibrary( "hola" );
}
}
Podemos decir que la implementación del método presentaSaludo() de la clase HolaMundo está escrito en otro lenguaje, porque la palabra reservada native aparece como parte de la definición del método. Esta definición, proporciona solamente la definición para presentaSaludo() y no porporciona ninguna implementación para él. La implementación la proporcionaremos desde un fichero fuente separado, escrito en lenguaje C.
La definición para presentaSaludo() también indica que el método es un método público, no acepta argumentos y no devuelve ningún valor. Al igual que cualquier otro método, los métodos nativos deben estar definidos dentro de una clase Java.
El código C que implementa el método presentaSaludo() debe ser compilado en una librería dinámica y cargado en la clase Java que lo necesite. Esta carga, mapea la implementación del método nativo sobre su definición.
El siguiente bloque de código carga la librería dinámica, en este caso hola. El sistema Java ejecutará un bloque de código estático de la clase cuando la cargue.
Todo el código anterior forma parte del fichero HolaMundo.java, que contiene la clase HolaMundo. En un fichero separado, Main.java, vamos a crear una aplicación Java que instancie a la clase HolaMundo y llame al método nativo presentaSaludo().
class Main {
public static void main( String args[] ) {
new HolaMundo().presentaSaludo();
}
}
Como se puede observar, llamamos al método nativo del mismo modo que a cualquier otro método Java; añadimos el nombre del método al final del nombre del objeto con un punto ("."). El conjunto de paréntesis que sigue al nombre del método encierra los argumentos que se le pasen. En este caso, el método presentaSaludo() no recibe ningún tipo de argumento.
10.2 Compilar el Código Java
Utilizaremos ahora el compilador javac para compilar el código Java que hemos desarrollado. Compilaremos los dos ficheros fuentes de código Java que hemos creado, tecleando los siguientes comandos:
> javac HolaMundo.java
> javac Main.java
10.3 Crear el Fichero de Cabecera
Ahora debemos utilizar la aplicación javah para conseguir el fichero de cabecera .h. El fichero de cabecera define una estructura que representa la clase HolaMundo sobre código C y proporciona la definición de una función C para la implementación del método nativo presentaSaludo() definido en ese clase.
Ejecutamos javah sobre la clase HolaMundo, con el siguiente comando:
> javah HolaMundo
Por defecto, javah creará el nuevo fichero .h en el mismo directorio en que se encuentra el fichero .class, obtenido al compilar con javac el código fuente Java correspondiente a la clase. El fichero que creará, será un fichero de cabecera del mismo nombre que la clase y con extensión .h. Por ejemplo, el comando anterior habrá creado el fichero HolaMundo.h, cuyo contenido será el siguiente:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <native.h>
/* Header for class HolaMundo */
#ifndef _Included_HolaMundo
#define _Included_HolaMundo
typedef struct ClassHolaMundo {
char PAD; /* ANSI C requires structures to have a least one member */
} ClassHolaMundo;
HandleTo(HolaMundo);
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HolaMundo_presentaSaludo(struct HHolaMundo *);
#ifdef __cplusplus
}
#endif
#endif
Este fichero de cabecera contiene la definición de una estructura llamada ClassHolaMundo. Los miembros de esta estructura son paralelos a los miembros de la clase Java correspondiente; es decir, los campos en la estructura corresponden a las variables de la clase. Pero como HolaMundo no tiene ninguna variable, la estructura se encuentra vacía. Se pueden utilizar los miembros de la estructura para referenciar a variables instanciadas de la clase desde las funciones C. Además de la estructura C similar a la clase Java, vemos que la llamada de la función C está declarada como:
extern void HolaMundo_presentaSaludo( struct HHolaMundo *);
Esta es la definición de la función C que deberemos escribir para implementar el método nativo presentaSaludo() de la clase HolaMundo. Debemos utilizar esa definición cuando lo implementemos. Si HolaMundo llamase a otros métodos nativos, las definiciones de las funciones también aparecerían aquí.
El nombre de la función C que implementa el método nativo está derivado del nombre del paquete, el nombre de la clase y el nombre del método nativo. Así, el método nativo presentaSaludo() dentro de la clase HolaMundo es HolaMundo_presentaSaludo(). En este ejemplo, no hay nombre de paquete porque HolaMundo se considera englobado dentro del paquete por defecto.
La función C acepta un parámetro, aunque el método nativo definido en la clase Java no acepte ninguno. Se puede pensar en este parámetro como si fuese la variable this de C++. En nuestro caso, ignoramos el parámetro this.
10.4 Crear el Fichero de stubs
Volvemos a utilizar la aplicación javah para crear el fichero de stubs, que contiene todas las declaraciones de métodos, con sus llamadas y argumentos, listos para que nosotros rellenemos el cuerpo de los métodos con los algoritmos que necesitemos implementar. Proporciona la unión entre la clase Java y su estructura C paralela.
Para generar este fichero, debemos indicar el parámetro .stubs al ejecutar la aplicación javah sobre la clase HolaMundo, de la siguiente forma:
> javah -stubs HolaMundo
Del mismo modo que se generaba el fichero .h; el nombre del fichero de stubs será el nombre de la clase con la extensión .c. En nuestro ejemplo, será HolaMundo.c, y su contenido será el siguiente:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <StubPreamble.h>
/* Stubs for class HolaMundo */
/* SYMBOL: "HolaMundo/presentaSaludo()V",
Java_HolaMundo_presentaSaludo_stub */
__declspec(dllexport) stack_item
*Java_HolaMundo_presentaSaludo_stub(stack_item *_P_,struct execenv *_EE_) {
extern void HolaMundo_presentaSaludo(void *);
(void) HolaMundo_presentaSaludo(_P_[0].p);
return _P_;
}
10.5 Escribir la funcion C
Escribiremos la función C para el método nativo en un fichero fuente de código C. La implementación será una función habitual C, que luego integraremos con la clase Java. La definición de la función C debe ser la misma que la que se ha generado con javah en el fichero HolaMundo.h. La implementación que proponemos la guardaremos en el fichero HolaImp.c, y contendrá las siguientes línea de código:
#include <StubPreamble.h>
#include "HolaMundo.h>
#include <stdio.h>
void HolaMundo_presentaSaludo( struct HHolaMundo *this ) {
printf( "Hola Mundo, desde el Tutorial de Java\n" );
return;
}
Como se puede ver, la implementación no puede ser más sencilla: hace una llamada a la función printf() para presentar el saludo y sale.
En el código se incluyen tres ficheros de cabecera:
StubsPreamble.h
Proporciona la información para que el código C pueda interactuar con el sistema Java. Cuando se escriben métodos nativos, siempre habrá que incluir este fichero en el código fuente C.
HolaMundo.h
Es el fichero de cabecera que hemos generado para nuestra clase. Contiene la estructura C que representa la clase Java para la que estamos escribiendo el método nativo y la definición de la función para ese método nativo.
stdio.h
Es necesario incluirlo porque utilizamos la función printf() de la librería estándar de C, cuya declaración se encuentra en este fichero de cabecera.
10.6 Crear la Libreria Dinámica
Utilizaremos el compilador C para compilar el fichero .h, el fichero de stubs y el fichero fuente .c; para crear una librería dinámica. Para crearla, utilizaremos el compilador C de nuestro sistema, haciendo que los ficheros HolaMundo.c y HolaImp.c generen una librería dinámica de nombre hola, que será la que el sistema Java cargue cuando ejecute la aplicación que estamos construyendo.
Vamos a ver cómo generamos esta librería en Unix y en Windows '95.
Unix
Teclearemos el siguiente comando:
% cc -G HolaMundo.c HolaImp.c -o libhola.so
En caso de que no encuentre el compilador los ficheros de cabecera, se puede utilizar el flag -I para indicarle el camino de búsqueda, por ejemplo:
% cc -G -I$JAVA_HOME/include HolaMundo.c HolaImp.c -o libhola.so
donde $JAVA_HOME es el directorio donde se ha instalado la versión actual del Java Development Kit.
Windows '95
El comando a utilizar en este caso es el siguiente:
c:\>cl HolaMundo.c HolaImp.c -Fhola.dll -MD -LD javai.lib
Este comando funciona con Microsoft Visual C++ 2.x y posteriores. Si queremos indicar al compilador donde se encuentran los ficheros de cabecera y las librerías, tendremos que fijar dos variables de entorno:
c:\>SET INCLUDE=%JAVAHOME%\include;%INCLUDE%
c:\>SET LIB=%JAVAHOME%\lib;%LIB%
donde %JAVAHOME% es el directorio donde se ha instalado la versión actual del Java Development Kit.
10.7 Ejecutar el Programa
Y, por fin, utilizaremos el intérprete de Java, java, para ejecutar el programa que hemos construido siguiendo todos los pasos anteriormente descritos. Si tecleamos el comando:
> java Main
obtendremos el resultado siguiente:
% Hola Mundo, desde el Tutorial de Java
Si no aparece este mensaje de saludo y lo que aparece en pantalla son expresiones como UnsatisfiedLinkError, es porque no tenemos fijado correctamente el camino de la librería dinámica que hemos generado. Este camino es la lista de directorios que el sistema Java utilizará para buscar las librerías que debe cargar. Debemos asegurarnos de que el directorio donde se encuentra nuestra librería hola recién creada, figura entre ellos.
Si fijamos el camino correcto y ejecutamos de nuevo el programa, veremos que ahora sí obtenemos el mensaje de saludo que esperábamos.
Con ello, hemos visto como integrar código C en programas Java. Quedan muchas cuestiones por medio, como la equivalencia de tipos entre Java y C, el paso de parámetros, el manejo de cadenas, etc. Pero eso supondría entrar en mucha más profundidad dentro de Java de la que aquí pretendemos (por ahora).