Search code examples
javajava-native-interfacejniwrapper

thisObj in JNI function called from constructor refers to the class and not to the instance


I'm trying to implement a JNI library. I've noticed that thisObj passed to JNI function when called from a constructor differs from the same function called from a method.

Here is my minimal code:

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

I call it like this:

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

JNI side looks like this:

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

The output is:

jni_test 0 0x7f3bb0288898
jni_test 1 0x7f3bb02888e0

As you see, thisObj is not the same. To be more precise, thisObj refers to the Test class when called from the constructor. And it refers to the instance of the Test when called from a method.

Why is this?

How to workaround it (except from explicitly passing this as one more parameter to the jni function)?


Solution

  • As you see, thisObj is not the same. To be more precise, thisObj refers to the Test class when called from the constructor. And it refers to the instance of the Test when called from a method.

    Why is this?

    You are incorrect in your theory. What you're printing out in the C code is an object handle, not the this pointer. Pointers to java objects are not exposed directly to native code. This wouldn't work as the garbage collector can move the objects around, even at the same time that the native code is executed.

    Instead the VM will allocate an object handle, which can be thought of as a token that indirectly refers to the Java object, and only the GC knows how to access correctly (which is encapsulated by the JNI api). Across multiple calls, this means that the value of the handle can change, because a new handle is created for every call.

    But in both cases, the handle will refer to the this object. This has nothing to do with the place from where the method is called. Because jni_test is an instance method, it requires a receiver argument. Both of the calls to jni_test(...) in your Java code, are just short for this.jni_test(...). And that this is what the thisObj handle refers to in the native code.

    This is also explained in the JNI specification:

    The JNI interface pointer is the first argument to native methods. The JNI interface pointer is of type JNIEnv. The second argument differs depending on whether the native method is static or nonstatic. The second argument to a nonstatic native method is a reference to the object. The second argument to a static native method is a reference to its Java class.

    The jni_test method is not static, so the second argument refers to the object.

    How to workaround it (except from explicitly passing this as one more parameter to the jni function)?

    I'm not sure what you're trying to achieve, or what kind of workaround you're looking for. Rest assured though, that there's no way to expose a stable native address pointing at an arbitrary Java object.