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 quela JVM1 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
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:

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;
}

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


Ozito