【问题标题】:Load multiple dependent libraries with JNA使用 JNA 加载多个依赖库
【发布时间】:2015-12-13 02:42:16
【问题描述】:

在 JNA 中有没有办法用 Java 加载多个依赖库?

我通常使用Native.loadLibrary(...) 来加载一个DLL。但我猜它不是这样工作的,因为我将此函数调用分配给实例成员。

【问题讨论】:

  • 多个原生库或来自同一个原生库的多个 JNA 库映射?
  • 多个原生库。它们完全不同。其中一个 DLL 以某种方式依赖于另一个。(我从 Dependency Walker 得到这个)
  • 只要确保您首先通过 JNA 或 System.loadLibrary() 加载一个没有依赖于另一个的。
  • 这其实就是问题所在。当我尝试调用某些函数时,使用 System.loadLibrary() 加载 DLL 会导致 UnsatisfiedLinkError。而且我不知道如何使用 JNA 加载多个库,因为我不允许使用静态初始化程序。还是我完全错了?

标签: java dll jna


【解决方案1】:

假设我有库 foo 和库 barbar 依赖于 foo;它还依赖于baz,我们不是与JNA映射:

public class Foo {
    public static final boolean LOADED;
    static {
        Native.register("foo");
        LOADED = true;
    }
    public static native void call_foo();
}

public class Bar {
    static {
        // Reference "Foo" so that it is loaded first
        if (Foo.LOADED) {
            System.loadLibrary("baz");
            // Or System.load("/path/to/libbaz.so")
            Native.register("bar");
        }
    }
    public static native void call_bar();
}

只有当baz 既不在您的库加载路径(PATH/LD_LIBRARY_PATH,分别适用于 windows/linux)也不在与 bar 相同的目录(windows仅)。

编辑

您也可以通过接口映射来做到这一点:

public interface Foo extends Library {
    Foo INSTANCE = (Foo)Native.loadLibrary("foo");
}
public interface Bar extends Library {
    // Reference Foo prior to instantiating Bar, just be sure
    // to reference the Foo class prior to creating the Bar instance
    Foo FOO = Foo.INSTANCE;
    Bar INSTANCE = (Bar)Native.loadLibrary("bar");
}

【讨论】:

  • 非常感谢!它比我想象的要容易。
  • 所以这假设 Bar 依赖于 Foo?如果它们都相互依赖,有没有办法加载它们?
  • 先弄清楚如何使用系统调用,然后在 JNA 中进行这些调用。
【解决方案2】:

从 JAR 资源中使用 JNA 加载 lib 瞬态依赖项。

我的资源文件夹 res:

res/
`-- linux-x86-64
    |-- libapi.so
    |-- libdependency.so

-

MyApiLibrary api = (MyApiLibrary) Native.loadLibrary("libapi.so", MyApiLibrary.class, options);

API 爆炸: 原因:java.lang.UnsatisfiedLinkError: Error loading shared library libdependency.so: No such file or directory

可以通过手动提前加载依赖来解决:

import com.sun.jna.Library;

Native.loadLibrary("libdependency.so", Library.class);
MyApiLibrary api = (MyApiLibrary) Native.loadLibrary("libapi.so", MyApiLibrary.class, options);

基本上,您必须自己手动反向构建依赖关系树。


我建议设置

java -Djna.debug_load=true -Djna.debug_load.jna=true

此外,将 jna.library.path 设置为 Resource 无效,因为 JNA 提取到文件系统,然后加载 lib。文件系统上的库不能访问 jar 中的其他库。

上下文类加载器类路径。部署的本机库可能是 安装在 ${os-prefix}/LIBRARY_FILENAME 下的类路径中,其中 ${os-prefix} 是由返回的 OS/Arch 前缀 Platform.getNativeLibraryResourcePrefix()。如果捆绑在 jar 文件中, 资源将被提取到 jna.tmpdir 进行加载,稍后 已移除(但仅当 jna.nounpack 为 false 或未设置时)。

Javadoc

RTFM 和快乐的编码。 JNA v.4.1.0

【讨论】:

    【解决方案3】:

    我处于类似情况,处理多平台和几个依赖库,但只需要加载一个。这是我的看法。

    假设您获得了一组 32/64 的 win/linux 库,其中包含依赖项。 假设您只需要对libapi 进行 JNA 绑定

    您需要像这样将它们组织到您的 jar 中:

    linux-x86-64
        |-- libapi.so
        |-- libdependency.so
    linux-x86
        |-- libapi.so
        |-- libdependency.so
    win32-x86-64
        |-- libapi.dll
        |-- libdependency.dll
    win32-x86
        |-- libapi.dll
        |-- libdependency.dll
    

    你可以:

    • 确定是否从 JAR 文件执行(避免从您喜欢的 IDE 执行时执行操作;请参阅How to get the path of a running JAR file?

    • 使用 JNA 确定您当前的执行平台

    • 提取所有适当的库文件到java临时文件夹(使用这个答案中的元素:https://stackoverflow.com/a/58318009/7237062(或相关答案)应该可以解决问题)

    • 告诉 JNA 查看新创建的临时文件夹

    • 瞧!

    • 代码示例中缺少的是应用程序关闭时的目录清理,但我把它留给你

    主要部分应该是这样的:

    MainClass.java

    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.nio.file.Files;
    import java.nio.file.InvalidPathException;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.nio.file.StandardCopyOption;
    import java.util.Optional;
    import java.util.jar.JarFile;
    
    import com.sun.jna.Platform;
    
    public class MainClass {
        private static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
        private static final String TEMP_DIR = System.getProperty(JAVA_IO_TMPDIR);
        private static final String JNA_LIBRARY_PATH = "jna.library.path";
    
        public static void main(String[] args) {
            // ...
            // path management here maybe suboptimal ... feel free to improve
            // from https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file
            URL current_jar_dir = Overview.class.getProtectionDomain().getCodeSource().getLocation();
            Path jar_path = Paths.get(current_jar_dir.toURI());
            String folderContainingJar = jar_path.getParent().toString();
    
            ResourceCopy r = new ResourceCopy(); // class from https://stackoverflow.com/a/58318009/7237062 
            Optional<JarFile> jar = r.jar(MainClass.class);
            if (jar.isPresent()) {
                try {
                System.out.println("JAR detected");
                File target_dir = new File(TEMP_DIR);
                System.out.println(String.format("Trying copy from %s %s to %s", jar.get().getName(), Platform.RESOURCE_PREFIX, target_dir));
                // perform dir copy
                r.copyResourceDirectory(jar.get(), Platform.RESOURCE_PREFIX, target_dir);
                // add created folders to JNA lib loading path
                System.setProperty(JNA_LIBRARY_PATH, target_dir.getCanonicalPath().toString());             
                } catch(Exception e) {
                    e.printStackTrace(); // TODO: handle exception ?
                }
            } else {
                System.out.println("NO JAR");
            }
            // ...
        }
    

    ResourceCopy.java(为了完整起见复制到这里;取自https://stackoverflow.com/a/58318009

    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.URL;
    import java.nio.file.Files;
    import java.util.Enumeration;
    import java.util.Optional;
    import java.util.jar.JarEntry;
    import java.util.jar.JarFile;
    /**
     * A helper to copy resources from a JAR file into a directory. source :
     * https://stackoverflow.com/a/58318009
     */
    public final class ResourceCopy {
    
        /**
         * URI prefix for JAR files.
         */
        private static final String JAR_URI_PREFIX = "jar:file:";
    
        /**
         * The default buffer size.
         */
        private static final int BUFFER_SIZE = 8 * 1024;
    
        /**
         * Copies a set of resources into a temporal directory, optionally
         * preserving the paths of the resources.
         * 
         * @param preserve
         *            Whether the files should be placed directly in the directory
         *            or the source path should be kept
         * @param paths
         *            The paths to the resources
         * @return The temporal directory
         * @throws IOException
         *             If there is an I/O error
         */
        public File copyResourcesToTempDir(final boolean preserve, final String... paths) throws IOException {
            final File parent = new File(System.getProperty("java.io.tmpdir"));
            File directory;
            do {
                directory = new File(parent, String.valueOf(System.nanoTime()));
            } while (!directory.mkdir());
            return this.copyResourcesToDir(directory, preserve, paths);
        }
    
        /**
         * Copies a set of resources into a directory, preserving the paths and
         * names of the resources.
         * 
         * @param directory
         *            The target directory
         * @param preserve
         *            Whether the files should be placed directly in the directory
         *            or the source path should be kept
         * @param paths
         *            The paths to the resources
         * @return The temporal directory
         * @throws IOException
         *             If there is an I/O error
         */
        public File copyResourcesToDir(final File directory, final boolean preserve, final String... paths)
                throws IOException {
            for (final String path : paths) {
                final File target;
                if (preserve) {
                    target = new File(directory, path);
                    target.getParentFile().mkdirs();
                } else {
                    target = new File(directory, new File(path).getName());
                }
                this.writeToFile(Thread.currentThread().getContextClassLoader().getResourceAsStream(path), target);
            }
            return directory;
        }
    
        /**
         * Copies a resource directory from inside a JAR file to a target directory.
         * 
         * @param source
         *            The JAR file
         * @param path
         *            The path to the directory inside the JAR file
         * @param target
         *            The target directory
         * @throws IOException
         *             If there is an I/O error
         */
        public void copyResourceDirectory(final JarFile source, final String path, final File target) throws IOException {
            final Enumeration<JarEntry> entries = source.entries();
            final String newpath = String.format("%s/", path);
            while (entries.hasMoreElements()) {
                final JarEntry entry = entries.nextElement();
                if (entry.getName().startsWith(newpath) && !entry.isDirectory()) {
                    final File dest = new File(target, entry.getName().substring(newpath.length()));
                    final File parent = dest.getParentFile();
                    if (parent != null) {
                        parent.mkdirs();
                    }
                    this.writeToFile(source.getInputStream(entry), dest);
                }
            }
        }
    
        /**
         * The JAR file containing the given class.
         * 
         * @param clazz
         *            The class
         * @return The JAR file or null
         * @throws IOException
         *             If there is an I/O error
         */
        public Optional<JarFile> jar(final Class<?> clazz) throws IOException {
            final String path = String.format("/%s.class", clazz.getName().replace('.', '/'));
            final URL url = clazz.getResource(path);
            Optional<JarFile> optional = Optional.empty();
            if (url != null) {
                final String jar = url.toString();
                final int bang = jar.indexOf('!');
                if (jar.startsWith(ResourceCopy.JAR_URI_PREFIX) && bang != -1) {
                    optional = Optional.of(new JarFile(jar.substring(ResourceCopy.JAR_URI_PREFIX.length(), bang)));
                }
            }
            return optional;
        }
    
        /**
         * Writes an input stream to a file.
         * 
         * @param input
         *            The input stream
         * @param target
         *            The target file
         * @throws IOException
         *             If there is an I/O error
         */
        private void writeToFile(final InputStream input, final File target) throws IOException {
            final OutputStream output = Files.newOutputStream(target.toPath());
            final byte[] buffer = new byte[ResourceCopy.BUFFER_SIZE];
            int length = input.read(buffer);
            while (length > 0) {
                output.write(buffer, 0, length);
                length = input.read(buffer);
            }
            input.close();
            output.close();
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2014-10-24
      • 2011-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多