Introducción a Java Native Interface
Java Native Interface (JNI) es un mecanismo que nos permite interactuar con aplicaciones nativas desde un programa escrito en Java. Las aplicaciones nativas son bibliotecas o funciones escritas en lenguajes como C, C++ y ensamblador para un SO donde se ejecuta la JVM. Este mecanismo es útil en aquellas aplicaciones que no pueden ser escritas íntegramente en Java, dado que API no proporciona todo el soporte necesario para las funciones de nuestro proyecto. Suele ser utilizado para recursos de bajo nivel de la plataforma como funcionalidades de sonido, ficheros o lectura de datos. La interfaz es bidireccional, permite la comunicación de Java con las aplicaciones nativas y viceversa.
- Native Methods: Java llama a funciones implementada en código nativo.
- Invocation Interface: permite incrustar una JVM en una aplicación nativa.
Los métedos nativos se implementan por separado en archivos .c o .cpp, por los cuales se generará una biblioteca de enlace dinámico. Una biblioteca de enlace dinámico es un fichero con código ejecutable el cual se carga en tiempo de ejecución bajo demanda de un programa por parte del SO. Aportan una gran ventaja con respecto al aprovechamiento de la memoria del sistema, dado que gran parte del código ejecutable puede encontrarse en bibliotecas, pudiendo así ser compartido entre distintas aplicaciones. Por supuesto dependen de cada SO, siendo su tratamiento y manejo distintos. Cada SO tiene sus propias reglas para el uso de las bibliotecas. Estas deben ser colocadas en determinados directorios, para que cuando el programa requiera de su uso, el SO pueda enlazarlas. Los sistemas tipo UNIX buscarán las bibliotecas en las rutas indicadas en la variable de entorno LD_LIBRARY_PATH. Como siempre Mac OSX está incluido en este saco pero con alguna particularidad, en este caso Mac busca en el directorio de la aplicación y en la variable de entorno DYLD_LIBRARY_PATH además de en LD_LIBRARY_PATH. Por último Windows también busca en el directorio donde se encuentra la aplicación, así como en los directorios indicados en la variable PATH. Recordar que para configurar las variables de entorno en sistemas UNIX lo podemos hacer por medio del comando.
export LD_LIBRARY_PATH="VARIABLE"
Así que en el caso de que por ejemplo queramos añadir el directorio actual a la configuración, lo haríamos por medio de:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
Para generar una biblioteca de enlace dinámico se ha de añadir una flag (opción) al compilador para el código fuente de los métodos o funciones que hemos implementado. En Linux es la opción -shared:
gcc mydylib.c -shared -o mydylib.so
Ejemplo
Vayamos con un ejemplo sencillo.
1. Declarar los métodos nativos
Lo primero que tenemos que hacer es definir nuestra clase con los métodos nativos como miembros de esta. Estos llevaran el modificador native y estarán sin implementar.
public class MyJNIClass {
public static void main(String [] args) {
new MyJNIClass().printf("Hello World!");
}
static {
// Carga la biblioteca dinamica: libmyJNIlib.so
System.loadLibrary("myJNIlib");
}
private native void printf(String str);
}
Compilamos el archivo:
javac MyJNIClass.java
La biblioteca de enlace dinámico es cargada por el método:
void loadLibrary(String libraryName)
El cual recibe el nombre de la biblioteca libraryName de distintas maneras dependiendo del SO:
SO | Archivo | libraryName |
---|---|---|
Linux | libmyJNIlib.so | myJNIlib |
Mac OSX | libmyJNIlib.jnilib | myJNIlib |
Windows | myJNIlib.dll | myJNIlib |
2. Generar el fichero de cabecera
Generamos la cabecera con los prototipos de los métodos nativos a implementar.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include ;
/* Header for class MyJNIClass */
#ifndef _Included_MyJNIClass
#define _Included_MyJNIClass
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MyJNIClass
* Method: printf
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_MyJNIClass_printf
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
3. Implementación del método nativo
Creamos un archivo llamado MyJNIClass.c, por ejemplo, e implementamos el método:
#include ;
#include "MyJNIClass.h"
JNIEXPORT void JNICALL Java_MyJNIClass_printf(JNIEnv * env, jobject objc, jstring javaString)
{
// Obtenemos la cadena de caracteres
const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
printf("%s", nativeString);
// Liberamos la memoria
(*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
4. Compilación de la biblioteca
En Linux:
gcc MyJNIClass.c -shared -o libmyJNIlib.so
En Mac OSX:
gcc -bundle -I/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers -framework JavaVM MyJNIClass.c -o libmyJNIlib.jnilib
5. Ejecutar el programa
java MyJNIClass
Hello World!
Si estas probando en Linux recuerda añadir el directorio desde el cual ejecutas el programa a la variable de entorno LD_LIBRARY_PATH y colocar la biblioteca en esa ruta.
Relación de tipos
Esta es la correspondencia entre los tipos de datos en C y Java (JNI)
Tipo nativo | Tipo Java | Descripción |
---|---|---|
unsigned char | jboolean | 8 bits sin signo |
char | jbyte | 8 bits con signo |
unsigned short | jchar | 16 bits sin signo |
short | jshort | 16 bits |
long | jint | 32 bits |
long long | jlong | 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
Este tutorial tiene un carácter introductorio. Si quieres profundizar más en la materia, te recomiendo este extenso tutorial de la web MacProgramadores. Aquí os dejo un proyecto que hice basado en JNI para comunicarse con distintos sensores conectados a una Beaglebone a través del puerto I2C. Un saludo a todos. Si tenéis cualquier consulta acerca de este tutorial no dudes en dejar un comentario.