java JNI编程指南 下载本文

/* Access the static field si */

si = (*env)->GetStaticIntField(env, cls, fid); printf(\ (*env)->SetStaticIntField(env, cls, fid, 200); }

运行程序可得到输出结果:

In C:

StaticFieldAccess.si = 100 In Java:

StaticFieldAccess.si = 200

访问静态字段和对象实例字段的不同点:

1、 访问静态字段使用GetStaticFieldID,而访问对象的实例字段使用

GetFieldID,但是,这两个方法都有相同的返回值类型:jfieldID。

4.2 调用方法

JAVA中有几种不同类型的方法,实例方法必须在一个类的某个对象实例上面调用。而静态方法可以在任何一个对象实例上调用。对于构建方法的调用我们推迟到下一节。

JNI支持一系列完整的函数让你可以在本地代码中回调JAVA方法,下面例子演示了如何从本地代码中调用一个JAVA中的实例方法:

class InstanceMethodCall {

private native void nativeMethod(); private void callback() {

System.out.println(\ }

public static void main(String args[]) {

InstanceMethodCall c = new InstanceMethodCall(); c.nativeMethod(); }

static {

System.loadLibrary(\ } }

下面的是本地方法的实现:

JNIEXPORT void JNICALL

Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {

jclass cls = (*env)->GetObjectClass(env, obj); jmethodID mid =

(*env)->GetMethodID(env, cls, \ if (mid == NULL) {

return; /* method not found */ }

printf(\

(*env)->CallVoidMethod(env, obj, mid); }

运行程序,得到如下输出:

In C

In Java

4.2.1 调用实例方法

本地方法Java_InstanceMethodCall_nativeMethod的实现演示了在本地代码中调用JAVA方法的两步:

1、 本地方法首先调用JNI函数GetMethodID。这个函数在指定的类中寻找相应的方法。这个寻找过程是基于方法描述符的。如果方法不存在,GetMethodID返回NULL。这时,立即从本地方法中返回,并引发一个NoSuchMethodError错误。

2、 本地方法通过调用CallVoidMethod来调用返回值为void的实例方法。

除了CallVoidMethod这个函数以外,JNI也支持对返回值为其它类型的方法的调用。如果你调用的方法返回值类型为int,你的本地方法会使用CallIntMethod。类似地,你可以调用CallObjectMethod来调用返回值为java.lang.String、数组等对象类型的方法。

你也可以使用CallMethod系列的函数来调用接口方法。你必须从接口类型中获取方法ID,下面的代码演示了如何在java.lang.Thread实例上面调用Runnable.run方法:

jobject thd = ...; /* a java.lang.Thread instance */ jmethodID mid;

jclass runnableIntf =

(*env)->FindClass(env, \ if (runnableIntf == NULL) { ... /* error handling */ }

mid = (*env)->GetMethodID(env, runnableIntf, \ if (mid == NULL) {

... /* error handling */ }

(*env)->CallVoidMethod(env, thd, mid); ... /* check for possible exceptions */

在3.3.5中,我们使用FindClass来获取一个类的引用,在这里,我们可以学到如何获取一个接口的引用。

4.2.2 生成方法描述符

JNI中描述字段使用字段描述符,描述方法同样有方法描述符。一个方法描述符包含参数类型和返回值类型。参数类型出现在前面,并由一对圆括号将它们括起来,参数类型按它们在方法声明中出现的顺序被列出来,并且多个参数类型之间没有分隔符。如果一个方法没有参数,被表示为一对空圆括号。方法的返回值类型紧跟参数类型的右括号后面。 例如,“(I)V”表示这个方法的一个参数类型为int,并且有一个void类回值。“()D”表示这个方法没有参数,返回值类型为double。

方法描述符中可能会包含类描述符(12.3.2),如方法native private String getLine(String);的描述符为:“(Ljava/lang/String;)Ljava/lang/String;”

数组类型的描述符以“[”开头,后面跟着数组元素类型的描述符。如,public static void main(String[] args);的描述符是:\12.3.4详细描述了怎么样生成一个JNI方法描述符。同样,你可以使用javap工具来打印出JNI方法描述符。

4.2.3 调用静态方法

前一个例子演示了一个本地方法怎样调用实例方法。类似地,本地方法中同样可以调用静态方法: 1、 通过GetStaticMethodID获取方法ID。对应于调用实例方法时的GetMethodID。 2、 传入类、方法ID、参数,并调用提供静态方法调用功能的JNI系列函数中的一个,如:CallStaticVoidMethod,CallStaticBooleanMethod等。 调用静态方法和调用实例方法的JNI函数有一个很大的不同,前者第二个参数是类引用,后者是对象实例的引用。

在JAVA访问一个静态方法可以通过类,也可以通过对象实例。而JNI的规定是,在本地代码中回调JAVA中的静态方法时,必须指定一个类引用才行。下面的例子演示了这个用法:

class StaticMethodCall {

private native void nativeMethod(); private static void callback() { System.out.println(\ }

public static void main(String args[]) {

StaticMethodCall c = new StaticMethodCall(); c.nativeMethod(); }

static {

System.loadLibrary(\ } }

下面是本地方法的实现: JNIEXPORT void JNICALL

Java_StaticMethodCall_nativeMethod(JNIEnv *env, jobject obj) {

jclass cls = (*env)->GetObjectClass(env, obj); jmethodID mid =

(*env)->GetStaticMethodID(env, cls, \ if (mid == NULL) {

return; /* method not found */ }

printf(\

(*env)->CallStaticVoidMethod(env, cls, mid); }

当调用CallStaticVoidMethod时,确保你传入的是类引用cls而不是对象引用obj。运行程序,输出为: In C In Java

4.2.4 调用父类的实例方法

如果一个方法被定义在父类中,在子类中被覆盖,你也可以调用这个实例方法。JNI提供了一系列完成这些功能的函数:CallNonvirtualMethod。为了调用一个定义在父类中的实例方法,你必须遵守下面的步骤: 1、 使用GetMethodID从一个指向父类的引用当中获取方法ID。 2、 传入对象、父类、方法ID和参数,并调用CallNonvirtualVoidMethod、CallNonvirtualBooleanMethod等一系列函数中的一个。 这种调用父类实例方法的情况其实很少遇到,通常在JAVA中可以很简单地做到:super.f();

CallNonvirtualVoidMethod也可以被用来调用父类的构造函数。这个在下节就会讲到。

4.3 调用构造函数

JNI中,构造函数可以和实例方法一样被调用,调用方式也相似。传入“”作为方法名,“V”作为返回类型。你可以通过向JNI函数NewObject传入方法来调用构造函数。下面的代码实现了与JNI函数NewString相同的功能:把存储在C缓冲区内的Unicode编码的字符序列,创建成一个java.lang.String对象: jstring

MyNewString(JNIEnv *env, jchar *chars, jint len) {

jclass stringClass; jmethodID cid;

jcharArray elemArr; jstring result;

stringClass = (*env)->FindClass(env, \ if (stringClass == NULL) {

return NULL; /* exception thrown */ }

/* Get the method ID for the String(char[]) constructor */ cid = (*env)->GetMethodID(env, stringClass, \ if (cid == NULL) {

return NULL; /* exception thrown */ }

/* Create a char[] that holds the string characters */ elemArr = (*env)->NewCharArray(env, len); if (elemArr == NULL) {

return NULL; /* exception thrown */ }

(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);

/* Construct a java.lang.String object */

result = (*env)->NewObject(env, stringClass, cid, elemArr);

/* Free local references */

(*env)->DeleteLocalRef(env, elemArr); (*env)->DeleteLocalRef(env, stringClass); return result; }

上面这个本地方法有些复杂,需要详细解释一下。首先,FindClass返回一个java.lang.String类的引用,接着,GetMethodID返回构造函数String(char[] chars)的方法ID。我们调用NewCharArray分配一个字符数组来保存字符串元素。JNI函数NewObject调用方法ID所标识的构造函数。NewObject函数需要的参数有:类的引用、构造方法的ID、构造方法需要的参数。

DeleteLocalRef允许VM释放被局部引用elemArr和stringClass引用的资源。5.2.1中详细描述了调用DeleteLocalRef的时机和原因。 这个例子引出了一个问题,既然我们可以利用JNI函数来实现相同的功能,为什么JNI还需要NewString这样的内置函数?原因是,内置函数的效率远高于在本地代码里面调用构造函数的API。而字符串又是最常用到的对象类型,因此需要在JNI中给予特殊的支持。

你也可以做到通过CallNonvirtualVoidMethod函数来调用构造函数。这种情况下,本地代码必须首先通过调用AllocObject函数创建一个未初始化的对象。上面例子中的result = (*env)->NewObject(env, stringClass, cid, elemArr);可以被如下代码替换:

result = (*env)->AllocObject(env, stringClass); if (result) {

(*env)->CallNonvirtualVoidMethod(env, result, stringClass, cid, elemArr); /* we need to check for possible exceptions */ if ((*env)->ExceptionCheck(env)) {

(*env)->DeleteLocalRef(env, result); result = NULL; } }

AllocObject创建了一个未初始化的对象,使用时一定要非常小心,确保一个对象上面,构造函数最多被调用一次。本地代码不应该在一个对象上面调用多次构造函数。有时,你可能会发现创建一个未初始化的对象然后一段时间以后再调用