2008-10-23 20 views
46

Me gustaría una manera consistente y simple de lanzar excepciones en el código JNI; algo que maneja excepciones encadenadas (implícitamente desde el método env-> ExceptionOccurred, o explícitamente por parámetros, de cualquier manera es bueno) y me ahorra buscar constructores cada vez que quiero hacer esto. Todo lo anterior está preferiblemente en C, aunque podría traducirlo de C++ cuando lo necesite.¿La mejor manera de lanzar excepciones en el código JNI?

¿Alguien en SO tiene algo como esto que puedan compartir?

+0

Por 'maneja excepciones encadenadas' ¿quiere decir que su código notará una excepción de nivel Java al regresar de Java a C++, lo envuelve en alguna otra excepción y lanza esa nueva excepción desde C++ a Java? –

Respuesta

38

Solo codificamos los métodos de utilidad para cada uno de los tipos de excepciones que deseamos arrojar. He aquí algunos ejemplos:

jint throwNoClassDefError(JNIEnv *env, char *message) 
{ 
    jclass exClass; 
    char *className = "java/lang/NoClassDefFoundError"; 

    exClass = (*env)->FindClass(env, className); 
    if (exClass == NULL) { 
     return throwNoClassDefError(env, className); 
    } 

    return (*env)->ThrowNew(env, exClass, message); 
} 

jint throwNoSuchMethodError(
     JNIEnv *env, char *className, char *methodName, char *signature) 
{ 

    jclass exClass; 
    char *exClassName = "java/lang/NoSuchMethodError" ; 
    LPTSTR msgBuf; 
    jint retCode; 
    size_t nMallocSize; 

    exClass = (*env)->FindClass(env, exClassName); 
    if (exClass == NULL) { 
     return throwNoClassDefError(env, exClassName); 
    } 

    nMallocSize = strlen(className) 
      + strlen(methodName) 
      + strlen(signature) + 8; 

    msgBuf = malloc(nMallocSize); 
    if (msgBuf == NULL) { 
     return throwOutOfMemoryError 
       (env, "throwNoSuchMethodError: allocating msgBuf"); 
    } 
    memset(msgBuf, 0, nMallocSize); 

    strcpy(msgBuf, className); 
    strcat(msgBuf, "."); 
    strcat(msgBuf, methodName); 
    strcat(msgBuf, "."); 
    strcat(msgBuf, signature); 

    retCode = (*env)->ThrowNew(env, exClass, msgBuf); 
    free (msgBuf); 
    return retCode; 
} 

jint throwNoSuchFieldError(JNIEnv *env, char *message) 
{ 
    jclass exClass; 
    char *className = "java/lang/NoSuchFieldError" ; 

    exClass = (*env)->FindClass(env, className); 
    if (exClass == NULL) { 
     return throwNoClassDefError(env, className); 
    } 

    return (*env)->ThrowNew(env, exClass, message); 
} 

jint throwOutOfMemoryError(JNIEnv *env, char *message) 
{ 
    jclass exClass; 
    char *className = "java/lang/OutOfMemoryError" ; 

    exClass = (*env)->FindClass(env, className); 
    if (exClass == NULL) { 
     return throwNoClassDefError(env, className); 
    } 

    return (*env)->ThrowNew(env, exClass, message); 
} 

De esta manera, es fácil encontrarlos, su editor de código-finalización le ayudará a escribir en ellas, y se puede pasar parámetros simples.

Estoy seguro de que podría ampliar esto para manejar excepciones encadenadas u otros enfoques más complicados. Esto fue suficiente para satisfacer nuestras necesidades.

+20

Acabo de encontrar esto, gracias. Sin embargo, ¿no será la condición de error en 'throwNoClassDefError' el resultado de una recursión infinita y un inevitable desbordamiento de la pila? Realmente nunca debería suceder, lo admito, pero esa no parece ser la forma adecuada de manejarlo. Tal vez recurrir a 'java.lang.error', y' abort() 'o algo si eso no funciona. –

+0

Sí, también lo vi. Convenido. No puedo hacer que mis llamadas a ThrowNew() hagan _ cualquier cosa_, a pesar de que devuelvan NULL (éxito, eso es). Nada es fácil ... –

+1

Lo siento, pero ¿alguien puede explicarme por qué la función throwNoClassDefError no caerá en recursión infinita en caso de que no se encuentre la clase "java/lang/NoClassDefFoundError"? –

15

simplemente utilizar 2 líneas:

sprintf(exBuffer, "NE%4.4X: Caller can %s %s print", marker, "log", "or"); 
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/Exception"), exBuffer); 

Produce:

Exception in thread "main" java.lang.Exception: NE0042: Caller can log or print. 
+3

Se me ha dado a entender que la captura de java.lang.Exception se considera una mala práctica: estoy lanzando com.mycompany.JniException donde quiero un caso de falla general de JNI. –

+10

@ android.weasel: Amigo, es un código de muestra en StackOverflow para ilustrar la API ThrowNew. No pretende ser código de producción en un servidor de misión crítica. Dale al chico un descanso... – deltamind106

6

Mi código comienza en Java, invoca C++, que a su vez invoca Java volver de nuevo para cosas como encontrar, conseguir y configuración valores de campo.

En caso de que alguien en busca de un C++ enfoque encuentra esta página, voy a arar con esto:

lo que ahora estoy haciendo está envolviendo mis cuerpos de los métodos JNI con un C++ try/catch,

JNIEXPORT void JNICALL Java_com_pany_jni_JNIClass_something(JNIEnv* env, jobject self) 
{ 
    try 
    { 
     ... do JNI stuff 
     // return something; if not void. 
    } 
    catch (PendingException e) // (Should be &e perhaps?) 
    { 
     /* any necessary clean-up */ 
    } 
} 

donde PendingException se declara trivialmente:

class PendingException {}; 

y estoy invocando el método siguiente después de invocar cualquier JNI de C++, por lo que si el estado de excepción de Java indican Es un error, voy a rescatar de inmediato y dejar que el manejo normal excepción de Java añadir la línea (método nativo) para el seguimiento de la pila, al tiempo que el C++ la oportunidad de limpiar mientras se relajan:

PendingException PENDING_JNI_EXCEPTION; 
void throwIfPendingException(JNIEnv* env) 
{ 
    if (env->ExceptionCheck()) { 
     throw PENDING_JNI_EXCEPTION; 
    } 
} 

Mi pila de Java rastro parece que esto para un env> GetFieldID() llamada fallida:

java.lang.NoSuchFieldError: no field with name='opaque' signature='J' in class Lcom/pany/jni/JniClass; 
    at com.pany.jni.JniClass.construct(Native Method) 
    at com.pany.jni.JniClass.doThing(JniClass.java:169) 
    at com.pany.jni.JniClass.access$1(JniClass.java:151) 
    at com.pany.jni.JniClass$2.onClick(JniClass.java:129) 
    at android.view.View.performClick(View.java:4084) 

y bastante similares si llamo a un método Java que arroja:

java.lang.RuntimeException: YouSuck 
    at com.pany.jni.JniClass.fail(JniClass.java:35) 
    at com.pany.jni.JniClass.getVersion(Native Method) 
    at com.pany.jni.JniClass.doThing(JniClass.java:172) 

no puedo hablar con envoltura la excepción de Java dentro de otra excepción de Java desde dentro de C++, que creo que es parte de su pregunta, no he encontrado la necesidad de hacerlo, pero si lo hiciera, lo haría con un envoltorio de nivel Java alrededor del métodos nativos, o simplemente extender mis métodos de lanzamiento de excepciones para tomar un jthrowable y reemplazar el env-> ThrowNew() llamada con algo feo: es desafortunado que Sun no haya proporcionado una versión de ThrowNew que haya sido eliminada.

void impendNewJniException(JNIEnv* env, const char *classNameNotSignature, const char *message) 
{ 
    jclass jClass = env->FindClass(classNameNotSignature); 
    throwIfPendingException(env); 
    env->ThrowNew(jClass, message); 
} 

void throwNewJniException(JNIEnv* env, const char* classNameNotSignature, const char* message) 
{ 
    impendNewJniException(env, classNameNotSignature, message); 
    throwIfPendingException(env); 
} 

no consideraría referencias almacenamiento en caché (excepción) constructor de la clase porque las excepciones no se supone que es un mecanismo de control de flujo de costumbre, por lo que no debería importar si son lentos. Me imagino que la búsqueda no es terriblemente lenta de todos modos, ya que Java presumiblemente hace su propio almacenamiento en caché para ese tipo de cosas.

Cuestiones relacionadas