美文网首页
C/C++ 访问 Java 实例变量和静态变量

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

作者: 程序员学园 | 来源:发表于2017-07-17 11:17 被阅读0次

    实例变量和静态变量
    在上一章中我们学习到了如何在本地代码中访问任意 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
    我的简书
    开发笔记

    相关文章

      网友评论

          本文标题:C/C++ 访问 Java 实例变量和静态变量

          本文链接:https://www.haomeiwen.com/subject/kbefkxtx.html