• 热门专题

JNI由浅入深9 JNI异常处理

作者:  发布日期:2015-01-12 22:27:42
  •  

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

    下面的代码中演示了如何声明一个会抛出异常的本地方法。CatchThrow这个类声明了一个会抛出IllegalArgumentException异常的名叫doit的本地方法。
    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:
    	" + e);
             }
         }
         static {
            System.loadLibrary("CatchThrow");
         }
     }
    Main方法调用本地方法doit,doit方法的实现如下:
    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");
         }
     }

    运行程序,输出是:
    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函数生成异常。为了简化这个过程,我们写了一个工具函数专门用来生成一个指定名字的异常。
    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);
     }


    本书中,如果一个函数有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。
     /* 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 */
      }

    第二种方式:
    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 */
     }

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

    2.2 异常处理


    本地代码通常有两种方式来处理一个异常:
    1、一旦发生异常,立即返回,让调用者处理这个异常。
    2、通过ExceptionClear清除异常,然后执行自己的异常处理代码。
    当一个异常发生后,必须先检查、处理、清除异常后再做其它JNI函数调用,否则的话,结果未知。当前线程中有异常的时候,你可以调用的JNI函数非常少,11.8.2节列出了这些JNI函数的详细列表。通常来说,当有一个未处理的异常时,你只可以调用两种JNI函数:异常处理函数和清除VM资源的函数。
    当异常发生时,释放资源是一件很重要的事,下面的例子中,调用GetStringChars函数后,如果后面的代码发生异常,不要忘了调用ReleaseStringChars释放资源。
    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);
     }
     

    2.3 工具函数中的异常


    程序员编写工具函数时,一定要把工具函数内部分发生的异常传播到调用它的方法中去。这里有两个需要注意的地方:
    1、对调用者来说,工具函数提供一个错误返回码比简单地把异常传播过去更方便一些。
    2、工具函数在发生异常时尤其需要注意管理局部引用的方式。
    为了说明这两点,我们写了一个工具函数,这个工具函数根据对象实例方法的名字和描述符做一些方法回调。
     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;
              }


    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方法的实现:
           JNIEXPORT void JNICALL
              Java_InstanceMethodCall_nativeMethod(JNIEnv*env, jobject obj)
              {
                  printf("In C
    ");
                  JNU_CallMethodByName(env, NULL, obj,"callback", "()V");
              }

    调用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");
    	}
    }

     

延伸阅读:

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规