Checking errors and handling exceptions in JNI

JNI functions can fail because of system constraint (for example, lack of memory) or invalid arguments (for example, passing a native UTF-8 string when the function is expecting a UTF-16 string). This recipe discusses how to handle errors and exceptions in JNI programming.

Getting ready

The following recipes should be read first before proceeding with this recipe:

  • Manipulating strings in JNI
  • Managing references in JNI
  • Accessing Java static and instance fields in native code
  • Calling static and instance methods from native code

How to do it…

Follow these steps to create a sample Android project that illustrates errors and exception handling in JNI:

  1. Create a project named ExceptionHandling. Set the package name as cookbook.chapter2. Create an activity named ExceptionHandlingActivity. Under the project, create a folder named jni. Refer to the Loading native libraries and registering native methods recipe of this chapter for more detailed instructions.
  2. Create a file named exceptiontest.c under the jni folder, then implement the ExceptionDemo and FatalErrorDemo methods.
  3. Modify the ExceptionHandlingActivity.java file by adding code to load the native library, then declare and invoke the native methods.
  4. Modify the layout XML file, add the Android.mk build file, and build the native library. Refer to steps 8 to 10 of the the Loading native libraries and registering native methods recipe of this chapter for more details.
  5. We're now ready to run the project. We'll present the output while discussing each native method, in the following section.

How it works…

This recipe discusses error checking and exception handling at JNI:

  • Check for errors and exceptions: Many JNI functions return a special value to indicate failure. For example, the FindClass function returns NULL to indicate it failed to load the class. Many other functions do not use the return value to signal failure; instead an exception is thrown.

    Tip

    Besides JNI functions, the Java code invoked by native code can also throw exceptions. We should make sure we check for such cases in order to write robust native code.

    For the first group of functions, we can simply check the return value to see if an error occurs. For the second group of functions, JNI defines two functions to check for exceptions, as follows:

    jboolean ExceptionCheck(JNIEnv *env);
    jthrowable ExceptionOccurred(JNIEnv *env);

    The first function returns JNI_TRUE to indicate that an exception occurs, and JNI_FALSE otherwise. The second function returns a local reference to the exception. When the second function is used, an additional JNI function can be called to examine the details of the exception:

    void ExceptionDescribe(JNIEnv *env);

    The function prints the exception and a back trace of the stack to the logcat.

    In the native method ExceptionDemo, we used both approaches to check for occurrence of exceptions and ExceptionDescribe to print out the exception details.

  • Handle errors and exceptions: Exceptions at JNI are different from Java exceptions. At Java, when an error occurs, an exception object is created and handed to the runtime. The runtime then searches the call stack for an exception handler that can handle the exception. The search starts at the method where the exception occurred and proceeds in the reverse order in which the methods are called. When such a code block is found, the runtime handles the control to the exception handler. The normal control flow is therefore interrupted. In contrast, JNI exception doesn't change the control flow, and we'll need to explicitly check for exception and handle it properly.

    There are generally two ways to handle an exception. The first approach is to free the resources allocated at JNI and return. This will leave the responsibility of handling the exception to the caller of the native method.

    The second practice is to clear the exception and continue executing. This is done through the following JNI function call:

    void ExceptionClear(JNIEnv *env);

    In the native method ExceptionDemo, we used the second approach to clear java.lang.NullPointerException, and the first approach to return java.lang.RuntimeException to the caller, which is the Java method callExceptionDemo at ExceptionHandlingActivity.java.

    When an exception is pending, not all the JNI functions can be called safely. The following functions are allowed when there are pending exceptions:

    • DeleteGlobalRef
    • DeleteLocalRef
    • DeleteWeakGlobalRef
    • ExceptionCheck
    • ExceptionClear
    • ExceptionDescribe
    • ExceptionOccurred
    • MonitorExit
    • PopLocalFrame
    • PushLocalFrame
    • Release<PrimitiveType>ArrayElements
    • ReleasePrimitiveArrayCritical
    • ReleaseStringChars
    • ReleaseStringCritical
    • ReleaseStringUTFChars

    They're basically exception check and handle functions, or functions that clear resources allocated at native code.

    Note

    Calling JNI functions other than the functions listed here can lead to unexpected results when an exception is pending. We should handle the pending exception properly and then proceed.

  • Throw exceptions in the native code: JNI provides two functions to throw an exception from native code. They have the following prototypes:
    jint Throw(JNIEnv *env, jthrowable obj);
    jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);

    The first function accepts a reference to a jthrowable object and throws the exception, while the second function accepts a reference to an exception class. It will create an exception object of the clazz class with the message argument and throw it.

    In the ExceptionDemo native method, we used the ThrowNew function to throw java.lang.NullPointerException and a Throw function to throw java.lang.RuntimeException.

    The following logcat output indicates how the exceptions are checked, cleared, and thrown:

    How it works…

    The last exception is not cleared at the native method. In the Java code, we catch the exception and display the message on the phone screen:

    How it works…
  • Fatal error: A special type of error is the fatal error, which is not recoverable. JNI defines a function FatalError, as follows, to raise a fatal error:
    void FatalError(JNIEnv *env, const char *msg);

    This function accepts a message and prints it to logcat. After that, the VM instance for the application is terminated. We demonstrated the usage of this function in the native method FatalErrorDemo and Java method callFatalErrorDemo. The following output is captured at logcat:

    How it works…

    Note that the code after the FatalError function is never executed, in neither the native nor Java code, because FatalError never returns, and the VM instance is terminated. On my Android device, this does not lead the Android application to crash, but causes the application to freeze instead.

There's more...

C++ exception is currently not supported on Android JNI programming. In other words, the native C++ exceptions do not propagate to Java world through JNI. Therefore, we should handle C++ exceptions within C++ code. Alternatively, we can write a C wrapper to throw an exception or return an error code to Java.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset