JNI – 为什么 rt.jar 中的 System.load 方法不起作用,但封装的方法起作用呢?

huangapple go评论83阅读模式
英文:

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); // 第一个UnsatisfiedLinkError由此工具打印

// 第二个UnsatisfiedLinkError由本地方法调用打印,我省略了它。

得到了这个结果:

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

即使抛出了重复加载异常,本地调用仍然正常工作。

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

英文:

And here is a demo (I omit utils, They just check if exception and print message):

First try, It should work:

C++ part:

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

No exception but after that, Call a native method cause UnsatisfiedLinkError.

Second try, Write a wrapper method:

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

And call it from C++

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

It just a wrapper for System.load and nothing else, It working fine. The native call working properly.

Then for more test but not make any sense - Use both of them:

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

jclass jClass_Driver = env-&gt;FindClass(&quot;Driver&quot;);
jmethodID jMethodID_Driver_load = env-&gt;GetStaticMethodID(jClass_Driver, &quot;load&quot;, &quot;(Ljava/lang/String;)V&quot;);
env-&gt;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.

Got this result:

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

That make it more confuse, The first try show load by java.lang.System-load() not working but in fact the library is loaded. Then throw a duplicate load exception.

And reverse the order:

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

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

And got this result:

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

Even throw a duplicate load exception, The native call working propproperly.

Question: What happen? How to solve?

答案1

得分: 1

通过System.load()加载本机库时,虚拟机将尝试将其找到的JNI函数绑定到其Java对应函数,即声明本机方法的类。只有在已加载该类时,才能进行绑定。如果此后再加载该类,则会出现未绑定的本机方法,当调用这些方法时会产生UnsatisfiedLinkError错误。

为了能够调用您的包装方法,您确实需要加载该类,因此虚拟机可以绑定本机方法。要想仅通过调用System.load()使其正常工作,请确保虚拟机已经具有该类。尽管如此,最好还是使用通常的方式,在类本身的静态初始化程序中加载本机库。loadLibrary还会查找静态链接库。因此,如果将JNI函数与其余代码分开,将它们放入自己的库中,您可以进行静态链接,然后使用简单的名称调用loadLibrary

英文:

When you load a native library with System.load() the VM will try to bind any JNI functions it finds to their Java counterparts, i.e. the class that declares the native methods. It can only do that when that class is already loaded. If you load the class afterwards you will have unbound native methods, and when you call them you get an UnsatisfiedLinkError.

To be able to call your wrapper method, you do load the class and therefore the VM can bind the native methods. To make this work with only a call to System.load(), make sure the VM already has the class. That said, it would probably be better to use the usual way of loading the native library from a static initializer in the class itself. loadLibrary will also find statically linked libraries. So if you separate the JNI functions from the rest of your code, and put them into their own library, you can statically link that and use loadLibrary with a simple name.

huangapple
  • 本文由 发表于 2020年8月20日 10:29:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/63497482.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定