JNI - 为什么 rt.jar System.load 不工作但包装方法工作?

JNI - Why rt.jar System.load not working but wrapped method working?

这是一个演示(我省略了实用程序,它们只是检查是否有异常并打印消息):

第一次尝试,应该可以:

C++ 部分:

jclass jClass_java_lang_System = env->FindClass("java/lang/System");
jmethodID jMethodID_java_lang_System_load = env->GetStaticMethodID(jClass_java_lang_System, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_java_lang_System, jMethodID_java_lang_System_load, library_path);

没有例外,但在那之后,调用本地方法导致 UnsatisfiedLinkError

第二次尝试,写一个包装器方法:

public static void load(String path) {
    System.load(path);
}

并从 C++ 中调用它

jmethodID jMethodID_Driver_load = env->GetStaticMethodID(jClass_Driver, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_Driver, jMethodID_Driver_load, library_path);
checkException(env);

它只是 System.load 的包装器,没有别的,它工作正常。本机调用正常。

然后进行更多测试但没有任何意义-同时使用它们:

jclass jClass_java_lang_System = env->FindClass("java/lang/System");
jmethodID jMethodID_java_lang_System_load = env->GetStaticMethodID(jClass_java_lang_System, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_java_lang_System, jMethodID_java_lang_System_load, library_path);
if (!checkException(env)) std::cout << "Load by rt.jar no Exception" << std::endl;

jclass jClass_Driver = env->FindClass("Driver");
jmethodID jMethodID_Driver_load = env->GetStaticMethodID(jClass_Driver, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_Driver, jMethodID_Driver_load, library_path);
checkException(env); // The first UnsatisfiedLinkError print by this util

// Second UnsatisfiedLinkError print by native method call, I omit it.

得到这个结果:

Load by rt.jar no Exception
java.lang.UnsatisfiedLinkError: Native Library XXXXX already loaded in another classloader
java.lang.UnsatisfiedLinkError: XXXXXXX

这让它更加混乱,第一次尝试通过 java.lang.System-load() 显示加载不起作用,但实际上库已加载。然后抛出重复加载异常。

并颠倒顺序:

jclass jClass_Driver = env->FindClass("Driver");
jmethodID jMethodID_Driver_load = env->GetStaticMethodID(jClass_Driver, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_Driver, jMethodID_Driver_load, library_path);
checkException(env);

jclass jClass_java_lang_System = env->FindClass("java/lang/System");
jmethodID jMethodID_java_lang_System_load = env->GetStaticMethodID(jClass_java_lang_System, "load", "(Ljava/lang/String;)V");
env->CallStaticVoidMethod(jClass_java_lang_System, jMethodID_java_lang_System_load, library_path);
if (!checkException(env)) std::cout << "Load by rt.jar no Exception" << std::endl;

得到这个结果:

Load by wrapper no Exception
java.lang.UnsatisfiedLinkError: Native Library XXXX already loaded in another classloader
Result is - 2468
Result is - 2468

即使抛出重复加载异常,原生调用也能正常工作。

问:发生了什么事?如何解决?

当您使用 System.load() 加载本机库时,VM 将尝试将它找到的任何 JNI 函数绑定到它们的 Java 对应项,即声明本机方法的 class。它只能在 class 已经加载时执行此操作。如果你之后加载 class 你将拥有未绑定的本地方法,当你调用它们时你会得到一个 UnsatisfiedLinkError.

为了能够调用包装器方法,您执行加载class,因此VM可以绑定本机方法。要仅调用 System.load() 即可完成这项工作,请确保 VM 已具有 class。也就是说,使用通常的方法从 class 本身的静态初始化程序加载本机库可能会更好。 loadLibrary 还将找到静态 linked 库。因此,如果您将 JNI 函数与其余代码分开,并将它们放入它们自己的库中,您可以静态 link 并使用 loadLibrary 和一个简单的名称。