java.lang.UnsatisfiedLinkError 即使库和类有方法



我正在使用Python和Py4J来测试JNI代码。但是当我调用 JNI 代码时,出现以下错误:

py4j.protocol.Py4JJavaError: An error occurred while calling o37.createInstance.
: java.lang.UnsatisfiedLinkError: com.mgr_api_JNI.createInstance(Lcom/mgr_api_types$EDisplayType;Ljava/lang/String;Lcom/mgr_api_types$ECommType;Ljava/lang/String;)V
at com.mgr_api_JNI.createInstance(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)
at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:357)
at py4j.Gateway.invoke(Gateway.java:282)
at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
at py4j.commands.CallCommand.execute(CallCommand.java:79)
at py4j.GatewayConnection.run(GatewayConnection.java:238)
at java.base/java.lang.Thread.run(Thread.java:834)

我已经查看了这些链接链接 1、链接 2、链接 3、链接 4、链接 5 和链接 6 以及其他链接,但它们都没有解决我的问题。

法典

mgr_api_JNI.java:

package com;
import com.mgr_api_types.*;
public class mgr_api_JNI
{
static
{
try
{
System.loadLibrary("Mngr"); // Use "-Djava.library.path=[path to library]" option to load this library
}
catch (UnsatisfiedLinkError e)
{
System.err.println("Native code library 'Mngr' failed to load.n" + e);
}
}
public native void createInstance(com.mgr_api_types.EDisplayType displayType,
String displaySerialNumber,
com.mgr_api_types.ECommType commType,
String portName);
}

testsJNI.java:

import com.*;
import py4j.GatewayServer;
public class testsJNI
{
public static void main(String args[])
{
testsJNI testApp = new testsJNI();
// Py4J server
GatewayServer server = new GatewayServer(testApp);
server.turnLoggingOff();
server.start();
}
}

com_mgr_api_JNI.h(在mgr_api_JNI.java上使用javac -h创建):

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_mgr_api_JNI */
#ifndef _Included_com_mgr_api_JNI
#define _Included_com_mgr_api_JNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     com_mgr_api_JNI
* Method:    createInstance
* Signature: (Lcom/mgr_api_types/EDisplayType;Ljava/lang/String;Lcom/mgr_api_types/ECommType;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_mgr_1api_1JNI_createInstance
(JNIEnv *, jobject, jobject, jstring, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif

com_mgr_api_JNI.cpp:

#include "com_mgr_api_JNI.h"
static manager::CManagerApi* manager = NULL;
JNIEXPORT void JNICALL Java_com_mgr_1api_1JNI_createInstance(
JNIEnv *env,
jobject thisObj, 
jobject displayType,
jstring displaySerialNumber,
jobject commType,
jstring portName)
{
manager::EDisplayType dType = convertObjectToDisplayType(env, displayType);
const char* serialNumber = env->GetStringUTFChars(displaySerialNumber, 0);
manager::ECommType comm = convertObjectToCommType(env, commType);
const char* port = env->GetStringUTFChars(portName, 0);
char buf[100];
sprintf(buf,"%s",port);
std::string portStr = buf;
sprintf(buf,"%s",serialNumber);
std::string serialNumStr = buf;
if (manager == NULL)
{
manager = manager::CManagerApi::get();
manager->initialize(dType, serialNumStr, comm, portStr);
}
// Release memory
env->ReleaseStringUTFChars(displaySerialNumber, serialNumber);
env->ReleaseStringUTFChars(portName, port);
}

Java 代码的命令行执行:

java -cp /mnt/c/Workspace/library/java/:.:/home/fred/.local/share/py4j/py4j0.10.7.jar -Djava.library.path=/mnt/c/Workspace/build/library/ testsJNI

执行-XshowSettings:properties将显示以下属性:

awt.toolkit = sun.awt.X11.XToolkit
file.encoding = UTF-8
file.separator = /
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
java.awt.printerjob = sun.print.PSPrinterJob
java.class.path = /mnt/c/Workspace/library/java/
.
/home/fred/.local/share/py4j/py4j0.10.7.jar
java.class.version = 55.0
java.home = /usr/lib/jvm/java-11-openjdk-amd64
java.io.tmpdir = /tmp
java.library.path = /mnt/c/Workspace/build/library/

尝试解决问题

确保我有一个有效的本机库

在 java.library.path/mnt/c/Workspace/build/library/上执行ls将显示库libMngr.so

如果我从该位置删除libMngr.so,然后尝试运行Java,则抱怨找不到库。

libMngr.so执行nm命令将显示以下内容:

000000000021f269 T Java_com_mgr_1api_1JNI_createInstance

所以Java可以找到原生库,它有函数的符号。

执行objdump命令:

$objdump -f build/library/libMngr.so
build/library/libMngr.so:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x000000000018aee0

显示本机库是 64 位的,根据-XshowSettings:properties我使用的是 64 位 Java。

我什至在mgr_api_JNI.javaSystem.loadLibrary("Mngr");之前放置了一个 print 语句,以确保本机库只加载一次。

更新

我从mgr_api_JNI.java重新生成了头文件,并将函数声明复制到.cpp文件中,以确保函数名称正确。但我仍然收到同样的错误。

确保我有有效的 Java 类

如果我在java.class.path上做一个ls/mnt/c/Workspace/library/java/我会从编译中找到所有Java类mgr_api_JNI.java

如果我删除类并尝试运行Java,那么Java会抱怨它找不到这些类。

在 java.class.path 上执行grep -r createInstance/mnt/c/Workspace/library/java/返回:

Binary file com/mgr_api_JNI.class matches
com/mgr_api_JNI.java: public native void createInstance(com.mgr_api_types.EDisplayType displayType,

因此,Java可以找到已编译的Java类,并且其中包含createInstance方法。

问题

在我看来,Java可以找到所有需要的类和本机库,但我仍然收到UnsatisfiedLinkError错误。

为什么我仍然收到此错误?

我需要做什么来帮助 Java 查找/识别createInstance方法?

这是你的错误

你有库文件:/mnt/c/Workspace/build/library/libMgr.so但你在代码中加载了这个文件:System.loadLibrary("Mngr");- 你有一个错别字

您可以确保在名称正确的情况下它按预期工作

当您"离开"初始JVM时,-Djava.library.path不再是向其他JVM提供信息的有效位置。我不知道py4j.GatewayConnection的细节,但最好确保您不会运行另一个JVM实例,或者不要在那里使用JNI

我建议进行以下测试:

  • LD_LIBRARY_PATH设置为库所在的位置

  • 确保你可以只用public class mgr_api_JNI访问代码中的库(没有 Python 桥引擎)

  • 如果第二个 JVM 有可能正在加载本机代码(并且找不到它),请尝试使用_JAVA_OPTIONS=-Djava.library.path=[path to library]。这样,所有JVMs都将"看到"该位置 - 但您应该仅出于测试目的这样做

从另一个进程加载库绝对看起来像是问题。如果我将您的代码简化为这样:

.
|-- Makefile
|-- README.md
|-- c
|   `-- com_mgr_api_JNI.cc
|-- java
|   `-- com
|       |-- mgr_api_JNI.java
|       `-- mgr_api_types
|           |-- ECommType.java
|           `-- EDisplayType.java
|-- lib
`-- target

和代码本身

com_mgr_api_JNI.cc

#include <iostream>
#include "jni.h"
#include "com_mgr_api_JNI.h"
using namespace std;
JNIEXPORT void JNICALL Java_com_mgr_1api_1JNI_createInstance
(JNIEnv *env, jobject cls, jobject a, jstring b, jobject c, jstring d) {
cout << "Hello";
}

java/com/mgr_api_JNI.java

package com;
import com.mgr_api_types.*;
public class mgr_api_JNI
{
static {
try {
System.loadLibrary("Mngr"); // Use "-Djava.library.path=[path to library]" option to load this library
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library 'Mngr' failed to load.n" + e);
}
}
public native void createInstance(  com.mgr_api_types.EDisplayType displayType,
String displaySerialNumber,
com.mgr_api_types.ECommType commType,
String portName);
public static void main(String [] arg) {
mgr_api_JNI obj = new mgr_api_JNI();
obj.createInstance(new com.mgr_api_types.EDisplayType(), "", new com.mgr_api_types.ECommType(), "");
}
}

java/com/mgr_api_types/ECommType.java

package com.mgr_api_types;
public class ECommType { }

cat java/com/mgr_api_types/EDisplayType.java

package com.mgr_api_types;
public class EDisplayType { }

我构建代码

Makefile.common

ARCH=$(shell uname -s | tr '[:upper:]' '[:lower:]')
ifeq ($(ARCH),darwin)
EXT=dylib
else
EXT=so
endif

生成文件

include Makefile.common
all: compilejava compilec
compilec:
c++ -std=c++11 -g -shared -fpic -I${JAVA_HOME}/include -I${JAVA_HOME}/include/$(ARCH) c/com_mgr_api_JNI.cc -o lib/libMngr.$(EXT)
compilejava:
$(JAVA_HOME)/bin/javac -h c -d target -cp target java/com/mgr_api_types/EDisplayType.java
$(JAVA_HOME)/bin/javac -h c -d target -cp target java/com/mgr_api_types/ECommType.java
$(JAVA_HOME)/bin/javac -h c -d target -cp target java/com/mgr_api_JNI.java
test:
$(JAVA_HOME)/bin/java -Djava.library.path=$(LD_LIBRARY_PATH):./lib -cp target com.mgr_api_JNI
clean:
-rm -rfv target/*
-rm c/*.h
-rm -rf lib/*

它按预期工作

make test
/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home/bin/java -Djava.library.path=:./lib -cp target com.mgr_api_JNI
Hello

我认为您遇到这样一种情况,即您的类是从另一个 JVM 实例调用的,或者从通过 JNI 调用的东西调用的,或者从另一个进程调用并且您的-Djava.library.path消失了。

此外,请尝试使用System.load("full/path/of/library.so")来确保您可以从JVM访问库。

当运行时无法在共享库中加载相应的本机函数时,会出现此问题。

因此,在这些情况下,重新生成头文件以查看函数名称是否正确总是好的。

在这种情况下,您的函数名称看起来很可疑

Java_com_1mgr_1api_1JNI_createInstance

生成的名称使用_1对 Java 类/函数名称中的文字下划线进行编码。因此,根据类名mgr_api_JNI

名称应该是
Java_com_mgr_1api_1JNI_createInstance

即第一个_1只是一个普通的下划线_,这也是我在为具有该名称的类生成头文件时看到的。

最新更新