java JNI编程指南 下载本文

当编写实现本地方法的本地代码时,当心不要造成全局引用和弱引用的累加,因

为本地方法执行完毕后,这两种引用不会被自动释放。

当编写一个工具函数的本地代码时,当心不要在函数的调用轨迹上面遗漏任何的

局部引用,因为工具函数被调用的场合是不确定的,一旦被大量调用,很有可能造成内存溢出。

编写工具函数时,请遵守下面的规则: 1、 一个返回值为基本类型的工具函数被调用时,它决不能造成局部、全局、弱引用不被回收的累加。 2、 当一个返回值为引用类型的工具函数被调用时,它除了返回的引用以外,它决不能造成其它局部、全局、弱引用的累加。

对工具函数来说,为了使用缓存技术而创建一些全局引用或者弱引用是正常的。 如果一个工具函数返回一个引用,你应该详细说明返回的引用的类型,以便于调用者更好地管理它们。下面的代码中,频繁地调用工具函数GetInfoString,我们需要知道GetInfoString返回的引用的类型,以便于在每次使用完成后可以释放掉它: ? while (JNI_TRUE) { ? jstring infoString = GetInfoString(info); ? ... /* process infoString */ ? ? ??? /* we need to call DeleteLocalRef, DeleteGlobalRef, ? or DeleteWeakGlobalRef depending on the type of ? reference returned by GetInfoString. */ ? }

函数NewLocalRef有时被用来确保一个工具函数返回一个局部引用。为了演示这个用法,我们对MyNewString函数做了一些修改。下面的版本把一个被频繁调用的字符串“CommonString” 缓存在了全局引用里: ? jstring ? MyNewString(JNIEnv *env, jchar *chars, jint len) ? { ? static jstring result; ? ? /* wstrncmp compares two Unicode strings */ ? if (wstrncmp(\? /* refers to the global ref caching \? static jstring cachedString = NULL; ? if (cachedString == NULL) { ? /* create cachedString for the first time */ ? jstring cachedStringLocal = ... ; ? /* cache the result in a global reference */ ? cachedString = ? (*env)->NewGlobalRef(env, cachedStringLocal); ? } ? return (*env)->NewLocalRef(env, cachedString); ? }

? ... /* create the string as a local reference and store in ? result as a local reference */ ? return result; ? }

在管理局部引用的生命周期中,Push/PopLocalFrame是非常方便的。你可以在本地函数的入口处调用PushLocalFrame,然后在出口处调用PopLocalFrame,这样的话,在函数对中间任何位置创建的局部引用都会被释放。而且,这两个函数是非常高效的,强烈建议使用它们。

如果你在函数的入口处调用了PushLocalFrame,记住在所有的出口(有return出现的地方)调用PopLocalFrame。在下面的代码中,对PushLocalFrame的调用只有一次,但对PopLocalFrame的调用却需要多次。 ? jobject f(JNIEnv *env, ...) ? { ? jobject result; ? if ((*env)->PushLocalFrame(env, 10) < 0) { ? /* frame not pushed, no PopLocalFrame needed */ ? return NULL; ? } ? ... ? result = ...; ? if (...) { ? /* remember to pop local frame before return */ ? result = (*env)->PopLocalFrame(env, result); ? return result; ? } ? ... ? result = (*env)->PopLocalFrame(env, result); ? /* normal return */ ? return result; ? }

上面的代码同样演示了函数PopLocalFrame的第二个参数的用法。局部引用result一开始在PushLocalFrame创建的当前frame里面被创建,而把result传入PopLocalFrame中时,PopLocalFrame在弹出当前的frame前,会由result生成一个新的局部引用,再把这个新生成的局部引用存储在上一个frame当中。

第六章 异常

很多情况下,本地代码做JNI调用后都要检查是否有错误发生,本章讲的就是怎么样检查错误和处理错误。

我重点放在JNI函数调用引发的错误上面。如果一个本地方法中调用了一个JNI函数,它必须遵守下面几个步骤来检查和处理这个JNI函数调用时可能引发的错误。至于其它可能的错误,比如本地代码中调用了一个可能引发错误的系统方法,那只需要按照该系统方法的标准文档中规定的来处理就可以了。

?

6.1 概述

我们通过一些例子来介绍一些JNI异常处理函数

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

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

private native void doit()

throws IllegalArgumentException;

private void callback() throws NullPointerException {

throw new NullPointerException(\ }

public static void main(String args[]) { CatchThrow c = new CatchThrow(); try {

c.doit();

} catch (Exception e) {

System.out.println(\ } }

static {

System.loadLibrary(\ } }

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, \ 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,

\ if (newExcCls == NULL) {

/* Unable to find the exception class, give up. */ return; }

(*env)->ThrowNew(env, newExcCls, \ } }

运行程序,输出是:

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程序员必须手动处理。

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

抛出异常通常需要两步:通过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不会产生作用。

6.2 妥善地处理异常

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

6.2.1 异常检查

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

第一种方式是:大部分JNI函数会通过特定的返回值(比如NULL)来表示已经发生了一个错误,并且当前线程中有一个异常需要处理。在C语言中,用返回值来标识错误信息是一个很常见的方式。下面的例子中演示了如何通过

GetFieldID的返回值来检查错误。这个例子包含两部分,定义了一些实例字段(handle、length、width)的类Window和一个缓存这些字段的字段ID的本地方法。虽然这些字段位于Window类中,调用GetFieldID时,我们仍然需要检查是否有错误发生,因为VM可能没有足够的内存分配给字段ID。 1. /* 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 code that 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. {