JNI由浅入深_9_JNI 异常处理
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 异常处理的更多相关文章
- JNI由浅入深_10_JNI 综合开发
1.使用ndk-build时如果找不到某个类,可以使用下面两种方法解决: 1.1 进入src目录 D:\project3\JNIAndroid\src>set classpath=D:\proj ...
- JNI由浅入深_8_JNI缓存字段和方法ID
获取字段ID和方法ID时,需要用字段.方法的名字和描述符进行一个检索.检索过程相对比较费时,因此本节讨论用缓存技术来减少这个过程带来的消耗.缓存字段ID和方法ID的方法主要有两种.两种区别主要在于缓存 ...
- JNI由浅入深_7_c调用Java方法一
1.在Java中声明方法 <span style="font-size:14px;">/** * javah -encoding utf-8 -jni com.exam ...
- JNI由浅入深_6_简单对象的应用
1.声明native方法 public class ComplexObject { /** * 返回一个对象数组 * @param val * @return */ public native Per ...
- JNI由浅入深_5_基本类型应用
1.基本类型应用 对于JNI处理基本类型还是比较简单的,下面是Java代码: <span style="font-size:14px;"> public native ...
- JNI由浅入深_4_JNI基础知识详解
Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 ...
- JNI由浅入深_3_Hello World
1.需要准备的工具,eclipse,cdt(c++)插件,cygwin(unix)和 android ndk. 在cygwin的etc目录下将ndk的路径引入到profile文件中,可以在cygwin ...
- JNI由浅入深_2_C语言基础
*含义 1.乘法 3*5 2.定义指针变量 int * p://定义了一个名字叫p的变量,能够存放int数据类型的地址 3.指针运算符, //如果p是一个已经定义好的指针变量则*p表示以p的内容为地址 ...
- Android JNI技术介绍【转】
本文转载自:http://blog.csdn.net/yangwen123/article/details/8085833 JNI是JavaNative Interface 的缩写,通过JNI,Jav ...
随机推荐
- Linux 更改时区
原文:https://www.cnblogs.com/st-jun/p/7737188.html Linux修改时区的正确方法 CentOS和Ubuntu的时区文件是/etc/localtime,但是 ...
- 简单的sqlserver批量插入数据easy batch insert data use loop function in sqlserver
--example 1: DECLARE @pid INT,@name NVARCHAR(50),@level INT,@i INT,@column2 INT SET @pid=0 SET @name ...
- 用Java开发贪吃蛇游戏
贪吃蛇游戏的设计步骤: Part 1: 设计游戏图纸 画出900*700的白色窗口 在窗口上添加画布 在画布上添加标题 在画布上添加黑色游戏区 Part 2: 放置静态的蛇:一个头.两个身体 加上开始 ...
- The difference between a local variable and a member variable
package com.itheima_04; /* * 成员变量和局部变量的区别: * A:在类中的位置不同 * 成员变量:类中,方法外 * 局部变量:方法中或者方法声明上(形式参数) * B:在内 ...
- 微信小程序开发7-JavaScript脚本
1.小程序的主要开发语言是 JavaScript ,开发者使用 JavaScript 来开发业务逻辑以及调用小程序的 API 来完成业务需求. 2.ECMAScript 在大部分开发者看来,ECMAS ...
- 《ArcGIS Runtime SDK for Android开发笔记》——(4)、基于Android Studio构建ArcGIS Android开发环境
1.前言 2015年1月15日,发布ArcGIS Runtime SDK for Android v10.2.5版本.从该版本开始默认支持android studio开发环境,示例代码的默认开发环境也 ...
- Qt消息机制和事件、事件过滤
一,事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等 ...
- c# 设计模式 之:工厂模式之---简单工厂
1.uml类图如下: 具体实现和依赖关系: 实现:SportCar.JeepCar.HatchbackCar 实现 Icar接口 依赖: Factory依赖 SportCar.JeepCar.Hatc ...
- win10系统80端口被System (PID=4)占用的解决
今天想用wamp搭建虚拟目录.发现80端口被占用,操作挺麻烦的,所以想要更改. 具体流程如下: 1.“win+R”输入“cmd”,然后输入“netstat -ano | findstr "8 ...
- 使用TFHpple解析html
使用TFHpple解析html https://github.com/topfunky/hpple 前期准备工作 引入静态库文件 添加库文件的 header search paths(注意,必须选中 ...