使用 JNA 加载多个依赖库
Load multiple dependent libraries with JNA
JNA有没有办法用Java加载多个依赖库?
我通常使用Native.loadLibrary(...)
加载一个DLL。但我猜它不是这样工作的,因为我将这个函数调用分配给了实例成员。
假设我有图书馆 foo
和图书馆 bar
。 bar
依赖于 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/loadLibrary
(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");
}
正在从 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).
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();
}
}
JNA有没有办法用Java加载多个依赖库?
我通常使用Native.loadLibrary(...)
加载一个DLL。但我猜它不是这样工作的,因为我将这个函数调用分配给了实例成员。
假设我有图书馆 foo
和图书馆 bar
。 bar
依赖于 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/loadLibrary
(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");
}
正在从 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).
RTFM 和愉快的编码。 JNA v.4.1.0
我遇到了类似的情况,处理多平台和多个依赖库,但只需要加载一个。这是我的看法。
假设您得到一组 32/64 win/linux 具有依赖项的库。
假设您只需要为 libapi
您需要像这样将它们组织到您的 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();
}
}