即使库和类都具有方法

时间:2019-06-08 15:59:35

标签: java java-native-interface 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)

我查看了这些链接link 1link 2link 3link 4link 5link 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);
}

测试JNI.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 ls上进行/mnt/c/Workspace/build/library/显示库libMngr.so

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

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

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。

我什至在System.loadLibrary("Mngr");的{​​{1}}前面放置了一条打印语句,以确保仅将本机库加载一次。

更新

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

确保我具有有效的Java类

如果我在java.class.path .cpp上进行了ls的访问,我发现编译/mnt/c/Workspace/library/java/时会发现所有Java类。

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

在java.class.path mgr_api_JNI.java上进行grep -r createInstance返回:

/mnt/c/Workspace/library/java/

因此Java可以找到已编译的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可以找到所有需要的类和本机库,但是我仍然遇到createInstance错误。

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

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

2 个答案:

答案 0 :(得分:1)

这是您的错误

您有一个库文件:<T>,但是您在代码中加载了这个文件:/mnt/c/Workspace/build/library/libMgr.so-您有错字

您可以确保在正确名称的情况下可以正常使用

当“离开”初始System.loadLibrary("Mngr");时,JVM不再是向其他JVM提供信息的有效位置。我不知道-Djava.library.path的详细信息,但是最好不要运行另一个py4j.GatewayConnection实例,或者不要在其中使用JVM

我建议进行以下测试:

  • JNI设置为您的lib所在的位置

  • 确保仅使用LD_LIBRARY_PATH(无需Python桥引擎)即可访问代码中的库

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

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

JVMs

和代码本身

com_mgr_api_JNI.cc

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

java / com / mgr_api_JNI.java

#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_types / ECommType.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 / EDisplayType.java

package com.mgr_api_types;

public class ECommType { }

然后我构建代码

Makefile.common

package com.mgr_api_types;

public class EDisplayType { }

Makefile

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/*

我认为您遇到的情况是,您的类是从另一个JVM实例,通过JNI调用的东西或另一个进程中调用的,而您的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 已经消失了。

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

答案 1 :(得分:0)

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

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

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

Java_com_1mgr_1api_1JNI_createInstance

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

Java_com_mgr_1api_1JNI_createInstance

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