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:

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
FirmaTipo 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-retornotipo 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: 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: 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