简体   繁体   中英

Persistent UnsatisfiedLinkError while running Java invoking native .so on Linux

I'm trying to get a small/sample Java application, which invokes native C++ code using JNI calls, running on Linux.

I use Eclipse to build, configure the environment and run the application.

I have divided the "overall" project in 2 separate Eclipse projects: 1 Java project and 1 C++ project, containing the native code.

Eclipse project view

The Java part consists of: 1: a "main" class, from which an "adapter" class is invoked to load the .so library and call the native interface/C++ methods 2: an "adapter" class, containing the native method declarations

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

The C++ part consists of: 1: a *.h file, generated with javah, containing the native method definition 2: a *.cpp file, containing the C++ implementation of the native methods

Both very rudimentary, only meant to sanity-test the JNI environment setup. Added this, omitted that in the original post for this issue.

.h file: 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 file: 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;
}

From the C++ project an .so is built, including jni.h and jni_md.h. for the Java project, the resulting .so is included as external library in the Java build path and added as VM argument "java.library.path" in the run configuration (for the applicable Java project/main class).

First, the .so is built from the C++ project: this results in an .so binary in the C++ project folder within the common workspace. The the Java build is performed. Thereafter the Java program is run invoking the project's main class.

Result: the .so appears to be loaded, but thereafter the process is aborted with an (very persistent) "UnsatisfiedLinkError".

For what I understand, this error is thrown if the Java Virtual Machine cannot find an appropriate native-language definition of a method declared native.

This sounds like the native method definition (C++ side) is not compliant with the native method declaration on the Java-side. But the C++ definitions are as far as I see/know entirely compliant with the native method declaration in Java, and with the applicable native method naming conventions. For me, it appears that all building/configuration options are exhausted - does anyone have a suggestion what might be the cause, and to solve this?

It looks like name of your native method is not valid. Make sure to properly escape _ . _ is used to separate packages in native methods. You need to follow naming convention:

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

If you have "_" in method name, you need to escape it with "_1" in native code.

Example. For method in Java:

public static native void display_Message();

you need:

JNIEXPORT void JNICALL Java_recipeNo001_HelloWorld_display_1Message
  (JNIEnv *, jclass);

Note " _1 " that is between " display " and " Message ".

Source taken (and slightly modified) from here: http://jnicookbook.owsiak.org/recipe-No-001/

Update

Where should you pay attention:

  1. If everything else fails, make sure to set LD_LIBRARY_PATH before running Eclipse. This will give you some insight whether Eclipse plays nasty or something else is broken
  2. Make sure to pass java.library.path using "-D" while starting your code. You can set it in Debug configuration as JVM arguments
  3. Sometimes, it might be tricky, and it may turn out that your lib doesn't contain symbol at all. You can check it using nm

     nm libSomeLibFile.so 
  4. You can also set native code location inside project's Properties configuration in Eclipse

    在此处输入图片说明

Update. I have slightly simplified your code to make it easier to check what is wrong. I suggest you to remove "_" from class names as well as they are again mixed up in native code.

Take a look here:

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 class

public class NativeCppAdapter {
    public native void testKickoffSoForPrint();

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

C++ code (pay attention to C export - it has impact on function name !)

#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

Compile code and 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

Just few more remarks

If you compile code without

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

your symbols inside library will be incorrect (not what JVM expects).

Another thing is. If you use "loadLibrary" you have to make sure that file name is in the form: lib SomeName .so and you load file via System.loadLibrary("SomeName"); - and you have to make sure that either java.library.path or LD_LIBRARY_PATH point to the file. If you use System.load , on the other hand, you don't have to make any assumptions regarding name. Just make sure you provide full path to file. For example: /tmp/someFileWithMyLib

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM