JNI学习笔记(七)——异常处理

时间:2022-12-12 12:10:28

我们已经碰到过在一个JNI函数调用后,native代码进行错误检查的情形。本节解释native代码如何从这些错误条件中检查和恢复。


我们将关注发生错误的JNI函数调用上(而不是native代码上的二进制错误)。如果一个native方法有调用了一个系统调用,只需要简单地按照系统文件表明的方法来检查系统调用可能的失败。另一方面,native方法,呼叫了一个回调函数——java API方法,这时必须按照本节描述的步骤来从可能的异常中,检查和恢复。



概述


下面将以几个例子来介绍JNI的异常处理函数。

在native代码中捕获和抛出异常

以下程序展示了如何声明一个能够抛出异常的native方法。CatchThrow类声明了doit native方法并且指定它抛出一个IllegalArgumentException异常:
class CatchThrow {
private native void doit()
throws IllegalArgumentException;
private void callback() throws NullPointerException {
throw new NullPointerException("CatchThrow.callback");
}

public static void main(String args[]) {
CatchThrow c = new CatchThrow();
try {
c.doit();
} catch (Exception e) {
System.out.println("In Java:\n\t" + e);
}
}
static {
System.loadLibrary("CatchThrow");
}
}

以下是native代码实现:
JNIEXPORT void JNICALL
Java_CatchThrow_doit(JNIEnv *env, jobject obj)
{
jthrowable exc;
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return;
}
(*env)->CallVoidMethod(env, obj, mid);
exc = (*env)->ExceptionOccurred(env);
if (exc) {
/* We don't do much with the exception, except that
we print a debug message for it, clear it, and
throw a new exception. */
jclass newExcCls;
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
newExcCls = (*env)->FindClass(env,
"java/lang/IllegalArgumentException");
if (newExcCls == NULL) {
/* Unable to find the exception class, give up. */
return;
}
(*env)->ThrowNew(env, newExcCls, "thrown from C code");
}
}

以下是运行的输出:
java.lang.NullPointerException:
at CatchThrow.callback(CatchThrow.java)
at CatchThrow.doit(Native Method)
at CatchThrow.main(CatchThrow.java)
In Java:
java.lang.IllegalArgumentException: thrown from C code

回调方法抛出了一个NullPointerException。当CallVoidMethod返回控制给native方法时,native代码会通过调用JNI函数的ExceptionOccurred来检查这个异常。在这个代码中,当一个异常发生了,native代码通过调用ExceptionDescribe函数来输出描述信息。然后调用ExceptionClear来清除该异常,然后向外抛出一个IlleagalArgumentException来代替。

一个有JNI导致的异常,不会马上终止native方法的执行。这一点和java语言的不同,当java编程语言抛出了一个异常,VM会在自动转换控制流到最近的满足异常类型的try/catch语句,然后清除附加的异常,并且执行这个异常处理。与之对比,JNI程序员,在一个异常发生时必须显示地实现控制流。

适当的异常处理

异常处理有时和乏味,但是对程序的健壮性却很有必要。

检查异常

有两种方式可以检查异常: 1)多数JNI函数以返回一个非正常的值来表示有一个错误发生了。该错误的返回值表明当前进程中有一个附加的异常。例如以下例子:
/* a class in the Java programming language */
public class Window {
long handle;
int length;
int width;
static native void initIDs();
static {
initIDs();
}
}


/* C code that implements Window.initIDs */
jfieldID FID_Window_handle;
jfieldID FID_Window_length;
jfieldID FID_Window_width;
JNIEXPORT void JNICALL
Java_Window_initIDs(JNIEnv *env, jclass classWindow)
{
FID_Window_handle =
(*env)->GetFieldID(env, classWindow, "handle", "J");
if (FID_Window_handle == NULL) { /* important check. */
return; /* error occurred. */
}
FID_Window_length =
(*env)->GetFieldID(env, classWindow, "length", "I");
if (FID_Window_length == NULL) { /* important check. */
return; /* error occurred. */
}
FID_Window_width =
(*env)->GetFieldID(env, classWindow, "width", "I");
/* no checks necessary; we are about to return anyway */
}

2)当使用一个不能由返回值断定发生了错误的JNI函数时,native代码必须依赖提出的异常,来进行错误检查。JNI函数在当前线程中执行异常检查的是ExceptionOccurred,在java2 sdk1.2时加入了ExceptionCheck。例如:
public class Fraction {
// details such as constructors omitted
int over, under;
public int floor() {
return Math.floor((double)over/under);
}
}

/* Native code that calls Fraction.floor. Assume method ID
MID_Fraction_floor has been initialized elsewhere. */
void f(JNIEnv *env, jobject fraction)
{
jint floor = (*env)->CallIntMethod(env, fraction,
MID_Fraction_floor);
/* important: check if an exception was raised */
if ((*env)->ExceptionCheck(env)) {
return;
}
... /* use floor */
}


处理异常


native代码可以有两种方式处理异常: 1)当异常发生时,native方法可以选择立即返回。 2)native方法可以清除异常(通过方法ExceptionClear),然后执行异常处理代码。
当异常发生时,在推出native代码执行流程时,有必要清除占用的资源,例如:
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 (...) { /* exception occurred */
(*env)->ReleaseStringChars(env, jstr, cstr);
return;
}
...
/* normal return */
(*env)->ReleaseStringChars(env, jstr, cstr);
}