2008-10-23 5 views
46

Je voudrais un moyen cohérent et simple de lancer des exceptions dans le code JNI; quelque chose qui gère les exceptions chaînés (implicitement du env-> méthode ExceptionOccurred ou explicitement par des paramètres, de toute façon est bonne) et me sauve à la recherche des constructeurs chaque fois que je veux faire. Tout ce qui précède est de préférence en C, bien que je puisse le traduire à partir de C++ au besoin.La meilleure façon de lancer des exceptions dans le code JNI?

Quelqu'un de SO ont quelque chose comme ça qu'ils peuvent partager?

+0

Par «exceptions enchaînées de poignées», voulez-vous dire que votre code remarquerait une exception au niveau Java lors du retour de Java vers C++, l'encapsulerait dans une autre exception et relèverait cette nouvelle exception de C++ vers Java? –

Répondre

38

Nous venons juste de méthodes utilitaires de code pour chacun des types d'exceptions que nous voulons jeter. Voici quelques exemples:

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 cette façon, il est facile de les trouver, votre rédacteur complétion de code vous aidera à les saisir, et vous pouvez passer des paramètres simples.

Je suis sûr que vous pouvez étendre cela à gérer les exceptions enchaînées, ou d'autres approches plus complexes. C'était suffisant pour répondre à nos besoins.

+20

Je viens de trouver cela, merci. Cependant, la condition d'erreur dans 'throwNoClassDefError' n'entraînera-t-elle pas une récursion infinie et un débordement de pile inévitable? Je dois admettre que cela ne devrait jamais arriver, mais cela ne semble pas être la bonne façon de le gérer. Peut-être retomber sur 'java.lang.error', et' abort() 'ou quelque chose si cela ne fonctionne pas. –

+0

Oui, j'ai vu ça aussi. D'accord. Je ne peux pas obtenir mes appels ThrowNew() pour faire _anything_, même s'ils retournent NULL (succès, c'est-à-dire). Nothin est toujours facile ... –

+1

Désolé, mais quelqu'un peut-il m'expliquer pourquoi la fonction throwNoClassDefError ne tombera pas dans la récursion infinie au cas où la classe "java/lang/NoClassDefFoundError" ne sera pas trouvée? –

15

J'utilise simplement 2 lignes:

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

produit:

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

Je suis amené à comprendre que attraper java.lang.Exception est considéré comme une mauvaise pratique: je lance com.mycompany.JniException où je veux un cas d'échec JNI général. –

+10

@ android.weasel: Mec, c'est un exemple de code sur StackOverflow pour illustrer l'API ThrowNew. Ce n'est pas destiné à être un code de production dans un serveur critique. Donne une pause au gars ... – deltamind106

6

Mon code commence en Java, appelle C++, qui appelle ensuite Java à nouveau pour des choses comme la recherche, l'obtention et le réglage valeurs de champ.

Dans le cas où une personne à la recherche d'une approche C++ trouve cette page, je laboure avec ceci:

Ce que je suis en train de faire est emballer mes corps de méthode JNI avec un C++ bloc 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 */ 
    } 
} 

où PendingException est déclaré trivialement:

class PendingException {}; 

et j'invoque la méthode suivante après avoir invoqué une JNI de C++, donc si l'état d'exception Java indiquent est une erreur, je vais immédiatement en liberté sous caution et laisser la gestion des exceptions normale Java ajouter la (méthode native) ligne à la trace de la pile, tout en donnant le C++ la possibilité de nettoyer tout déroulage:

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

Ma pile Java trace ressemble à ceci pour un échec env-> GetFieldId() appel:

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) 

et assez similaire si j'appelle à une méthode Java qui lance:

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) 

Je ne peux pas parler à l'emballage l'exception Java au sein d'une autre exception Java au sein de C++, ce qui, je pense, fait partie de votre question - je n'ai pas trouvé le besoin de le faire - mais si je le faisais, je le ferais avec un wrapper Java autour du méthodes natives ou étendent juste mes exceptions jetant des méthodes pour prendre un jthrowable et remplacer le env-> ThrowNew() appeler avec quelque chose de laid: il est malheureux Sun n'a pas fourni une version de ThrowNew qui a pris un jthrowable.

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); 
} 

Je ne considère pas la mise en cache (exception) références constructeur de classe parce que les exceptions ne sont pas censés être un mécanisme de contrôle de flux d'habitude, il devrait donc pas d'importance si elles sont lentes. J'imagine que la recherche n'est pas terriblement lente de toute façon, puisque Java fait probablement sa propre mise en cache pour ce genre de chose.