/* 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、数组等对象类型的方法。
你也可以使用Call
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提供了一系列完成这些功能的函数:CallNonvirtual
CallNonvirtualVoidMethod也可以被用来调用父类的构造函数。这个在下节就会讲到。
4.3 调用构造函数
JNI中,构造函数可以和实例方法一样被调用,调用方式也相似。传入“
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创建了一个未初始化的对象,使用时一定要非常小心,确保一个对象上面,构造函数最多被调用一次。本地代码不应该在一个对象上面调用多次构造函数。有时,你可能会发现创建一个未初始化的对象然后一段时间以后再调用