C/C++ 访问 Java 实例变量和静态变量

实例变量和静态变量
在上一章中我们学习到了如何在本地代码中访问任意 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=80
In Java--->ClassField.str=This is C String
In c--->ClassField.num=10
In 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
我的简书
开发笔记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 通过前面 5 章的学习,我们知道了如何通过 JNI 函数来访问 JVM 中的基本数据类型、字符串和数组这些数据类型...
    程序员学园阅读 755评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,627评论 0 11
  • 日精进:一切行为都应该有预期的效果,完成了就是结果,不以结果为导向的行为是很难坚持到底的! 精进:增强结果意识,明...
    胡玉梅阅读 179评论 0 0