使用 JNA 加载多个依赖库

Load multiple dependent libraries with JNA

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

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

假设我有图书馆 foo 和图书馆 barbar 依赖于 foo;它还依赖于 baz,我们 not 与 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 不在您的库加载路径中时才需要调用 System.load/loadLibraryPATH/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");
}

正在从 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:加载共享库时出错libdependency.so:没有那个文件或目录

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

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 中的其他库。

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

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

假设您得到一组 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临时文件夹中(使用此答案中的元素:(或相关答案)应该可以解决问题)

  • 告诉 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 
        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  
        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(为完整起见,在此处复制;摘自 )

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 :
 * 
 */
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();
    }

}