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 Java desde dentro de código nativo implica estos tres pasos:
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;
}
La firma del método usasa se muestra como un comentario después de cada declaración de método como se ve aquí:javap -s Class
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.
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:
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.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
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.
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;
}
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.
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);
}
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.
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.
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.
#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);
}
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);
}
_______
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.