简体   繁体   English

Java:加载具有依赖项的共享库

[英]Java: load shared libraries with dependencies

I am wrapping a shared library (written in C) with Java using JNA.我正在使用 JNA 用 Ja​​va 包装一个共享库(用 C 编写)。 The shared library is written internally, but that library uses functions from another external library, which again depends another external library.共享库是在内部编写的,但该库使用来自另一个外部库的函数,该库又依赖于另一个外部库。 So the situation is something like this:所以情况是这样的:

ext1 <- ext2 <- internal ext1 <- ext2 <- 内部

Ie the internal uses external library ext2 which again uses external library ext1.即内部使用外部库 ext2,它再次使用外部库 ext1。 What I have tried is:我尝试过的是:

System.loadLibrary("ext1");
System.loadLibrary("ext2");
NativeLIbrary.loadLibrary("internal",xxx.class);  

This approach fails with "UnresolvedException" when loading the library "ext2";加载库“ext2”时,此方法失败并显示“UnresolvedException”; the linker complains about symbols which are indeed present in the library "ext1".链接器抱怨确实存在于库“ext1”中的符号。 So it semmes that the System.loadLibrary() function does not make the symbols from "ext1" globally available?因此,System.loadLibrary() 函数似乎没有使“ext1”中的符号全局可用? When using the stdlib function dlopen() as:使用 stdlib 函数 dlopen() 时:

handle = dlopen( lib_name , RTLD_GLOBAL );

All the symbols found in @lib_name will be available for symbol resolution in subsequent loads;在@lib_name 中找到的所有符号都可用于后续加载中的符号解析; I guess what I would like was something similar for the java variety System.loadLibrary()?我想我想要的是类似于 java 品种 System.loadLibrary() 的东西?

Regards - Joakim Hove问候 - 乔金霍夫

It's an old question, but I've found an acceptable solution, which should also be portable, and I thought I should post an answer.这是一个老问题,但我找到了一个可以接受的解决方案,它也应该是可移植的,我想我应该发布一个答案。 The solution is to use JNA 's NativeLibrary#getInstance() , because on Linux this will pass RTLD_GLOBAL to dlopen() (and on Windows this is not needed).解决方案是使用JNANativeLibrary#getInstance() ,因为在 Linux 上这会将RTLD_GLOBAL传递给dlopen() (而在 Windows 上则不需要)。

Now, if you are using this library to implement a Java native method, you will also need to call System.load() (or Sysem.loadLibrary() ) on the same library, after calling NativeLibrary#getInstance() .现在,如果您使用这个库来实现 Java native方法,您还需要在调用NativeLibrary#getInstance()之后在同一个库上调用System.load() (或Sysem.loadLibrary() NativeLibrary#getInstance()

First, a link to a JNA bug: JNA-61首先,一个指向 JNA 错误的链接: JNA-61

A comment in there says that basically one should load dependencies before the actual library to use from within JNA, not the standard Java way.那里的一条评论说,基本上应该在 JNA 中使用实际库之前加载依赖项,而不是标准的 Java 方式。 I'll just copy-paste my code, it's a typical scenario:我只是复制粘贴我的代码,这是一个典型的场景:

String libPath =
        "/path/to/my/lib:" + // My library file
        "/usr/local/lib:" +  // Libraries lept and tesseract
        System.getProperty("java.library.path");

System.setProperty("jna.library.path", libPath);

NativeLibrary.getInstance("lept");
NativeLibrary.getInstance("tesseract");
OcrTesseractInterf ocrInstance = (OcrTesseractInterf)
        Native.loadLibrary(OcrTesseractInterf.JNA_LIBRARY_NAME, OcrTesseractInterf.class);

I've written a small library to provide OCR capability to my Java app using Tesseract.我编写了一个小型库,使用 Tesseract 为我的 Java 应用程序提供 OCR 功能。 Tesseract dependes on Leptonica, so to use my library, I need to load libraries lept and tesseract first. Tesseract 依赖于 Leptonica,所以要使用我的库,我需要先加载库lepttesseract Loading the libraries with the standard means (System.load() and System.loadLibrary()) doesn't do the trick, neither does setting properties jna.library.path or java.library.path .使用标准方法(System.load() 和 System.loadLibrary())加载库并不能解决问题,设置属性jna.library.pathjava.library.path也不行。 Obviously, JNA likes to load libraries its own way.显然,JNA 喜欢以自己的方式加载库。

This works for me in Linux, I guess if one sets the proper library path, this should work in other OSs as well.这在 Linux 中对我有用,我想如果设置了正确的库路径,这也应该适用于其他操作系统。

There is yet another solution for that.还有另一种解决方案。 You can dlopen directly inside JNI code, like this:您可以直接在 JNI 代码中 dlopen,如下所示:

void loadLibrary() {
  if(handle == NULL) {
    handle = dlopen("libname.so", RTLD_LAZY | RTLD_GLOBAL);
    if (!handle) {
      fprintf(stderr, "%s\n", dlerror());
      exit(EXIT_FAILURE);
    }
  }
}

...
...

loadLibrary();

This way, you will open library with RTLD_GLOBAL.这样,您将使用 RTLD_GLOBAL 打开库。

You can find detailed description here: http://www.owsiak.org/?p=3640您可以在此处找到详细说明: http : //www.owsiak.org/?p=3640

OK;好的;

I have found an acceptable solution in the end, but not without significant amount of hoops.我最终找到了一个可以接受的解决方案,但并非没有大量的箍。 What I do is我做的是

  1. Use the normal JNA mechanism to map the dlopen() function from the dynamic linking library (libdl.so).使用普通的 JNA 机制从动态链接库 (libdl.so) 映射 dlopen() 函数。
  2. Use the dlopen() function mapped in with JNA to load external libraries "ext1" and "ext2" with the option RTLD_GLOBAL set.使用与 JNA 映射的 dlopen() 函数加载带有选项 RTLD_GLOBAL 设置的外部库“ext1”和“ext2”。

It actually seems to work :-)它实际上似乎有效:-)

As described at http://www.owsiak.org/?p=3640 , an easy but crude solution on Linux is to use LD_PRELOAD .http://www.owsiak.org/?p=3640 所述,Linux 上一个简单但粗略的解决方案是使用LD_PRELOAD

If that's not acceptable, then I'd recommend the answer by Oo.oO: dlopen the library with RTLD_GLOBAL within JNI code.如果这是不可接受的,那么我会推荐 Oo.oO 的答案:在 JNI 代码中使用RTLD_GLOBAL dlopen库。

Try this, add this function to your code.试试这个,将此功能添加到您的代码中。 Call it before you load your dlls.在加载 dll 之前调用它。 For the parameter, use the location of your dlls.对于参数,请使用 dll 的位置。


    public boolean addDllLocationToPath(String dllLocation)
    {
        try
        {
            System.setProperty("java.library.path", System.getProperty("java.library.path") + ";" + dllLocation);
            Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
            fieldSysPath.setAccessible(true);
            fieldSysPath.set(null, null);
        }
        catch (Exception e)
        {
            System.err.println("Could not modify path");
            return false;
        }
        return true;
    }
}

In order to fix your issue you can use this package: https://github.com/victor-paltz/global-load-library .为了解决您的问题,您可以使用此包: https : //github.com/victor-paltz/global-load-library It loads the libraries directly with the RTLD_GLOBAL flag.它使用 RTLD_GLOBAL 标志直接加载库。

Here is an example:下面是一个例子:

import com.globalload.LibraryLoaderJNI;

public class HelloWorldJNI {
 
    static {
        // Loaded with RTLD_GLOBAL flag
        try {
            LibraryLoaderJNI.loadLibrary("/path/to/my_native_lib_A");
        } catch (UnsatisfiedLinkError e) {
            System.Println("Couldn't load my_native_lib_A");
            System.Println(e.getMessage());
            e.printStackTrace();
        }

        // Not loaded with RTLD_GLOBAL flag
        try {
            System.load("/path/to/my_native_lib_B");
        } catch (UnsatisfiedLinkError e) {
            System.Println("Couldn't load my_native_lib_B");
            System.Println(e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        new HelloWorldJNI().sayHello();
    }
 
    private native void sayHello();
}

It is using the same dlopen() trick as the previous answers, but it is packaged in a standalone code.它使用与先前答案相同的 dlopen() 技巧,但它打包在独立代码中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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