JNI/NDK Development Guide (11) - JNI Exception Handling

Anomaly Introduction

Exception, the obvious opinion is that the program is not executed according to the normal program logic during operation, and some kind of error occurs during the execution process, causing the program to crash. Exceptions in Java are divided into runtime exceptions (RuntimeException) and compile-time exceptions. We will use try...catch... to handle the logic that an exception may occur during runtime in the program. If it is not handled, an exception will occur during runtime. cause the program to crash. Compile-time exceptions are handled at compile time. This chapter focuses on runtime exceptions.
Example 1:

// 运行时异常
public static void exceptionCallback() {
    int a = 20 / 0;
    System.out.println("--->" + a);
}

Example 2:

// 编译期间异常
public static void testException() throws Exception {
    // ...
    System.out.println("testException() invoked!");
}
public static void main(String[] args) {
    exceptionCallback(); 
    try {
        testException();
    } catch (Exception e) {
        e.printStackTrace();
    }
    // ....
}
在示例2中,testException方法声明时显示抛出了一个java.lang.Exception异常,所以在程序调用的地方必须用try...catch处理。

As we all know, if the main method in example 2 is executed to call the exceptionCallback method, the first line of the method has a division by 0 operation, so the method will throw java.lang.ArithmeticExceptiona mathematical exception, and this function is not processed when the main method is called. An exception that may occur at runtime, so that the program ends immediately, and the following code try{testException();}catch(Exception e) {e.printStackTrace();}will not be executed. Running the example 2 program you will see the following results:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.study.jnilearn.JNIException.exceptionCallback(JNIException.java:8)
    at com.study.jnilearn.JNIException.main(JNIException.java:22)

Let's improve the program above:

public static void main(String[] args) {
    try {
        exceptionCallback();
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        testException();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

At this time, when we run the program, an exception exceptionCallbackwill be thrown when the method is called java.lang.ArithmeticException: / by zero. Since we use the try...catch block to display the exception, the program will continue to execute, call the testException() function, and print testException() invoked!. The result of running is as follows:

java.lang.ArithmeticException: / by zero
    at com.study.jnilearn.JNIException.exceptionCallback(JNIException.java:8)
    at com.study.jnilearn.JNIException.main(JNIException.java:24)
testException() invoked!

The difference between Java and JNI handling exceptions

Let's summarize:
1. If you feel that a certain piece of logic may cause an exception in Java, you can use the try...catch mechanism to catch and handle the exception
. 2. If a runtime exception occurs in Java, try...catch is not used to catch it. , it will cause the program to crash and exit directly, and the subsequent code will not be executed
. 3. Compile-time exceptions are displayed when an exception is declared with throw when the method is declared. The compiler requires that the capture processing
public static void testException() throws Exception {}
above must be displayed when calling. A few points, friends who have written Java know, and it is very simple, but why do I still mention it, in fact, I want to point out that the exceptions that occur in JNI are completely different from Java. When we are writing JNI programs, JNI does not have an exception handling mechanism like try...catch...final like Java, and if an exception occurs when calling a JNI interface in the native code, the subsequent native code will not stop immediately Execute, and continue to execute the following code.

Exception Handling Example

Example 3:
This example calls the doit native method in main, which calls back the method in the native method exceptionCallback, which throws a run-time exception java.lang.ArithmeticExceptionof division by 0. We use this example to learn how to properly handle this in JNI abnormal.

package com.study.jnilearn;

public class JNIException {

    public static native void doit();

    public static void exceptionCallback() {
        int a = 20 / 0;
        System.out.println("--->" + a);
    }

    public static void normalCallback() {
        System.out.println("In Java: invoke normalCallback.");
    }

    public static void main(String[] args) {
        doit();
    }

    static {
        System.loadLibrary("JNIException");
    }
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_JNIException */

#ifndef _Included_com_study_jnilearn_JNIException
#define _Included_com_study_jnilearn_JNIException
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_JNIException
 * Method:    doit
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_JNIException_doit
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

// JNIException.c
#include "com_study_jnilearn_JNIException.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_com_study_jnilearn_JNIException_doit(JNIEnv *env, jclass cls) {
    jthrowable exc = NULL;
    jmethodID mid = (*env)->GetStaticMethodID(env,cls,"exceptionCallback","()V");
    if (mid != NULL) {
        (*env)->CallStaticVoidMethod(env,cls,mid);
    }
    printf("In C: Java_com_study_jnilearn_JNIException_doit-->called!!!!");
    if ((*env)->ExceptionCheck(env)) {  // 检查JNI调用是否有引发异常
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);        // 清除引发的异常,在Java层不会打印异常的堆栈信息
        (*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"JNI抛出的异常!");
        //return;
    }
    mid = (*env)->GetStaticMethodID(env,cls,"normalCallback","()V");
    if (mid != NULL) {
        (*env)->CallStaticVoidMethod(env,cls,mid);
    }
}

The result of running the program is as follows:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.study.jnilearn.JNIException.exceptionCallback(JNIException.java:8)
    at com.study.jnilearn.JNIException.doit(Native Method)
    at com.study.jnilearn.JNIException.main(JNIException.java:17)
Exception in thread "main" java.lang.Exception: JNI抛出的异常!
    at com.study.jnilearn.JNIException.doit(Native Method)

In Java: invoke normalCallback.
    at com.study.jnilearn.JNIException.main(JNIException.java:17)
In C: Java_com_study_jnilearn_JNIException_doit-->called!!!!

After calling the doit local method in the Main method, the control of the program is handed over to JNI, and the exceptionCallback method is called back in the doit local method, which causes an java.lang.ArithmeticExceptionexception, but the local interface will not exit immediately, but will continue to execute the following , so after calling any JNI interface, one thing we must do is to check whether an exception has occurred in this JNI call. There will be unpredictable consequences. In this example, we call the JNI ExceptionCheckfunction to check whether an exception occurred in the last JNi call, if there is an exception, the function returns JNI_TRUE, otherwise it returns JNI_FALSE. When an exception is detected, we call ExceptionDescribethe function to print the stack information of the exception, and then call ExceptionClearthe function to clear the buffer of the exception stack information (if not cleared, the exception stack information thrown by the subsequent call to ThrowNew will overwrite the previous exception information), and finally The calling ThrowNewfunction manually throws a java.lang.Exception. However, throwing an uncaught exception in JNI is different from Java's exception handling mechanism. In JNI, the execution of the native method is not immediately terminated, but the execution of the following code is continued. This situation requires us to handle it manually. In line 38 in the example, if you exit the method immediately without return, the code after ThrowNew in line 37 will continue to execute, and the normalCallbackmethod will still be called back like the result of the program running, and print out: invoke normalCallback.
Exception checking JNI also provides Another interface, ExceptionOccurred, if an exception is detected, the function will return a reference to the current exception. The function is ExceptionCheckthe same, the difference between the two is that the return value is different. Let's modify the above example:

// ....
jthrowable exc = NULL;
exc = (*env)->ExceptionOccurred(env);  // 返回一个指向当前异常对象的引用
if (exc) {
    (*env)->ExceptionDescribe(env); // 打印Java层抛出的异常堆栈信息
    (*env)->ExceptionClear(env);        // 清除异常信息

    // 抛出我们自己的异常处理
    jclass newExcCls;
    newExcCls = (*env)->FindClass(env,"java/lang/Exception");
    if (newExcCls == NULL) {
        return;
    }
    (*env)->ThrowNew(env, newExcCls, "throw from C Code.");
}

// ....

Write a utility class that throws exceptions

When you need to throw your own exception handling logic, you need two steps, call FindClass to find the exception handling class, and then call ThrowNew to throw an exception. In order to simplify the operation steps, we write a utility function, which is specially used to generate an exception with a specified name according to an exception class name:

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg)
 {
     // 查找异常类
     jclass cls = (*env)->FindClass(env, name);
     /* 如果这个异常类没有找到,VM会抛出一个NowClassDefFoundError异常 */
     if (cls != NULL) {
         (*env)->ThrowNew(env, cls, msg);  // 抛出指定名字的异常
     }
     /* 释放局部引用 */
     (*env)->DeleteLocalRef(env, cls);
 }

Release resources after an exception occurs

It is important to release resources after an exception occurs. In the following example, after calling the GetStringChars function, if an exception occurs in the following code, remember to call ReleaseStringChars to release resources.

JNIEXPORT void JNICALL Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr)
 {
     const jchar *cstr = (*env)->GetStringChars(env, jstr);
     if (c_str == NULL) {
         return; 
     }
     ...
     if ((*env)->ExceptionCheck(env)) { /* 异常检查 */
         (*env)->ReleaseStringChars(env, jstr, cstr); // 发生异常后释放前面所分配的内存
         return; 
     }
     ...
     /* 正常返回 */
     (*env)->ReleaseStringChars(env, jstr, cstr);
}

Summarize

1. After calling a JNI function, you must first check, handle, and clear the exception before calling other JNI functions, otherwise unpredictable results will occur.
2. Once an exception occurs, return immediately and let the caller handle the exception. Or call ExceptionClear to clear the exception and then execute your own exception handling code.
3. Summary of JNI functions related to exception handling:
1> ExceptionCheck: Check whether an exception has occurred, if there is an exception, return JNI_TRUE, otherwise return JNI_FALSE
2> ExceptionOccurred: Check whether an exception has occurred, if an exception is used, return the reference of the exception, otherwise return NULL
3> ExceptionDescribe: Print exception stack information
4> ExceptionClear: Clear exception stack information
5> ThrowNew: Trigger an exception in the current thread, and customize the output exception information
jint (JNICALL *ThrowNew) (JNIEnv *env, jclass clazz, const char *msg);
6> Throw: Discard an existing exception object, in the current thread Trigger a new exception
jint (JNICALL *Throw) (JNIEnv *env, jthrowable obj);
7> FatalError: Fatal exception, used to output an exception information, and terminate the current VM instance (ie exit the program)
void (JNICALL *FatalError) (JNIEnv *env, const char *msg);

Guess you like

Origin blog.csdn.net/shangsongwww/article/details/122493351