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
和一个简单的名称。
这是一个演示(我省略了实用程序,它们只是检查是否有异常并打印消息):
第一次尝试,应该可以:
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
和一个简单的名称。