简体   繁体   中英

JNI RegisterNatives does not work on class loaded by ClassLoader.loadClass()

I am embedding a JVM in an existing C++ application and need to register implementation of native Java functions with a class.

Consider this simple class with native functions:

class Native {
  static {
    System.out.println("Class 'Native' static initializer called.");
  }

  public native int f(int i);
}

Inside the JVM I am running OSGi which is the reason I need to get the classes using Java code (using the correct class loader) instead of loading them from JNI. However, to keep this example simple, I have left out OSGi.

I have these four different Java methods for getting a jclass value in C++:

class Bridge {
  public Class<?> getNativeClass1() throws ClassNotFoundException {
    return getClass().getClassLoader().loadClass("org.example.Native");
  }

  public Class<?> getNativeClass2() throws ClassNotFoundException {
    return Class.forName("org.example.Native", false, getClass().getClassLoader());
  }

  public Class<?> getNativeClass3() throws ClassNotFoundException {
    final Class<?> clazz = getClass().getClassLoader()
                                     .loadClass("org.example.Native");
    clazz.getMethods();
    return clazz;
  }

  public Class<?> getNativeClass4() throws ClassNotFoundException {
    return Class.forName("org.example.Native", true, getClass().getClassLoader());
  }
}

To register the native function implementation in C++ for Native.f() I call:

JNIEnv* env = ...
jclass clazz = ...; // Calling one of the four methods above.
JNINativeMethod nativeMethod = {
  (char*) "f",      // Method name 'f'.
  (char*)  "(I)I;", // Signature 'int --> int'.
  (void*) f         // Pointer to C++ implementation of function.
};
env->RegisterNatives(clazz, &nativeMethod, 1);

Depending on which method I use for getting the Class<?> instance, I get different results:

  • getNativeClass1() : The static initializer is not executed in class Native when loading the class (but of course when creating an instance of the class) and the native implementation is not bound correctly. (Calling the native function in Java gives incorrect results or crashes the JVM.)
  • getNativeClass2() : Same as above.
  • getNativeClass3() : The static initializer is still not called in class Native when the class is loaded but the native implementation is bound correctly and I can call f() successfully.
  • getNativeClass3() : The static initializer is called in class Native when the class is loaded and the native implementation is bound correctly.

So it seems that ClassLoader.loadClass() loads the class in a way so it is not properly initialized and JNIEnv::RegisterNatives() will not work properly. However, calling Class.getMethods() will somehow initalize the class (without invoking the static initializer) so that binding the native methods works.

On the other hand, Class.forName(clazz, false, classLoader) seems to work exactly like Class.loadClass() returning an uninitialized Class instance.

Can anybody explain the difference between

  1. an uninitialized class like the one returned by getNativeClass1() and getNativeClass2()
  2. a partially initialized class like the one returned by getNativeClass3()
  3. a fully initialized class like the one returned by getNativeClass4()

and what is the most portable way to load a class before calling JNIEnv::RegisterNatives() ?

So it seems that ClassLoader.loadClass() loads the class in a way so it is not properly initialized

According to the documentation :

loadClass(String) : "Invoking this method is equivalent to invoking loadClass(name, false)."

loadClass(String,boolean) (emphasis added): "If the class was found using the above steps, and the resolve flag is true , this method will then invoke the resolveClass(Class) method on the resulting Class object"

Those two methods are intended for internal use by classloaders that need to do something between loading and linking. I'm not sure why loadClass(String) is marked public, but it arguably shouldn't be.

and what is the most portable way to load a class before calling

Class.forName() , which uses the context classloader and ensures that the class is ready for use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM