多语言编程之 C and Java:JNI进阶–字段访问与方法回调
		
		目录
在上一章(《多语言编程之 C and Java:JNI 30 分钟入门》)中讲解了 JNI 的基本使用。这一章将讨论一些更加实用的用法:如何在 JNI 中访问 Java 对象的成员变量或静态变量,以及回调 Java 中的成员方法或静态方法。
1 JNI 访问 Java 成员变量
1.1 实例
Java 实现
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package transvar; public class TransVar{     static{ 	System.loadLibrary("hello");     }     //成员变量(字段)     private int age = 88;     private String name = "Jims";     //静态成员变量     private static int years = 20;     private native void modifyInstanceVariable();   } | 
这个 Java 类中包含 3 个私有成员变量,名为 age 的 int 型变量, 名为 name 的 String 型变量和一个名为 years 的 int 型静态变量。我们将在使用 C 来访问或修改这些成员变量。
C 实现
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | JNIEXPORT void JNICALL Java_transvar_TransVar_modifyInstanceVariable   (JNIEnv *env, jobject obj) {     //获取 this object 的类     jclass thisClass = (*env)->GetObjectClass(env,obj);     if(thisClass == NULL)     {       printf("thisClass null\n");     }     //获取字段ID     jfieldID fidAge = (*env)->GetFieldID(env, thisClass, "age",  "I");     jfieldID fidName = (*env)->GetFieldID(env, thisClass, "name", "Ljava/lang/String;");     jfieldID fidYear = (*env)->GetStaticFieldID(env, thisClass, "years", "I");     //获取字段     jint age = (*env)->GetIntField(env, obj, fidAge);     jint years = (*env)->GetStaticIntField(env, obj, fidYear);     jstring name = (*env)->GetObjectField(env, obj, fidName);     const char* pszName = (*env)->GetStringUTFChars(env, name, NULL);     printf("[%d Years Later for  %s] \n", years, pszName);     (*env)->ReleaseStringUTFChars(env, name, pszName);     age += years;     //修改成员变量     (*env)->SetIntField(env, obj, fidAge, age); } | 
1.2 访问 Java 变量
在 JNI 中我们获取了这 3 个变量,并对 age 变量进行了修改。
由这段代码可以看出,访问 Java 成员变量的一般步骤为:
- 通过 GetObjectClass() 获取 Java 类的引用。
- 通过 GetFieldID()/GetStaticFieldID() 从 Java 类中获取 FieldID 。
- 基于 FieldID, 通过 Get<type>Field()/GetStatic<type>Field() 取回变量。
- 基于 FieldID, 通过 Set<type>Field()/GetStatic<type>Field() 修改变量。
GetFieldID()/GetStaticFieldID() 原型如下:
| 1 2 | jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); | 
此函数可以通过字段名和字段标识(signature)获取类或对象的字段 ID.
| Java 类型 | signature | 例子 | 
|---|---|---|
| Boolean | Z | |
| Byte | B | |
| Char | C | |
| Int | I | |
| Short | S | |
| Long | J | |
| Float | F | |
| Double | D | |
| Void | V | |
| Object[注] | L<package>/<class>; | 如 String 为 Ljava/lang/String; | 
| Array[注] | [<type> | 如 int[] 为 [I | 
注 :数组和引用类型的对象(Object),都是以分号(;)结尾。对于嵌套类,使用 $ 符号来表示。
Get<type>Field()/Set<type>Field() 有一组函数,用于从对象中获取字段值或给变量设置值。
| 1 | <type> Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID); | 
<type> 为 Java 原始类型名称。
还有一组函数用于获取或设置静态字段 : GetStatic<type>Field() / SetStatic<type>Field() 。
2 JNI 访问 Java 成员方法
我们可以在 native code 中访问 Java 的成员方法和静态成员方法。这种访问也可以称之为回调。
和访问 Java 成员变量类似,访问Java 成员方法的一般步骤为:
- 通过 GetObjectClass() 获取 Java 对象的类。
- 通过 GetMethodID() 获取 MethodID.
- 基于 MethodID, 调用 Call<type>Method() / CallVoidMethod() / CallObjectMethod(), 这些方法会返回 <type> 类型的值。 相应地,有 CallStatic<type>Method() / CallStaticVoidMethod() / CallStaticObjectMethod()用于回调静态方法。
GetMethodID() 原型如下:
| 1 | jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig); | 
最后一个参数 sig 为方法的标识(signature)。对于这个标识,我们并不陌生,在上一章,我们使用 javah 命令生成的头文件中,对方法有一个名为 Signature 的注释,就是这个标识。 方法标识以括号 "(" 开始,一对括号内部为方法参数的标识,括号后面跟返回值的标识。如方法
| 1 | void  MethedName(int a, int b); | 
的标识为: (II)V 。
除了参考 .h 文件的注释,还可以使用 Java 工具 javap 来查看方法标识。
| 1 | > javap -s -p <JavaClassName> | 
在得到 MethodID 后,即可以通过如下函数来调用 Java 成员方法或表态方法:
| 1 2 3 4 5 6 7 | <type> Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...); <type> Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); <type> Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); <type> CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...); <type> CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); <type> CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); | 
3 JNI 创建 Java 对象
通过JNI提供的 NewObject() / NewObjectArray() 函数,可以在 JNI 中创建 Java 对象(jobject),并传递回 Java 程序。
3.1 在 JNI 中调用 Java 对象构造器(Constructor)
这个过程和上一节调用 Java 方法没有什么不同,只不过,GetMethodID() 中,方法名和标识不一样,这里分别为 "<init>" 和 "V" .获取 MethodID 后,就可以通过 NewObject() 来创建 Java 对象。
创建 Java 对象相关的函数还有:
| 1 2 3 4 5 6 7 8 9 | jclass FindClass(JNIEnv *env, const char *name); jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...); jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args); jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);    // Constructs a new Java object. The method ID indicates which constructor method to invoke jobject AllocObject(JNIEnv *env, jclass cls);   // Allocates a new Java object without invoking any of the constructors for the object. | 
此外,还有如下函数可以创建 Java 数组对象:
| 1 2 3 4 5 6 7 8 9 | jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);    // Constructs a new array holding objects in class elementClass.    // All elements are initially set to initialElement. jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);    // Returns an element of an Object array. void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);    // Sets an element of an Object array. | 
3.2 示例
定义 Person 类,有构造函数 Person(String, int):
| 1 2 3 4 5 6 7 8 9 | public class Person{     public Person(){};     public Person(String name , int age){ 	_name = name; 	_age = age;     }     private int _age;     private String _name;   } | 
并有 native 方法,将 name 和 age 传入 JNI 中,用于实例化一个 Person 对象并传回 Java:
| 1 | private native Person createPerson(String name, int age); | 
则在 native 实现中,有如下过程:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | JNIEXPORT jobject JNICALL Java_package_class_createPerson(JNIEnv *env, jobject obj, jstring name, jint age) {     jclass personClass = (*env)->FindClass(env, "packagename/Person");     if(personClass == NULL)     {         printf("can not find class : packagename.Person");         return NULL;     }     jmethodID midpersonInit = (*env)->GetMethodID(env, personClass, "<init>", "(Ljava/lang/String;I)V");     if(midpersonInit == NULL)     {         printf("can not get methodID : <init>(Ljava/lang/String;I)V");         return NULL;     }     jobject newPerson = (*env)->NewObject(env, personClass, midpersonInit, name, age);     return newPerson; } | 
4 附录
4.1 本章源码
TransVar.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package transvar; public class TransVar{     static{         System.loadLibrary("hello");     }     //成员变量(字段)     private int age = 8;     private String name = "Jims";     //静态成员变量     private static int years = 20;     public int progress(int pro){         System.out.println("[... " + pro + " YEARS LATER ...]");         return 1;     }     private native void modifyInstanceVariable();     private native Person createPerson(String name, int age);     //For test     public static void main(String args[]){         TransVar tv = new TransVar();         System.out.println(tv.name + " is  in the age of " + tv.age);         tv.modifyInstanceVariable();         System.out.println(tv.name + " is  in the age of " + tv.age);         Person pjimSon = tv.createPerson("Jimson",1);          if(pjimSon != null){            System.out.println("The person: " + pjimSon.getName() + " create from JNI in the age of " + pjimSon.getAge());         }     }     } | 
Person.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | package transvar; public class Person{     public Person(){};     public Person(String name , int age){         _name = name;         _age = age;     }     private int _age;     private String _name;       public int getAge(){return _age;}     public String getName(){return _name;}; } | 
transvar_TransVar.c
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #include <jni.h> #include <stdio.h> #include "transvar_TransVar.h" JNIEXPORT void JNICALL Java_transvar_TransVar_modifyInstanceVariable   (JNIEnv *env, jobject obj) {     //获取 this object 的类     jclass thisClass = (*env)->GetObjectClass(env,obj);     if(thisClass == NULL)     {       printf("thisClass null\n");     }     //获取字段ID     jfieldID fidAge = (*env)->GetFieldID(env, thisClass, "age",  "I");     jfieldID fidName = (*env)->GetFieldID(env, thisClass, "name", "Ljava/lang/String;");     jfieldID fidYear = (*env)->GetStaticFieldID(env, thisClass, "years", "I");     //获取字段     jint age = (*env)->GetIntField(env, obj, fidAge);     jint years = (*env)->GetStaticIntField(env, obj, fidYear);     jstring name = (*env)->GetObjectField(env, obj, fidName);     const char* pszName = (*env)->GetStringUTFChars(env, name, NULL);     printf("[... Time flies for %s ...] \n", pszName);     (*env)->ReleaseStringUTFChars(env, name, pszName);     jmethodID mid = (*env)->GetMethodID(env, thisClass, "progress", "(I)I");     jint y = 0;      for(; y< years; y+=5)     {         jint i = (*env)->CallIntMethod(env,obj,mid,y);         if(i != 1) break;     }     age += y;     //修改成员变量     (*env)->SetIntField(env, obj, fidAge, age); } JNIEXPORT jobject JNICALL Java_transvar_TransVar_createPerson(JNIEnv *env, jobject obj, jstring name, jint age) {     jclass personClass = (*env)->FindClass(env, "transvar/Person");     if(personClass == NULL)     {         printf("can not find class : transvar.Person");         return NULL;     }     jmethodID midpersonInit = (*env)->GetMethodID(env, personClass, "<init>", "(Ljava/lang/String;I)V");     if(midpersonInit == NULL)     {         printf("can not get methodID : <init>(Ljava/lang/String;I)V");         return NULL;     }     jobject newPerson = (*env)->NewObject(env, personClass, midpersonInit, name, age);     return newPerson; } |