1 、本地代码中如何缓存和抛出异常

下面的代码中演示了如何声明一个会抛出异常的本地方法。CatchThrow这个类声明了一个会抛出IllegalArgumentException异常的名叫doit的本地方法。

<span style="font-family:Comic Sans MS;font-size:14px;">class CatchThrow {
private native void doit()
throws IllegalArgumentException;
private void callback() throwsNullPointerException {
throw newNullPointerException("CatchThrow.callback");
} public static void main(String args[]) {
CatchThrow c = new CatchThrow();
try {
c.doit();
} catch (Exception e) {
System.out.println("InJava:\n\t" + e);
}
}
static {
System.loadLibrary("CatchThrow");
}
}</span>

Main方法调用本地方法doit,doit方法的实现如下:

<span style="font-family:Comic Sans MS;font-size:14px;">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 exceptionclass, give up. */
return;
}
(*env)->ThrowNew(env, newExcCls,"thrown from C code");
}
}</span>

运行程序,输出是:

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把控制权交给本地方法时,本地代码会通过ExceptionOccurred来检查这个异常。在我们的例子中,当一个异常被检测到时,本地代码通过调用ExceptionDescribe来输出一个关于这个异常的描述信息,然后通过调用ExceptionClear清除异常信息,最后,抛出一个IllegalArgumentException。

和JAVA中的异常机制不一样,JNI抛出的异常(例如,通过ThrowNew方法)不被处理的话,不会立即终止本地方法的执行。异常发生后,JNI程序员必须手动处理。

1.1 制作一个抛出异常的工具函数

抛出异常通常需要两步:通过FindClass找到异常类、调用ThrowNew函数生成异常。为了简化这个过程,我们写了一个工具函数专门用来生成一个指定名字的异常。

<span style="font-family:Comic Sans MS;font-size:14px;">void
JNU_ThrowByName(JNIEnv *env, const char *name,const char *msg)
{
jclass cls = (*env)->FindClass(env, name);
/* if cls is NULL, an exception has already been thrown */
if (cls != NULL) {
(*env)->ThrowNew(env, cls, msg);
}
/* free the local ref */
(*env)->DeleteLocalRef(env, cls);
}</span>

本书中,如果一个函数有JNU前缀的话,意味它是一个工具函数。JNU_ThrowByName这个工具函数首先使用FindClass函数来找到异常类,如果FindClass执行失败(返回NULL),VM会抛出一个异常(比如NowClassDefFoundError),这种情况下JNI_ThrowByName不会再抛出另外一个异常。如果FindClass执行成功的话,我们就通过ThrowNew来抛出一个指定名字的异常。当函数JNU_ThrowByName返回时,它会保证有一个异常需要处理,但这个异常不一定是name参数指定的异常。当函数返回时,记得要删除指向异常类的局部引用。向DeleteLocalRef传递NULL不会产生作用。

2 妥善地处理异常

JNI程序员必须能够预测到可能会发生异常的地方,并编写代码进行检查。妥善地异常处理有时很繁锁,但是一个高质量的程序不可或缺的。

2.1 异常检查

检查一个异常是否发生有两种方式。

第一种方式是:大部分JNI函数会通过特定的返回值(比如NULL)来表示已经发生了一个错误,并且当前线程中有一个异常需要处理。在C语言中,用返回值来标识错误信息是一个很常见的方式。下面的例子中演示了如何通过GetFieldID的返回值来检查错误。这个例子包含两部分,定义了一些实例字段(handle、length、width)的类Window和一个缓存这些字段的字段ID的本地方法。虽然这些字段位于Window类中,调用GetFieldID时,我们仍然需要检查是否有错误发生,因为VM可能没有足够的内存分配给字段ID。

 

<span style="font-family:Comic Sans MS;font-size:14px;"> /* a class in the Java programming language */
public class Window {
long handle;
int length;
int width;
static native void initIDs();
static {
initIDs();
}
} /* C codethat 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; /* erroroccurred. */
}
FID_Window_length =
(*env)->GetFieldID(env, classWindow,"length", "I");
if (FID_Window_length == NULL) { /* important check. */
return; /* erroroccurred. */
}
FID_Window_width =
(*env)->GetFieldID(env, classWindow,"width", "I");
/* no checks necessary; weare about to return anyway */
}</span>

第二种方式:

<span style="font-family:Comic Sans MS;font-size:14px;">public class Fraction {
// details such as constructors omitted
int over, under;
public int floor() {
return Math.floor((double)over/under);
}
}
/* Native code that callsFraction.floor. Assume method ID
MID_Fraction_floor has been initializedelsewhere. */
void f(JNIEnv*env, jobject fraction)
{
jint floor = (*env)->CallIntMethod(env, fraction,
MID_Fraction_floor);
/* important: check if an exception wasraised */
if ((*env)->ExceptionCheck(env)) {
return;
}
... /* use floor */
}</span>

当一个JNI函数返回一个明确的错误码时,你仍然可以用ExceptionCheck来检查是否有异常发生。但是,用返回的错误码来判断比较高效。一旦JNI函数的返回值是一个错误码,那么接下来调用ExceptionCheck肯定会返回JNI_TRUE。

2.2 异常处理

本地代码通常有两种方式来处理一个异常:

1、一旦发生异常,立即返回,让调用者处理这个异常。

2、通过ExceptionClear清除异常,然后执行自己的异常处理代码。

当一个异常发生后,必须先检查、处理、清除异常后再做其它JNI函数调用,否则的话,结果未知。当前线程中有异常的时候,你可以调用的JNI函数非常少,11.8.2节列出了这些JNI函数的详细列表。通常来说,当有一个未处理的异常时,你只可以调用两种JNI函数:异常处理函数和清除VM资源的函数。

当异常发生时,释放资源是一件很重要的事,下面的例子中,调用GetStringChars函数后,如果后面的代码发生异常,不要忘了调用ReleaseStringChars释放资源。

<span style="font-family:Comic Sans MS;font-size:14px;">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);
}
</span>

2.3 工具函数中的异常

程序员编写工具函数时,一定要把工具函数内部分发生的异常传播到调用它的方法中去。这里有两个需要注意的地方:

1、对调用者来说,工具函数提供一个错误返回码比简单地把异常传播过去更方便一些。

2、工具函数在发生异常时尤其需要注意管理局部引用的方式。

为了说明这两点,我们写了一个工具函数,这个工具函数根据对象实例方法的名字和描述符做一些方法回调。

        

<span style="font-family:Comic Sans MS;font-size:14px;"> jvalue
JNU_CallMethodByName(JNIEnv*env,
jboolean *hasException,
jobject obj,
const char *name,
const char *descriptor,...)
{
va_list args;
jclass clazz;
jmethodID mid;
jvalue result;
if ((*env)->EnsureLocalCapacity(env, 2)== JNI_OK) {
clazz = (*env)->GetObjectClass(env,obj);
mid = (*env)->GetMethodID(env,clazz, name,
descriptor);
if (mid) {
const char *p = descriptor;
/* skip over argument types to findout the
return type */
while (*p != ')') p++;
/* skip ')' */
p++;
va_start(args, descriptor);
switch (*p) {
case 'V':
(*env)->CallVoidMethodV(env,obj, mid, args);
break;
case '[':
case 'L':
result.l =(*env)->CallObjectMethodV(
env,obj, mid, args);
break;
case 'Z':
result.z =(*env)->CallBooleanMethodV(
env,obj, mid, args);
break;
case 'B':
result.b =(*env)->CallByteMethodV(
env, obj, mid, args);
break;
case 'C':
result.c =(*env)->CallCharMethodV(
env,obj, mid, args);
break;
case 'S':
result.s =(*env)->CallShortMethodV(
env,obj, mid, args);
break;
case 'I':
result.i =(*env)->CallIntMethodV(
env,obj, mid, args);
break;
case 'J':
result.j =(*env)->CallLongMethodV(
env,obj, mid, args);
break;
case 'F':
result.f =(*env)->CallFloatMethodV(
env,obj, mid, args);
break;
case 'D':
result.d =(*env)->CallDoubleMethodV(
env,obj, mid, args);
break;
default:
(*env)->FatalError(env,"illegal descriptor");
}
va_end(args);
}
(*env)->DeleteLocalRef(env, clazz);
}
if (hasException) {
*hasException =(*env)->ExceptionCheck(env);
}
return result;
}</span>

JNU_CallMethodByName的参数当中有一个jboolean指针,如果函数执行成功的话,指针指向的值会被设置为JNI_TRUE,如果有异常发生的话,会被设置成JNI_FALSE。这就可以让调用者方便地检查异常。

JNU_CallMethodByName首先通过EnsureLocalCapacity来确保可以创建两个局部引用,一个类引用,一个返回值。接下来,它从对象中获取类引用并查找方法ID。根据返回类型,switch语句调用相应的JNI方法调用函数。回调过程完成后,如果hasException不是NULL,我们调用ExceptionCheck检查异常。

函数ExceptionCheck和ExceptionOccurred非常相似,不同的地方是,当有异常发生时,ExceptionCheck不会返回一个指向异常对象的引用,而是返回JNI_TRUE,没有异常时,返回JNI_FALSE。而ExceptionCheck这个函数不会返回一个指向异常对象的引用,它只简单地告诉本地代码是否有异常发生。上面的代码如果使用ExceptionOccurred的话,应该这么写:

         if (hasException) {

                  jthrowable exc =(*env)->ExceptionOccurred(env);

                  *hasException = exc != NULL;

                  (*env)->DeleteLocalRef(env, exc);

   }

为了删除指向异常对象的局部引用,DeleteLocalRef方法必须被调用。

使用JNU_CallMethodByName这个工具函数,我们可以重写Instance-MethodCall.nativeMethod方法的实现:

  

<span style="font-family:Comic Sans MS;font-size:14px;">       JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv*env, jobject obj)
{
printf("In C\n");
JNU_CallMethodByName(env, NULL, obj,"callback", "()V");
}</span>

调用JNU_CallMethodByName函数后,我们不需要检查异常,因为本地方法后面会立即返回。

测试代码:

	/**
* 异常处理
*/
public native void doExcepton() throws IllegalArgumentException;
/**
*
* @throws NullPointerException
*/
public void excepton() throws NullPointerException {
throw new NullPointerException("doExcepton.excepton");
}

jni:

/**
* 异常处理
*/
JNIEXPORT void JNICALL Java_com_example_jniandroid_service_CFunction_doExcepton(
JNIEnv * env, jobject obj) {
jthrowable exc;
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid =
(*env)->GetMethodID(env, cls, "excepton", "()V");
if (mid == NULL) {
LOGI(" MID IS NULL");
return;
}
(*env)->CallVoidMethod(env, obj, mid);
exc = (*env)->ExceptionOccurred(env);
//有异常
if (exc) {
jclass newExcCls;
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
newExcCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");
if (newExcCls == NULL) {
return;
}
(*env)->ThrowNew(env, newExcCls, "thrown from C code doExcepton");
}
}

前面所有代码下载

JNI由浅入深_9_JNI 异常处理的更多相关文章

  1. JNI由浅入深_10_JNI 综合开发

    1.使用ndk-build时如果找不到某个类,可以使用下面两种方法解决: 1.1 进入src目录 D:\project3\JNIAndroid\src>set classpath=D:\proj ...

  2. JNI由浅入深_8_JNI缓存字段和方法ID

    获取字段ID和方法ID时,需要用字段.方法的名字和描述符进行一个检索.检索过程相对比较费时,因此本节讨论用缓存技术来减少这个过程带来的消耗.缓存字段ID和方法ID的方法主要有两种.两种区别主要在于缓存 ...

  3. JNI由浅入深_7_c调用Java方法一

    1.在Java中声明方法 <span style="font-size:14px;">/** * javah -encoding utf-8 -jni com.exam ...

  4. JNI由浅入深_6_简单对象的应用

    1.声明native方法 public class ComplexObject { /** * 返回一个对象数组 * @param val * @return */ public native Per ...

  5. JNI由浅入深_5_基本类型应用

    1.基本类型应用 对于JNI处理基本类型还是比较简单的,下面是Java代码: <span style="font-size:14px;"> public native ...

  6. JNI由浅入深_4_JNI基础知识详解

    Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 ...

  7. JNI由浅入深_3_Hello World

    1.需要准备的工具,eclipse,cdt(c++)插件,cygwin(unix)和 android ndk. 在cygwin的etc目录下将ndk的路径引入到profile文件中,可以在cygwin ...

  8. JNI由浅入深_2_C语言基础

    *含义 1.乘法 3*5 2.定义指针变量 int * p://定义了一个名字叫p的变量,能够存放int数据类型的地址 3.指针运算符, //如果p是一个已经定义好的指针变量则*p表示以p的内容为地址 ...

  9. Android JNI技术介绍【转】

    本文转载自:http://blog.csdn.net/yangwen123/article/details/8085833 JNI是JavaNative Interface 的缩写,通过JNI,Jav ...

随机推荐

  1. MySql的InnoDB存储引擎--索引

    索引分类: 1.聚集索引:索引顺序与物理顺序一致. MySql 的 InnoDB 中,主键索引就是聚集索引.好处是,进行搜索的时候,因为索引和物理顺序一致,所以找数据的时候更快. 2.非聚集索引:索引 ...

  2. Code Signal_练习题_Are Similar?

    Two arrays are called similar if one can be obtained from another by swapping at most one pair of el ...

  3. django框架中form表单Post方法无法提交 Forbidden (403) CSRF verification failed. Request aborted.

    问题如图: 解决方法: 在视图函数中引入并使用装饰器 from django.views.decorators.csrf import csrf_exempt @csrf_exempt

  4. Luogu4887 第十四分块(前体)

    sto \(lxl\) orz 考虑莫队,每次移动端点,我们都要询问区间内和当前数字异或有 \(k\) 个 \(1\) 的数字个数 询问 \([l,r]\) 可以再次离线,拆成询问 \([1,l-1] ...

  5. 008Spring & JPA & Hibernate & MySQL

    01下载免安装版MySQL 02安装MySQL a)将MySQL压缩包解压到合适的位置,以C:\programmer\Tools\mysql-5.7.20-winx64路径为例: b)新建系统变量,变 ...

  6. Centos7 下安装Apache2 + MySQL + PHP7

    Apache 1.安装Apache yum install httpd 2.设置服务器开机自动启动Apache systemctl enable httpd.service 若要验证是否自动启动可在重 ...

  7. 【转】PHP如何快速读取大文件

    在PHP中,对于文件的读取时,最快捷的方式莫过于使用一些诸如file.file_get_contents之类的函数,简简单单的几行代码就能 很漂亮的完成我们所需要的功能.但当所操作的文件是一个比较大的 ...

  8. Reverse Integer 旋转数字

    Reverse digits of an integer. Example1: x = 123, return 321Example2: x = -123, return -321 本地注意正负号判断 ...

  9. android端的ormlite框架

    安卓端有很多优秀的数据库框架来操作sqlite,如ormlite框架,这个框架可以用来实现表到对象的解析和转化. 使用: 首先去官网下载两个jar包,core和android(如果在安卓端开发的话), ...

  10. vs2015新建web应用程序空模板和添加webapi的模板生成文件的比较

    文件名为全红色的,是使用webapi模板生成的新文件夹或文件