通过JNI调用System.loadLibrary()不会加载本机方法

Calling System.loadLibrary() via JNI does not load native methods

本文关键字:加载 本机 方法 JNI 调用 System loadLibrary 通过      更新时间:2023-10-16

我们有一个本地C++应用程序,它创建了一个嵌入式JVM。这个JVM中的类使用SWIG包装器回调到C++对象方法中(尽管SWIG的使用并不重要;它可以很容易地是从javah生成的本机函数存根)。例如,我们有一个Java类,它有一个本地方法,如:

package net.foo;
public class CppWrapJNI {
  public final static native long foo(
    long l, Stuff jarg1_, String s
  );
}

在C++DLL中有一个相应的实现:

extern "C" {
  __declspec(dllexport) jlong JNICALL 
  Java_net_foo_CppWrapJNI_1foo(
    JNIEnv *jenv, jclass jcls, 
    jlong jarg1, jobject jarg1_, jstring jarg2
  ) {
    return 1;
  }
}

假设这个DLL名为"foo.DLL"。

然后,我们尝试让JVM通过JNI加载DLL,使用如下代码:

jclass cls = env->FindClass("java/lang/System");
jmethodID mid = env->GetStaticMethodID(
  cls, "loadLibrary", "(Ljava/lang/String;)V"
);
jstring jstr = env->NewStringUTF("foo.dll");
env->CallStaticVoidMethodV(cls, mid, jstr);

这一切都有效,并且对loadLibrary()的调用没有报告任何错误(这里没有显示JNI异常处理,但我们会这样做)。然而,稍后对CppWrapJNI.foo()的调用失败,并返回如下错误:

java.lang.UnsatisfiedLinkError: 
net.foo.CppWrapJNI.foo(JLnet/foo/Stuff;Ljava/lang/String;)J

奇怪的是,如果我纯粹用Java编写一个测试工具,用同样的方式调用loadLibrary,一切都会正常工作。这很令人沮丧,因为我在网上读到的一切都表明这应该很好。

最后,我查看了System.loadLibrary的源代码:

@CallerSensitive
public static void loadLibrary(String libname) {
    Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}

啊哈!反射.getCallerClass()是关键。没有调用者类!它来自于直接的C++。解决方案相当简单,围绕System.load*()编写一个门面,我们可以从C++调用它,它将反过来调用System方法:

public class SystemFacade {
    public static void load(String path) {
        java.lang.System.load(path);
    }
    public static void loadLibrary(String name) {
        java.lang.System.loadLibrary(name);
    }
}

瞧,它起作用了。据推测,任何java而不是C++通过JNI进行调用的变体也可以避免这个问题。@CallerSensitive标签似乎是一条线索;不要通过JNI呼叫这些!我希望这篇帖子能让其他人省去我所经历的挫折。我仍然困惑于为什么加载似乎成功了,但却没有找到任何符号。