Skip to content

访问实例域

代码实现

假设我们有一个FisherTom类,里面有money。Java方法addMoney()表示“增加money”。

进行一些运算后改变了money的值。

    private double money = 100;

    static {
        System.loadLibrary("fisher-pole");
    }

    public double addMoney() {
        money = money * 1.2 + 4;
        return money;
    }

    public native double nAddMoney();

我们想用C++代码来实现“增加money”的功能。新建一个本地方法nAddMoney()

extern "C"
JNIEXPORT jdouble JNICALL
Java_com_rustfisher_fishpole_worker_FisherTom_nAddMoney(JNIEnv *env, jobject thiz) {
    jclass tom_clz = env->GetObjectClass(thiz); // 获取到这个Java类
    jfieldID money_fid = env->GetFieldID(tom_clz, "money", "D"); // 获取到属性id
    jdouble money = env->GetDoubleField(thiz, money_fid); // 获取到当前值
    money = money * 1.2 + 4.2; // 简单的运算
    env->SetDoubleField(thiz, money_fid, money); // 把值存进去
    return money;
}

代码流程

  • 在C++代码中,先调用GetObjectClass方法获取到jclass,也就是对应的Java类。
  • 然后找到money的jfieldID,这样才能直接操作它。
  • 读取和设置money的数值,分别用GetDoubleFieldSetDoubleField方法。
  • SetDoubleField之后,在Java层,我们可以看到money的数值已经被修改了。

通过以上例子,我们了解如何用C++修改对象里的属性。

分析

下面我们来仔细看一下Java_com_rustfisher_fishpole_worker_FisherTom_nAddMoney(JNIEnv *env, jobject thiz)

第二个参数是jobject类型。实际上,它和Java中的this引用等价。 静态方法得到的是类的引用,而非静态方法得到的是对隐式的this参数对象的引用。

现在,我们访问private double money。 JNI 要求程序员通过调用特殊的JNI函数来获取和设置数据的值。 在我们的例子里,要使用GetdoubleFieldSetDoubleField函数,因为money是double类型的。

对于其他类型,可以使用的函数有: GetIntField/SetIntFieldGetObjectField/SetObjectField等等。

其通用语法是:

x = env->GetXxxField(thiz, x);
env->SetXxxField(thiz, fieldID, x);

这里,fieldID是一个特殊类型jfieldID的值,jfieldID标识结构中的一个域,而Xxx代表Java数据类型(Object, Boolean, Byte 或其他)。为了获得fieldID,必须先获得一个表示类的值,有两种方法可以实现此目的。 GetObjectClass函数可以返回任意对象的类。 例如:

jclass tom_clz = env->GetObjectClass(thiz);

FindClass函数可以让你以字符串形式来指定类名(有点奇怪的是,要以/代替句号作为包名之间的分隔符)。

jclass class_String = env->FindClass("java/lang/String);

之后,可以使用GetFieldID函数来获得fieldID。 必须提供域的名字、它的签名以及它的类型的编码。

例如,下面是从money域得到域ID的代码:

jfieldID money_fid = env->GetFieldID(tom_clz, "money", "D");

字符串"D"表示类型是double。后面我们会讲解编码签名的全部规则。

你可能会认为访问数据域相当令人费解。JNI的设计者不想把数据域直接暴露在外,所以他们不得不提供获取和设置数据域值的函数。 为了使这些函数的开销最小化,从域名计算域ID (代价最大的一个步骤)被分解出来作为单独的一步操作。 也就是说,如果你反复地获取和设置一个特定的城,你计算域标识符的开销就只有一次。