有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java使用JNA加载多个依赖库

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

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


共 (3) 个答案

  1. # 1 楼答案

    我也遇到过类似的情况,处理多平台和几个依赖库,但只需要加载一个。这是我的照片

    假设您得到一组32/64 win/linux库,其中包含依赖项。 假设您只需要为libapi拥有一个JNA绑定

    你需要像这样将它们组织到你的罐子中:

    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 temp文件夹中(使用此答案中的元素:https://stackoverflow.com/a/58318009/7237062(或相关答案)应该可以做到这一点)

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

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

    主要部分应该是这样的:

    主课。爪哇

    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");
            }
            // ...
        }
    

    资源拷贝。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();
        }
    
    }
    
  2. # 2 楼答案

    假设我有库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)时,才需要调用System.load/loadLibrary

    编辑

    您还可以通过接口映射来实现这一点:

    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");
    }
    
  3. # 3 楼答案

    从JAR资源加载带有JNA的库临时依赖项

    我的资源文件夹res:

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

    -

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

    API爆炸: 原因:java。lang.UnsatifiedLinkError:加载共享库libdependency时出错。所以:没有这样的文件或目录

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

    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。图书馆到资源的路径不起作用,因为JNA提取到文件系统,然后加载lib。文件系统上的Lib无法访问jar中的其他Lib

    Context class loader classpath. Deployed native libraries may be installed on the classpath under ${os-prefix}/LIBRARY_FILENAME, where ${os-prefix} is the OS/Arch prefix returned by Platform.getNativeLibraryResourcePrefix(). If bundled in a jar file, the resource will be extracted to jna.tmpdir for loading, and later removed (but only if jna.nounpack is false or not set).

    Javadoc

    RTFM和快乐编码。JNA v.4.1.0