La plataforma Java es relativamente nueva, lo que significa que algunas veces podr�amos necesitar integrar programas escritos en Java con servicios, programas o APIs existentes escritos en lenguajes distintos en Java. La plataforma Java proporciona el Interfa Nativo Java (JNI) para ayudarnos con este tipo de integraci�n.
El JNI define una convenci�n de nombres y llamadas para que la M�quina Virtual Java pueda localizar e invocar a los m�todos nativos. De hecho, JNI est� construido dentro de la m�quina virtual Java, por lo que �sta puede llamar a sistemas locales para realizar entrada/salida, g�raficos, trabajos de red y operaciones de threads sobre el host del sistema operativo.
Este cap�tulo explica como usar JNI en programas escritos en Java para llamar a cualquier librer�a de la m�quina local, llamar a m�todos del lenguaje Java desde dentro del c�digo nativo, y c�mo crear y ejecutar un ejemplar de la JVM. Para mostrar c�mo podemos hacer funcionar el JNI, los ejemplos de este cap�tulo incluyen integraci�n de JNI con el API de bases de datos Xbase de C++. y c�mo podemos llamar a una funci�n matem�tica. Xbase tiene fuentes que podemos descargar.
�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. 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
�Strings y Arrays
Esta secci�n explica c�mo pasar datos string y array entre un programa escrito en Java y otros lenguajes.
�Pasar Strings
El objeto String en el lenguaje Java, que est� representado como jstring en JNI, es string unicode de 16 bits. En C un string por defecto est� construido con caracteres de 8 bits. Por eso, para acceder a objetos String Java pasados a un funci�n C � C++ o devolver objetos un string C � C++ a un m�todo Java, necesitamos utilizar las funciones de conversi�n JNI en nuestra implementaci�n del m�todo nativo.
La funci�n GetStringUTFChar recupera caracteres de bits desde un jstring de 16 bits usando el Formato de Transformaci�n Unicode (UTF). UTF representa los caracteres Unicode como un string de 8 � 16 bits sin perder ninguna informaci�n. El terpcer par�metro GetStringUTFChar es el resultado JNI_TRUE si se hace una copia olcar de jstring o JNI_FALSE si no se hace.
C Version: (*env)->GetStringUTFChars(env, name, iscopy) C++ Version: env->GetStringUTFChars(name, iscopy)
La siguiente funci�n C de JNI convierte un array de caracteres C en un jstring:
(*env)->NewStringUTF(env, lastfile)
El siguiente ejemplo convierte el array de caracteres C lastfile[80] en un jstring, que es devuelto al m�todo Java que lo llam�:
static char lastfile[80];
JNIEXPORT jstring JNICALL Java_ReadFile_lastFile
(JNIEnv *env, jobject jobj) {
return((*env)->NewStringUTF(env, lastfile));
}
Para permitir que la JVM conozca como hemos terminado la representaci�n UTF, llamamos a la funci�n de conversi�n ReleaseStringUTFChars como se muestra abajo. El segundo argumento es el valor del jstring original usado para construir la representaci�n UTF, y el tercer argumento es la referencia a la representaci�n local de ese String.
(*env)->ReleaseStringUTFChars(env, name, mfile);
Si nuestro c�digo nativo puede funcionar con Unicode, sin necesidar de representaciones UTF intermedias, llamamos al funci�n GetStringChars para recuperar el string Unicode, y liberar la referencia con una llamada a ReleaseStringChars:
JNIEXPORT jbyteArray JNICALL Java_ReadFile_loadFile
(JNIEnv * env, jobject jobj, jstring name) {
caddr_t m;
jbyteArray jb;
struct stat finfo;
jboolean iscopy;
const jchar *mfile = (*env)->GetStringChars(env,
name, &iscopy);
//...
(*env)->ReleaseStringChars(env, name, mfile);
�Pasar Arrays
En el ejemplo presentado en la �ltima secci�n, el m�todo nativo loadFile devuelve el contenido de un fichero en un array de bytes, que es un tipo primitivo del lenguaje Java. Podemos recuperar y crear tipos primitivos java llamando a la funci�n TypeArray apropiada.
Por ejemplo, para crear un nuevo array de floats, llamamos a NewFloatArray, o para crear un nuevo array de bytes, llamamos a NewByteArray. Este esquema de nombres se extiende para la recuperaci�n de elementos, para a�adir elementos, y para modificar elementos del array. Para obtener un nuevo array de bytes, llamamos a GetByteArrayElements. Para a�adir o modificar elementos en el array, llamamos a Set<type>ArrayElements.
La funci�n GetByteArrayElements afecta a todo el array. Para trabajar con un proci�n del array, llamamos a GetByteArrayRegion. S�lo hay una funci�n Set<type>ArrayRegion para modificar elementos de un array. Sin embargo la regi�n podr�a tener un tama�o 1, lo que ser�a equivalente a la no-existente Sete<type>ArrayElements.
| Tipo de C�digo Nativo | Funciones usadas |
|---|---|
| jboolean | NewBooleanArray |
� | GetBooleanArrayElements |
� | GetBooleanArrayRegion/SetBooleanArrayRegion |
� | ReleaseBooleanArrayRegion |
| jbyte | NewByteArray |
� | GetByteArrayElements |
� | GetByteArrayRegion/SetByteArrayRegion |
� | ReleaseByteArrayRegion |
| jchar | NewCharArray |
� | GetCharArrayElements |
� | GetCharArrayRegion/SetCharArrayRegion |
� | ReleaseCharArrayRegion |
| jdouble | NewDoubleArray |
� | GetDoubleArrayElements |
� | GetDoubleArrayRegion/SetDoubleArrayRegion |
� | ReleaseDoubleArrayRegion |
| jfloat | NewFloatArray |
� | GetFloatArrayElements |
� | GetFloatArrayRegion/SetFloatArrayRegion |
� | ReleaseFloatArrayRegion |
| jint | NewIntArray |
� | GetIntArrayElements |
� | GetIntArrayRegion/SetIntArrayRegion |
� | ReleaseIntArrayRegion |
| jlong | NewLongArray |
� | GetLongArrayElements |
� | GetLongArrayRegion/SetLongArrayRegion |
� | ReleaseLongArrayRegion |
| jobject | NewObjectArray |
� | GetObjectArrayElement/SetObjectArrayElement |
| jshort | NewShortArray |
� | GetShortArrayElements |
� | GetShortArrayRegion/SetShortArrayRegion |
� | ReleaseShortArrayRegion |
En el m�todo nativo loadFile del ejemplo de la secci�n anterior, se actualiza el array entero especificando una regi�n que tiene el tam�o del fichero que est� siendo le�do:
jbyteArray jb; jb=(*env)->NewByteArray(env, finfo.st_size); (*env)->SetByteArrayRegion(env, jb, 0, finfo.st_size, (jbyte *)m); close(fd);
El array es devuelto al m�todo Java llamandte, que luego, env�a al recolector de basura la referencia del array cuando ya no es utilizado. El array puede ser liberado expl�citamente con la siguiente llamada:
(*env)-> ReleaseByteArrayElements(env, jb,
(jbyte *)m, 0);
El �ltimo argumento de la funci�n ReleaseByteArrayElements puede tener los siguientes valores:
- 0: Las actualizaciones del array desde dentro del c�digo C ser�n reflejadas en la copia Java.
- JNI_COMMIT: La copia Java es actualizada, pero el jbyteArray local no es liberado.
- JNI_ABORT: Los Cambios no son copiados de vuelta, pero el jbyteArray es liberado. El valor usado su el array se obtiene con el mode get de JNI_TRUE significa que el array es una copia.
�Pinning Array
Cuando recuperamos un array, podemos especificar si es una copia (JNI_TRUE) o una referecia del array que reside en el programa Java (JNI_FALSE). Si usamos una referencia al array, querremos que el array permanezca en la pila java y que no sea eliminado por el recolector de basura cuando compacte la pila de memoria. Para evitar que las referencias al array sean eliminadas, la M�quina Virtual Java "clava" el array en la memoria. Clavar el array nos asegura que cuando el array sea liberado, los elementos correctos ser�n actualziados en la JVM.
En el m�todo nativo loadfile del ejemplo de la p�gina anterior, el array no se liber� expl�citamente. Una forma de asegurarnos de que el array es recolectado por el recolector de basura cuando ya no lo necesitamos, es llamar al m�todo Java, pasarle el array de bytes y luego liberar la copia local del array. Esta t�cnica se muestra en la secci�n Arrays Multi-Dimensionales.
�Arrays de Objetos
Podemos almacenar cualquier objeto Java enun array con llamadas a las funciones NewObjectArray y SetObjectArrayElement. La principal diferencia entre un array de objetos y un array de tipos primitivos es que cuando se construyen se usa una clase jobjectarray Java, como un par�metro.
El siguiente ejemplo C++ muestra c�mo llamar a NewObjectArray para crear un array deobjetos String. El tama�o del array se configurar� a cinco. la definici�n de la clase es devuelta desde una llamada a FindClass, y los elementos del array ser�n inicializados con un cadena vac�a. Los elementos del array se actualizar�n llamando a SetObjectArrayElement con la posici� y el valor a poner en el array.
#include <jni.h>
#include "ArrayHandler.h"
JNIEXPORT jobjectArray JNICALL
Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
jobjectArray ret;
int i;
char *message[5]= {"first",
"second",
"third",
"fourth",
"fifth"};
ret= (jobjectArray)env->NewObjectArray(5,
env->FindClass("java/lang/String"),
env->NewStringUTF(""));
for(i=0;i<5;i++) {
env->SetObjectArrayElement(
ret,i,env->NewStringUTF(message[i]));
}
return(ret);
}
La clase java que llama a este m�todo nativo es la siguiente:
public class ArrayHandler {
public native String[] returnArray();
static{
System.loadLibrary("nativelib");
}
public static void main(String args[]) {
String ar[];
ArrayHandler ah= new ArrayHandler();
ar = ah.returnArray();
for (int i=0; i<5; i++) {
System.out.println("array element"+i+
"=" + ar[i]);
}
}
}
�Arrays Multi-Dimensionales
Podr�amos necesitar llamar a liber�as num�ricas y matem�ticas existentes como la librer�a de �lgebra lineal CLAPACK/LAPACK u otros programas de c�lculo de matrices desde nuestro programa Java. Muchas de estas librer�as y programas usando arrays de dos o m�s dimensiones.
En el lenguaje java, cualquier array que tenga m�s de una dimensi�n es tratado como un array de arrys. Por ejemplo, un array de enteros de dos dimensiones es manejado como un array de arrays de enteros. El array se lee horizontalmente, o tambi�n conocido como �rden de fila.
Otros lenguajes como FORTRAN usan la ordenaci�n por columnas, por eso es necesario un cuidado extra su nuestro programa maneja un array Java a una funci�n FORTRAN. Tambi�n, los elementos de un array de una aplicaci�n Java no est� garantizado que sean contiguos en la memoria. Algunas librer�as usan el conocimiento de que los elementos de un array se almacenan uno junto al otro en la memoria para realizar optimizaciones de velocidad, por eso podr�amos necesitar hacer una copia local del array para pasarselo a estas funciones.
El siguiente ejemplo pasad un array de dos dimensiones a un m�todo nativo que extrae los elementos, realiza un c�lculo, y llama al m�todo Java para devolver los resultados.
El array es pasado como un objeto array que contiene un array de jints. Los elementos individuales se extraen primero recuperando un ejemplar de jintArray desde el objeto array llamando a GetObjectArrayElement, y luego se extraen los elementos desde la fila jintArray.
El ejemplo usa una matriz de tama�o fijo. Su no conocemos el tama�o del array que se est� utilizando, la funci�n GetArrayLength(array) devuelve el tama�o del array m�s exterior. Necesitaremos llamar a la funci�n GetArrayLength(array) sobre cada dimensi�n del array para descubrir su tama�o total.
El nuevo array enviado de vuelta al programa Java est� construido a la inversa. Primero, se crea un ejemplar de jintArray y este ejemplar se pone en el objeto array llamando a SetObjectArrayElement.
public class ArrayManipulation {
private int arrayResults[][];
Boolean lock=new Boolean(true);
int arraySize=-1;
public native void manipulateArray(
int[][] multiplier, Boolean lock);
static{
System.loadLibrary("nativelib");
}
public void sendArrayResults(int results[][]) {
arraySize=results.length;
arrayResults=new int[results.length][];
System.arraycopy(results,0,arrayResults,
0,arraySize);
}
public void displayArray() {
for (int i=0; i<arraySize; i++) {
for(int j=0; j <arrayResults[i].length;j++) {
System.out.println("array element "+i+","+j+
"= " + arrayResults[i][j]);
}
}
}
public static void main(String args[]) {
int[][] ar = new int[3][3];
int count=3;
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
ar[i][j]=count;
}
count++;
}
ArrayManipulation am= new ArrayManipulation();
am.manipulateArray(ar, am.lock);
am.displayArray();
}
}
#include <jni.h>
#include <iostream.h>
#include "ArrayManipulation.h"
JNIEXPORT void
JNICALL Java_ArrayManipulation_manipulateArray
(JNIEnv *env, jobject jobj, jobjectArray elements,
jobject lock){
jobjectArray ret;
int i,j;
jint arraysize;
int asize;
jclass cls;
jmethodID mid;
jfieldID fid;
long localArrayCopy[3][3];
long localMatrix[3]={4,4,4};
for(i=0; i<3; i++) {
jintArray oneDim=
(jintArray)env->GetObjectArrayElement(
elements, i);
jint *element=env->GetIntArrayElements(oneDim, 0);
for(j=0; j<3; j++) {
localArrayCopy[i][j]= element[j];
}
}
// With the C++ copy of the array,
// process the array with LAPACK, BLAS, etc.
for (i=0;i<3;i++) {
for (j=0; j<3 ; j++) {
localArrayCopy[i][j]=
localArrayCopy[i][j]*localMatrix[i];
}
}
// Create array to send back
jintArray row= (jintArray)env->NewIntArray(3);
ret=(jobjectArray)env->NewObjectArray(
3, env->GetObjectClass(row), 0);
for(i=0;i<3;i++) {
row= (jintArray)env->NewIntArray(3);
env->SetIntArrayRegion((jintArray)row,(
jsize)0,3,(jint *)localArrayCopy[i]);
env->SetObjectArrayElement(ret,i,row);
}
cls=env->GetObjectClass(jobj);
mid=env->GetMethodID(cls, "sendArrayResults",
"([[I)V");
if (mid == 0) {
cout <<"Can't find method sendArrayResults";
return;
}
env->ExceptionClear();
env->MonitorEnter(lock);
env->CallVoidMethod(jobj, mid, ret);
env->MonitorExit(lock);
if(env->ExceptionOccurred()) {
cout << "error occured copying array back" << endl;
env->ExceptionDescribe();
env->ExceptionClear();
}
fid=env->GetFieldID(cls, "arraySize", "I");
if (fid == 0) {
cout <<"Can't find field arraySize";
return;
}
asize=env->GetIntField(jobj,fid);
if(!env->ExceptionOccurred()) {
cout<< "Java array size=" << asize << endl;
} else {
env->ExceptionClear();
}
return;
}
�Otros Problemas de Programaci�n
Esta secci�n presenta informaci�n sobre acceso a clases, m�todos y campos, y cubre los threads, la memoria y la JVM.
�Problemas de Lenguaje
Hasta ahora, los ejemplos de m�todos nativos han cuvierto llamadas solitarias a funciones C y c++ que o devuelven un resultado o modifican los par�metro pasados a la funci�n. Sin embargo, C++ al igual que utiliza ejemplares de clases. si creamos una clase en un m�todo nativo, la referencia a esta clase no tiene una clase equivalente en el lenguaje Java, lo que hace d�ficil llamar a funciones de la clase C++ que se cre� primero.
Una forma de manejar esta situaci�n es mantener un registtro de las clases C++ referencias y pasadas de vuelta a un proxy o al programa llamante. Para asegurarnos de que una clase C++ persiste a trav�s de llamadas a m�todo nativos, usamos el operador new de C++ para crear una referencia al objeto C++ en la pila.
El siguiente c�digo proporciona un mapeo entre la base de datos Xbase y c�digo en lenguaje Java. La base de datos Xbase tiene un API C++ y usa inicializaci�nde clases para realizar operaciones subsecuentes en la base de datos. Cuando se crea el objeto clase, se devuelve un puntero a este objeto como una valor int al lenguaje Java. Podemos usar un valor long o mayor para m�quinas mayores de 32 bits.
public class CallDB {
public native int initdb();
public native short opendb(String name, int ptr);
public native short GetFieldNo(
String fieldname, int ptr);
static {
System.loadLibrary("dbmaplib");
}
public static void main(String args[]) {
String prefix=null;
CallDB db=new CallDB();
int res=db.initdb();
if(args.length>=1) {
prefix=args[0];
}
System.out.println(db.opendb("MYFILE.DBF", res));
System.out.println(db.GetFieldNo("LASTNAME", res));
System.out.println(db.GetFieldNo("FIRSTNAME", res));
}
}
El valor del resultado devuelto desde la llamada al m�todo nativo initdb, se pasa a las sigueintes llamadas al m�todo nativo. El c�digo nativo incluido en la librer�a dbmaplib.cc des-referencia el objeto Java pasado como par�metro y recupera el objeto puntero. La l�nea xbDbf* Myfile=(xbDbf*)ptr; fuerza el valor del puntero init a ser un punetro del tipo Xbase xbDbf.
#include <jni.h>
#include <xbase/xbase.h>
#include "CallDB.h"
JNIEXPORT jint JNICALL Java_CallDB_initdb(
JNIEnv *env, jobject jobj) {
xbXBase* x;
x= new xbXBase();
xbDbf* Myfile;
Myfile =new xbDbf(x);
return ((jint)Myfile);
}
JNIEXPORT jshort JNICALL Java_CallDB_opendb(
JNIEnv *env, jobject jobj,
jstring dbname, jint ptr) {
xbDbf* Myfile=(xbDbf*)ptr;
return((*Myfile).OpenDatabase( "MYFILE.DBF"));
}
JNIEXPORT jshort JNICALL Java_CallDB_GetFieldNo
(JNIEnv *env, jobject jobj,
jstring fieldname,
jint ptr) {
xbDbf* Myfile=(xbDbf*)ptr;
return((*Myfile).GetFieldNo(
env->GetStringUTFChars(fieldname,0)));
}
�Llamar a M�todos
La secci�n sobre los arrays ilumin� algunas razones por las que llamar a m�todo Java desde dentro de c�digo nativo; por ejemplo, cuando necesitamos liberar el resultado que intentamos devolver. Otros usos de las llamadas a m�todo java desde dentro de c�digo nativo podr�a ser si necesitamos devolver m�s de un resultado o simplemente queremos modificar valores jaba desde dentro del c�digo nativo.
Llamar a m�todos Java desde dentro de c�digo nativo implica estos tres pasos:
- Recuperar una Referencia a la Clase.
- Recuperar un identificador de m�todo.
- LLamar a los m�todos.
�Recuperar una Referencia de Clase
Es primer paso es recuperar una referencia a una clase que contenga los m�todos a los que queremos acceder. Para recuperar una referencia, podemos usar el m�todo FindClass o aceder a los argumentos jobject p jclass para el m�todo nativo:
Usa el m�todo FindClass:
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
jclass cls = (*env)->FindClass(env, "ClassName");
}
Usa el argumento jobject:
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
jclass cls=(*env)->GetObjectClass(env, jobj);
}
Usa el argumento jclass:
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jclass jcls){
jclass cls=jcls;
}
�Recuperar un identificador de M�todo
Una vez que hemos obtenido la clase, el segundo paso es llamar a la funci�n GetMethodID para recuperar un identificador para un m�todo que seleccionemos de la clase. El identificador es necesario cuando llamamos al m�todo de este ejemplar de la clase. Como el lenguaje Java soporta sobrecarga de m�todo, tambi�n necesitamos espec�ficar la firma particular del m�todo al que queremos llamar. Para encontar qu� firma usa nuestro m�todo Java, ejecutamos el comando javap de esta forma:
javap -s Class
La firma del m�todo usasa se muestra como un comentario despu�s de cada declaraci�n de m�todo como se ve aqu�:
bash# javap -s ArrayHandler
Compiled from ArrayHandler.java
public class ArrayHandler extends java.lang.Object {
java.lang.String arrayResults[];
/* [Ljava/lang/String; */
static {};
/* ()V */
public ArrayHandler();
/* ()V */
public void displayArray();
/* ()V */
public static void main(java.lang.String[]);
/* ([Ljava/lang/String;)V */
public native void returnArray();
/* ()V */
public void sendArrayResults(java.lang.String[]);
/* ([Ljava/lang/String;)V */
}
Usamos la funci�n GetMethodID para llamar a m�todos de ejemplar de un ejemplar del objeto. o usamos la funci�n GetStaticMethodID para llamar a un m�todo est�tico. Sus listas de argumentos son iguales.
�Llamar a M�todos
Tercero, se llama al m�todo de ejemplar correspndiente usando una funci�n Call<type>Method. El valor type puede ser Void, Object, Boolean, Byte, Char, Short, Int, Long, Float, o Double.
Los param�tros para el m�todo pueden pasarse como una lista separada por coma, un array de valores a la funci�n Call<type>MethodA, o como una va_list. El va_list es una construccu�n usada frecuentemente como lista de argumentos en C. CallMethodV es la funci�n usada para pasar un va_list ().
Los m�todos est�ticos son llamados de una forma similar excepto en que el nombre del m�todo incluye un indenficador Satic adicional, CallStaticByteMethodA, y se usa el valor jclass en lugar del valor jobject.
El siguiente ejemplo devuelve un objeto array llamando al m�todo sendArrayResults desde la clase ArrayHandler.
// ArrayHandler.java
public class ArrayHandler {
private String arrayResults[];
int arraySize=-1;
public native void returnArray();
static{
System.loadLibrary("nativelib");
}
public void sendArrayResults(String results[]) {
arraySize=results.length;
arrayResults=new String[arraySize];
System.arraycopy(results,0,
arrayResults,0,arraySize);
}
public void displayArray() {
for (int i=0; i<arraySize; i++) {
System.out.println("array element "+i+ "= " + arrayResults[i]);
}
}
public static void main(String args[]) {
String ar[];
ArrayHandler ah= new ArrayHandler();
ah.returnArray();
ah.displayArray();
}
}
El c�digo nativo C++ se define de esta forma:
#include <jni.h>
#include <iostream.h>
#include "ArrayHandler.h"
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
jobjectArray ret;
int i;
jclass cls;
jmethodID mid;
char *message[5]= {"first",
"second",
"third",
"fourth",
"fifth"};
ret=(jobjectArray)env->NewObjectArray(5,
env->FindClass("java/lang/String"),
env->NewStringUTF(""));
for(i=0;i<5;i++) {
env->SetObjectArrayElement(
ret,i,env->NewStringUTF(message[i]));
}
cls=env->GetObjectClass(jobj);
mid=env->GetMethodID(cls,
"sendArrayResults",
"([Ljava/lang/String;)V");
if (mid == 0) {
cout "<<Can't find method sendArrayResults";
return;
}
env->ExceptionClear();
env->CallVoidMethod(jobj, mid, ret);
if(env->ExceptionOccurred()) {
cout << "error occured copying array back" <<endl;
env->ExceptionDescribe();
env->ExceptionClear();
}
return;
}
Para construir esto sobre Linux, ejecutamos los siguientes comandos:
javac ArrayHandler.java javah -jni ArrayHandler g++ -o libnativelib.so -shared -Wl,-soname,libnative.so -I/export/home/jdk1.2/include -I/export/home/jdk1.2/include/linux nativelib.cc -lc
Si queremos especificar un m�todo de superclase, por ejemplo para llamar al constructor de padre, podemos hacerlo llamando a las funciones CallNonvirtual<type>Method.
Un punto importante cuando llamamos a m�todos Java o a campos desde dentro del c�digo nativo es que necesitamos capturar las excepciones lanzadas. La funci�n ExceptionClear limpia cualquier excepci�n pendiente miesntras que la funci�n ExceptionOccured chequea para ver si se ha lanzado alguna excepci�n en la sesi�n actual JNI.
�Acceder a Campos
Acceder a campos Java desde dentro de c�digo nativo es similar a llamar a m�todos Java. Sin emnargo, el campo es recuperado con un ID de campo en lugar de un ID de m�todo.
Lo primero que necesitamos es recuperar el ID de un campo. Podemos usar la funci�n GetFieldID, especificando el nombre del campo y la firma en lugar del nombre y la firma del m�todo. Una vez que tenemos el ID del campo, llamamos a una funci�n Get<type>Field. El <type> es el mismo tipo nativo que est� siendo devuelto excepto que se quita la j y la primera letra se pone en may�sculas. Por ejemplo el valor <type> es Int para el tipo nativo jint, y Byte para el tipo nativo jbyte.
El resultado de la funci�n Get<type>Field es devuelto como el tipo nativo. Por ejemplo, para recuperar el campo arraySize de la clase ArrayHandler, llamamos a GetIntField como se ve en el siguiente ejemplo.
El campo puede ser seleccionado llamando a las funciones env->SetIntField(jobj, fid, arraysize) . Los campos est�ticos pueden ser configurados llamando a SetStaticIntField(jclass, fid, arraysize) y recuperados llamando a GetStaticIntField(jobj, fid).
#include <jni.h>
#include <iostream.h>
#include "ArrayHandler.h"
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
jobjectArray ret;
int i;
jint arraysize;
jclass cls;
jmethodID mid;
jfieldID fid;
char *message[5]= {"first",
"second",
"third",
"fourth",
"fifth"};
ret=(jobjectArray)env->NewObjectArray(5,
env->FindClass("java/lang/String"),
env->NewStringUTF(""));
for(i=0;i<5;i++) {
env->SetObjectArrayElement(
ret,i,env->NewStringUTF(message[i]));
}
cls=env->GetObjectClass(jobj);
mid=env->GetMethodID(cls,
"sendArrayResults",
"([Ljava/lang/String;)V");
if (mid == 0) {
cout <<Can't find method sendArrayResults";
return;
}
env->ExceptionClear();
env->CallVoidMethod(jobj, mid, ret);
if(env->ExceptionOccurred()) {
cout << "error occured copying
array back" << endl;
env->ExceptionDescribe();
env->ExceptionClear();
}
fid=env->GetFieldID(cls, "arraySize", "I");
if (fid == 0) {
cout <<Can't find field arraySize";
return;
}
arraysize=env->GetIntField(jobj, fid);
if(!env->ExceptionOccurred()) {
cout<< "size=" << arraysize << endl;
} else {
env->ExceptionClear();
}
return;
}
�Threads y Sincronizaci�n
Aunque la librer�a nativa se carga una vez por cada clase, los threads individuales de una aplicaci�n escrita en Java usan su propio puntero interface cuando llaman a un m�todo nativo. Si necesitamos restringir el acceso a un objeto Java desde dentro del c�digo nativo, podemos asegurarnos de los m�todos Java a los que llamamos tienen sincronizaci�n expl�cita o podemos usar las funciones MonitorEnter y MonitorExit.
En el lenguaje Java, el c�digo est� protegido por un monitor siempre que especifiquemos la palabra clave synchronized. En Java el monitor que entra y sale de las rutinas normalmente est� oculto para el desarrollador de la aplicaci�n. En JNI, necesitamos delinear expl�citamente los puntos de la entrada y de salida del c�digo de seguridad del thread.
El siguiente ejemplo usa un objeto Boolean para reestringir el acceso a la funci�n CallVoidMethod.
env->ExceptionClear();
env->MonitorEnter(lock);
env->CallVoidMethod(jobj, mid, ret);
env->MonitorExit(lock);
if(env->ExceptionOccurred()) {
cout << "error occured copying array back" << endl;
env->ExceptionDescribe();
env->ExceptionClear();
}
Podr�amos encontrar que en caso donde queremos accder a recursos locales del sistema como un manejador MFC windows o una cola de mensajes, es mejor usar un Thread Java y acceder a la cola de eventos nativa o al sistema de mensajes dentro del c�digo nativo.
�Problemas de Memoria
Por defecto, JNI usa referencias locales cuando crea objetos dentro de un m�todo nativo. Esto significa que cuando el m�todo retorna, las referencias est�n disponibles para el recolector de basura. Si queremos que un objeto persista a trav�s de las llamadas a un m�todo nativo, debemos usar una referencia golbal. Una referencia global se crea desde una referencia local llamando a NewGlobalReference sobre la referencia local.
Podemos marcar expl�ctamente para el recolector de basura llamando a DeleteGlobalRef sobre la referencia. Tambi�n podemos crear una referencia global al estilo weak que sea accesible desde fuera del m�todo, pero puede ser recolectado por el recolector de basura. Para crear una de estas referencias, llamamos a NewWeakGlobalRef y DeleteWeakGlobalRef para marcar la referencia para la recolecci�n de basura.
Incluso podemos marcar expl�citamente una referencia local para la recolecci�n de basura llamando al m�todo env->DeleteLocalRef(localobject). Esto es �til si estamo usando una gran cantidad de datos temporales:
static jobject stringarray=0;
JNIEXPORT void JNICALL Java_ArrayHandler_returnArray
(JNIEnv *env, jobject jobj){
jobjectArray ret;
int i;
jint arraysize;
int asize;
jclass cls, tmpcls;
jmethodID mid;
jfieldID fid;
char *message[5]= {"first",
"second",
"third",
"fourth",
"fifth"};
ret=(jobjectArray)env->NewObjectArray(5,
env->FindClass("java/lang/String"),
env->NewStringUTF(""));
//Make the array available globally
stringarray=env->NewGlobalRef(ret);
//Process array
// ...
//clear local reference when finished..
env->DeleteLocalRef(ret);
}
�Invocaciones
La secci�n sobre llamadas a m�todos nos mostraba como llamar a un m�todo o campo Java usando el interface JNI y una clase cargada usando la funci�n FindClass. Con un poco m�s de c�digo, podemos crear un programa que invoque a la m�quina virtual Java e incluya su propio puntero al interface JNI que puede ser usado para crear ejemplares de clases Java. En Java 2, el programa de ejecuci�n llamando java es una peque�a aplicaci�n JNI que hace exactamente esto.
Podemos crear una m�quina virtual Java con una llamada a JNI_CreateJavaVM, y desconectar la m�quina virtual Java creada con una llamada a JNI_DestroyJavaVM. Una JVM tambi�n podr�a necesitar algunas propiedades adicionales de entorno. Estas propiedades podr�an pasarse a la funci�n JNI_CreateJavaVM en un estructura JavaVMInitArgs.
La estructura JavaVMInitArgs contiene un puntero a un valor JavaVMOption usado para almacenar informaci�n del entorno como el classpath y la versi�n de la m�quina virtual Java, o propiedades del sistema que podr�an pasarse normalmente en la l�nea de comandos del programa.
Cuando retorna la funci�n JNI_CreateJavaVM, podemos llamar a m�todo y crear ejemplares de clases usando las funciones FindClass y NewObject de la misma forma que lo har�amos con c�digo nativo embebido.
Nota: La invocaci�n de la m�quina virtual Java s�lo se usa para threads nativos en m�quinas virtuales Java. Algunas antiguas m�quinas virtuales Java tienen una opci�n de threads verdes que es estable para el uso de invocaciones, Sobre una plataforma Unix, podr�amos necesitar enlazar expl�citamente con -lthread o -lpthread.
El siguiente programa invoca una m�quina virtual Java, carga la clase ArrayHandler y recupera el campo arraySize que deber�a tener el valor menos uno. Las opciones de la m�quina virtual Java incluyen el path actual en el classpath y desactivar del compilador Just-In_Time (JIT) -Djava.compiler=NONE.
#include <jni.h>
void main(int argc, char *argv[], char **envp) {
JavaVMOption options[2];
JavaVMInitArgs vm_args;
JavaVM *jvm;
JNIEnv *env;
long result;
jmethodID mid;
jfieldID fid;
jobject jobj;
jclass cls;
int i, asize;
options[0].optionString = ".";
options[1].optionString = "-Djava.compiler=NONE";
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 2;
vm_args.ignoreUnrecognized = JNI_FALSE;
result = JNI_CreateJavaVM(
&jvm,(void **)&env, &vm_args);
if(result == JNI_ERR ) {
printf("Error invoking the JVM");
exit (-1);
}
cls = (*env)->FindClass(env,"ArrayHandler");
if( cls == NULL ) {
printf("can't find class ArrayHandler\n");
exit (-1);
}
(*env)->ExceptionClear(env);
mid=(*env)->GetMethodID(env, cls, "<init>", "()V");
jobj=(*env)->NewObject(env, cls, mid);
fid=(*env)->GetFieldID(env, cls, "arraySize", "I");
asize=(*env)->GetIntField(env, jobj, fid);
printf("size of array is %d",asize);
(*jvm)->DestroyJavaVM(jvm);
}
�Adjuntar Threads
Despu�s de invocar la m�quina virtual Java, hay un thread local ejecut�ndose en ella. Podemos crear m�s threads en el sistema operativo local y adjuntar threads en la m�quina virtual Java para estos nuevos threads. Podriamos querer hacer esto su nuestra aplicaci�n nativa es multi-threads.
Adjuntamos el thread local a la m�quina virtual Java con una llamada a AttachCurrentThread. Necesitamos suministrar punteros al ejemplar de la m�quina virtual Java y al entorno JNI. En la plataforma Java 2, podemos espec�ficar en el tercer par�metro el nombre del thread y/o el grupo bajo el que queremos que viva nuestro thread. Es importante eliminar cualquier thread que haya sido pr�viamente adjuntado; de otra forma, el programa no saldr� cuando llamemos a DestroyJavaVM.
#include <jni.h>
#include <pthread.h>
JavaVM *jvm;
void *native_thread(void *arg) {
JNIEnv *env;
jclass cls;
jmethodID mid;
jfieldID fid;
jint result;
jobject jobj;
JavaVMAttachArgs args;
jint asize;
args.version= JNI_VERSION_1_2;
args.name="user";
args.group=NULL;
result=(*jvm)->AttachCurrentThread(
jvm, (void **)&env, &args);
cls = (*env)->FindClass(env,"ArrayHandler");
if( cls == NULL ) {
printf("can't find class ArrayHandler\n");
exit (-1);
}
(*env)->ExceptionClear(env);
mid=(*env)->GetMethodID(env, cls, "<init>", "()V");
jobj=(*env)->NewObject(env, cls, mid);
fid=(*env)->GetFieldID(env, cls, "arraySize", "I");
asize=(*env)->GetIntField(env, jobj, fid);
printf("size of array is %d\n",asize);
(*jvm)->DetachCurrentThread(jvm);
}
void main(int argc, char *argv[], char **envp) {
JavaVMOption *options;
JavaVMInitArgs vm_args;
JNIEnv *env;
jint result;
pthread_t tid;
int thr_id;
int i;
options = (void *)malloc(3 * sizeof(JavaVMOption));
options[0].optionString = "-Djava.class.path=.";
options[1].optionString = "-Djava.compiler=NONE";
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 2;
vm_args.ignoreUnrecognized = JNI_FALSE;
result = JNI_CreateJavaVM(&jvm,(void **)&env, &vm_args);
if(result == JNI_ERR ) {
printf("Error invoking the JVM");
exit (-1);
}
thr_id=pthread_create(&tid, NULL, native_thread, NULL);
// If you don't have join, sleep instead
//sleep(1000);
pthread_join(tid, NULL);
(*jvm)->DestroyJavaVM(jvm);
exit(0);
}