在Linux上运行Java调用本机.so时出现持久性UnsatisfiedLinkError

时间:2017-10-02 14:08:17

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

我正在尝试获取一个小型/示例Java应用程序,该应用程序使用在Linux上运行的JNI调用来调用本机C ++代码。

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

我在两个独立的Eclipse项目中划分了“整体”项目:1个Java项目和1个C ++项目,包含本机代码。

Eclipse project view

Java部分包括: 1:一个“main”类,从中调用“adapter”类来加载.so库并调用本机接口/ 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:使用javah生成的* .h文件,包含本机方法定义 2:a * .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 ++项目构建的:这会在公共工作区内的C ++项目文件夹中生成.so二进制文件。 执行Java构建。此后,Java程序运行调用项目的主类。

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

根据我的理解,如果Java虚拟机无法找到声明为native的方法的相应本机语言定义,则会抛出此错误。

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

1 个答案:

答案 0 :(得分:0)

看起来您的原生方法的名称无效。确保正确逃脱 _ _ 用于在本机方法中分隔包。您需要遵循命名约定:

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

如果你有&#34; _&#34;在方法名称中,您需要使用&#34; _1&#34;在本机代码中。

实施例。对于Java中的方法:

public static native void display_Message();

你需要:

JNIEXPORT void JNICALL Java_recipeNo001_HelloWorld_display_1Message
  (JNIEnv *, jclass);

注意&#34; _1 &#34;介于&#34; 显示&#34;和&#34; 消息&#34;。

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

<强>更新

你应该在哪里注意:

  1. 如果其他所有方法都失败了,请确保在运行Eclipse之前设置LD_LIBRARY_PATH。这将为您提供一些洞察,让您了解Eclipse是否令人讨厌或其他东西是否已被破坏
  2. 确保使用&#34; -D&#34;通过java.library.path在启动代码时。您可以在调试配置中将其设置为JVM参数
  3. 有时,它可能会很棘手,而且可能会发现你的lib根本不包含符号。您可以使用nm

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

    enter image description here

  5. 更新。我稍微简化了你的代码,以便更容易检查出错了什么。我建议你删除&#34; _&#34;从类名称以及它们再次以本机代码混合。

    看看这里:

    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();
        }
    }
    

    Native Adapter类

    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期望的那样)。

    另一件事是。如果您使用&#34; loadLibrary&#34;您必须确保文件名的格式为:lib SomeName .so并且您通过System.loadLibrary("SomeName");加载文件 - 您必须确保java.library.path或{ {1}}指向该文件。另一方面,如果您使用LD_LIBRARY_PATH,则不必对名称做出任何假设。只需确保提供完整的文件路径。例如:System.load