Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching exceptions thrown from native code running on Android

The project I'm currently working on requires me to code up the android portion of a cross platform program implementation.

A core set of functionality is built and included in my app through android-ndk. I've found that any exception/crash which happens in the native code is only reported now and again at best. When an error occurs I get one of the following behaviours:

  • A stacktrace / memory dump occurs and is written to the log file. The program disappears (no indication is given on the device as to why suddenly the app is no longer there).
  • No stacktrace / dump or other indication is given that the native code has crashed. The program disappears.
  • The java code crashes with a NullPointerException (usually in the same place per native code exception which is a massive pain). Usually causing me to spend quite a while trying to debug why the Java code has thrown an error only to discover the Java code is fine & the native code error has been entirely masked.

I can't seem to find any way to "insulate" my code against errors which occur in native code. Try/catch statements are resoundingly ignored. Apart from when my code is fingered as the culprit I don't even get a chance to warn the user than an error has occurred.

Can someone please help me out as to how to respond to the situation of crashing native code?

like image 469
Graeme Avatar asked Dec 07 '11 12:12

Graeme


People also ask

How do I handle exceptions in Android?

How Exceptions Work in JVM and Android. In Java, all exception and error types are subclasses of Throwable. The Throwable class changes the execution flow of JVM apps by throwing exceptions and deciding how to recover from an error. Adding an exception to a code changes its execution flow.

What is native code in Android?

The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input.


2 Answers

I used to have the same problem, it is true that in android (inside any VM in general when executing native code) if you throw a C++ exception and this one is not caught, the VM dies (If I understood correctly, I think it is your problem). The solution I adopted was to catch any exception in C++ and throw a java exception instead of using JNI. The next code it is a simplified example of my solution. First of all you have a JNI method that catches a C++ exception and then in the try clause the Java exception is annotated.

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

Note that after a ThrowNew, the native method does not abruptly terminate automatically. That is, control flow returns to your native method, and the new exception is pending at this point. The exception will be thrown after your JNI method is finished.

I hope it was the solution you are looking for.

like image 111
javier-sanz Avatar answered Sep 19 '22 02:09

javier-sanz


EDIT:   See also this more elegant answer.


Below mechanism is based on a C preprocessor macro that I have successfully implemented within a JNI layer.

The above macro CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION converts the C++ exceptions into Java exceptions.

Replace mypackage::Exception by your own C++ Exception. If you do not have defined the corresponding my.group.mypackage.Exception in Java, then replace "my/group/mypackage/Exception" by "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");          \   } 

The file Java_my_group_mypackage_example.cpp using the above macro:

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 } 

Just for information or curiosity, I provide below the corresponding Java code (file example.java). Note the "my-DLL-name" is the above C/C++ code compiled as a DLL ("my-DLL-name" without the ".dll" extension). This also works perfectly using Linux/Unix shared library *.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   } } 

First, generate example.class from example.java (using javac or your favorite IDE or maven...). Second, generate C/C++ header file Java_my_group_mypackage_example.h from example.class using javah.

like image 22
oHo Avatar answered Sep 21 '22 02:09

oHo