28
LIBRERÍAS NATIVAS Una de las aplicaciones de la JNI es escribir métodos nativos que aprovechan código existentes en las bibliotecas nativas. Un enfoque típico, cubierto en este capítulo, es la producción de una biblioteca de clase que ajusta un conjunto de funciones nativas. En este primer capítulo se analiza la forma más sencilla de escribir envoltura clases-uno-a-uno. Luego introducimos una técnica, talones compartidos, que simplifica la tarea de escribir clases contenedoras. Uno-a-uno y recibos comunes son técnicas para envolver nativo funciones. Al final de este capítulo, también vamos a discutir la forma de envolver los datos nativos estructuras mediante las clases de pares. Los enfoques descritos en este capítulo se exponen directamente a una librería nativa utilizando métodos nativos, y por lo tanto tienen la desventaja de hacer una aplicación que llama tales métodos nativos que dependen de la biblioteca nativa. Tal solicitud puede ejecutar sólo en un sistema operativo que suministra la biblioteca nativa. Una aproximación preferida es declarar independiente del sistema operativo métodos nativos. Sólo las funciones nativas la aplicación de esos métodos nativos utilizan las bibliotecas nativas directamente, limitando la necesidad de portar a las funciones nativas. La aplicación, incluyendo el declaraciones de métodos nativos, no necesita ser portado. 9.1 Uno-a-uno Empecemos con un ejemplo sencillo. Supongamos que queremos escribir un contenedor clase que expone la función atol en la biblioteca estándar de C: long atol(const char *str);

Traduccion capitulo 9 (completo)

Embed Size (px)

Citation preview

Page 1: Traduccion capitulo 9 (completo)

LIBRERÍAS NATIVAS

Una de las aplicaciones de la JNI es escribir métodos nativos que aprovechan código existentes en las bibliotecas nativas. Un enfoque típico, cubierto en este capítulo, es la producción de una biblioteca de clase que ajusta un conjunto de funciones nativas.

En este primer capítulo se analiza la forma más sencilla de escribir envoltura clases-uno-a-uno. Luego introducimos una técnica, talones compartidos, que simplifica la tarea de escribir clases contenedoras.

Uno-a-uno y recibos comunes son técnicas para envolver nativo funciones. Al final de este capítulo, también vamos a discutir la forma de envolver los datos nativos estructuras mediante las clases de pares.

Los enfoques descritos en este capítulo se exponen directamente a una librería nativa utilizando métodos nativos, y por lo tanto tienen la desventaja de hacer una aplicación que llama tales métodos nativos que dependen de la biblioteca nativa. Tal solicitud puede ejecutar sólo en un sistema operativo que suministra la biblioteca nativa. Una aproximación preferida es declarar independiente del sistema operativo métodos nativos. Sólo las funciones nativas la aplicación de esos métodos nativos utilizan las bibliotecas nativas directamente, limitando la necesidad de portar a las funciones nativas. La aplicación, incluyendo el declaraciones de métodos nativos, no necesita ser portado.

9.1 Uno-a-uno

Empecemos con un ejemplo sencillo. Supongamos que queremos escribir un contenedor clase que expone la función atol en la biblioteca estándar de C:

long atol(const char *str);

La función atol analiza una cadena y devuelve el valor decimal representado por la cadena. Quizás haya pocas razones para definir un método nativo en la práctica porque el método Integer.parseInt, parte de la API Java, suministra la funcionalidad equivalente. Evaluación de atol ("100"), por ejemplo, los resultados en el entero valor 100. Definimos una clase contenedora de la siguiente manera:

public class C {public static native int atol(String str);...}

Page 2: Traduccion capitulo 9 (completo)

En aras de ilustrar la programación de JNI en C + +, implementaremos métodos nativos en este capítulo usando C + + (§ 8.6). El C + + aplicación de la C.atol método nativo es el siguiente:

JNIEXPORT jint JNICALLJava_C_atol(JNIEnv *env, jclass cls, jstring str){const char *cstr = env->GetStringUTFChars(str, 0);if (cstr == NULL) {return 0; /* out of memory */}int result = atol(cstr);env->ReleaseStringUTFChars(str, cstr);return result;}

La aplicación es muy sencillo. Utilizamos GetStringUTFChars a convertir la cadena Unicode porque los números decimales son caracteres ASCII.

Veamos ahora un ejemplo más complejo que involucra la estructura pasa punteros a una función de C. Supongamos que queremos escribir una clase contenedora que expone la función CreateFile de la API Win32:

typedef void * HANDLE;typedef long DWORD;typedef struct {...} SECURITY_ATTRIBUTES;HANDLE CreateFile(const char *fileName, // file nameDWORD desiredAccess, // access (read-write) modeDWORD shareMode, // share modeSECURITY_ATTRIBUTES *attrs, // security attributesDWORD creationDistribution, // how to createDWORD flagsAndAttributes, // file attributesHANDLE templateFile // file with attr. to copy);

La función CreateFile es compatible con una serie de características específicas de Win32 no disponible en la API de archivos independiente de la plataforma Java. Por ejemplo, la función Create-File puede ser utilizado para especificar los modos especiales de acceso y los atributos de archivo a abrir las tuberías Win32 con nombre y para manejar las comunicaciones del puerto serie.

No vamos a discutir más detalles sobre la función CreateFile en este libro.

Page 3: Traduccion capitulo 9 (completo)

La atención se centrará en cómo CreateFile se pueden asignar a un método nativo definido en una clase de contenedor llamado Win32:

public class Win32 {public static native int CreateFile(String fileName, // file nameint desiredAccess, // access (read-write) modeint shareMode, // share modeint[] secAttrs, // security attributesint creationDistribution, // how to createint flagsAndAttributes, // file attributesint templateFile); // file with attr. to copy...}

El mapeo del tipo de puntero char a String es obvio. Mapeamos el nativo Win32 tipo long (DWORD) a int en el lenguaje de programación Java. El Win32 tipo HANDLE, un opaco 32-bit tipo de puntero, también se asigna a int.

Debido a las posibles diferencias en cómo los campos están expuestos en la memoria, lo que hacemos no del mapa estructuras C a clases en el lenguaje de programación Java. En su lugar, se utiliza una matriz para almacenar los contenidos de los SECURITY_ATTRIBUTES estructura C. La persona que llama

También puede pasar null como secAttrs para especificar la configuración predeterminada de Win32 atributos de seguridad.

No vamos a discutir el contenido de la estructura SECURITY_ATTRIBUTES o cómo que codificar en una matriz int.

Un C + + implementación del método nativo anterior es como sigue:

JNIEXPORT jint JNICALL Java_Win32_CreateFile(JNIEnv *env,jclass cls,jstring fileName, // file namejint desiredAccess, // access (read-write) modejint shareMode, // share modejintArray secAttrs, // security attributesjint creationDistribution, // how to createjint flagsAndAttributes, // file attributesjint templateFile) // file with attr. to copy{jint result = 0;jint *cSecAttrs = NULL;if (secAttrs) {cSecAttrs = env->GetIntArrayElements(secAttrs, 0);if (cSecAttrs == NULL) {

Page 4: Traduccion capitulo 9 (completo)

return 0; /* out of memory */}}

char *cFileName = JNU_GetStringNativeChars(env, fileName);if (cFileName) {/* call the real Win32 function */result = (jint)CreateFile(cFileName,desiredAccess,shareMode,(SECURITY_ATTRIBUTES *)cSecAttrs,creationDistribution,flagsAndAttributes,(HANDLE)templateFile);free(cFileName);}/* else fall through, out of memory exception thrown */if (secAttrs) {env->ReleaseIntArrayElements(secAttrs, cSecAttrs, 0);}return result;}

En primer lugar, convertir los atributos de seguridad almacenado en la matriz en una matriz de int jint. Si el argumento es una referencia secAttrs NULL, se pasa NULL como el atributo de seguridad a la función CreateFile de Win32. A continuación, hacemos un llamado a los JNU_GetStringNativeChars función de utilidad (§ 8.2.2) para obtener el nombre de archivo representado como una cadena de configuración regional específica C. Una vez que se han convertido los atributos de seguridad y el nombre del archivo, pasamos los resultados de las conversiones y el resto de argumentos a la función CreateFile de Win32.

Nosotros nos encargamos de comprobar las excepciones y liberar los recursos de la máquina virtual (como cSecAttrs).

Los ejemplos C.atol y Win32.CreateFile demostrar un enfoque común a la escritura de clases contenedoras y los métodos nativos. Cada función nativa (por ejemplo, CreateFile) se asigna a una función stub nativo único (por ejemplo, Java_Win32_CreateFile), que a su vez los mapas a una definición de método nativo único (por ejemplo, Win32.CreateFile). En uno-a-uno, el talón de función sirve para dos propósitos:

1. El talón se adapta convención la función nativa de paso de argumentos a lo que es esperado por la máquina virtual de Java. La máquina virtual espera que el nativo implementación del método a seguir una convención de nombres dado y aceptar dos argumentos adicionales (el puntero JNIEnv y el puntero "this").

2. El talón de conversión entre tipos de programación Java y los tipos nativos.

Page 5: Traduccion capitulo 9 (completo)

Por ejemplo, la función Java_Win32_CreateFile traduce la jstring nombre de archivo en una cadena específica de la configuración regional C.

9.2 Stubs compartidas

El enfoque de uno a uno de mapeo requiere escribir una función derivada para cada función nativa que desee ajustar. Esto se convierte en tedioso cuando se enfrentan con la tarea de escribir clases de envoltura para un gran número de funciones nativas. En esta sección se introduce el concepto de recibos comunes y demostrar cómo compartir talones se puede utilizar para simplificar la tarea de escribir clases contenedoras.

Un talón compartido es un método nativo que se distribuye a otras funciones nativas. La stub compartido es responsable de convertir los tipos de argumentos a partir de lo que se proporciona por la persona que llama lo que es aceptado por las funciones nativas.

Pronto presentaremos un CFunction código auxiliar de clase compartida, pero primero vamos a mostrar cómo puede simplificar la implementación del método C.atol:

public class C {private static CFunction c_atol =new CFunction("msvcrt.dll", // native library name"atol", // C function name"C"); // calling conventionpublic static int atol(String str) {return c_atol.callInt(new Object[] {str});}...}

C.atol ya no es un método nativo (y por lo tanto ya no se necesita una función stub).

En cambio, C.atol se define mediante la clase CFunction. La clase CFunction internamente implementa un trozo compartida. El C.c_atol variable estática almacena una CFunction objeto que corresponde a la función atol C en la biblioteca msvcrt.dll (la biblioteca C multihebra en Win32). El constructor CFunction llamar también especifica que atol sigue la convención de llamada de C (§ 11.4). Una vez que el c_atol campo se inicializa, las llamadas al método C.atol sólo necesitan la reexpedición a través de c_atol.callInt, el talón compartida.

La clase CFunction pertenece a una jerarquía de clases que vamos a construir y utilizar en breve:

Page 6: Traduccion capitulo 9 (completo)

Las instancias de la clase CFunction denotar un puntero a una función de C. CFunction es una subclase de CPointer, que denota punteros arbitrarias C:

public class CFunction extends CPointer {public CFunction(String lib, // native library nameString fname, // C function nameString conv) { // calling convention...}public native int callInt(Object[] args);...}

El método callInt toma como argumento una matriz de java.lang.Object. Lo inspecciona los tipos de los elementos de la matriz, los convierte (de jstring a char *, por ejemplo), y los pasa como argumentos a la función de C subyacente.

El método callInt entonces devuelve el resultado de la función subyacente C como int. La clase CFunction puede definir métodos como callFloat o callDouble para manejar las funciones de C con tipos de retorno.

La clase CPointer se define como sigue:

public abstract class CPointer {public native void copyIn(int bOff, // offset from a C pointerint[] buf, // source dataint off, // offset into sourceint len); // number of elements to be copiedpublic native void copyOut(...);...}

Page 7: Traduccion capitulo 9 (completo)

CPointer es una clase abstracta que permite el acceso arbitrario a los punteros de C. La copyin método, por ejemplo, copia un número de elementos de una matriz a int la memoria apuntada por el puntero de C. Este método debe usarse con cuidado ya que se puede utilizar fácilmente para corromper ubicaciones arbitrarias de memoria en la dirección espacio. Los métodos nativos como CPointer.copyIn son tan malas condiciones como puntero directo manipulación en C.

CMalloc es una subclase de CPointer que apunta a un bloque de memoria asignada en el montón de C usando malloc:

public class CMalloc extends CPointer {public CMalloc(int size) throws OutOfMemoryError { ... }public native void free();...}

El constructor CMalloc asigna un bloque de memoria del tamaño indicado en el C montón. El método CMalloc.free libera el bloque de memoria.

Equipado con las clases CFunction y CMalloc, podemos reimplementar Win32.CreateFile como sigue:

public class Win32 {private static CFunction c_CreateFile =new CFunction ("kernel32.dll", // native library name"CreateFileA", // native function"JNI"); // calling conventionpublic static int CreateFile(String fileName, // file nameint desiredAccess, // access (read-write) modeint shareMode, // share modeint[] secAttrs, // security attributesint creationDistribution, // how to createint flagsAndAttributes, // file attributesint templateFile) // file with attr. to copy{CMalloc cSecAttrs = null;if (secAttrs != null) {cSecAttrs = new CMalloc(secAttrs.length * 4);cSecAttrs.copyIn(0, secAttrs, 0, secAttrs.length);}try {return c_CreateFile.callInt(new Object[] {fileName,new Integer(desiredAccess),new Integer(shareMode),cSecAttrs,new Integer(creationDistribution),new Integer(flagsAndAttributes),new Integer(templateFile)});} finally {if (secAttrs != null) {cSecAttrs.free();

Page 8: Traduccion capitulo 9 (completo)

}}}...}

Estamos en caché el objeto CFunction en una variable estática. La API de Win32 Create-

El archivo se exporta desde kernel32.dll como CreateFileA. Otra entrada exportado, CreateFileW, toma una cadena Unicode como el argumento de nombre de archivo. Esta función sigue la convención JNI de llamada, que es la convención estándar de Win32 llamando (stdcall).

La aplicación Win32.CreateFile primero asigna un bloque de memoria en la C montón que es lo suficientemente grande para contener los atributos de seguridad temporal. A continuación, los paquetes todos los argumentos de una matriz y llama a la función subyacente C Create- FILEA a través de la operadora compartida. Finalmente, el método Win32.CreateFile libera el bloque de memoria C se utiliza para mantener los atributos de seguridad. Llamamos cSecAttrs.free en una cláusula finally para asegurarse de que la memoria temporal C se libera incluso si el llamada c_CreateFile.callInt lanza una excepción.

9.3 Uno-a-uno frente a Stubs compartidas

Uno-a-uno y talones compartidos son dos maneras de crear clases contenedoras para las bibliotecas nativas. Cada uno tiene sus propias ventajas.

La principal ventaja de talones compartidos es que el programador no necesita escribir una gran número de funciones auxiliares en código nativo. Una vez que una aplicación compartida stub tales como CFunction está disponible, el programador puede ser capaz de construir envoltura clases sin necesidad de escribir una sola línea de código nativo.

Talones compartidas deben usarse con cuidado, sin embargo. Con compartidos talones, programadores

son esencialmente escribir código C en el lenguaje de programación Java. Esta derrota a la seguridad de tipos del lenguaje de programación Java. Los errores en el uso de talones compartidos puede conducir a la memoria dañada y aplicación se bloquea.

La ventaja de uno-a-uno, es que normalmente es más eficaz en la conversión de los tipos de datos que se transfieren entre la máquina virtual Java y código nativo. Talones compartidas, por otro lado, puede manejar un máximo predeterminado conjunto de tipos de argumentos y no se puede

Page 9: Traduccion capitulo 9 (completo)

lograr un rendimiento óptimo incluso para estos tipos de argumentos. El llamador de CFunction.callInt siempre tiene que crear un Integer objeto para cada argumento int. Esto se suma a la vez sobrecarga de espacio y tiempo a la compartido esquema talones.

En la práctica, es necesario equilibrar el rendimiento, portabilidad y productividad a corto plazo.

Talones compartidos puede ser adecuado para aprovechar intrínsecamente no portátiles código nativo que puede tolerar una ligera degradación del rendimiento, mientras que uno-a-uno mapeo se debe utilizar en los casos en que el máximo rendimiento es necesario o cuando materia de portabilidad.

9.4 Aplicación de Stubs compartidas

Tenemos CFunction tratado hasta ahora, CPointer, y clases CMalloc como cajas negras.

En esta sección se describe la forma en que se puede implementar utilizando las funciones básicas de JNI.

9.4.1 La Clase CPointer

Nos fijamos en la clase CPointer primero porque es la superclase de ambos CFunction y CMalloc. El CPointer clase abstracta contiene un campo de 64 bits, los compañeros, que almacena el puntero subyacente C:

public abstract class CPointer {protected long peer;public native void copyIn(int bOff, int[] buf,int off,int len);public native void copyOut(...);...}

La implementación en C + + de métodos nativos como copyin es sencillo:

JNIEXPORT void JNICALLJava_CPointer_copyIn__I_3III(JNIEnv *env, jobject self,jint boff, jintArray arr, jint off, jint len){long peer = env->GetLongField(self, FID_CPointer_peer);env->GetIntArrayRegion(arr, off, len, (jint *)peer + boff);}

Page 10: Traduccion capitulo 9 (completo)

FID_CPointer_peer es el ID de campo calculado previamente para CPointer.peer. La implementación del método nativo utiliza el esquema de codificación de nombre largo (§ 11,3) a resolver los conflictos con las implementaciones de los métodos sobrecargados copyin nativas para otros tipos de matriz en la clase CPointer.

9.4.2 La Clase CMalloc

La clase CMalloc añade dos métodos nativos utilizados para asignar y liberar memoria C bloques:

public class CMalloc extends CPointer {private static native long malloc(int size);public CMalloc(int size) throws OutOfMemoryError {peer = malloc(size);if (peer == 0) {throw new OutOfMemoryError();}}public native void free();...}

El constructor CMalloc llama a un método nativo CMalloc.malloc, y lanza OutOfMemoryError CMalloc.malloc si no vuelve a la memoria recién asignada bloquear C en el montón. Podemos implementar la CMalloc.malloc y CMalloc. métodos libres de la siguiente manera:

JNIEXPORT jlong JNICALLJava_CMalloc_malloc(JNIEnv *env, jclass cls, jint size){return (jlong)malloc(size);}JNIEXPORT void JNICALLJava_CMalloc_free(JNIEnv *env, jobject self){long peer = env->GetLongField(self, FID_CPointer_peer);free((void *)peer);}

9.4.3 La Clase CFunction

La implementación de la clase CFunction requiere el uso de soporte dinámico que une en el sistema operativo, así como específico de la CPU código ensamblador. La implementación se presenta a continuación está dirigida específicamente hacia el medio ambiente Win32/Intel x86.

Page 11: Traduccion capitulo 9 (completo)

Una vez que entienda los principios detrás de la aplicación de la CFunction clase, puede seguir los mismos pasos para ponerla en práctica en otras plataformas. La clase CFunction se define como sigue:

public class CFunction extends CPointer {private static final int CONV_C = 0;private static final int CONV_JNI = 1;private int conv;private native long find(String lib, String fname);public CFunction(String lib, // native library nameString fname, // C function nameString conv) { // calling conventionif (conv.equals("C")) {conv = CONV_C;} else if (conv.equals("JNI")) {conv = CONV_JNI;} else {throw new IllegalArgumentException("bad calling convention");}peer = find(lib, fname);}

public native int callInt(Object[] args);...}

La clase CFunction declara una conv campo privado utiliza para almacenar el llamado convención de la función C. El método nativo CFunction.find se implementa como sigue:

JNIEXPORT jlong JNICALLJava_CFunction_find(JNIEnv *env, jobject self, jstring lib,jstring fun){void *handle;void *func;char *libname;char *funname;if ((libname = JNU_GetStringNativeChars(env, lib))) {if ((funname = JNU_GetStringNativeChars(env, fun))) {if ((handle = LoadLibrary(libname))) {if (!(func = GetProcAddress(handle, funname))) {JNU_ThrowByName(env,"java/lang/UnsatisfiedLinkError",funname);}} else {JNU_ThrowByName(env,"java/lang/UnsatisfiedLinkError",libname);}free(funname);}free(libname);

Page 12: Traduccion capitulo 9 (completo)

}return (jlong)func;}

CFunction.find convierte el nombre de la biblioteca y el nombre de la función para la configuración regional específica Cadenas de C, y luego llama a la API de Win32 y funciones LoadLibrary GetProcAddress para localizar la función C en la biblioteca llamada nativa.

El método callInt, aplicada como sigue, lleva a cabo la tarea principal de redistribución de la carga a la función subyacente C:

JNIEXPORT jint JNICALLJava_CFunction_callInt(JNIEnv *env, jobject self,jobjectArray arr){#define MAX_NARGS 32jint ires;int nargs, nwords;jboolean is_string[MAX_NARGS];word_t args[MAX_NARGS];nargs = env->GetArrayLength(arr);if (nargs > MAX_NARGS) {JNU_ThrowByName(env,"java/lang/IllegalArgumentException","too many arguments");return 0;}// convert argumentsfor (nwords = 0; nwords < nargs; nwords++) {is_string[nwords] = JNI_FALSE;jobject arg = env->GetObjectArrayElement(arr, nwords);if (arg == NULL) {args[nwords].p = NULL;} else if (env->IsInstanceOf(arg, Class_Integer)) {args[nwords].i =env->GetIntField(arg, FID_Integer_value);} else if (env->IsInstanceOf(arg, Class_Float)) {args[nwords].f =env->GetFloatField(arg, FID_Float_value);} else if (env->IsInstanceOf(arg, Class_CPointer)) {args[nwords].p = (void *)env->GetLongField(arg, FID_CPointer_peer);} else if (env->IsInstanceOf(arg, Class_String)) {char * cstr =JNU_GetStringNativeChars(env, (jstring)arg);if ((args[nwords].p = cstr) == NULL) {goto cleanup; // error thrown}is_string[nwords] = JNI_TRUE;} else {JNU_ThrowByName(env,"java/lang/IllegalArgumentException","unrecognized argument type");goto cleanup;}

Page 13: Traduccion capitulo 9 (completo)

env->DeleteLocalRef(arg);}

void *func =(void *)env->GetLongField(self, FID_CPointer_peer);int conv = env->GetIntField(self, FID_CFunction_conv);// now transfer control to func.ires = asm_dispatch(func, nwords, args, conv);cleanup:// free all the native strings we have createdfor (int i = 0; i < nwords; i++) {if (is_string[i]) {free(args[i].p);}}return ires;}

Suponemos que hemos puesto en marcha una serie de variables globales para almacenar en caché el referencias apropiadas de clase e identificadores de campo. Por ejemplo, variable global FID_CPointer_peer almacena el identificador de campo para CPointer.peer y variable global Class_String es una referencia mundial para el objeto de la clase java.lang.String. La tipo word_t representa una palabra máquina y se define como sigue:

typedef union {jint i;jfloat f;void *p;} word_t;

La función Java_CFunction_callInt recorre en iteración la matriz de argumentos,

y comprueba el tipo de cada elemento:

• Si el elemento es una referencia nula, se pasa como un puntero NULL a la función C.

• Si el elemento es una instancia de la clase java.lang.Integer, el entero valor se recupera y pasa a la función C.

• Si el elemento es una instancia de la clase java.lang.Float, el punto flotante valor se recupera y pasa a la función C.

• Si el elemento es una instancia de la clase CPointer, el puntero del par se recupera y pasa a la función C.

Page 14: Traduccion capitulo 9 (completo)

• Si el argumento es una instancia de java.lang.String, se convierte en un específico de la localidad cadena C y se pasan a la función C.

• De lo contrario, una IllegalArgumentException es lanzada.

Nos revise cuidadosamente los posibles errores durante la conversión argumento y libre todo el almacenamiento temporal asignado para cadenas de C antes de volver del Java_CFunction_callInt función.

El código que transfiere los argumentos de los argumentos de búfer temporal para la C función necesita manipular la pila C directamente. Está escrito en ensamblador inline:

int asm_dispatch(void *func, // pointer to the C functionint nwords, // number of words in args arrayword_t *args, // start of the argument dataint conv) // calling convention 0: C// 1: JNI{__asm {mov esi, argsmov edx, nwords// word address -> byte addressshl edx, 2sub edx, 4jc args_done// push the last argument firstargs_loop:mov eax, DWORD PTR [esi+edx]push eaxsub edx, 4jge SHORT args_loopargs_done:call func// check for calling conventionmov edx, convor edx, edxjnz jni_call// pop the argumentsmov edx, nwordsshl edx, 2add esp, edxjni_call:// done, return value in eax}}

La rutina de ensamblado copia los argumentos en la pila C, entonces redespachos a la función func C. Devolución funciones, la rutina comprueba asm_dispatch convención de llamada de función. Si func sigue la convención de llamada de C, asm_dispatch hace estallar los argumentos pasados a

Page 15: Traduccion capitulo 9 (completo)

func. Si func sigue la llamada JNI convención, asm_dispatch no salta los argumentos, los argumentos de función aparece antes de que vuelva.

9.5 Clases de pares

Uno-a-uno y talones de ambos compartidos aborden el problema de envolver funciones nativas. También se encontró con el problema de envolver estructuras de datos nativas en el curso de la construcción de la aplicación talones compartida. Recordemos la definición de la clase CPointer:

public abstract class CPointer {protected long peer;public native void copyIn(int bOff, int[] buf,int off, int len);public native void copyOut(...);...}

Contiene un campo de par 64-bit que se refiere a la estructura de datos original (en este caso, una parte de memoria en el espacio de direcciones C). Las subclases de CPointer asignar significados específicos en el campo de pares. La clase CMalloc, por ejemplo, utiliza el compañeros campo para que apunte a un trozo de memoria en el montón de C:

Las clases que se corresponden directamente con las estructuras de datos nativas, como CPointer y CMalloc, se llaman clases de pares. Usted puede construir clases peer para una variedad de estructuras de datos nativas, incluyendo, por ejemplo:

• descriptores de fichero

• descriptores de socket

• ventanas u otros gráficos de interfaz de usuario componentes

Page 16: Traduccion capitulo 9 (completo)

9.5.1 Clases de pares en la Plataforma Java

La corriente de JDK y versiones del SDK de Java 2 (1,1 y 1,2) utilizar las clases de pares internos para implementar el java.io, java.net y paquetes java.awt. Una instancia de la java.io.FileDescriptor clase, por ejemplo, contiene un campo privado que representa fd un descriptor de fichero nativo:

// Implementation of the java.io.FileDescriptor classpublic final class FileDescriptor {private int fd;...}

Supongamos que usted desea llevar a cabo una operación de archivo que no está respaldada por la Plataforma Java API. Usted puede tener la tentación de utilizar la JNI para averiguar el subyacente descriptor de archivo nativo de una instancia java.io.FileDescriptor. El JNI permite para acceder a un campo privado, siempre y cuando usted sabe su nombre y el tipo. Se podría pensar que podría llevar a cabo la operación de archivo nativo directamente en el descriptor de archivo.

Este enfoque, sin embargo, tiene un par de problemas:

• En primer lugar, usted está confiando en la aplicación java.io.FileDescriptor que almacena el descriptor de archivo nativo en un campo privado llamado fd. No hay garantía, sin embargo, que las implementaciones futuras implementaciones de Sun o de otros fabricantes de la clase java.io.FileDescriptor seguirá usando el mismo privado campo fd nombre para el descriptor de archivo nativo. El código nativo que asume el nombre del campo de los compañeros puede no funcionar con una implementación diferente de la Java plataforma.

• En segundo lugar, la operación se realiza directamente en el descriptor de archivo nativo puede alterar la consistencia interna de la clase peer. Por ejemplo, casos java.io.FileDescriptor mantener un estado interno que indica si el descriptor de archivo nativo subyacente ha sido cerrado. Si utiliza nativo código para omitir la clase peer y cerrar el descriptor de fichero subyacente, el estado mantenido en el ejemplo java.io.FileDescriptor ya no será consistente con el verdadero estado del descriptor de archivo nativo. Peer implementaciones de la clase suelen asumir que tienen acceso exclusivo a la nativa subyacente la estructura de datos.

La única manera de superar estos problemas es definir sus propias clases peer que las estructuras de envoltura de datos nativos. En el caso anterior, se puede definir su propio archivo descriptor de la clase peer que soporta el conjunto necesario de las operaciones. Este enfoque no te permite usar tus propias clases peer para implementar clases de Java API. Usted no puede, por ejemplo, pasar su propia instancia descriptor de fichero a un método que espera una instancia java.io.FileDescriptor. Puede, sin embargo, fácilmente definir su propia clase peer que implementa

Page 17: Traduccion capitulo 9 (completo)

una interfaz estándar de la API de Java. Esta es una fuerte argumento para diseñar APIs basados en interfaces en lugar de clases.

9.5.2 Estructuras de liberar a los nativos de Datos

Clases de pares se definen en el lenguaje de programación Java, por lo que los casos de compañeros las clases serán basura recogida automáticamente. Usted necesita asegurarse de que, sin embargo, que las estructuras subyacentes de datos nativos será liberado también.

Recordemos que la clase contiene un método CMalloc libre para liberar explícitamente malloc'ed la memoria C:

public class CMalloc extends CPointer {public native void free();...}

Usted debe recordar llamar gratis a instancias de la clase CMalloc, de lo contrario un ejemplo CMalloc puede ser basura recogida, pero su correspondiente malloc'ed C memoria nunca se recuperó.

Algunos programadores gusta poner un finalizador en clases peer, como CMalloc:

public class CMalloc extends CPointer {public native synchronized void free();protected void finalize() {free();}...}

La máquina virtual llama al método finalize antes de que la basura se acumula un instancia de CMalloc. Incluso si usted se olvida de llamar libre, el método finalice libera malloc'ed la memoria C para ti.

Es necesario hacer un pequeño cambio en la implementación del método nativo CMalloc.free para tener en cuenta la posibilidad de que pueda ser llamado varias veces. Usted También es necesario que CMalloc.free un método sincronizado para evitar raza hilo condiciones:

Page 18: Traduccion capitulo 9 (completo)

JNIEXPORT void JNICALLJava_CMalloc_free(JNIEnv *env, jobject self){long peer = env->GetLongField(self, FID_CPointer_peer);if (peer == 0) {return; /* not an error, freed previously */}free((void *)peer);peer = 0;env->SetLongField(self, FID_CPointer_peer, peer);}

Hemos establecido el campo par con dos declaraciones:

peer = 0;env->SetLongField(self, FID_CPointer_peer, peer);

en lugar de una declaración:

env->SetLongField(self, FID_CPointer_peer, 0);

porque compiladores C + + se consideran el 0 literal como un entero de 32-bit, en lugar de un

64-bit entero. Algunos compiladores C + + permiten especificar 64-bit literales enteros, pero el uso de 64-bit literales no será tan portátil.

Definición de un método de finalizar es una garantía adecuada, pero nunca debe depender en los finalizadores como único medio de liberar a las estructuras de datos nativas. La razón es que las estructuras de datos nativas pueden consumir muchos más recursos que sus pares instancias. La máquina virtual de Java no puede recoger la basura y finalizar instancias de clases peer suficientemente rápido para liberar a sus compañeros nativos.

Definición de un finalizador tiene consecuencias de rendimiento. Es típicamente más lento para crear y recuperar instancias de clases con finalizadores de crear y recuperar los que no tienen finalizadores.

Si usted siempre puede asegurarse de que liberar manualmente la estructura de datos nativa para clases peer, no es necesario definir un finalizador. Usted debe asegurarse, sin embargo, sin estructuras de datos nativas en todos los caminos de la ejecución, de lo contrario puede haber creado una pérdida de recursos. Preste especial atención a las posibles excepciones lanzadas

Page 19: Traduccion capitulo 9 (completo)

durante el proceso de utilizar una instancia de pares. Siempre estructuras nativas libres de datos en una finalmente cláusula:

CMalloc cptr = new CMalloc(10);try {... // use cptr} finally {cptr.free();}

La cláusula finally asegura que cptr se libera incluso si se produce una excepción dentro del bloque try.

9.5.3 Backpointers to Peer instancias

Hemos demostrado que las clases de pares contienen típicamente un campo privado que se refiere a la subyacente estructura de datos original. En algunos casos es deseable incluir también una referencia de la estructura de datos nativa de las instancias de la clase peer. Esto sucede, por ejemplo, cuando el código nativo tiene que iniciar devoluciones de llamada a los métodos de instancia en la clase de pares.

Supongamos que estamos construyendo un componente de interfaz de usuario hipotética llamada KeyInput. KeyInput nativo de C + + componente, key_input, recibe un evento como un KEY_PRESSED C + + llamada de función del sistema operativo cuando el usuario presiona una tecla. El key_input C + + componente reporta el evento del sistema operativo para la KeyInput instancia llamando al método keyPressed en la instancia KeyInput.

Las flechas en la figura a continuación indica cómo un evento de pulsación de tecla se origina por una prensa de usuario y clave propaga desde el key_input C + + componente al-Key. Entrada instancia archivos:

Page 20: Traduccion capitulo 9 (completo)

La clase peer KeyInput se define como sigue:

class KeyInput {private long peer;private native long create();private native void destroy(long peer);public KeyInput() {peer = create();}public destroy() {destroy(peer);}private void keyPressed(int key) {... /* process the key event */}}

La implementación del método nativo asigna a crear una instancia de la C + + estructura key_input. C + + estructuras son similares a clases C + +, con la única diferencia es que todos los miembros son por defecto público en lugar de privado. Utilizamos una estructura de C + + en lugar de una clase de C + + en este ejemplo principalmente para evitar confusiones con clases en el lenguaje de programación Java.

// C++ structure, native counterpart of KeyInputstruct key_input {jobject back_ptr; // back pointer to peer instanceint key_pressed(int key); // called by the operating system};JNIEXPORT jlong JNICALLJava_KeyInput_create(JNIEnv *env, jobject self){key_input *cpp_obj = new key_input();cpp_obj->back_ptr = env->NewGlobalRef(self);return (jlong)cpp_obj;}JNIEXPORT void JNICALLJava_KeyInput_destroy(JNIEnv *env, jobject self, jlong peer){key_input *cpp_obj = (key_input*)peer;env->DeleteGlobalRef(cpp_obj->back_ptr);delete cpp_obj;return;}

El método create nativo asigna el C + + y se inicializa la estructura de su back_ptr campo a una referencia global a la instancia de par KeyInput. La destruir elimina métodos nativos en la referencia mundial para la instancia de pares y el C + estructura + que hace referencia el ejemplo de pares. El constructor llama al crear KeyInput método nativo para establecer la relación entre una instancia de pares y su contraparte nativa:

Page 21: Traduccion capitulo 9 (completo)

Cuando el usuario pulsa una tecla, el sistema operativo llama al miembro de C + + función key_input :: KEY_PRESSED. Esta función miembro responde a eventos por la emisión de una devolución de llamada al método keyPressed en la instancia de par KeyInput.

// returns 0 on success, -1 on failureint key_input::key_pressed(int key){jboolean has_exception;JNIEnv *env = JNU_GetEnv();JNU_CallMethodByName(env,&has_exception,java_peer,"keyPressed","()V",key);if (has_exception) {env->ExceptionClear();return -1;} else {return 0;}}

La función miembro key_press limpia cualquier excepción después de la devolución de llamada y Devoluciones Condiciones de error en el sistema operativo con el código de retorno -1. referirse a Secciones 6.2.3 y 8.4.1 para las definiciones de JNU_CallMethodByName y JNU_GetEnv funciones de utilidad respectivamente.

Vamos a discutir un tema final antes de concluir esta sección. Supongamos que usted añadir un método de finalizar la clase KeyInput para evitar posibles fugas de memoria:

class KeyInput {...public synchronized destroy() {if (peer != 0) {

Page 22: Traduccion capitulo 9 (completo)

destroy(peer);peer = 0;}}protect void finalize() {destroy();}}

El método destroy comprueba si el campo de pares es cero, y establece los pares campo a cero después de llamar al método sobrecargado destruir nativo. Se define como un sincronizado método para evitar las condiciones de carrera.

El código anterior no funcionará como es de esperar, sin embargo. El virtual máquina nunca será recoger la basura cualquier instancia KeyInput a menos que llame destruir explícitamente. El constructor KeyInput crea una referencia JNI global la instancia KeyInput. La referencia mundial impide la instancia de KeyInput siendo recolector de basura. Puede superar este problema mediante el uso de un débil mundial referencia en lugar de una referencia global:

JNIEXPORT jlong JNICALLJava_KeyInput_create(JNIEnv *env, jobject self){key_input *cpp_obj = new key_input();cpp_obj->back_ptr = env->NewWeakGlobalRef(self);return (jlong)cpp_obj;}JNIEXPORT void JNICALLJava_KeyInput_destroy(JNIEnv *env, jobject self, jlong peer){key_input *cpp_obj = (key_input*)peer;env->DeleteWeakGlobalRef(cpp_obj->back_ptr);delete cpp_obj;return;}