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.

  1. Native Methods: Java llama a funciones implementada en código nativo.
  2. 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.