web-dev-qa-db-fra.com

Attraper les exceptions levées à partir du code natif exécuté sur Android

Le projet sur lequel je travaille actuellement m'oblige à coder la partie Android d'une implémentation de programme multiplateforme.

Un ensemble de fonctionnalités de base est créé et inclus dans mon application via Android-ndk. J'ai trouvé que toute exception/crash qui se produit dans le code natif n'est signalée que de temps en temps au mieux. Lorsqu'une erreur se produit, j'obtiens l'un des comportements suivants:

  • Un vidage de trace de pile/mémoire se produit et est écrit dans le fichier journal. Le programme disparaît (aucune indication n'est donnée sur l'appareil pour expliquer pourquoi soudainement l'application n'est plus là).
  • Aucune trace de pile/vidage ou autre indication n'est donnée que le code natif s'est écrasé. Le programme disparaît.
  • Le code Java se bloque avec un NullPointerException (généralement au même endroit par exception de code natif, ce qui est une énorme douleur). Cela me fait généralement passer un certain temps à essayer de déboguer pourquoi. le code Java a généré une erreur uniquement pour découvrir le code Java est correct et l'erreur de code natif a été entièrement masquée).

Je n'arrive pas à trouver de moyen "d'isoler" mon code contre les erreurs qui se produisent dans le code natif. Les instructions try/catch sont résolument ignorées. À part quand mon code est pointé du doigt comme coupable, je n'ai même pas la possibilité d'avertir l'utilisateur qu'une erreur s'est produite.

Quelqu'un peut-il m'aider à répondre à la situation de plantage du code natif?

50
Graeme

J'ai eu le même problème, il est vrai que dans Android (à l'intérieur de tout VM en général lors de l'exécution de code natif) si vous lâchez une exception C++ et celui-ci n'est pas pris, le VM meurt (si j'ai bien compris, je pense que c'est votre problème). La solution que j'ai adoptée a été de capturer toute exception en C++ et de lancer un Java exception au lieu d'utiliser JNI. Le code suivant est un exemple simplifié de ma solution. Tout d'abord, vous avez une méthode JNI qui intercepte une exception C++, puis dans la clause try la Java est annotée.

JNIEXPORT void JNICALL Java_com_MyClass_foo (JNIEnv *env, jobject o,jstring param)
{
    try
    {
        // Your Stuff
        ...
    }
    // You can catch std::exception for more generic error handling
    catch (MyCxxException e)
    {
        throwJavaException (env, e.what());
    }
}


void throwJavaException(JNIEnv *env, const char *msg)
{
    // You can put your own exception here
    jclass c = env->FindClass("company/com/YourException");

    if (NULL == c)
    {
        //B plan: null pointer ...
        c = env->FindClass("Java/lang/NullPointerException");
    }

    env->ThrowNew(c, msg);
}

Notez qu'après un ThrowNew, la méthode native ne se termine pas brusquement automatiquement. Autrement dit, le flux de contrôle revient à votre méthode native et la nouvelle exception est en attente à ce stade. L'exception sera levée une fois votre méthode JNI terminée.

J'espère que c'était la solution que vous recherchez.

48
javier-sanz

EDIT: Voir aussi cette réponse plus élégante .


Le mécanisme ci-dessous est basé sur une macro de préprocesseur C que j'ai réussi à implémenter dans une couche JNI .

La macro ci-dessus CATCH_CPP_EXCEPTION_AND_THROW_Java_EXCEPTION Convertit les exceptions C++ en Java exceptions.

Remplacez mypackage::Exception Par votre propre exception C++. Si vous n'avez pas défini le my.group.mypackage.Exception Correspondant en Java, remplacez "my/group/mypackage/Exception" Par "Java/lang/RuntimeException".

#define CATCH_CPP_EXCEPTION_AND_THROW_Java_EXCEPTION              \
                                                                  \
  catch (const mypackage::Exception& e)                           \
  {                                                               \
    jclass jc = env->FindClass("my/group/mypackage/Exception");   \
    if(jc) env->ThrowNew (jc, e.what());                          \
    /* if null => NoClassDefFoundError already thrown */          \
  }                                                               \
  catch (const std::bad_alloc& e)                                 \
  {                                                               \
    /* OOM exception */                                           \
    jclass jc = env->FindClass("Java/lang/OutOfMemoryError");     \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (const std::ios_base::failure& e)                         \
  {                                                               \
    /* IO exception */                                            \
    jclass jc = env->FindClass("Java/io/IOException");            \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (const std::exception& e)                                 \
  {                                                               \
    /* unknown exception */                                       \
    jclass jc = env->FindClass("Java/lang/Error");                \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (...)                                                     \
  {                                                               \
    /* Oops I missed identifying this exception! */               \
    jclass jc = env->FindClass("Java/lang/Error");                \
    if(jc) env->ThrowNew (jc, "unidentified exception");          \
  }

Le fichier Java_my_group_mypackage_example.cpp Utilisant la macro ci-dessus:

JNIEXPORT jlong JNICALL Java_my_group_mypackage_example_function1
  (JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
    return jlong(result);
  }
  CATCH_CPP_EXCEPTION_AND_THROW_Java_EXCEPTION
  return 0;
}

JNIEXPORT jstring JNICALL Java_my_group_mypackage_example_function2
  (JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
    jstring jstr = env->NewStringUTF("my result");
    return  jstr;
  }
  CATCH_CPP_EXCEPTION_AND_THROW_Java_EXCEPTION
  return 0;
}

JNIEXPORT void JNICALL Java_my_group_mypackage_example_function3
  (JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
  }
  CATCH_CPP_EXCEPTION_AND_THROW_Java_EXCEPTION
}

Pour information ou curiosité, je fournis ci-dessous le code Java Java (fichier example.Java) Correspondant. Notez que "my-DLL-name" Est le code C/C++ ci-dessus compilé en tant que DLL ("my-DLL-name" sans l'extension ".dll"). Cela fonctionne également parfaitement en utilisant la bibliothèque partagée Linux/Unix *.so .

package my.group.mypackage;

public class Example {
  static {
    System.loadLibrary("my-DLL-name");
  }

  public Example() {
    /* ... */
  }

  private native int    function1(int); //declare DLL functions
  private native String function2(int); //using the keyword
  private native void   function3(int); //'native'

  public void dosomething(int value) {
    int result = function1(value);  
    String str = function2(value);  //call your DLL functions
    function3(value);               //as any other Java function
  }
}

D'abord, générez example.class À partir de example.Java (En utilisant javac ou votre favori IDE ou maven ... ). Ensuite, générez le fichier d'en-tête C/C++ Java_my_group_mypackage_example.h À partir de example.class En utilisant javah.

5
olibre

Avez-vous pensé à intercepter cette exception, puis à l'intégrer dans une exception d'exécution, juste pour la remonter plus haut dans la pile?

J'ai utilisé un "hack" similaire dans SCJD. Généralement, NPE indique une erreur de votre part, mais si vous êtes convaincu que vous ne faites rien de mal, alors faites simplement un RuntimeException bien documenté qui explique que l'exception est utilisée pour propager l'exception. Déballez-le ensuite et testez s'il s'agit par exemple de NPE et traitez-le comme votre propre exception.

Si cela aboutit à des données erronées, alors vous n'avez pas d'autre option que d'aller à la racine.

0
thejartender