【问题标题】:Calling System.loadLibrary() via JNI does not load native methods通过 JNI 调用 System.loadLibrary() 不会加载本机方法
【发布时间】:2015-02-24 19:43:23
【问题描述】:

我们有一个创建嵌入式 JVM 的本机 C++ 应用程序。此 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,一切正常。这很令人沮丧,因为我在网上看到的所有内容都表明这应该可以正常工作。

【问题讨论】:

    标签: c++ java-native-interface


    【解决方案1】:

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

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

    啊哈! Reflection.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 通过 JNI 进行调用而不是 C++ 的任何变体也可以避免该问题。 @CallerSensitive 标签似乎是一个线索;不要通过 JNI 调用这些!我希望这篇文章可以帮助其他人摆脱我所经历的挫败感。我仍然对为什么加载似乎成功但没有找到任何符号感到困惑。

    【讨论】:

    • 你为什么要为了打电话给dyloadLoadLibraryEx而这么麻烦?
    • 因为 LoadLibraryEx 不会使 C++ 代码可由 java 调用。我们的应用程序是一个重要的 c++/java 混合体,两种语言相互调用。我实际上并没有编写该 JNI 代码。我们有一个复杂的包装器生成器,它使这些调用成为来自 c++ 的一个衬里。加载本机库只是集成工作的一小部分。当我们启动嵌入式 jvm 时,从 c++ 进行调用很容易。除了它没有按预期工作,并且花费了大量精力来寻找原因。我要写一篇关于 jni 陷阱的博客。
    • 明白了。这就说得通了。有兴趣看博文,希望你把它链接在这里。
    • 我也有兴趣阅读有关此集成工作的更多信息!我相信我可以学到很多东西。
    • 另外值得注意的是,您必须在 Java 中加载库,即使 so/dll 已经被构建嵌入式 JVM 的本机代码加载。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多