Llamar a Métodos Java
Esta página ilustra cómo llamar a métodos Java desde métodos nativos. Nuestro programa de ejemplo,
Callbacks.java, llama a un método nativo. El método nativo hace una nueva llamada al método Java. Para hacer las cosas un poco más interesantes, el método Java llama de nuevo (recursivamente) al método nativo. Este proceso continúa hasta que la recursión alcanza cinco niveles de profundidad, en ese momento el método Java retorna sin hacer más llamadas al método nativo. Para ayudarnos a ver esto, los dos métodos imprimen una secuencia de trazado.
Llamar a un Método Java desde un Método Nativo
Para ver como el código nativo llama al método Java, enfoquémonos en la implementación de
Callbacks_nativeMethod, que está implementada en
Callbacks.c. Este método nativo contiene una llamada al método java
Callbacks.callback.
JNIEXPORT void JNICALL
Java_Callbacks_nativeMethod(JNIEnv *env, jobject obj, jint depth)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
if (mid == 0)
return;
printf("In C, depth = %d, about to enter Java\n", depth);
(*env)->CallVoidMethod(env, obj, mid, depth);
printf("In C, depth = %d, back from Java\n", depth);
}
Se puede llamar a un método de ejemplar (no estático) siguiendo estos tres pasos:
- El método nativo llama a la función JNI GetObjectClass. GetObjectClass devuelve el objeto class Java al que pertenece el objeto.
- Entonces el método nativo llama a la función JNI GetMethodID. GetMethodID realiza un búsqueda del método Java dentro de la clase dada. La búsqueda está basada tanto en el nombre del método como en su firma. Si el método no existe, GetMethodID devuelve cero (0). Un retorno inmediato desde el método nativo en ese punto causa el lanzamiento de una excepción
NoSuchMethodError en el código Java.
-
Finalmente, el método nativo llama a la función JNI CallVoidMethod. CallVoidMethod llama a un método de ejemplar que tiene el tipo de retorno como void. Se pasan el objeto, el ID del método y los argumentos reales a CallVoidMethod.
Formar el Nombre del Método y su Firma
El JNI realiza un búsqueda simbólica basándose en el nombre y la firma del método. Esto asegura que el mismo método nativo seguirá funcionando después de haber añadido nuevos métodos a la clase Java correspondiente.
El nombre del método es el nombre del método en formato UTF-8. Especifica el nombre del método para un constructor de una clase encerrando la palabra init entre "< y >".
Observa que el JNI utiliza la firma del método para denotar el tipo de retorno del método Java. Esta firma (I)V, por ejemplo, denota un método Java que toma un argumento del tipo int y tiene un tipo de retorno void. La forma general para una firma de método es:
"(tipos-argumentos)tipo-retorno"
La siguiente tabla sumariza la codificación Java para las firmas:
Tipos de Firmas de la VM de Java
| Firma | Tipo Java |
| Z | boolean |
| B | byte |
| C | char |
| S | short |
| I | int |
| J | long |
| F | float |
| D | double |
| L clases-totalmente-cualificada ; | clase-totalmente-cualificada |
| [ tipo | tipo[] |
| ( tipo-argumento ) tipo-retorno | tipo de método |
Por ejemplo, el método Prompt.getLine tiene la firma:
(Ljava/lang/String;)Ljava/lang/String;
Prompt.getLine tiene un parámetro, un objeto String, y el tipo del método también es String.
El método Callbacks.main tiene la firma:
([Ljava/lang/String;)V
La firma indica que el método Callbacks.main toma un parámetro, un objeto String, y el tipo del método es void.
Los tipos de arrays son indicados con corchete abierto ([) seguido por el tipo de los elementos del array.
Utilizar javap para Generar Firmas de Métodos
Para evitar los errores derivados de la firma de métodos manual, se puede utilizar la herramienta
javap para imprimir las firmas de métodos. Por ejemplo, ejecutando:
javap -s -p Prompt
Se puede obtener la siguiente salida:
Compiled from Prompt.java
class Prompt extends java.lang.Object
/* ACC_SUPER bit set */
{
private native getLine (Ljava/lang/String;)Ljava/lang/String;
public static main ([Ljava/lang/String;)V
<init> ()V
static <clinit> ()V
}
La bandera "-s" informa a
javap para que saque las firmas en vez de los tipos Java. La bandera "-p" instruye a
javap para que incluya los miembros privados.
Llamar a Métodos Java utilizando los IDs
Cuando se llama a un método en el JNI, se le pasa un ID del método a la función real de llamada. Obtener un ID de un método es una operación que consume muchos recursos . Como se obtiene el ID del método de forma separada de la llamada al método, sólo se necesita realizar esta operación una vez. Así, es posible, obtener primero el ID del método y luego utilizarlo las veces necesarias para invocar al mismo método.
Es importante tener en mente que un ID de método sólo es válido mientras que no se descargue la clase de la se deriva. Una vez que la clase se ha descargado, el ID del método no es válido. Como resultado, si se quiere guardar el ID del método, debemos asegurarnos de mantener viva a una referencia a la clase Java de la que se deriva el ID del método. Mientras exista la referencia a la clase Java (el valor jclass), el código nativo mantendrá una referencia viva a la clase. La página
Referencias Locales y Globales explica como mantener viva una referencia incluso después de que el método nativo retorne y el valor de jclass salga fuera de ámbito.
Pasar Argumentos al Método Java
El JNI proporciona varias formas de pasar argumentos al método Java. La más frecuente, se pasan los argumentos siguiendo al ID del método. También hay dos variaciones de llamadas a métodos que toman argumentos en un formato alternativo. Por ejemplo, la función
CallVoidMethodV recibe los argumentos en un
va_list y la función
CallVoidMethodA espera los argumentos en un array de uniones
jvalue. Los tipos del array de uniones
jvalue son los siguientes:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
Además de la función
CallVoidMethod, el JNI también soporta funciones de llamada a métodos con tipos de retorno, como
CallBooleanMethod,
CallIntMethod, etc. El tipo de retorno de la función de llamada a método debe corresponder con el tipo del método Java que se desea invocar.
Llamar a Métodos Estáticos
Se puede llamar a métodos estáticos Java de forma similar a como se llama a métodos de ejemplar. Se deben seguir estos pasos:
- Obtener el ID del método utilizando la función JNI GetStaticMethodID en vez la función GetMethodID.
- Pasar la clase, el ID del método y los argumentos a la familia de funciones de llamadas a métodos estáticos: CallStaticVoidMethod, CallStaticBooleanMethod, etc.
Si se comparan las funciones de llamada para métodos de ejemplar y estáticos, veremos que las funciones de llamada a métodos de ejemplar reciben el
object, en vez de la clase, como el segundo argumento, seguido por el arguemnto
JNIEnv. Por ejemplo, supongamos, que hemos añadido un método estático:
static int incDepth(int depth) {return depth + 1};
dentro de
Callback.java. Podremos llamar a este método estático
incDepth desde
Java_Callback_nativeMethod utilizando las siguientes funciones JNI:
JNIEXPORT void JNICALL
Java_Callbacks_nativeMethod(JNIEnv *env, jobject obj, jint depth)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "incDepth", "(I)I");
if (mid == 0)
return;
depth = (*env)->CallStaticIntMethod(env, cls, mid, depth);
Llamar a Métodos de Ejemplar de una Superclase
Se puede llamar a métodos definidos en una superclase que se han sobreescrito en la clase a la que pertenece el objeto. El JNI proporciona un conjunto de funciones
CallNonvirtual<type>Method para este propósito. Para llamar a un método de ejemplar de una superlclase, se debe hacer lo siguiente:
- Obtener el ID del método de la superclase utilizando GetMethodID en vez de GetStaticMethodID.
- Pasar el objeto, la superclase, el ID del método, y los arguemntos a la familia de funciones de llamadas no vituales: CallNonvirtualVoidMethod, CallNonvirtualBooleanMethod, etc.
Será raro que necesitemos invocar a métodos de ejemplar de una superclase. Esta facilidad es similar a llamar al método de una clase, digamos
f, utilizando:
super.f();
en el lenguaje Java.
Ozito