JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量

转载请注明出处:http://blog.csdn.net/xyang81/article/details/42836783


       在上一章中我们学习到了如何在本地代码中访问任意Java类中的静态方法和实例方法,本章我们也通过一个示例来学习Java中的实例变量和静态变量,在本地代码中如何来访问和修改。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过【类名.变量名】来访问。实例变量也称为成员变量(属性),每个实例都拥有一份实例变量数据的拷贝,它们之间修改后的数据互不影响。下面看一个例子:

[java]  view plain  copy
 
  1. package com.study.jnilearn;  
  2.   
  3. /** 
  4.  * C/C++访问类的实例变量和静态变量 
  5.  * @author yangxin 
  6.  */  
  7. public class AccessField {  
  8.       
  9.     private native static void accessInstanceField(ClassField obj);  
  10.       
  11.     private native static void accessStaticField();  
  12.   
  13.     public static void main(String[] args) {  
  14.         ClassField obj = new ClassField();  
  15.         obj.setNum(10);  
  16.         obj.setStr("Hello");  
  17.           
  18.         // 本地代码访问和修改ClassField为中的静态属性num  
  19.         accessStaticField();  
  20.         accessInstanceField(obj);  
  21.           
  22.         // 输出本地代码修改过后的值  
  23.         System.out.println("In Java--->ClassField.num = " + obj.getNum());  
  24.         System.out.println("In Java--->ClassField.str = " + obj.getStr());  
  25.     }  
  26.   
  27.     static {  
  28.         System.loadLibrary("AccessField");  
  29.     }  
  30.       
  31. }  

        AccessField是程序的入口类,定义了两个native方法:accessInstanceField和accessStaticField,分别用于演示在本地代码中访问Java类中的实例变量和静态变量。其中accessInstaceField方法访问的是类的实例变量,所以该方法需要一个ClassField实例作为形参,用于访问该对象中的实例变量。

[java]  view plain  copy
 
  1. package com.study.jnilearn;  
  2.   
  3. /** 
  4.  * ClassField.java 
  5.  * 用于本地代码访问和修改该类的属性 
  6.  * @author yangxin 
  7.  * 
  8.  */  
  9. public class ClassField {  
  10.   
  11.     private static int num;  
  12.       
  13.     private String str;  
  14.       
  15.     public int getNum() {  
  16.         return num;  
  17.     }  
  18.   
  19.     public void setNum(int num) {  
  20.         ClassField.num = num;  
  21.     }  
  22.   
  23.     public String getStr() {  
  24.         return str;  
  25.     }  
  26.   
  27.     public void setStr(String str) {  
  28.         this.str = str;  
  29.     }  
  30. }  

        在本例中没有将实例变量和静态变量定义在程序入口类中,新建了一个ClassField的类来定义类的属性,目的是为了加深在C/C++代码中可以访问任意Java类中的属性。在这个类中定义了一个int类型的实例变量num,和一个java.lang.String类型的静态变量str。这两个变量会被本地代码访问和修改。

[cpp]  view plain  copy
 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_study_jnilearn_AccessField */  
  4.   
  5. #ifndef _Included_com_study_jnilearn_AccessField  
  6. #define _Included_com_study_jnilearn_AccessField  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     com_study_jnilearn_AccessField 
  12.  * Method:    accessInstanceField 
  13.  * Signature: (Lcom/study/jnilearn/ClassField;)V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField  
  16.   (JNIEnv *, jclass, jobject);  
  17.   
  18. /* 
  19.  * Class:     com_study_jnilearn_AccessField 
  20.  * Method:    accessStaticField 
  21.  * Signature: ()V 
  22.  */  
  23. JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField  
  24.   (JNIEnv *, jclass);  
  25.   
  26. #ifdef __cplusplus  
  27. }  
  28. #endif  
  29. #endif  
以上代码是程序入口类AccessField.class为native方法生成的本地代码函数原型头文件

[cpp]  view plain  copy
 
  1. // AccessField.c  
  2.   
  3. #include "com_study_jnilearn_AccessField.h"  
  4.   
  5. /* 
  6.  * Class:     com_study_jnilearn_AccessField 
  7.  * Method:    accessInstanceField 
  8.  * Signature: ()V 
  9.  */  
  10. JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField  
  11. (JNIEnv *env, jclass cls, jobject obj)  
  12. {  
  13.     jclass clazz;  
  14.     jfieldID fid;  
  15.     jstring j_str;  
  16.     jstring j_newStr;  
  17.     const char *c_str = NULL;  
  18.       
  19.     // 1.获取AccessField类的Class引用  
  20.     clazz = (*env)->GetObjectClass(env,obj);  
  21.     if (clazz == NULL) {  
  22.         return;  
  23.     }  
  24.       
  25.     // 2. 获取AccessField类实例变量str的属性ID  
  26.     fid = (*env)->GetFieldID(env,clazz,"str""Ljava/lang/String;");  
  27.     if (clazz == NULL) {  
  28.         return;  
  29.     }  
  30.       
  31.     // 3. 获取实例变量str的值  
  32.     j_str = (jstring)(*env)->GetObjectField(env,obj,fid);  
  33.       
  34.     // 4. 将unicode编码的java字符串转换成C风格字符串  
  35.     c_str = (*env)->GetStringUTFChars(env,j_str,NULL);  
  36.     if (c_str == NULL) {  
  37.         return;  
  38.     }  
  39.     printf("In C--->ClassField.str = %s\n", c_str);  
  40.     (*env)->ReleaseStringUTFChars(env, j_str, c_str);  
  41.       
  42.     // 5. 修改实例变量str的值  
  43.     j_newStr = (*env)->NewStringUTF(env, "This is C String");  
  44.     if (j_newStr == NULL) {  
  45.         return;  
  46.     }  
  47.       
  48.     (*env)->SetObjectField(env, obj, fid, j_newStr);  
  49.       
  50.     // 6.删除局部引用  
  51.     (*env)->DeleteLocalRef(env, clazz);  
  52.     (*env)->DeleteLocalRef(env, j_str);  
  53.     (*env)->DeleteLocalRef(env, j_newStr);  
  54. }  
  55.   
  56. /* 
  57.  * Class:     com_study_jnilearn_AccessField 
  58.  * Method:    accessStaticField 
  59.  * Signature: ()V 
  60.  */  
  61. JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField  
  62. (JNIEnv *env, jclass cls)  
  63. {  
  64.     jclass clazz;  
  65.     jfieldID fid;  
  66.     jint num;  
  67.       
  68.     //1.获取ClassField类的Class引用  
  69.     clazz = (*env)->FindClass(env,"com/study/jnilearn/ClassField");  
  70.     if (clazz == NULL) {    // 错误处理  
  71.         return;  
  72.     }  
  73.       
  74.     //2.获取ClassField类静态变量num的属性ID  
  75.     fid = (*env)->GetStaticFieldID(env, clazz, "num""I");  
  76.     if (fid == NULL) {  
  77.         return;  
  78.     }  
  79.       
  80.     // 3.获取静态变量num的值  
  81.     num = (*env)->GetStaticIntField(env,clazz,fid);  
  82.     printf("In C--->ClassField.num = %d\n", num);  
  83.       
  84.     // 4.修改静态变量num的值  
  85.     (*env)->SetStaticIntField(env, clazz, fid, 80);  
  86.       
  87.     // 删除属部引用  
  88.     (*env)->DeleteLocalRef(env,clazz);  
  89. }  
以上代码是对头文件中函数原型的实现。


运行程序,输出结果如下:

JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量_第1张图片

代码解析:


一、访问实例变量


在main方法中,通过调用accessInstanceField()方法来调用本地函数Java_com_study_jnilearn_AccessField_accessInstanceField,定位到函数32行:

[cpp]  view plain  copy
 
  1. j_str = (jstring)(*env)->GetObjectField(env,obj,fid);   
该函数就是用于获取ClassField对象中num的值。下面是函数的原型:

[cpp]  view plain  copy
 
  1. jobject (JNICALL *GetObjectField) (JNIEnv *env, jobject obj, jfieldID fieldID);  
        因为实例变量str是String类型,属于引用类型。在JNI中获取引用类型字段的值,调用GetObjectField函数获取。同样的,获取其它类型字段值的函数还有GetIntField,GetFloatField,GetDoubleField,GetBooleanField等。这些函数有一个共同点,函数参数都是一样的,只是函数名不同,我们只需学习其中一个函数如何调用即可,依次类推,就自然知道其它函数的使用方法。

        GetObjectField函数接受3个参数,env是JNI函数表指针,obj是实例变量所属的对象,fieldID是变量的ID(也称为属性描述符或签名),和上一章中方法描述符是同一个意思。env和obj参数从Java_com_study_jnilearn_AccessField_accessInstanceField函数形参列表中可以得到,那fieldID怎么获取呢?了解Java反射的童鞋应该知道,在Java中任何一个类的.class字节码文件被加载到内存中之后,该class子节码文件统一使用Class类来表示该类的一个引用(相当于Java中所有类的基类是Object一样)。然后就可以从该类的Class引用中动态的获取类中的任意方法和属性。注意:Class类在Java SDK继承体系中是一个独立的类,没有继承自Object。请看下面的例子,通过Java反射机制,动态的获取一个类的私有实例变量的值:

[java]  view plain  copy
 
  1. public static void main(String[] args) throws Exception {  
  2.     ClassField obj = new ClassField();  
  3.     obj.setStr("YangXin");  
  4.     // 获取ClassField字节码对象的Class引用  
  5.     Class<?> clazz = obj.getClass();   
  6.     // 获取str属性  
  7.     Field field = clazz.getDeclaredField("str");  
  8.     // 取消权限检查,因为Java语法规定,非public属性是无法在外部访问的  
  9.     field.setAccessible(true);  
  10.     // 获取obj对象中的str属性的值  
  11.     String str = (String)field.get(obj);  
  12.     System.out.println("str = " + str);  
  13. }  
       运行程序后,输出结果当然是打印出str属性的值“YangXin”。 所以我们在本地代码中调用JNI函数访问Java对象中某一个属性的时候,首先第一步就是要获取该对象的Class引用,然后在Class中查找需要访问的字段ID,最后调用JNI函数的GetXXXField系列函数,获取字段(属性)的值。上例中,首先调用GetObjectClass函数获取ClassField的Class引用:

[cpp]  view plain  copy
 
  1. clazz = (*env)->GetObjectClass(env,obj);  

然后调用GetFieldID函数从Class引用中获取字段的ID(str是字段名,Ljava/lang/String;是字段的类型)

[cpp]  view plain  copy
 
  1. fid = (*env)->GetFieldID(env,clazz,"str""Ljava/lang/String;");  
最后调用GetObjectField函数,传入实例对象和字段ID,获取属性的值
[cpp]  view plain  copy
 
  1. j_str = (jstring)(*env)->GetObjectField(env,obj,fid);  
调用SetXXXField系列函数,可以修改实例属性的值,最后一个参数为属性的值。引用类型全部调用SetObjectField函数,基本类型调用SetIntField、SetDoubleField、SetBooleanField等

[cpp]  view plain  copy
 
  1. (*env)->SetObjectField(env, obj, fid, j_newStr);  

二、访问静态变量

访问静态变量和实例变量不同的是,获取字段ID使用GetStaticFieldID,获取和修改字段的值使用Get/SetStaticXXXField系列函数,比如上例中获取和修改静态变量num:

[cpp]  view plain  copy
 
  1. // 3.获取静态变量num的值  
  2. num = (*env)->GetStaticIntField(env,clazz,fid);  
  3. // 4.修改静态变量num的值  
  4. (*env)->SetStaticIntField(env, clazz, fid, 80);  

总结:

1、由于JNI函数是直接操作JVM中的数据结构,不受Java访问修饰符的限制。即,在本地代码中可以调用JNI函数可以访问Java对象中的非public属性和方法

2、访问和修改实例变量操作步聚:

   1>、调用GetObjectClass函数获取实例对象的Class引用

   2>、调用GetFieldID函数获取Class引用中某个实例变量的ID

   3>、调用GetXXXField函数获取变量的值,需要传入实例变量所属对象和变量ID

   4>、调用SetXXXField函数修改变量的值,需要传入实例变量所属对象、变量ID和变量的值

3、访问和修改静态变量操作步聚:
   1>、调用FindClass函数获取类的Class引用

   2>、调用GetStaticFieldID函数获取Class引用中某个静态变量ID

   3>、调用GetStaticXXXField函数获取静态变量的值,需要传入变量所属Class的引用和变量ID

   4>、调用SetStaticXXXField函数设置静态变量的值,需要传入变量所属Class的引用、变量ID和变量的值

示例代码下载地址:https://code.csdn.net/xyang81/jnilearn

你可能感兴趣的:(JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量)