Hace un mes empece un proyecto, una herramienta de administración remota, bastante interesante. Tenía pensado escribirla en un lenguaje nativo (Object Pascal o C/C++), pero quien paga manda :). Me pidieron realizar-la en Java para posteriormente poder implementar-le el soporte multiplataforma para sistemas Mac y Linux.

Como sabemos los programadores de Java o los “Javeros”, este lenguaje tiene bastante potencial, pero también inconvenientes. Imaginemos una situaciones en la que una aplicación no puede ser enteramente escrita en Java, como por ejemplo en el caso de que la biblioteca estándar de clases no proporcione soporte para funcionalidades dependientes de la plataforma, pues para dar solución a estos problemas existen varias  formas / técnicas, aunque en este articulo solo se comentará y se explicara una forma .

Después de redactar ésta pequeña introducción sobre el tema que vamos a tratar, pasaremos a explicar algunos conceptos importantes, requerimientos y caso práctico.

¿Que es JNI?

Java Native Interface (JNI) es un framework de programación que permite que un programa escrito en Java ejecutado en la máquina virtual java (JVM) pueda interactuar con programas escritos en otros lenguajes como CC++ y ensamblador.

Conexión entre Java y C Conexión entre Java y C

Requisitos

En este caso, se interactuara con librerías escritas en C/C++ por lo tanto es imprescindible el conocimiento del mismo.

Conceptos básicos

Ahora se explicaran algunas cosas esenciales para entender mejor el funcionamiento del JNI.

El Java Development Kit (JDK) proporciona herramientas y rutinas de la biblioteca que ayudan a la interfaz de programador de Java con el código nativo. Microsoft y Sun, ambos proporcionan sus propias implementaciones del JDK, y son incompatibles entre sí.

El Javah es una herramienta muy útil que nos permite crear encabezados de C a partir de una clase determinada. El encabezado generado describe la clase en términos de C. Aun así no es obligatorio usar esta herramienta para generarlos, se puede hacer manualmente aunque no es aconsejable. Javah sabe como se asignan exactamente en los tipos y los objetos de Java.

El encabezado jni.h  se incluye con el JDK. Este archivo define todos los tipos de datos necesarios. Contiene principalmente #define y typedef que ocultan la complejidad de asignar tipos de Java para tipos nativos. También incluye muchos otros archivos de cabecera específicos de la plataforma. Les dejo el enlace de descarga del encabezado para que podáis seguir la explicación.

Descarga: Encabezados JNI

Caso práctico

Se expondrá un ejemplo, donde pasando-le parámetros al método,  nos devolverá información según el valor. Primero, lo que se hará, es escribir una pequeña clase en Java que servirá para interactuar con métodos nativos.

Yo llamare la clase como CLS_NativeData:

public class CLS_NativeData {

    private native String getValue(int i);

	public CLS_NativeData() {
		super();
	}

	static {
		System.loadLibrary("NativeData");
	}

	public String getData(int i) {
		return getValue(i);
	}
}

Como se puede ver, es una clase sencilla, no tiene nada complejo para entender. Se puede observar 2 cosas importantes. El método “getValue” tiene un atributo llamado “native” lo cual indica que es un método nativo. También el método “System.loadLibrary” a cual pasando el nombre de la librería que se va a enlazar con la aplicación. El método “getData” servirá para acceder al método nativo “getValue” ya que ésta, esta declarada como privada, aunque esto es solo en éste caso, ustedes pueden declararlo como publico y evitar de poner otro método “accesor”.

Una vez teniendo la clase escrita, se generará el encabezado de C mediante la herramienta  Javah mencionada anteriormente. Para realizar todo el proceso de manera correcta y ordenada, se creara una carpeta en el escritorio. Llamarlo como quieran, yo la llamaré JNI. Una vez creada la carpeta, se copia la clase Java que escribimos.

Ahora, ejecutamos el CMD (intérprete de comandos del Windows), navegamos hacia la carpeta del JNI y escribimos “javah -jni CLS_NativeData“:

CMD CMD

Veremos que se nos genero el encabezado de C:

Encabezado de C Encabezado de C

Antes de avanzar, quiero comentarles otra cosa importante. Si nuestra clase Java se encuentra en un Package, entonces tendremos que generar el encabezado un poco diferente. Supongamos que la clase se encuentra en un package llamado “pkg_main”. Lo que tendremos que hacer es, en la carpeta JNI que tenemos creada, crear otra carpeta con el nombre de nuestro package y copiar ahí la clase Java (en mi caso la clase CLS_NativeData):

Carpeta del Package Carpeta del Package

Entonces volvemos al CMD y escribimos “javah -jni pkg_main.CLS_NativeData“. Si se fijan solo indicamos delante de “CLS_NativeData” el nombre del Package “pkg_main” concatenando mediante un punto.

Ahora podemos abrir el encabezado generado y ver el código:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class CLS_NativeData */

#ifndef _Included_CLS_NativeData
#define _Included_CLS_NativeData
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     CLS_NativeData
 * Method:    getValue
 * Signature: (I)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_CLS_1NativeData_getValue
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif

Se puede observar el método que se genero a partir del método nativo definido en la clase Java:

JNIEXPORT jstring JNICALL Java_CLS_1NativeData_getValue
  (JNIEnv *, jobject, jint);

Ahora, pasaremos a crear la DLL (Biblioteca de enlace dinámico). Yo usaré el Visual Studio 2012. Ustedes puedan usar cualquier otro IDE de C/C++.

Creamos nuevo proyecto, “Aplicación Win32” y seleccionamos la opción de “Biblioteca de vínculos dinámicos” y le damos al botón “Finalizar

Aplicación Win32 Aplicación Win32

Listo, lo que se hará ahora, es importar los encabezados “jni.h“,  “jni_md.h” y el encabezado que se genero con Javah (en mi caso “CLS_NativeData.h“) en el proyecto recién creado.

Encabezados en el proyecto Encabezados en el proyecto

Ahora a escribir el código. Primero se harán los “include” necesarios que en este caso seria solo:

#include "pkg_main_CLS_NativeData.h"

Así se podrá acceder al método generado. Vamos a copiar el método en el archivo CPP y definir bien las variables del método, quedando algo similar a:

#include "stdafx.h"
#include "pkg_main_CLS_NativeData.h"

JNIEXPORT jstring JNICALL Java_pkg_1main_CLS_1NativeData_getValue
  (JNIEnv * env, jobject obj, jint i)
{

}

Ahora programaremos el resto del código. Pondremos un switch para realizar alguna acción según el valor que se envíe:

#include "stdafx.h"
#include "pkg_main_CLS_NativeData.h"

JNIEXPORT jstring JNICALL Java_pkg_1main_CLS_1NativeData_getValue
  (JNIEnv * env, jobject obj, jint i)
{
	switch(i) {
	case 0:
		return env->NewStringUTF("Caso 0: Esto es una prueba de JNI!");
		break;
	case 1:
		return env->NewStringUTF("Caso 1: Esto es una prueba de JNI!");
		break;
	default:
		return env->NewStringUTF("Security Null");
	}
}

Antes de seguir, quiero comentarles, que dependiendo de la arquitectura del JVM, se tendrá que compilar la librería en x86 o x64, porque si no, recibirán un bonito “Crash” de la aplicación.

Creo que el código esta listo para ser compilado y testeado :). Después de compilar, se renombra la librería compilada con el nombre que tenemos puesto en la clase Java, porque en caso contrario, saltará una excepción del intento de enlazar la librería.

Finalmente instanciamos la clase Java y llamamos al método nativo pasando-le los parámetros:

CLS_NativeData tNativeData = new CLS_NativeData();

System.out.println(tNativeData.getData(0));
System.out.println(tNativeData.getData(1));

La salida:

Caso 0: Esto es una prueba de JNI!
Caso 1: Esto es una prueba de JNI!

Conclusión

Como verán, con unos pasos sencillos, se puede enlazar librerías nativas a la aplicación Java y darle mucho mas potencial de lo que tiene.

Yo estoy muy contento porque esta forma de tener acceso a nivel sistema operativo me facilitó la faena mas de una vez. Espero que les haya gustado el articulo, cualquier duda, comenten. 🙂

Saludos!