Catching exceptions thrown from native code running on Android

AndroidException HandlingJava Native-InterfaceAndroid NdkNullpointerexception

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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.

[IDE]: https://en.wikipedia.org/wiki/Integrated_development_environment "Integrated Development Environment"

Solution 3 - Android

Have you considered catching this exception and then wrapping it in a runtime exception, just to get it up higher in the stack?

I used a similar 'hack' in SCJD. Generally NPE indicates an error on your part, but if you're convinced you're not doing anything wrong, then simply make a well documented RuntimeException that explains that the exception is used to bubble the Exception. Then unwrap it and test if for instance of NPE and deal with it as your own Exception.

If it's going to result in erroneous data, then you have no other option, but to get to the root of it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGraemeView Question on Stackoverflow
Solution 1 - Androidjavier-sanzView Answer on Stackoverflow
Solution 2 - AndroidoHoView Answer on Stackoverflow
Solution 3 - AndroidthejartenderView Answer on Stackoverflow