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 JVM
1. 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:
- JNIEnv *: Un puntero al entorno JNI. Este puntero es un manejador del thread actual en la máquina virtual Java y contiene mapeos y otra información útil.
- jobject: Una referencia a un método que llama a este código nativo. Si el método llamante es estático, esta parámetro podría ser del tipo jclass en lugar de jobject.
- jstring: El parámetro suministrado al método nativo. En este ejemplo, es el nombre del fichero a leer.
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:
- 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.
- 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