Programación en castellano
Inicio > Tutoriales > Lenguajes orientados a objeto > J2EE > Escribir Aplicaciones Avanzadas para la Plataforma Java 2
-Tutoriales

Escribir Aplicaciones Avanzadas para la Plataforma Java 2


Tecnología JNI

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:

  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

. 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 NativoFunciones usadas
jbooleanNewBooleanArray

 

GetBooleanArrayElements

 

GetBooleanArrayRegion/SetBooleanArrayRegion

 

ReleaseBooleanArrayRegion
jbyteNewByteArray

 

GetByteArrayElements

 

GetByteArrayRegion/SetByteArrayRegion

 

ReleaseByteArrayRegion
jcharNewCharArray

 

GetCharArrayElements

 

GetCharArrayRegion/SetCharArrayRegion

 

ReleaseCharArrayRegion
jdoubleNewDoubleArray

 

GetDoubleArrayElements

 

GetDoubleArrayRegion/SetDoubleArrayRegion

 

ReleaseDoubleArrayRegion
jfloatNewFloatArray

 

GetFloatArrayElements

 

GetFloatArrayRegion/SetFloatArrayRegion

 

ReleaseFloatArrayRegion
jintNewIntArray

 

GetIntArrayElements

 

GetIntArrayRegion/SetIntArrayRegion

 

ReleaseIntArrayRegion
jlongNewLongArray

 

GetLongArrayElements

 

GetLongArrayRegion/SetLongArrayRegion

 

ReleaseLongArrayRegion
jobjectNewObjectArray

 

GetObjectArrayElement/SetObjectArrayElement
jshortNewShortArray

 

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:

  1. Recuperar una Referencia a la Clase.
  2. Recuperar un identificador de método.
  3. 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);
}
 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad
Mantenida por: Claudio y Dani.

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: jugar gratis | amor | navidad 2009 | registro de dominios | servidores dedicados
más internet: comprar | gratis | posicionamiento en buscadores | decoración libre | gifs animados