Must call RegisterNatives after getMethodID, else get UnsatisfiedLinkError

Martin Buchholz martinrb at google.com
Tue Jun 8 16:12:11 PDT 2010


Hi ClassLoader maintainers,

This is a bug report.

(I think this is a hotspot bug, but it might be a core library
ClassLoader bug or even a URLClassLoader bug)

If you use RegisterNatives with a class loaded using URLClassLoader,
you must call GetMethodID
before RegisterNatives, or you get an UnsatisfiedLinkError as in the
test case below

(you will need to edit the below to fill in the location of your jni
include directory and your libjvm.so)

$ cat Test.java; echo ---------------------; cat jvm.cc; echo
--------------------------; g++ -I$JDK/include -I$JDK/include/linux
-ldl jvm.cc && ./a.out
public class Test {
  public Test() {
    System.out.println("My class loader is " + getClass().getClassLoader());
    testNative();
    System.out.println("back to Java");
  }
  public static native void testNative();
}
---------------------
#include <cstdio>
#include <dlfcn.h>
#include <jni.h>

JavaVM* jvm;
JNIEnv* env;
const char* libjvm =
    "$JDKDIR/libjvm.so";
const char* loader_url = "file://./";
const char* class_name = "Test";

void InitializeJVM() {
  JavaVMOption options[1];
  options[0].optionString = (char*)"-Djava.class.path=.";
  //options[1].optionString = (char*)"-verbose:jni";

  JavaVMInitArgs vm_args;
  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = 2;
  vm_args.ignoreUnrecognized = JNI_TRUE;

  void* handle = dlopen(libjvm, RTLD_LAZY);
  if (handle == NULL) perror("dlopen");
  jint JNICALL (*func_create_java_vm)(JavaVM**, void**, void*) =
      reinterpret_cast<jint JNICALL (*)(JavaVM**, void**, void*)>
      (dlsym(handle, "JNI_CreateJavaVM"));
  if (func_create_java_vm == NULL) perror("dlsym");
  jint result = (*func_create_java_vm)(&jvm, (void**)(&env), &vm_args);
}

void TestNative(JNIEnv* env, jclass cls) {
  printf("Successfully called registered native method\n");
}

void RegisterNativeMethod(jclass cls) {
  static const JNINativeMethod jni_method =
      { (char*)"testNative",
        (char*)"()V",
        (void*)&TestNative };
  env->RegisterNatives(cls, &jni_method, 1);
  if (env->ExceptionCheck()) env->ExceptionDescribe();
}

void Test() {
  // URL[] urls = {new URL(url)}
  jclass cls = env->FindClass("java/net/URL");
  jmethodID method = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  jstring jurl_str = env->NewStringUTF(loader_url);
  jobject jurl = env->NewObject(cls, method, jurl_str);
  jobjectArray jurls = env->NewObjectArray(1, cls, jurl);

  // URLClassLoader loader = new URLClassLoaer(urls)
  cls = env->FindClass("java/net/URLClassLoader");
  method = env->GetMethodID(cls, "<init>", "([Ljava/net/URL;)V");
  jobject jloader = env->NewObject(cls, method, jurls);

  // Class cls = loader.loadClass(name)
  method = env->GetMethodID(
      cls, "loadClass", "(Ljava/lang/String;Z)Ljava/lang/Class;");
  jstring jname = env->NewStringUTF(class_name);
  cls = (jclass)env->CallObjectMethod(jloader, method, jname, (jboolean) true);

  method = env->GetMethodID(cls, "<init>", "()V");
  if (env->ExceptionCheck()) env->ExceptionDescribe();

  // RegisterNatives must be called after GetMethodID.
  // If the order is reversed, we get UnsatisfiedLinkError,
  // which seems like a bug.
  RegisterNativeMethod(cls);

  env->NewObject(cls, method);
  if (env->ExceptionCheck()) env->ExceptionDescribe();
}

int main(int argc, char** argv) {
  InitializeJVM();
  Test();

  return 0;
}
--------------------------
My class loader is sun.misc.Launcher$AppClassLoader at 1f7182c1
Successfully called registered native method
back to Java

Thanks,

Martin



More information about the net-dev mailing list