鍍金池/ 教程/ Java/ C/C++ 訪問 Java 實(shí)例變量和靜態(tài)變量
Android NDK 開發(fā)環(huán)境
Android NDK 簡介
JNI 字符串處理
JVM 查找 native 方法的規(guī)則
JNI 開發(fā)流程
JNI 局部引用、全局引用和弱全局引用
JNI 數(shù)據(jù)類型與 Java 數(shù)據(jù)類型的映射關(guān)系
JNI 概述
JNI 調(diào)用性能測試及優(yōu)化
C/C++ 訪問 Java 實(shí)例變量和靜態(tài)變量
JNI 調(diào)用構(gòu)造方法和父類實(shí)例方法
C/C++ 訪問 Java 實(shí)例方法和靜態(tài)方法
開發(fā)自己的 NDK 程序
JNI 訪問數(shù)組

C/C++ 訪問 Java 實(shí)例變量和靜態(tài)變量

實(shí)例變量和靜態(tài)變量

在上一章中我們學(xué)習(xí)到了如何在本地代碼中訪問任意 Java 類中的靜態(tài)方法和實(shí)例方法,本章我們也通過一個(gè)示例來學(xué)習(xí) Java 中的實(shí)例變量和靜態(tài)變量,在本地代碼中如何來訪問和修改。靜態(tài)變量也稱為類變量(屬性),在所有實(shí)例對象中共享同一份數(shù)據(jù),可以直接通過【類名.變量名】來訪問。實(shí)例變量也稱為成員變量(屬性),每個(gè)實(shí)例都擁有一份實(shí)例變量數(shù)據(jù)的拷貝,它們之間修改后的數(shù)據(jù)互不影響。下面看一個(gè)例子:

package com.study.jnilearn;  

/** 
 * C/C++訪問類的實(shí)例變量和靜態(tài)變量 
 * @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為中的靜態(tài)屬性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 是程序的入口類,定義了兩個(gè) native 方法:accessInstanceField 和 accessStaticField,分別用于演示在本地代碼中訪問 Java 類中的實(shí)例變量和靜態(tài)變量。其中 accessInstaceField 方法訪問的是類的實(shí)例變量,所以該方法需要一個(gè) ClassField 實(shí)例作為形參,用于訪問該對象中的實(shí)例變量。

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;  
    }  
}  

在本例中沒有將實(shí)例變量和靜態(tài)變量定義在程序入口類中,新建了一個(gè) ClassField 的類來定義類的屬性,目的是為了加深在 C/C++ 代碼中可以訪問任意 Java 類中的屬性。在這個(gè)類中定義了一個(gè) int 類型的實(shí)例變量 num,和一個(gè) java.lang.String 類型的靜態(tài)變量 str。這兩個(gè)變量會被本地代碼訪問和修改。

/* 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方法生成的本地代碼函數(shù)原型頭文件。

// 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類實(shí)例變量str的屬性ID  
    fid = (*env)->GetFieldID(env,clazz,"str", "Ljava/lang/String;");  
    if (clazz == NULL) {  
        return;  
    }  

    // 3. 獲取實(shí)例變量str的值  
    j_str = (jstring)(*env)->GetObjectField(env,obj,fid);  

    // 4. 將unicode編碼的java字符串轉(zhuǎn)換成C風(fēng)格字符串  
    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. 修改實(shí)例變量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類靜態(tài)變量num的屬性ID  
    fid = (*env)->GetStaticFieldID(env, clazz, "num", "I");  
    if (fid == NULL) {  
        return;  
    }  

    // 3.獲取靜態(tài)變量num的值  
    num = (*env)->GetStaticIntField(env,clazz,fid);  
    printf("In C--->ClassField.num = %d\n", num);  

    // 4.修改靜態(tài)變量num的值  
    (*env)->SetStaticIntField(env, clazz, fid, 80);  

    // 刪除屬部引用  
    (*env)->DeleteLocalRef(env,clazz);  
}  

以上代碼是對頭文件中函數(shù)原型的實(shí)現(xiàn)。

運(yùn)行程序,輸出結(jié)果如下:

In Java--->ClassField.num=80
In Java--->ClassField.str=This is C String
In c--->ClassField.num=10
In c--->ClassField.str=Hello

代碼解析

訪問實(shí)例變量

在 main 方法中,通過調(diào)用 accessInstanceField()方法來調(diào)用本地函數(shù) Java_com_study_jnilearn_AccessField_accessInstanceField。

j_str = (jstring)(*env)->GetObjectField(env,obj,fid);   

該函數(shù)就是用于獲取 ClassField 對象中 num 的值。下面是函數(shù)的原型

jobject (JNICALL *GetObjectField) (JNIEnv *env, jobject obj, jfieldID fieldID); 

因?yàn)閷?shí)例變量str是 String 類型,屬于引用類型。在 JNI 中獲取引用類型字段的值,調(diào)用 GetObjectField 函數(shù)獲取。同樣的,獲取其它類型字段值的函數(shù)還有 GetIntField,GetFloatField,GetDoubleField,GetBooleanField 等。這些函數(shù)有一個(gè)共同點(diǎn),函數(shù)參數(shù)都是一樣的,只是函數(shù)名不同,我們只需學(xué)習(xí)其中一個(gè)函數(shù)如何調(diào)用即可,依次類推,就自然知道其它函數(shù)的使用方法。

GetObjectField 函數(shù)接受 3 個(gè)參數(shù),env 是 JNI 函數(shù)表指針,obj 是實(shí)例變量所屬的對象,fieldID 是變量的ID(也稱為屬性描述符或簽名),和上一章中方法描述符是同一個(gè)意思。env 和 obj 參數(shù)從Java_com_study_jnilearn_AccessField_accessInstanceField 函數(shù)形參列表中可以得到,那 fieldID 怎么獲取呢?了解 Java 反射的童鞋應(yīng)該知道,在 Java 中任何一個(gè)類的.class字節(jié)碼文件被加載到內(nèi)存中之后,該class子節(jié)碼文件統(tǒng)一使用 Class 類來表示該類的一個(gè)引用(相當(dāng)于 Java 中所有類的基類是 Object一樣)。然后就可以從該類的 Class 引用中動態(tài)的獲取類中的任意方法和屬性。注意:Class 類在 Java SDK 繼承體系中是一個(gè)獨(dú)立的類,沒有繼承自 Object。請看下面的例子,通過 Java 反射機(jī)制,動態(tài)的獲取一個(gè)類的私有實(shí)例變量的值。

public static void main(String[] args) throws Exception {  
    ClassField obj = new ClassField();  
    obj.setStr("YangXin");  
    // 獲取ClassField字節(jié)碼對象的Class引用  
    Class<?> clazz = obj.getClass();   
    // 獲取str屬性  
    Field field = clazz.getDeclaredField("str");  
    // 取消權(quán)限檢查,因?yàn)镴ava語法規(guī)定,非public屬性是無法在外部訪問的  
    field.setAccessible(true);  
    // 獲取obj對象中的str屬性的值  
    String str = (String)field.get(obj);  
    System.out.println("str = " + str);  
}  

運(yùn)行程序后,輸出結(jié)果當(dāng)然是打印出 str 屬性的值“YangXin”。所以我們在本地代碼中調(diào)用 JNI 函數(shù)訪問 Java 對象中某一個(gè)屬性的時(shí)候,首先第一步就是要獲取該對象的 Class 引用,然后在 Class 中查找需要訪問的字段 ID,最后調(diào)用 JNI 函數(shù)的 GetXXXField 系列函數(shù),獲取字段(屬性)的值。上例中,首先調(diào)用 GetObjectClass 函數(shù)獲取 ClassField 的 Class 引用。

clazz = (*env)->GetObjectClass(env,obj);  

然后調(diào)用 GetFieldID 函數(shù)從 Class 引用中獲取字段的 ID(str 是字段名,Ljava/lang/String;是字段的類型)。

fid = (*env)->GetFieldID(env,clazz,"str", "Ljava/lang/String;");  

最后調(diào)用 GetObjectField 函數(shù),傳入實(shí)例對象和字段 ID,獲取屬性的值。

j_str = (jstring)(*env)->GetObjectField(env,obj,fid);  

調(diào)用 SetXXXField 系列函數(shù),可以修改實(shí)例屬性的值,最后一個(gè)參數(shù)為屬性的值。引用類型全部調(diào)用SetObjectField 函數(shù),基本類型調(diào)用 SetIntField、SetDoubleField、SetBooleanField 等。

(*env)->SetObjectField(env, obj, fid, j_newStr); 

訪問靜態(tài)變量

訪問靜態(tài)變量和實(shí)例變量不同的是,獲取字段 ID 使用 GetStaticFieldID,獲取和修改字段的值使用 Get/SetStaticXXXField 系列函數(shù),比如上例中獲取和修改靜態(tài)變量 num。

// 3.獲取靜態(tài)變量num的值  
num = (*env)->GetStaticIntField(env,clazz,fid);  
// 4.修改靜態(tài)變量num的值  
(*env)->SetStaticIntField(env, clazz, fid, 80);  

總結(jié)

  • 由于 JNI 函數(shù)是直接操作J VM 中的數(shù)據(jù)結(jié)構(gòu),不受 Java 訪問修飾符的限制。即,在本地代碼中可以調(diào)用 JNI 函數(shù)可以訪問 Java 對象中的非 public 屬性和方法
  • 訪問和修改實(shí)例變量操作步聚:
    • 調(diào)用 GetObjectClass 函數(shù)獲取實(shí)例對象的 Class 引用
    • 調(diào)用 GetFieldID 函數(shù)獲取 Class 引用中某個(gè)實(shí)例變量的 ID
    • 調(diào)用 GetXXXField 函數(shù)獲取變量的值,需要傳入實(shí)例變量所屬對象和變量 ID
    • 調(diào)用 SetXXXField 函數(shù)修改變量的值,需要傳入實(shí)例變量所屬對象、變量 ID 和變量的值
  • 訪問和修改靜態(tài)變量操作步聚:
    • 調(diào)用 FindClass 函數(shù)獲取類的 Class 引用
    • 調(diào)用 GetStaticFieldID 函數(shù)獲取 Class 引用中某個(gè)靜態(tài)變量 ID
    • 調(diào)用 GetStaticXXXField 函數(shù)獲取靜態(tài)變量的值,需要傳入變量所屬 Class 的引用和變量 ID
    • 調(diào)用 SetStaticXXXField 函數(shù)設(shè)置靜態(tài)變量的值,需要傳入變量所屬 Class 的引用、變量 ID和變量的值