67

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?

Graeme
  • 25,714
  • 24
  • 124
  • 186

3 Answers3

67

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.

javier-sanz
  • 2,504
  • 23
  • 23
  • I was hoping for something a little more... encompassing. But after bounty this does seem to be as good as it gets for insulating Java code from Native code crashes. – Graeme Dec 19 '11 at 11:36
  • 4
    While this answer is great for catching thrown C++ exceptions it doesn't deal with SIGNAL errors (which include C's version of a NullPointerException). This is a great post which, with above, should be able to tightly pin down error reporting in your native applications: http://stackoverflow.com/a/1789879/726954 – Graeme Oct 01 '12 at 08:34
  • 2
    This answer is a little more detailed and might be better for your needs: http://stackoverflow.com/a/12014833 – Ed Burnette Apr 05 '13 at 21:48
  • Why do we need plan B? What if it fails? – Maksim Dmitriev Nov 04 '14 at 16:48
  • @MaksimDmitriev The plan B is in case you are using your ow exception and it is not on the classpath. I've changed the code to make it more clear. – javier-sanz Oct 26 '17 at 09:25
8

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.

Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200
  • Hi @itsrajesh4uguys I think your issue is just before using `CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION`. To localize the line, you may replace `CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION` by the corresponding code (without the `\` at the end of each lines). Good luck, cheers ;-) – oHo Aug 28 '14 at 12:31
0

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.

thejartender
  • 9,339
  • 6
  • 34
  • 51
  • You mean, you think I should propagate the exception up by throwing a `RuntimeException`? When debugging the application a NPE will be thrown when a .set() (for instance) is called on a variable which you can see through the debugger and is reportable from a `Log.v()` directly before the .set() is called. – Graeme Dec 12 '11 at 10:00
  • I mean that if you can't get to the root of it because it's not your own API throwing it. Then wrap it for yourself in an Exception that you are fully aware of like 'BubbleException' you can then test higher up in your stack for this Exception and deal with it as your own. Generally though NPE indicates there is a null somewhere, but if you think it's root is lower in the stack (perhaps code you are using) then either ditch the code, or throw it in an RuntimeException of your own as to not inconvenience your API clients with an Exception that you can't explain yourself. – thejartender Dec 12 '11 at 10:24
  • What I'm saying is the native code doesn't "throw" and Exception, it dies in a totally different way which can't be caught with `try` / `catch`. The `NPE`'s reported I think are a side effect of the native code dying and are not directly related to the crash in any way. – Graeme Dec 12 '11 at 10:26
  • Not a problem - thanks for trying. Think the problem is a tricky one to pin down – Graeme Dec 12 '11 at 10:32
  • Plus, please note the "I think" in the previous comment :) – Graeme Dec 12 '11 at 10:50