web-dev-qa-db-fra.com

Le meilleur moyen de créer des exceptions dans le code JNI?

Je voudrais un moyen simple et cohérent de jeter des exceptions dans le code JNI; quelque chose qui gère les exceptions chaînées (implicitement de la méthode env-> ExceptionOccur, ou explicitement par les paramètres, de toute façon est bon) et m'empêche de chercher des constructeurs chaque fois que je veux le 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.

Quelqu'un sur SO a-t-il quelque chose comme ceci qu'il peut partager?

51
Chris R

Nous ne codons que des méthodes utilitaires pour chacun des types d'exceptions que nous voulons lancer. 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 éditeur d’achèvement de code vous aidera à les saisir et vous pourrez passer des paramètres simples.

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

42
Steven M. Cherry

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.
19
Java42

Mon code commence en Java, appelle C++, qui appelle Java à nouveau pour rechercher, obtenir et définir des valeurs de champ.

Au cas où quelqu'un cherchant une approche C++ trouve cette page, je vais continuer avec ceci:

Ce que je fais maintenant, c’est d’envelopper les corps de mes méthodes JNI avec un bloc try/catch C++,

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ée de manière triviale:

class PendingException {};

et j'appelle la méthode suivante après avoir appelé n'importe quel JNI à partir de C++. Par conséquent, si le statut de l'exception Java indique une erreur, je sauvegarde immédiatement et laisse la gestion des exceptions Java normale ajouter la ligne (méthode native) à la trace de pile, Donner au C++ la possibilité de nettoyer en décompressant:

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

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

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 jette:

 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 d'encapsuler l'exception Java dans une autre exception Java à partir de C++, ce qui, je pense, fait partie de votre question - je n'ai pas trouvé la nécessité de le faire - mais si je le faisais, je le ferais avec un wrapper de niveau Java autour des méthodes natives, ou simplement étendre mes méthodes de lancement d'exceptions pour prendre un jthrowable et remplacer l'appel env-> ThrowNew () par quelque chose de moche: c'est dommage que Sun n'ait pas fourni de version de ThrowNew joutable.

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 n'envisagerais pas de mettre en cache (exception) les références de constructeur de classe, car les exceptions ne sont pas censées être un mécanisme de flux de contrôle habituel. Par conséquent, peu importe si elles sont lentes. De toute façon, j’imagine que la recherche n’est pas très lente, car Java fait probablement sa propre mise en cache pour ce genre de choses.

6
android.weasel

Je vais donner une réponse plus complète et plus générale à ceux qui ont besoin d’un peu plus d’explications que moi.

Tout d’abord, il est agréable de définir votre méthode avec un Throw Exception afin que le IDE vous demande d’essayer/attraper.

public native int func(Param1, Param2, Param3) throws IOException;

Je décide pour IOException sur Exception à cause de ceci .

JNIEXPORT int JNICALL Java_YourClass_func
(int Param1, int Param2, int Param3) {
    if (Param3 == 0) { //something wrong
        jclass Exception = env->FindClass("Java/lang/Exception");
        env->ThrowNew(Exception, "Can't divide by zero."); // Error Message
    }
    return (Param1+Param2)/Param3;
}
0
Canato