Ejemplos JNI

Esta sección presenta el programa de ejemplo ReadFile. Este ejemplo muestra cómo podemos usar JNI para invocar un método nativo que hace llamadas a funciones C para mapear en fichero en la memoria.

Sobre el Ejemplo

Podemos llamar a código escrito en cualquier lenguaje de programación desde un pograma escrito en leguaje Java declarando un método nativo Java, cargando la librería que contiene el código nativo, y luego llamando al método nativo. El código fuente de ReadFile que hay más abajo hace exactamente esto.

Sin embargo, el exíto en la ejecución del programa requiere uno pocos pasos adicionales más allá de la compilación del fichero fuente Java. Después de compilar, pero antes de ejecutar el ejemplo, tenemos que generar un fichero de cabecera. El código nativo implementa las definiciones de funciones contenidas en el fichero de cabecera generado y también implementa la lógica de negocio. Las siguientes sección pasan a través de estos pasos:

import java.util.*;

class ReadFile {
//Native method declaration
  native byte[] loadFile(String name);
//Load the library
  static {
    System.loadLibrary("nativelib");
  }

  public static void main(String args[]) {
    byte buf[];
//Create class instance
    ReadFile mappedFile=new ReadFile();
//Call native method to load ReadFile.java
    buf=mappedFile.loadFile("ReadFile.java");
//Print contents of ReadFile.java
    for(int i=0;i<buf.length;i++) {
      System.out.print((char)buf[i]);
    }
  }
}

Declaración del método nativo

La declaración native proporciona el puente para ejecutar la función nativa en una JVM1. En este ejemplo, la función loadFile se mapea a un función C llamada Java_ReadFile_loadFile. La implementación de la función implementa un String que representa un nombre de fichero y devuelve el contenido de ese fichero en un array de bytes.
  native byte[] loadFile(String name);

Cargar la Librería

La librería que contiene la implementación del código nativo se carga con una llamada a System.loadLibrary(). Situando esta llamada en un inicializador estático nos aseguramos de que la librería sólo se cargará una vez por cada clase. La librería puede cargarse desde fuera del bloque estático si la aplicación así lo requiere. Podríamos necesitar configurar nuestro entorno para que el método loadLibrary pueda encontrar nuesta librería de código nativo:
  static {
    System.loadLibrary("nativelib");
  }

Compilar el Programa

Para compilar el program, sólo ejecutamos el comando del compilador javac como lo haríamos normalmente:
  javac ReadFile.java
Luego, necesitamos generar un fichero de cabecera con la declaración del método nativo y la implementación del método nativo para llamar a funciones para la carga y lectura de un fichero.

Generar el Fichero de Cabecera

Para generar un fichero de cabecera, ejecutamos el comando javah sobre la clase ReadFile. En este ejemplo, el fichero de cabecera generadp se llama ReadFile.h. Proporciona una firma de método que debemos utilizar cuando implementemos la función nativa loadfile.
  javah -jni ReadFile

Firma del Método

El fichero de cabecera ReadFile.h define el interface para mapear el método en lenguaje Java a la función nativa C. Utiliza una firma de método para mapear los argumentos y valor de retorno del método mappedfile.loadFile java al método nativo loadFile de la librería nativelib. Aquí está la firma del método nativo loadFile:
  /*
   * Class:     ReadFile
   * Method:    loadFile
   * Signature: (Ljava/lang/String;)[B
   */
  JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
    (JNIEnv *, jobject, jstring);
Los parámetros de la firma de la función son los siguientes:

Implementar el Método Nativo

En este fichero fuente nativo C, la definición de loadFile es una copia de la declaración C contenida en el fichero ReadFile.h. La definición es seguida por la implementación del método nativo. JNI proporciona mapeo por defecto tanto para C como para C++.
JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
  (JNIEnv * env, jobject jobj, jstring name) {
    caddr_t m;
    jbyteArray jb;
    jboolean iscopy;
    struct stat finfo;
    const char *mfile = (*env)->GetStringUTFChars(
                env, name, &iscopy);
    int fd = open(mfile, O_RDONLY);

    if (fd == -1) {
      printf("Could not open %s\n", mfile);
    }
    lstat(mfile, &finfo);
    m = mmap((caddr_t) 0, finfo.st_size,
                PROT_READ, MAP_PRIVATE, fd, 0);
    if (m == (caddr_t)-1) {
      printf("Could not mmap %s\n", mfile);
      return(0);
    }
    jb=(*env)->NewByteArray(env, finfo.st_size);
    (*env)->SetByteArrayRegion(env, jb, 0, 
	finfo.st_size, (jbyte *)m);
    close(fd);
    (*env)->ReleaseStringUTFChars(env, name, mfile);
    return (jb);
}
Podemos aproximarnos a llamar a un función C existente enlugar de implementar una, de alguna de estas formas:
  1. Mapear el nombre generado por JNI a un nombre de función C ya existente. La sección Problemas de Lenguaje muestra como mapear entre funciones de base de datos Xbase y código Java.

  2. Usar el código Stub compartido disponible desde la página JNI en la site de java.sun.com.

Compilar la Librería Dinámica o de Objetos Compartidos

La librería necesita ser compilada como una librería dinámica o de objetos compartidos para que pueda ser cargada durante la ejecución. Las librerías o archivos estáticos son compiladas dentro de un ejecutable y no pueden ser cargadas en tiempo de ejecución. La librería dinámica para el ejemplo loadFile se compila de esta forma:
Gnu C/Linux:

gcc  -o libnativelib.so -shared -Wl,-soname,libnative.so  
  -I/export/home/jdk1.2/
include -I/export/home/jdk1.2/include/linux nativelib.c  
  -static -lc

Gnu C++/Linux with Xbase

g++ -o libdbmaplib.so -shared -Wl,-soname,libdbmap.so  
  -I/export/home/jdk1.2/include 
  -I/export/home/jdk1.2/include/linux 
  dbmaplib.cc -static -lc -lxbase

Win32/WinNT/Win2000

cl -Ic:/jdk1.2/include 
  -Ic:/jdk1.2/include/win32 
  -LD nativelib.c -Felibnative.dll

Ejecutar el Ejemplo

Para ejecutar el ejemplo, la máquina virtual Java necesita poder encontrar la librería nativa. Para hacer esto, configurarmos el path de librerías al path actual de esta forma:
Unix or Linux:
  LD_LIBRARY_PATH=`pwd`
  export LD_LIBRARY_PATH


Windows NT/2000/95:
  set PATH=%path%;.
Con el path de librerías especificado de forma apropiada a nuestra plataforma, llamamos al programa como lo haríamos normalmente con el intérprete de comandos:
  java ReadFile

_______
1 Cuando se usan en toda esta site, los términos, "Java virtual machine" o "JVM" significa una máquina virtual de la plataforma Java.


Ozito