java.lang.UnsatisfiedLinkError, хотя в библиотеках и классах есть метод - PullRequest
3 голосов
/ 08 июня 2019

Я использую 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 (создается с помощью javac -h в mgr_api_JNI.java):

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

попытки решить проблему

Убедитесь, что у меня есть действительная нативная библиотека

Выполнение ls на java.library.path /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"); в mgr_api_JNI.java, чтобы убедиться, что собственная библиотека загружается только один раз.

Обновление

Я восстановил заголовочный файл из mgr_api_JNI.java и скопировал объявление функции в файл .cpp, чтобы убедиться, что имя функции указано правильно. Но я все еще получаю ту же ошибку.

Убедиться, что у меня есть допустимые классы Java

Если я сделаю ls на java.class.path /mnt/c/Workspace/library/java/, я найду все классы Java при компиляции mgr_api_JNI.java.

Если я удаляю классы и пытаюсь запустить Java, то Java жалуется, что не может найти классы.

Выполнение grep -r createInstance на java.class.path /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?

Ответы [ 2 ]

1 голос
/ 08 июня 2019

Это ваша ошибка

У вас есть библиотечный файл: /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

Makefile

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.

0 голосов
/ 08 июня 2019

Проблема возникает, когда среда выполнения не может загрузить соответствующую встроенную функцию в общей библиотеке.

Таким образом, в этих случаях всегда полезно заново создать файл заголовка, чтобы проверить правильность имени функции.

В этом случае название вашей функции выглядит подозрительно

Java_com_1mgr_1api_1JNI_createInstance

Сгенерированное имя использует _1 для кодирования буквального подчеркивания в имени класса / функции Java. Итак, исходя из имени класса mgr_api_JNI имя должно быть

Java_com_mgr_1api_1JNI_createInstance

вместо этого. то есть первый _1 - это просто подчеркивание _, и это также то, что я вижу при создании файла заголовка для класса с таким именем.

...