C/C++ 访问 Java 实例变量和静态变量
来源:互联网 发布:网络电话攻击软件 编辑:程序博客网 时间:2024/05/20 04:50
实例变量和静态变量
在上一章中我们学习到了如何在本地代码中访问任意 Java 类中的静态方法和实例方法,本章我们也通过一个示例来学习 Java 中的实例变量和静态变量,在本地代码中如何来访问和修改。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过【类名.变量名】来访问。实例变量也称为成员变量(属性),每个实例都拥有一份实例变量数据的拷贝,它们之间修改后的数据互不影响。下面看一个例子:
package com.study.jnilearn; /** * C/C++访问类的实例变量和静态变量 * @author yangxin */ public class AccessField { private native static void accessInstanceField(ClassField obj); private native static void accessStaticField(); public static void main(String[] args) { ClassField obj = new ClassField(); obj.setNum(10); obj.setStr("Hello"); // 本地代码访问和修改ClassField为中的静态属性num accessStaticField(); accessInstanceField(obj); // 输出本地代码修改过后的值 System.out.println("In Java--->ClassField.num = " + obj.getNum()); System.out.println("In Java--->ClassField.str = " + obj.getStr()); } static { System.loadLibrary("AccessField"); } }
AccessField 是程序的入口类,定义了两个 native 方法:accessInstanceField 和 accessStaticField,分别用于演示在本地代码中访问 Java 类中的实例变量和静态变量。其中 accessInstaceField 方法访问的是类的实例变量,所以该方法需要一个 ClassField 实例作为形参,用于访问该对象中的实例变量。
package com.study.jnilearn; /** * ClassField.java * 用于本地代码访问和修改该类的属性 * @author yangxin * */ public class ClassField { private static int num; private String str; public int getNum() { return num; } public void setNum(int num) { ClassField.num = num; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } }
在本例中没有将实例变量和静态变量定义在程序入口类中,新建了一个 ClassField 的类来定义类的属性,目的是为了加深在 C/C++ 代码中可以访问任意 Java 类中的属性。在这个类中定义了一个 int 类型的实例变量 num,和一个 java.lang.String 类型的静态变量 str。这两个变量会被本地代码访问和修改。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_study_jnilearn_AccessField */ #ifndef _Included_com_study_jnilearn_AccessField #define _Included_com_study_jnilearn_AccessField #ifdef __cplusplus extern "C" { #endif /* * Class: com_study_jnilearn_AccessField * Method: accessInstanceField * Signature: (Lcom/study/jnilearn/ClassField;)V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField (JNIEnv *, jclass, jobject); /* * Class: com_study_jnilearn_AccessField * Method: accessStaticField * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif
以上代码是程序入口类AccessField.class为native方法生成的本地代码函数原型头文件。
// AccessField.c #include "com_study_jnilearn_AccessField.h" /* * Class: com_study_jnilearn_AccessField * Method: accessInstanceField * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField (JNIEnv *env, jclass cls, jobject obj) { jclass clazz; jfieldID fid; jstring j_str; jstring j_newStr; const char *c_str = NULL; // 1.获取AccessField类的Class引用 clazz = (*env)->GetObjectClass(env,obj); if (clazz == NULL) { return; } // 2. 获取AccessField类实例变量str的属性ID fid = (*env)->GetFieldID(env,clazz,"str", "Ljava/lang/String;"); if (clazz == NULL) { return; } // 3. 获取实例变量str的值 j_str = (jstring)(*env)->GetObjectField(env,obj,fid); // 4. 将unicode编码的java字符串转换成C风格字符串 c_str = (*env)->GetStringUTFChars(env,j_str,NULL); if (c_str == NULL) { return; } printf("In C--->ClassField.str = %s\n", c_str); (*env)->ReleaseStringUTFChars(env, j_str, c_str); // 5. 修改实例变量str的值 j_newStr = (*env)->NewStringUTF(env, "This is C String"); if (j_newStr == NULL) { return; } (*env)->SetObjectField(env, obj, fid, j_newStr); // 6.删除局部引用 (*env)->DeleteLocalRef(env, clazz); (*env)->DeleteLocalRef(env, j_str); (*env)->DeleteLocalRef(env, j_newStr); }
/* * Class: com_study_jnilearn_AccessField * Method: accessStaticField * Signature: ()V */ JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField (JNIEnv *env, jclass cls) { jclass clazz; jfieldID fid; jint num; //1.获取ClassField类的Class引用 clazz = (*env)->FindClass(env,"com/study/jnilearn/ClassField"); if (clazz == NULL) { // 错误处理 return; } //2.获取ClassField类静态变量num的属性ID fid = (*env)->GetStaticFieldID(env, clazz, "num", "I"); if (fid == NULL) { return; } // 3.获取静态变量num的值 num = (*env)->GetStaticIntField(env,clazz,fid); printf("In C--->ClassField.num = %d\n", num); // 4.修改静态变量num的值 (*env)->SetStaticIntField(env, clazz, fid, 80); // 删除属部引用 (*env)->DeleteLocalRef(env,clazz); }
以上代码是对头文件中函数原型的实现。
运行程序,输出结果如下:
In Java--->ClassField.num=80In Java--->ClassField.str=This is C StringIn c--->ClassField.num=10In c--->ClassField.str=Hello
代码解析
访问实例变量
在 main 方法中,通过调用 accessInstanceField()方法来调用本地函数 Java_com_study_jnilearn_AccessField_accessInstanceField。
j_str = (jstring)(*env)->GetObjectField(env,obj,fid);
该函数就是用于获取 ClassField 对象中 num 的值。下面是函数的原型
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 反射机制,动态的获取一个类的私有实例变量的值。
public static void main(String[] args) throws Exception { ClassField obj = new ClassField(); obj.setStr("YangXin"); // 获取ClassField字节码对象的Class引用 Class<?> clazz = obj.getClass(); // 获取str属性 Field field = clazz.getDeclaredField("str"); // 取消权限检查,因为Java语法规定,非public属性是无法在外部访问的 field.setAccessible(true); // 获取obj对象中的str属性的值 String str = (String)field.get(obj); System.out.println("str = " + str); }
运行程序后,输出结果当然是打印出 str 属性的值“YangXin”。所以我们在本地代码中调用 JNI 函数访问 Java 对象中某一个属性的时候,首先第一步就是要获取该对象的 Class 引用,然后在 Class 中查找需要访问的字段 ID,最后调用 JNI 函数的 GetXXXField 系列函数,获取字段(属性)的值。上例中,首先调用 GetObjectClass 函数获取 ClassField 的 Class 引用。
clazz = (*env)->GetObjectClass(env,obj);
然后调用 GetFieldID 函数从 Class 引用中获取字段的 ID(str 是字段名,Ljava/lang/String;是字段的类型)。
fid = (*env)->GetFieldID(env,clazz,”str”, “Ljava/lang/String;”);
最后调用 GetObjectField 函数,传入实例对象和字段 ID,获取属性的值。
j_str = (jstring)(*env)->GetObjectField(env,obj,fid);
调用 SetXXXField 系列函数,可以修改实例属性的值,最后一个参数为属性的值。引用类型全部调用SetObjectField 函数,基本类型调用 SetIntField、SetDoubleField、SetBooleanField 等。
(*env)->SetObjectField(env, obj, fid, j_newStr);
访问静态变量
访问静态变量和实例变量不同的是,获取字段 ID 使用 GetStaticFieldID,获取和修改字段的值使用 Get/SetStaticXXXField 系列函数,比如上例中获取和修改静态变量 num。
// 3.获取静态变量num的值
num = (*env)->GetStaticIntField(env,clazz,fid);
// 4.修改静态变量num的值
(*env)->SetStaticIntField(env, clazz, fid, 80);
总结
由于 JNI 函数是直接操作J VM 中的数据结构,不受 Java 访问修饰符的限制。即,在本地代码中可以调用 JNI 函数可以访问 Java 对象中的非 public 属性和方法
访问和修改实例变量操作步聚:
调用 GetObjectClass 函数获取实例对象的 Class 引用
调用 GetFieldID 函数获取 Class 引用中某个实例变量的 ID
调用 GetXXXField 函数获取变量的值,需要传入实例变量所属对象和变量 ID
调用 SetXXXField 函数修改变量的值,需要传入实例变量所属对象、变量 ID 和变量的值
访问和修改静态变量操作步聚:
调用 FindClass 函数获取类的 Class 引用
调用 GetStaticFieldID 函数获取 Class 引用中某个静态变量 ID
调用 GetStaticXXXField 函数获取静态变量的值,需要传入变量所属 Class 的引用和变量 ID
调用 SetStaticXXXField 函数设置静态变量的值,需要传入变量所属 Class 的引用、变量 ID和变量的值
NdkDemo代码已上传至Github
如有不正支出,欢迎留言交流!
我的GitHub
我的CSDN
我的简书
开发笔记
- (JNI)C/C++ 访问 Java 实例变量和静态变量
- C/C++ 访问 Java 实例变量和静态变量
- JNI/NDK开发指南(六)--C/C++访问Java实例变量和静态变量
- JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量
- JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量
- JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量
- JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量
- JAVA中的实例变量和静态变量
- Java 的静态变量和实例变量
- C语言静态变量和参数变量
- Java中静态变量,实例变量和局部变量
- Java 中局部变量、静态变量和实例变量区别
- Android JNI开发(4)--访问Java的实例变量和静态变量
- C语言 静态变量和静态函数
- c语言静态变量和静态函数
- c语言静态变量和静态函数
- c语言静态变量和静态函数
- c语言静态变量和静态函数
- React Native入门笔记
- SpringMVC对于跨域访问的支持
- JNI类型签名和方法签名
- java基础(六):数组
- linux下无法加载验证码问题
- C/C++ 访问 Java 实例变量和静态变量
- yii2.0的http缓存
- linux中shell变量$#,$@,$0,$1,$2的含义解释
- Spring Boot(上)
- 展讯平台识别不到T卡
- kaptcha验证码使用
- leetcode题解-25. Reverse Nodes in k-Group
- springMVC与百度UEditor的整合
- 打印机无法打印