Interface Nativa Java
Mucho se habla de Java, principalmente del hecho de ser multiplataforma, talvez esa característica en especial, de compilarse una vez y poder correr en diversas plataformas, convirtió Java un lenguaje y plataforma mas popular en el mundo. Mucho se tiene explorado ya que toda complejidad es hecha por la JVM, pero surge la siguiente duda: Como la JVM hace eso? como consigue abstraer las otras plataformas para mi? Obtener ese conocimiento es muy importante ya que puede ser necesario utilizar un recurso específico de la maquina y hacer que converse con ese recurso directamente con la JVM.
La comunicación entre la JVM y el código nativo casi en su gran mayoria esta hecho en lenguaje C/C++
ya que ellas tienen el patrón ANSI y que es compatible con la gran mayoria de las plataformas, asi es posible un gran aprovechamiento del código, pero en algunos casos es necesario que sea hecha una implementación especifica para cada plataforma o que existe compatibilidad con un sistema legado que fue hecho en C
, por ejemplo. La puerta entre el codigo nativo y Java es conocido como JNI (Java Native Interface). Este recurso es muy interesante tambien para ser el puente para plataformas en que Java todavia no alcanzó. Dentro de JVM este recurso es usado por algunos en la plataforma JSE, por ejemplo, Java I/O, algunos métodos de la clase java.lang.System
, JavaSound, la comunicación entra la clase, el archivo, .class
, y su representación dentro de la JVM, la implementación de la interface java.lang.Class
, las implementaciones de Garbage Colector entre otros recursos. Con JNI es posible llama un metodo de un objeto, instanciar clases, verificar el estado del objeto creado dentro de la JVM, entre otros recursos.
No obstante, su uso requiere algunas responsabilidades, vale recordar que usar JNI pierde portabilidad, un error nativo no es controlado por la JVM (cabe recordar en la parte en que se hablo de los registradores, la pila nativa y el PC cuando apunta para un proceso nativo, no se sabe su valor preciso), no es posible debuggear el código nativo através de la plataforma Java, si sucede un error nativo puede romper la ejecución de la JVM, no proporciona un Garbage Collector automático o cualquier gestión por parte de la JVM. Asi es muy importante saber el momento en que se usará JNI.
Los objetos en Java pueden ser mapeados para objetos nativos y vice-versa, para garantizar la comunicación de dos manos, asi es posible estar pasando un objeto Java para el lado nativo ver su valor.
Tipo en Java | Tipo Nativo |
---|---|
boolean |
jboolean |
byte |
jbyte |
char |
jchar |
double |
jdouble |
float |
jfloat |
int |
jint |
long |
jlong |
short |
jshort |
void |
void |
Con el objetivo de dar un pequeño ejemplo con JNI será mostrado dos simples ejemplos, el primero será “hola mundo” con JNI y el segundo será un método estático que calcula el doble del resultado pasado, para eso es necesario que se tenga instalado el GCC y JDK. El código será bien simple, en el primer caso será enviado el nombre por parametro y ese String será pasado para el valor nativo, ya que en nativo será concatenado el “Hello world” con el nombre digitado, en el segundo ejemplo, el segundo parametro seria calculado su doble.
Primeramente será creado la clase HelloWorld.java.
public class HelloWorld {
private native void llamarMensaje(String nome);
public native static int doblar(int valor);
public static void main(String[] args) {
String nome=args[0]==null?"nome":args[0];
int valor=args[1]==null?2:Integer.valueOf(args[1]);
HelloWorld helloWorld=new HelloWorld();
helloWorld.llamarMensaje(nome);
int resultado=HelloWorld.doblar(valor);
System.out.println("El doble de "+valor+" es: "+ resultado);
}
static {System.loadLibrary("HelloWorld");}
}
Enseguida compilamos con el siguiente comando:
javac HelloWorld.java
Una vez el archivo compilado, será necesario generar la interface JNI con el siguiente comando:
javah -jni HelloWorld
Con el comando será generado un archivo HelloWorld.h
.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: llamarMensaje
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloWorld_llamarMensaje
(JNIEnv *, jobject, jstring);
/*
* Class: HelloWorld
* Method: dobro
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_HelloWorld_doblar
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Notar que en la interface el metodo tiene el siguiente formato:
Java_NomeClasse_nomeMetodo. En relación a los parametros el primer elemento, el JNIEnv, estee es un puntero que apunta para un vector en el cual posee todas las funciones de JNI, el segundo depende si el metodo es de la clase o de la instancia. Caso sea estático, o sea el método tenga la palabra-clave static
, el proximo parametro será jclass que contendrá las informaciones de la clase, caso sea de la instancia el proximo parametro será jobject que contendrá las informaciones de la instancia.
El proximo paso es la creación del archivo que “implemente” la interface de HelloWorld.h
, asi será creado HelloWorld.c que implemente tal interface.
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_llamarMensaje(JNIEnv * env, jobject obj,jstring nome){
const char * nomeNativo=(*env)->GetStringUTFChars(env, nome, NULL);
printf("Hello World!!!! %s\n", nomeNativo);
return;
}
JNIEXPORT jint JNICALL Java_HelloWorld_doblar(JNIEnv * env, jclass classe, jint valor){
return 2*valor;
}
Con el archivo creado el proximo paso es la compilación, teniendo en consideración las debidas importaciones, como se trata de libs nativas las carpetas varias de acuerdo a la plataforma. En caso de linux para compilar será necesario el siguiente comando:
gcc -o libHelloWorld.so -shared -I$JAVA_HOME/include -I$JAVA_HOME/linux HelloWorld.c
Una vez compilado el código fuente es transformado en un lib, en el caso de linux el archivo con extensión .so
de Shared Object. El proximo paso será “linkear” el archivonativo con el proyecto, el primer paso es cargar la biblioteca dentro del código java (para eso será utilizado el comando System.loadLibrary("NomedaLib");
).
El proximo paso es colocar la lib nativa en el classpath en el sistema operacional o definir su ruta con el parametro java.library.path al ejecutar el proyecto Java. En este ejemplo será utilizado la segunda opción junto con el parametro que será el nombre del que será impreso en consola, asi el comando quedará:
java -Djava.library.path=. HelloWorld Octavio 4
La salida será:
Hello World!!!! Octavio
- `El doble de 5 é: 10
Con eso se presentó el recurso de JNI, la interface en que se comunica JVM para lenguajes nativos como C
y C++
y de su importancia para la JVM como la implementación del Garbage Collector, su existencia en algunos APIs como JavaSound ademas de integrarse con codigo legado y con plataformas cuyo JVM hasta el momento no alcanzó soportar. No obstante, vale resaltar que se pierde el factor multiplataforma y no será mas gestionado por la JVM usando este recurso. Aprender sobre JNI es muy importante para comprender el codigo de la máquina virtual Java, pero es necesario un conocimiento en lenguaje C
y C++
.`