Este documento describe diferentes enfoques para escribir clases contenedoras que exponen funciones de bibliotecas nativas en Java. Explica el enfoque de uno-a-uno, donde cada función nativa se mapea a un método nativo único. Luego introduce el concepto de "stubs compartidas", donde una función stub distribuye llamadas a otras funciones nativas, simplificando la tarea de escribir clases contenedoras. Finalmente, discute las ventajas e inconvenientes de ambos enfoques.
1. LIBRERÍAS NATIVAS
Una de las aplicaciones de la JNI es escribir métodos nativos que aprovechan códigoexistentes en
las bibliotecas nativas. Un enfoque típico, cubierto en este capítulo, es la producción deuna
biblioteca de clase que ajusta un conjunto de funciones nativas.
En este primer capítulo se analiza la forma más sencilla de escribir envolturaclases-uno-a-uno.
Luego introducimos una técnica, talones compartidos, quesimplifica la tarea de escribir clases
contenedoras.
Uno-a-uno y recibos comunes son técnicas para envolver nativofunciones. Al final de este capítulo,
también vamos a discutir la forma de envolver los datos nativosestructuras mediante las clases de
pares.
Los enfoques descritos en este capítulo se exponen directamente a una librería nativa
utilizandométodos nativos, y por lo tanto tienen la desventaja de hacer una aplicación que
llamatales métodos nativos que dependen de la biblioteca nativa. Tal solicitud puede ejecutarsólo
en un sistema operativo que suministra la biblioteca nativa. Una aproximación preferidaes
declarar independiente del sistema operativo métodos nativos. Sólo las funciones nativasla
aplicación de esos métodos nativos utilizan las bibliotecas nativas directamente, limitandola
necesidad de portar a las funciones nativas. La aplicación, incluyendo eldeclaraciones de métodos
nativos, no necesita ser portado.
9.1 Uno-a-uno
Empecemos con un ejemplo sencillo. Supongamos que queremos escribir un contenedorclase 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 representadopor 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 lafuncionalidad equivalente. Evaluación de atol
("100"), por ejemplo, los resultados en el enterovalor 100. Definimos una clase contenedora de la
siguiente manera:
public class C {
public static native int atol(String str);
...
}
2. En aras de ilustrar la programación de JNI en C + +, implementaremosmétodos nativos en este
capítulo usando C + + (§ 8.6). El C + + aplicación de laC.atol método nativo es el siguiente:
JNIEXPORT jint JNICALL
Java_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 aconvertir la cadena Unicode porque
los números decimales son caracteres ASCII.
Veamos ahora un ejemplo más complejo que involucra la estructura pasapunteros a una función
de C. Supongamos que queremos escribir una clase contenedora queexpone la función CreateFile
de la API Win32:
typedef void * HANDLE;
typedef long DWORD;
typedef struct {...} SECURITY_ATTRIBUTES;
HANDLE CreateFile(
const char *fileName, // file name
DWORD desiredAccess, // access (read-write) mode
DWORD shareMode, // share mode
SECURITY_ATTRIBUTES *attrs, // security attributes
DWORD creationDistribution, // how to create
DWORD flagsAndAttributes, // file attributes
HANDLE templateFile // file with attr. to copy
);
La función CreateFile es compatible con una serie de características específicas de Win32
nodisponible 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 aabrir 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.
3. La atención se centrará en cómo CreateFile se pueden asignar a un método nativo definidoen una
clase de contenedor llamado Win32:
public class Win32 {
public static native int CreateFile(
String fileName, // file name
int desiredAccess, // access (read-write) mode
int shareMode, // share mode
int[] secAttrs, // security attributes
int creationDistribution, // how to create
int flagsAndAttributes, // file attributes
int templateFile); // file with attr. to copy
...
}
El mapeo del tipo de puntero char a String es obvio. Mapeamos elnativo 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
hacemosno del mapa estructuras C a clases en el lenguaje de programación Java. En su lugar, se
utilizauna 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ómoque 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 name
jint desiredAccess, // access (read-write) mode
jint shareMode, // share mode
jintArray secAttrs, // security attributes
jint creationDistribution, // how to create
jint flagsAndAttributes, // file attributes
jint templateFile) // file with attr. to copy
{
jint result = 0;
jint *cSecAttrs = NULL;
if (secAttrs) {
cSecAttrs = env->GetIntArrayElements(secAttrs, 0);
if (cSecAttrs == NULL) {
return 0; /* out of memory */
4. }
}
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 (porejemplo, 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ónsirve para dos propósitos:
1. El talón se adapta convención la función nativa de paso de argumentos a lo que esesperado por
la máquina virtual de Java. La máquina virtual espera que el nativoimplementación del método a
seguir una convención de nombres dado y aceptar dosargumentos 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.
5. Por ejemplo, la función Java_Win32_CreateFile traduce la jstringnombre 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 paracada función nativa
que desee ajustar. Esto se convierte en tedioso cuando se enfrentancon la tarea de escribir clases
de envoltura para un gran número de funciones nativas. Enesta sección se introduce el concepto
de recibos comunes y demostrar cómo compartirtalones 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. Lastub
compartido es responsable de convertir los tipos de argumentos a partir de lo que se
proporcionapor 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
mostrarcó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 convention
public 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 CFunctioninternamente
implementa un trozo compartida. El C.c_atol variable estática almacena una CFunctionobjeto que
corresponde a la función atol C en la biblioteca msvcrt.dll(la biblioteca C multihebra en Win32). El
constructor CFunction llamar tambiénespecifica que atol sigue la convención de llamada de C (§
11.4). Una vez que el c_atolcampo se inicializa, las llamadas al método C.atol sólo necesitan la
reexpedición a través dec_atol.callInt, el talón compartida.
La clase CFunction pertenece a una jerarquía de clases que vamos a construir yutilizar en breve:
6. Las instancias de la clase CFunction denotar un puntero a una función de C. CFunctiones una
subclase de CPointer, que denota punteros arbitrarias C:
public class CFunction extends CPointer {
public CFunction(String lib, // native library name
String fname, // C function name
String conv) { // calling convention
...
}
public native int callInt(Object[] args);
...
}
El método callInt toma como argumento una matriz de java.lang.Object. Loinspecciona los tipos de
los elementos de la matriz, los convierte (de jstring achar *, 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 comoint. La clase
CFunction puede definir métodos como callFloat o callDoublepara 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 pointer
int[] buf, // source data
int off, // offset into source
int len); // number of elements to be copied
public native void copyOut(...);
...
}
7. 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 intla memoria apuntada por
el puntero de C. Este método debe usarse con cuidadoya que se puede utilizar fácilmente para
corromper ubicaciones arbitrarias de memoria en la direcciónespacio. Los métodos nativos como
CPointer.copyIn son tan malas condiciones como puntero directomanipulación en C.
CMalloc es una subclase de CPointer que apunta a un bloque de memoria asignadaen 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 Cmontón. El
método CMalloc.free libera el bloque de memoria.
Equipado con las clases CFunction y CMalloc, podemos reimplementarWin32.CreateFile como
sigue:
public class Win32 {
private static CFunction c_CreateFile =
new CFunction ("kernel32.dll", // native library name
"CreateFileA", // native function
"JNI"); // calling convention
public static int CreateFile(
String fileName, // file name
int desiredAccess, // access (read-write) mode
int shareMode, // share mode
int[] secAttrs, // security attributes
int creationDistribution, // how to create
int flagsAndAttributes, // file attributes
int 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();
8. }
}
}
...
}
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 laC montón que es lo
suficientemente grande para contener los atributos de seguridad temporal. A continuación, los
paquetestodos 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 liberael bloque de
memoria C se utiliza para mantener los atributos de seguridad. Llamamos cSecAttrs.freeen una
cláusula finally para asegurarse de que la memoria temporal C se libera incluso si elllamada
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 contenedoraspara las
bibliotecas nativas. Cada uno tiene sus propias ventajas.
La principal ventaja de talones compartidos es que el programador no necesita escribir unagran
número de funciones auxiliares en código nativo. Una vez que una aplicación compartida stubtales
como CFunction está disponible, el programador puede ser capaz de construir envolturaclases 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. Estaderrota a la
seguridad de tipos del lenguaje de programación Java. Los errores en el uso detalones
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 enla conversión de los tipos de datos
que se transfieren entre la máquina virtual Java ycódigo nativo. Talones compartidas, por otro
lado, puede manejar un máximo predeterminadoconjunto de tipos de argumentos y no se puede
lograr un rendimiento óptimo incluso para estostipos de argumentos. El llamador de
9. CFunction.callInt siempre tiene que crear un Integerobjeto para cada argumento int. Esto se suma
a la vez sobrecarga de espacio y tiempo a lacompartido 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átilescódigo
nativo que puede tolerar una ligera degradación del rendimiento, mientras que uno-a-unomapeo
se debe utilizar en los casos en que el máximo rendimiento es necesario o cuandomateria 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 CFunctiony CMalloc. El
CPointer clase abstracta contiene un campo de 64 bits, los compañeros, que almacenael 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 JNICALL
Java_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);
}
10. 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)
aresolver los conflictos con las implementaciones de los métodos sobrecargados copyin nativas
paraotros 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 Cbloques:
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 lanzaOutOfMemoryError
CMalloc.malloc si no vuelve a la memoria recién asignadabloquear C en el montón. Podemos
implementar la CMalloc.malloc y CMalloc.métodos libres de la siguiente manera:
JNIEXPORT jlong JNICALL
Java_CMalloc_malloc(JNIEnv *env, jclass cls, jint size)
{
return (jlong)malloc(size);
}
JNIEXPORT void JNICALL
Java_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 uneen el
sistema operativo, así como específico de la CPU código ensamblador. La implementaciónse
presenta a continuación está dirigida específicamente hacia el medio ambiente Win32/Intel x86.
11. Una vez que entienda los principios detrás de la aplicación de la CFunctionclase, 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 name
String fname, // C function name
String conv) { // calling convention
if (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 llamadoconvención
de la función C. El método nativo CFunction.find se implementacomo sigue:
JNIEXPORT jlong JNICALL
Java_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);
}
12. return (jlong)func;
}
CFunction.find convierte el nombre de la biblioteca y el nombre de la función para la configuración
regional específicaCadenas de C, y luego llama a la API de Win32 y funciones
LoadLibraryGetProcAddress para localizar la función C en la biblioteca llamada nativa.
El método callInt, aplicada como sigue, lleva a cabo la tarea principal deredistribución de la carga a
la función subyacente C:
JNIEXPORT jint JNICALL
Java_CFunction_callInt(JNIEnv *env, jobject self,
jobjectArray arr)
{
#define MAX_NARGS 32
jint 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 arguments
for (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;
}
env->DeleteLocalRef(arg);
}
13. 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 created
for (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é
elreferencias apropiadas de clase e identificadores de campo. Por ejemplo, variable
globalFID_CPointer_peer almacena el identificador de campo para CPointer.peer y variable
globalClass_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 enterovalor se recupera y pasa a
la función C.
• Si el elemento es una instancia de la clase java.lang.Float, el punto flotantevalor se recupera y
pasa a la función C.
• Si el elemento es una instancia de la clase CPointer, el puntero del par se recuperay pasa a la
función C.
• Si el argumento es una instancia de java.lang.String, se convierte en unespecífico de la localidad
cadena C y se pasan a la función C.
14. • De lo contrario, una IllegalArgumentException es lanzada.
Nos revise cuidadosamente los posibles errores durante la conversión argumento y libretodo el
almacenamiento temporal asignado para cadenas de C antes de volver delJava_CFunction_callInt
función.
El código que transfiere los argumentos de los argumentos de búfer temporal para la Cfunción
necesita manipular la pila C directamente. Está escrito en ensamblador inline:
int asm_dispatch(void *func, // pointer to the C function
int nwords, // number of words in args array
word_t *args, // start of the argument data
int conv) // calling convention 0: C
// 1: JNI
{
__asm {
mov esi, args
mov edx, nwords
// word address -> byte address
shl edx, 2
sub edx, 4
jc args_done
// push the last argument first
args_loop:
mov eax, DWORD PTR [esi+edx]
push eax
sub edx, 4
jge SHORT args_loop
args_done:
call func
// check for calling convention
mov edx, conv
or edx, edx
jnz jni_call
// pop the arguments
mov edx, nwords
shl edx, 2
add esp, edx
jni_call:
// done, return value in eax
}
}
La rutina de ensamblado copia los argumentos en la pila C, entonces redespachosa 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
func. Si func sigue la llamada JNIconvención, asm_dispatch no salta los argumentos, los
argumentos de función apareceantes de que vuelva.
15. 9.5 Clases de pares
Uno-a-uno y talones de ambos compartidos aborden el problema de envolverfunciones nativas.
También se encontró con el problema de envolver estructuras de datos nativasen el curso de la
construcción de la aplicación talones compartida. Recordemos ladefinició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 estecaso, una
parte de memoria en el espacio de direcciones C). Las subclases de CPointer asignarsignificados
específicos en el campo de pares. La clase CMalloc, por ejemplo, utiliza elcompañ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 CPointery
CMalloc, se llaman clases de pares. Usted puede construir clases peer para una variedadde
estructuras de datos nativas, incluyendo, por ejemplo:
• descriptores de fichero
• descriptores de socket
• ventanas u otros gráficos de interfaz de usuario componentes
9.5.1 Clases de pares en la Plataforma Java
16. La corriente de JDK y versiones del SDK de Java 2 (1,1 y 1,2) utilizar las clases de pares internospara
implementar el java.io, java.net y paquetes java.awt. Una instancia de lajava.io.FileDescriptor
clase, por ejemplo, contiene un campo privado que representa fdun descriptor de fichero nativo:
// Implementation of the java.io.FileDescriptor class
public final class FileDescriptor {
private int fd;
...
}
Supongamos que usted desea llevar a cabo una operación de archivo que no está respaldada por
laPlataforma Java API. Usted puede tener la tentación de utilizar la JNI para averiguar el
subyacentedescriptor de archivo nativo de una instancia java.io.FileDescriptor. El JNI permitepara
acceder a un campo privado, siempre y cuando usted sabe su nombre y el tipo. Se podría
pensarque 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 quealmacena 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 fabricantesde la clase
java.io.FileDescriptor seguirá usando el mismo privadocampo fd nombre para el descriptor de
archivo nativo. El código nativo que asume el nombredel campo de los compañeros puede no
funcionar con una implementación diferente de la Javaplataforma.
• En segundo lugar, la operación se realiza directamente en el descriptor de archivo nativo
puedealterar la consistencia interna de la clase peer. Por ejemplo,casos java.io.FileDescriptor
mantener un estado interno que indicasi el descriptor de archivo nativo subyacente ha sido
cerrado. Si utiliza nativocódigo para omitir la clase peer y cerrar el descriptor de fichero
subyacente, el estadomantenido en el ejemplo java.io.FileDescriptor ya no será consistentecon el
verdadero estado del descriptor de archivo nativo. Peer implementaciones de la clasesuelen
asumir que tienen acceso exclusivo a la nativa subyacentela estructura de datos.
La única manera de superar estos problemas es definir sus propias clases peerque las estructuras
de envoltura de datos nativos. En el caso anterior, se puede definir su propio archivodescriptor de
la clase peer que soporta el conjunto necesario de las operaciones. Este enfoqueno 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 queespera una instancia
java.io.FileDescriptor. Puede, sin embargo, fácilmente definirsu propia clase peer que implementa
17. una interfaz estándar de la API de Java. Esta es unafuerte 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ñeroslas 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ícitamentemalloc'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 contrarioun ejemplo
CMalloc puede ser basura recogida, pero su correspondiente malloc'ed Cmemoria 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 uninstancia de
CMalloc. Incluso si usted se olvida de llamar libre, el método finalice liberamalloc'ed la memoria C
para ti.
Es necesario hacer un pequeño cambio en la implementación del método nativo CMalloc.freepara
tener en cuenta la posibilidad de que pueda ser llamado varias veces. UstedTambién es necesario
que CMalloc.free un método sincronizado para evitar raza hilocondiciones:
18. JNIEXPORT void JNICALL
Java_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 dependeren los
finalizadores como único medio de liberar a las estructuras de datos nativas. La razón es quelas
estructuras de datos nativas pueden consumir muchos más recursos que sus paresinstancias. La
máquina virtual de Java no puede recoger la basura y finalizar instanciasde clases peer
suficientemente rápido para liberar a sus compañeros nativos.
Definición de un finalizador tiene consecuencias de rendimiento. Es típicamentemás lento para
crear y recuperar instancias de clases con finalizadores de crear yrecuperar los que no tienen
finalizadores.
Si usted siempre puede asegurarse de que liberar manualmente la estructura de datos nativa
paraclases 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
creadouna pérdida de recursos. Preste especial atención a las posibles excepciones lanzadas
19. duranteel proceso de utilizar una instancia de pares. Siempre estructuras nativas libres de datos
en unafinalmente 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óndentro 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 lasubyacente estructura de datos original. En algunos casos es deseable incluir también
una referenciade 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
instanciaen la clase de pares.
Supongamos que estamos construyendo un componente de interfaz de usuario hipotética
llamadaKeyInput. KeyInput nativo de C + + componente, key_input, recibe un evento como
unKEY_PRESSED C + + llamada de función del sistema operativo cuando el usuario presionauna
tecla. El key_input C + + componente reporta el evento del sistema operativo para laKeyInput
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
unaprensa de usuario y clave propaga desde el key_input C + + componente al-Key. Entrada
instancia archivos:
20. 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 diferenciaes que todos los miembros son
por defecto público en lugar de privado. Utilizamosuna estructura de C + + en lugar de una clase
de C + + en este ejemplo principalmente para evitar confusionescon clases en el lenguaje de
programación Java.
// C++ structure, native counterpart of KeyInput
struct key_input {
jobject back_ptr; // back pointer to peer instance
int key_pressed(int key); // called by the operating system
};
JNIEXPORT jlong JNICALL
Java_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 JNICALL
Java_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 suback_ptr campo a una
referencia global a la instancia de par KeyInput. La destruirelimina 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 KeyInputmétodo nativo para establecer la relación entre
una instancia de pares y su contraparte nativa:
21. 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 porla emisión de una
devolución de llamada al método keyPressed en la instancia de par KeyInput.
// returns 0 on success, -1 on failure
int 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
yDevoluciones Condiciones de error en el sistema operativo con el código de retorno -1. referirse
aSecciones 6.2.3 y 8.4.1 para las definiciones de JNU_CallMethodByName yJNU_GetEnv funciones
de utilidad respectivamente.
Vamos a discutir un tema final antes de concluir esta sección. Supongamos que ustedañadir un
método de finalizar la clase KeyInput para evitar posibles fugas de memoria:
class KeyInput {
...
public synchronized destroy() {
if (peer != 0) {
22. destroy(peer);
peer = 0;
}
}
protect void finalize() {
destroy();
}
}
El método destroy comprueba si el campo de pares es cero, y establece los parescampo a cero después de
llamar al método sobrecargado destruir nativo. Se define como unsincronizado método para evitar las
condiciones de carrera.
El código anterior no funcionará como es de esperar, sin embargo. El virtualmáquina nunca será recoger la
basura cualquier instancia KeyInput a menos que llamedestruir explícitamente. El constructor KeyInput crea
una referencia JNI globalla instancia KeyInput. La referencia mundial impide la instancia de KeyInputsiendo
recolector de basura. Puede superar este problema mediante el uso de un débil mundialreferencia en lugar de
una referencia global:
JNIEXPORT jlong JNICALL
Java_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 JNICALL
Java_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;
}