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

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

  1. <span style="font-family:Comic Sans MS;font-size:14px;">class CatchThrow {
  2. private native void doit()
  3. throws IllegalArgumentException;
  4. private void callback() throwsNullPointerException {
  5. throw newNullPointerException("CatchThrow.callback");
  6. }
  7.  
  8. public static void main(String args[]) {
  9. CatchThrow c = new CatchThrow();
  10. try {
  11. c.doit();
  12. } catch (Exception e) {
  13. System.out.println("InJava:\n\t" + e);
  14. }
  15. }
  16. static {
  17. System.loadLibrary("CatchThrow");
  18. }
  19. }</span>

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

  1. <span style="font-family:Comic Sans MS;font-size:14px;">JNIEXPORT void JNICALL
  2. Java_CatchThrow_doit(JNIEnv*env, jobject obj)
  3. {
  4. jthrowable exc;
  5. jclass cls = (*env)->GetObjectClass(env,obj);
  6. jmethodID mid =
  7. (*env)->GetMethodID(env, cls,"callback", "()V");
  8. if (mid == NULL) {
  9. return;
  10. }
  11. (*env)->CallVoidMethod(env, obj, mid);
  12. exc = (*env)->ExceptionOccurred(env);
  13. if (exc) {
  14. /* We don't do much with the exception,except that
  15. we print a debug message for it,clear it, and
  16. throw a new exception. */
  17. jclass newExcCls;
  18. (*env)->ExceptionDescribe(env);
  19. (*env)->ExceptionClear(env);
  20. newExcCls = (*env)->FindClass(env,
  21. "java/lang/IllegalArgumentException");
  22. if (newExcCls == NULL) {
  23. /* Unable to find the exceptionclass, give up. */
  24. return;
  25. }
  26. (*env)->ThrowNew(env, newExcCls,"thrown from C code");
  27. }
  28. }</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函数生成异常。为了简化这个过程,我们写了一个工具函数专门用来生成一个指定名字的异常。

  1. <span style="font-family:Comic Sans MS;font-size:14px;">void
  2. JNU_ThrowByName(JNIEnv *env, const char *name,const char *msg)
  3. {
  4. jclass cls = (*env)->FindClass(env, name);
  5. /* if cls is NULL, an exception has already been thrown */
  6. if (cls != NULL) {
  7. (*env)->ThrowNew(env, cls, msg);
  8. }
  9. /* free the local ref */
  10. (*env)->DeleteLocalRef(env, cls);
  11. }</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。

 

  1. <span style="font-family:Comic Sans MS;font-size:14px;"> /* a class in the Java programming language */
  2. public class Window {
  3. long handle;
  4. int length;
  5. int width;
  6. static native void initIDs();
  7. static {
  8. initIDs();
  9. }
  10. }
  11.  
  12. /* C codethat implements Window.initIDs */
  13. jfieldID FID_Window_handle;
  14. jfieldID FID_Window_length;
  15. jfieldID FID_Window_width;
  16.  
  17. JNIEXPORT void JNICALL
  18. Java_Window_initIDs(JNIEnv *env, jclass classWindow)
  19. {
  20. FID_Window_handle =
  21. (*env)->GetFieldID(env, classWindow,"handle", "J");
  22. if (FID_Window_handle == NULL) { /* important check. */
  23. return; /* erroroccurred. */
  24. }
  25. FID_Window_length =
  26. (*env)->GetFieldID(env, classWindow,"length", "I");
  27. if (FID_Window_length == NULL) { /* important check. */
  28. return; /* erroroccurred. */
  29. }
  30. FID_Window_width =
  31. (*env)->GetFieldID(env, classWindow,"width", "I");
  32. /* no checks necessary; weare about to return anyway */
  33. }</span>

第二种方式:

  1. <span style="font-family:Comic Sans MS;font-size:14px;">public class Fraction {
  2. // details such as constructors omitted
  3. int over, under;
  4. public int floor() {
  5. return Math.floor((double)over/under);
  6. }
  7. }
  8. /* Native code that callsFraction.floor. Assume method ID
  9. MID_Fraction_floor has been initializedelsewhere. */
  10. void f(JNIEnv*env, jobject fraction)
  11. {
  12. jint floor = (*env)->CallIntMethod(env, fraction,
  13. MID_Fraction_floor);
  14. /* important: check if an exception wasraised */
  15. if ((*env)->ExceptionCheck(env)) {
  16. return;
  17. }
  18. ... /* use floor */
  19. }</span>

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

2.2 异常处理

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

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

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

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

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

  1. <span style="font-family:Comic Sans MS;font-size:14px;">JNIEXPORT void JNICALL
  2. Java_pkg_Cls_f(JNIEnv*env, jclass cls, jstring jstr)
  3. {
  4. const jchar *cstr =(*env)->GetStringChars(env, jstr);
  5. if (c_str == NULL) {
  6. return;
  7. }
  8. ...
  9. if (...) { /* exception occurred */
  10. (*env)->ReleaseStringChars(env,jstr, cstr);
  11. return;
  12. }
  13. ...
  14. /* normal return */
  15. (*env)->ReleaseStringChars(env, jstr,cstr);
  16. }
  17. </span>

2.3 工具函数中的异常

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

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

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

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

        

  1. <span style="font-family:Comic Sans MS;font-size:14px;"> jvalue
  2. JNU_CallMethodByName(JNIEnv*env,
  3. jboolean *hasException,
  4. jobject obj,
  5. const char *name,
  6. const char *descriptor,...)
  7. {
  8. va_list args;
  9. jclass clazz;
  10. jmethodID mid;
  11. jvalue result;
  12. if ((*env)->EnsureLocalCapacity(env, 2)== JNI_OK) {
  13. clazz = (*env)->GetObjectClass(env,obj);
  14. mid = (*env)->GetMethodID(env,clazz, name,
  15. descriptor);
  16. if (mid) {
  17. const char *p = descriptor;
  18. /* skip over argument types to findout the
  19. return type */
  20. while (*p != ')') p++;
  21. /* skip ')' */
  22. p++;
  23. va_start(args, descriptor);
  24. switch (*p) {
  25. case 'V':
  26. (*env)->CallVoidMethodV(env,obj, mid, args);
  27. break;
  28. case '[':
  29. case 'L':
  30. result.l =(*env)->CallObjectMethodV(
  31. env,obj, mid, args);
  32. break;
  33. case 'Z':
  34. result.z =(*env)->CallBooleanMethodV(
  35. env,obj, mid, args);
  36. break;
  37. case 'B':
  38. result.b =(*env)->CallByteMethodV(
  39. env, obj, mid, args);
  40. break;
  41. case 'C':
  42. result.c =(*env)->CallCharMethodV(
  43. env,obj, mid, args);
  44. break;
  45. case 'S':
  46. result.s =(*env)->CallShortMethodV(
  47. env,obj, mid, args);
  48. break;
  49. case 'I':
  50. result.i =(*env)->CallIntMethodV(
  51. env,obj, mid, args);
  52. break;
  53. case 'J':
  54. result.j =(*env)->CallLongMethodV(
  55. env,obj, mid, args);
  56. break;
  57. case 'F':
  58. result.f =(*env)->CallFloatMethodV(
  59. env,obj, mid, args);
  60. break;
  61. case 'D':
  62. result.d =(*env)->CallDoubleMethodV(
  63. env,obj, mid, args);
  64. break;
  65. default:
  66. (*env)->FatalError(env,"illegal descriptor");
  67. }
  68. va_end(args);
  69. }
  70. (*env)->DeleteLocalRef(env, clazz);
  71. }
  72. if (hasException) {
  73. *hasException =(*env)->ExceptionCheck(env);
  74. }
  75. return result;
  76. }</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方法的实现:

  

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

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

测试代码:

  1. /**
  2. * 异常处理
  3. */
  4. public native void doExcepton() throws IllegalArgumentException;
  5. /**
  6. *
  7. * @throws NullPointerException
  8. */
  9. public void excepton() throws NullPointerException {
  10. throw new NullPointerException("doExcepton.excepton");
  11. }

jni:

  1. /**
  2. * 异常处理
  3. */
  4. JNIEXPORT void JNICALL Java_com_example_jniandroid_service_CFunction_doExcepton(
  5. JNIEnv * env, jobject obj) {
  6. jthrowable exc;
  7. jclass cls = (*env)->GetObjectClass(env, obj);
  8. jmethodID mid =
  9. (*env)->GetMethodID(env, cls, "excepton", "()V");
  10. if (mid == NULL) {
  11. LOGI(" MID IS NULL");
  12. return;
  13. }
  14. (*env)->CallVoidMethod(env, obj, mid);
  15. exc = (*env)->ExceptionOccurred(env);
  16. //有异常
  17. if (exc) {
  18. jclass newExcCls;
  19. (*env)->ExceptionDescribe(env);
  20. (*env)->ExceptionClear(env);
  21. newExcCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");
  22. if (newExcCls == NULL) {
  23. return;
  24. }
  25. (*env)->ThrowNew(env, newExcCls, "thrown from C code doExcepton");
  26. }
  27. }

前面所有代码下载

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. Linux 更改时区

    原文:https://www.cnblogs.com/st-jun/p/7737188.html Linux修改时区的正确方法 CentOS和Ubuntu的时区文件是/etc/localtime,但是 ...

  2. 简单的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 ...

  3. 用Java开发贪吃蛇游戏

    贪吃蛇游戏的设计步骤: Part 1: 设计游戏图纸 画出900*700的白色窗口 在窗口上添加画布 在画布上添加标题 在画布上添加黑色游戏区 Part 2: 放置静态的蛇:一个头.两个身体 加上开始 ...

  4. The difference between a local variable and a member variable

    package com.itheima_04; /* * 成员变量和局部变量的区别: * A:在类中的位置不同 * 成员变量:类中,方法外 * 局部变量:方法中或者方法声明上(形式参数) * B:在内 ...

  5. 微信小程序开发7-JavaScript脚本

    1.小程序的主要开发语言是 JavaScript ,开发者使用 JavaScript 来开发业务逻辑以及调用小程序的 API 来完成业务需求. 2.ECMAScript 在大部分开发者看来,ECMAS ...

  6. 《ArcGIS Runtime SDK for Android开发笔记》——(4)、基于Android Studio构建ArcGIS Android开发环境

    1.前言 2015年1月15日,发布ArcGIS Runtime SDK for Android v10.2.5版本.从该版本开始默认支持android studio开发环境,示例代码的默认开发环境也 ...

  7. Qt消息机制和事件、事件过滤

    一,事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等 ...

  8. c# 设计模式 之:工厂模式之---简单工厂

    1.uml类图如下: 具体实现和依赖关系: 实现:SportCar.JeepCar.HatchbackCar 实现 Icar接口 依赖: Factory依赖 SportCar.JeepCar.Hatc ...

  9. win10系统80端口被System (PID=4)占用的解决

    今天想用wamp搭建虚拟目录.发现80端口被占用,操作挺麻烦的,所以想要更改. 具体流程如下: 1.“win+R”输入“cmd”,然后输入“netstat -ano | findstr "8 ...

  10. 使用TFHpple解析html

    使用TFHpple解析html https://github.com/topfunky/hpple 前期准备工作 引入静态库文件 添加库文件的 header search paths(注意,必须选中 ...