java - 在 Linux 上运行 Java 调用 native .so 时持续存在 UnsatisfiedLinkError

标签 java c++ linux java-native-interface

我正在尝试获得一个小型/示例 Java 应用程序,它使用 JNI 调用调用 native C++ 代码,在 Linux 上运行。

我使用 Eclipse 构建、配置环境并运行应用程序。

我将“整体”项目分为 2 个独立的 Eclipse 项目:1 个 Java 项目和 1 个 C++ 项目,其中包含 native 代码。

Eclipse project view

Java部分包括: 1:一个“主”类,从中调用一个“适配器”类来加载.so库并调用 native 接口(interface)/C++方法 2:一个“适配器”类,包含本地方法声明

public class Java_Main_For_So_Kickoff {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("Hello from Java main!");

        Native_Cpp_Adapter adapter = new Native_Cpp_Adapter();

        adapter.locSetProperty();
        adapter.locLoadLib();

        adapter.sayHello();

        adapter.test_Kickoff_So_For_Print();

    }

}


public class Native_Cpp_Adapter {   
    public native void test_Kickoff_So_For_Print();

    public void locSetProperty() {
        "/home/adminuser/workspace_Unit_Test_Java_Cpp/Unit_Test_Cpp/Debug");
        String libPathProp = System.getProperty("java.library.path");

        System.out.println("lib path set:" + libPathProp);
    }

    public void locLoadLib() {
        System.setProperty("java.library.path", 
        "//home//adminuser//workspace_Unit_Test_Java_Cpp//Unit_Test_Cpp//Debug//");
        String libPathProp = System.getProperty("java.library.path");
        String soLibName = "libUnit_Test_Cpp.so";
        String soLibWithPath = libPathProp.concat(soLibName);

        System.out.println("lib path set for loading:" + libPathProp);
        System.load(soLibWithPath);

        System.out.println("library loaded");
    }

    public void sayHello() {
        System.out.println("Hello from JNI Adapter (Java part)");
    }
}

C++ 部分包括: 1:一个*.h文件,用javah生成,包含native方法定义 2:一个*.cpp文件,包含本地方法的C++实现

两者都非常初级,仅用于对 JNI 环境设置进行完整性测试。添加了这个,在这个问题的原始帖子中省略了这个。

.h文件:Native_Cpp_adapter.h

     /*
 * Native_Cpp_Adapter.h
 *
 *  Created on: Aug 23, 2017
 *      Author: adminuser
 */

#ifndef SRC_NATIVE_CPP_ADAPTER_H_
#define SRC_NATIVE_CPP_ADAPTER_H_

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Native_Cpp_Adapter */

#ifndef _Included_Native_Cpp_Adapter
#define _Included_Native_Cpp_Adapter
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT void JNICALL Java_Native_Cpp_Adapter_test_Kickoff_So_For_Print
  (JNIEnv *, jobject);


#ifdef __cplusplus
}
#endif
#endif





#endif /* SRC_NATIVE_CPP_ADAPTER_H_ */

.cpp文件:Native_Cpp_Adapter.cpp

#include "jni.h"
#include <iostream>

using namespace std;

JNIEXPORT void JNICALL Java_Native_Cpp_Adapter_test_Kickoff_So_For_Print
 (JNIEnv *, jobject)
{
    cout << "Correct kickoff of Native JNI method nr. 1";
    return;
}

从 C++ 项目构建了一个 .so,包括 jni.h 和 jni_md.h。对于 Java 项目,生成的 .so 作为外部库包含在 Java 构建路径中,并作为 VM 参数“java.library.path”添加到运行配置中(对于适用的 Java 项目/主类)。

首先,.so 是从 C++ 项目构建的:这会在公共(public)工作区内的 C++ 项目文件夹中生成一个 .so 二进制文件。 执行 Java 构建。此后运行 Java 程序并调用项目的主类。

结果:.so 似乎已加载,但此后该过程因(非常持久的)“UnsatisfiedLinkError”而中止。

据我所知,如果 Java 虚拟机找不到声明为 native 的方法的适当 native 语言定义,则会抛出此错误。

这听起来像是本地方法定义(C++ 端)与 Java 端的本地方法声明不兼容。 但据我所知,C++ 定义完全符合 Java 中的 native 方法声明以及适用的 native 方法命名约定。 对我来说,似乎所有构建/配置选项都已用尽 - 有没有人建议可能是什么原因,并解决这个问题?

最佳答案

您的 native 方法名称似乎无效。确保正确转义 __ 用于分隔原生方法中的包。您需要遵循命名约定:

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names

如果方法名称中有“_”,则需要在 native 代码中使用“_1”对其进行转义。

例子。对于 Java 中的方法:

public static native void display_Message();

你需要:

JNIEXPORT void JNICALL Java_recipeNo001_HelloWorld_display_1Message
  (JNIEnv *, jclass);

请注意“_1”,它位于“显示”和“消息”之间。

来源(并稍作修改)取自此处:http://jnicookbook.owsiak.org/recipe-No-001/

更新

需要注意的地方:

  1. 如果一切都失败了,请确保在运行 Eclipse 之前设置 LD_LIBRARY_PATH。这将使您了解 Eclipse 运行不正常还是其他什么东西坏了
  2. 确保在启动代码时使用“-D”传递java.library.path。您可以在调试配置中将其设置为 JVM 参数
  3. 有时,这可能很棘手,结果可能是您的库根本不包含符号。您可以使用 nm

    检查它
    nm libSomeLibFile.so
    
  4. 您还可以在 Eclipse 中的项目的 Properties 配置中设置 native 代码位置

    enter image description here

更新。我稍微简化了您的代码,以便更轻松地检查问题所在。我建议您从类名中删除“_”,因为它们会在 native 代码中再次混淆。

看这里:

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("Hello from Java main!");
        NativeCppAdapter adapter = new NativeCppAdapter();
        adapter.locLoadLib();
        adapter.testKickoffSoForPrint();
    }
}

原生适配器类

public class NativeCppAdapter {
    public native void testKickoffSoForPrint();

    public void locLoadLib() {
        String soLibName = "/tmp/libNativeCppAdapter.so";
        System.load(soLibName);
    }
}

C++ 代码(注意 C 导出 - 它对函数名称有影响!)

#include "jni.h"
#include <iostream>

using namespace std;

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeCppAdapter
 * Method:    testKickoffSoForPrint
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_NativeCppAdapter_testKickoffSoForPrint
  (JNIEnv *, jobject) {

    cout << "Correct kickoff of Native JNI method nr. 1";
    return;

}

#ifdef __cplusplus
}
#endif

编译代码和Java

> javac *.java
> c++ -g -shared -fpic -I${JAVA_HOME}/include \
-I${JAVA_HOME}/include/darwin \
NativeCppAdapter.cpp \
-o /tmp/libNativeCppAdapter.so
> java -cp . Main
Hello from Java main!
Correct kickoff of Native JNI method nr. 1

再说几句

如果你不编译代码

#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif

库中的符号将不正确(不是 JVM 所期望的)。

还有一点。如果您使用“loadLibrary”,您必须确保文件名的格式为:libSomeName.so 并且您通过 System.loadLibrary("SomeName"); - 并且您必须确保 java.library.pathLD_LIBRARY_PATH 指向该文件。另一方面,如果您使用 System.load,则不必对名称做出任何假设。只要确保您提供文件的完整路径即可。例如:/tmp/someFileWithMyLib

关于java - 在 Linux 上运行 Java 调用 native .so 时持续存在 UnsatisfiedLinkError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46527151/

相关文章:

java - 广度优先搜索 : How many states of a vertex are needed?

java - JPanels和播放音乐

java - 如何将多个xml值存储到java中

c# - SQLite in 10 UWP App Assertion Error (p->iForeGuard==(int)FOREGUARD);

c++ - glibc 函数的 GCC、-flto、-fno-builtin 和自定义函数实现

linux - 找不到/xampp/lang.php

java - 将 byte[] 缓冲区重置为零?

c++ - 标准 C++ 中的正则表达式

linux - Linux服务器同时最大用户数限制

linux - SDL2/GL 上下文在 linux mint 上默认为 v3.0