java 从构造函数调用的JNI函数中的thisObj引用类而不是示例

t98cgbkg  于 2023-06-20  发布在  Java
关注(0)|答案(2)|浏览(102)

我正在尝试实现一个JNI库。我注意到,当从构造函数调用时传递给JNI函数的thisObj与从方法调用的相同函数不同。
下面是我的最小代码:

public final class Test {
    static {
        System.loadLibrary("jnitest");
    }

    private native void jni_test(int i);

    public Test() {
        jni_test(0);
    }

    public void m() {
        jni_test(1);
    }
}

我这样称呼它:

Test t = new Test();
t.m();

JNI端看起来像这样:

static void jni_test(JNIEnv *env, jobject thisObj, int i) {
    printf("jni_test %i %p\n", i, thisObj);
}

输出为:

jni_test 0 0x7f3bb0288898
jni_test 1 0x7f3bb02888e0

如你所见,ThisObj不一样。更准确地说,thisObj 在构造函数中调用时,指的是Test类。当从方法调用时,它引用Test**的 * 示例。
这是为什么呢?
如何解决它(除了显式地将 this 作为一个参数传递给jni函数)?

ff29svar

ff29svar1#

如你所见,ThisObj不一样。更准确地说,当从构造函数调用时,thisObj引用Test类。当从方法调用时,它引用Test的示例。
这是为什么呢?
你的理论不正确。在C代码中输出的是一个对象句柄,而不是this指针。指向java对象的指针不直接公开给本机代码。这样做是行不通的,因为垃圾收集器可以四处移动对象,即使在执行本机代码的同时也是如此。
相反,VM将分配一个对象句柄,它可以被认为是间接引用Java对象的令牌,只有GC知道如何正确访问(由API封装)。在多个调用中,这意味着句柄的值可以更改,因为每个调用都会创建一个新句柄。
但在这两种情况下,句柄都将引用this对象。这与调用方法的位置无关。因为jni_test是一个示例方法,所以它需要一个接收器参数。Java代码中对jni_test(...)的两个调用都是this.jni_test(...)的缩写。this就是thisObj句柄在本机代码中引用的内容。
这在JNI规范中也有解释:
JNI接口指针是本机方法的第一个参数。JNI接口指针的类型为JNIEnv。第二个参数根据本机方法是静态的还是非静态的而有所不同。非静态本机方法的第二个参数是对对象的引用。静态本机方法的第二个参数是对其Java类的引用。
jni_test方法不是static,因此第二个参数引用对象。
如何解决它(除了显式地将此作为一个参数传递给jni函数)?
我不知道你想达到什么目的,或者你在寻找什么样的解决方案。不过请放心,没有办法公开指向任意Java对象的稳定本机地址。

w1e3prcc

w1e3prcc2#

你将你的方法声明为private native void jni_test(int i);,这意味着对应的C签名是:

void Java_<package>_jni_1test(JNIEnv *env, jobject obj, jint i);

不管它是从哪里调用的,obj是 * Test* 类的示例。您可以安全地将obj传递给其他方法或调用obj上的方法,但在构造函数中进行操作时也需要注意同样的问题。
另一个答案已经解释了为什么你的测试是测试错误的东西,所以我不会重复它。
编辑:我写了下面的代码来反驳你的说法:在测试中添加了以下主要方法:

public static void main(String[] args) {
  Test t = new Test();
  t.m();
}

下面是jni_test方法中的:

JNIEXPORT void JNICALL Java_Test_jni_1test(JNIEnv * env, jobject obj, jint i) {
    jclass cls_Test = env->FindClass("Test");
    printf("Inside call with i=%d\n", i);
    printf("\tobj instanceOf Test: %d\n", env->IsInstanceOf(obj, cls_Test));
    printf("\tobj.getClass() == Test: %d\n", env->IsSameObject(cls_Test, env->GetObjectClass(obj)));

    jmethodID mid_Test_toString = env->GetMethodID(cls_Test, "toString", "()Ljava/lang/String;");
    jstring str = (jstring)env->CallObjectMethod(obj, mid_Test_toString);
    const char * str_output = env->GetStringUTFChars(str, nullptr);
    printf("\tobj.toString(): %s\n", str_output);
    env->ReleaseStringUTFChars(str, str_output);
}

其产生以下输出:

Inside call with i=0
    obj instanceOf Test: 1
    obj.getClass() == Test: 1
    obj.toString(): Test@1eb44e46
Inside call with i=1
    obj instanceOf Test: 1
    obj.getClass() == Test: 1
    obj.toString(): Test@1eb44e46

这最终证明了是的,即使在构造函数中,this也引用了正在构造的对象。我不知道你是如何得出不同的结论的,但我只能假设你的测试代码是错误的。

相关问题