Java 从类路径加载本机库
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23189776/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Load Native Library from Class path
提问by mschrimpf
I have a project setup that follows the Standard Directory Layout(not using Maven though):
我有一个遵循标准目录布局的项目设置(虽然不使用 Maven):
src/main
| java
| resources
| library.dll
Native DLLs are located in the resources folder and sources in the java folder. The resources folder is a member of the Java class path.
本机 DLL 位于资源文件夹中,源文件位于 java 文件夹中。资源文件夹是 Java 类路径的成员。
I would now like to load a DLL without having to set the JRE -Djava.library.path
option or setting the PATH
variable so the resulting jar file can be started with a simple double click.
我现在想加载一个 DLL,而不必设置 JRE-Djava.library.path
选项或设置PATH
变量,这样可以通过简单的双击启动生成的 jar 文件。
Is it possible to add the resource folder to the library search path without having to do additional configuration when running the jar file?
E.g. with a setting similar to the Class-Path
in the Manifest?
运行jar文件时是否可以将资源文件夹添加到库搜索路径中而无需进行额外配置?例如,使用类似于Class-Path
清单中的设置?
采纳答案by Java42
There is an old-time hack that still works as of today ( 1.7.0_55 & 1.8.0_05 ) to allow you to do to a runtime update using System.setProperty() and have the JVM notice the change. In general, you do the following:
有一个旧的 hack 直到今天仍然有效( 1.7.0_55 & 1.8.0_05 ),允许您使用 System.setProperty() 进行运行时更新并让 JVM 注意到更改。通常,您执行以下操作:
System.setProperty("java.library.path", yourPath);
Field sysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
sysPath.setAccessible( true );
sysPath.set( null, null );
System.loadLibrary(libraryName);
Google java sys_pathsand look for articles about this technique.
谷歌java sys_paths并查找有关此技术的文章。
Take care to handle errors/exceptions. Restore original paths as needed.
注意处理错误/异常。根据需要恢复原始路径。
回答by Alex Barker
I have done something very similar in JNativeHook, you will need some helper code to determine the correct arch and os to load code for (See NativeSystemClass)
我在JNativeHook做了一些非常相似的事情,你需要一些帮助代码来确定正确的 arch 和 os 来加载代码(参见NativeSystem类)
// The following code covered under the GNU Lesser General Public License v3.
static {
String libName = System.getProperty("jnativehook.lib.name", "JNativeHook");
try {
// Try to load the native library assuming the java.library.path was
// set correctly at launch.
System.loadLibrary(libName);
}
catch (UnsatisfiedLinkError linkError) {
// Get the package name for the GlobalScreen.
String basePackage = GlobalScreen.class.getPackage().getName().replace('.', '/');
// Compile the resource path for the native lib.
StringBuilder libResourcePath = new StringBuilder("/");
libResourcePath.append(basePackage).append("/lib/");
libResourcePath.append(NativeSystem.getFamily()).append('/');
libResourcePath.append(NativeSystem.getArchitecture()).append('/');
// Get what the system "thinks" the library name should be.
String libNativeName = System.mapLibraryName(libName);
// Hack for OS X JRE 1.6 and earlier.
libNativeName = libNativeName.replaceAll("\.jnilib$", "\.dylib");
// Slice up the library name.
int i = libNativeName.lastIndexOf('.');
String libNativePrefix = libNativeName.substring(0, i) + '-';
String libNativeSuffix = libNativeName.substring(i);
String libNativeVersion = null;
// This may return null in some circumstances.
InputStream libInputStream = GlobalScreen.class.getResourceAsStream(libResourcePath.toString().toLowerCase(Locale.English) + libNativeName);
if (libInputStream != null) {
try {
// Try and load the Jar manifest as a resource stream.
URL jarFile = GlobalScreen.class.getProtectionDomain().getCodeSource().getLocation();
JarInputStream jarInputStream = new JarInputStream(jarFile.openStream());
// Try and extract a version string from the Manifest.
Manifest manifest = jarInputStream.getManifest();
if (manifest != null) {
Attributes attributes = manifest.getAttributes(basePackage);
if (attributes != null) {
String version = attributes.getValue("Specification-Version");
String revision = attributes.getValue("Implementation-Version");
libNativeVersion = version + '.' + revision;
}
else {
Logger.getLogger(GlobalScreen.class.getPackage().getName()).warning("Invalid library manifest!\n");
}
}
else {
Logger.getLogger(GlobalScreen.class.getPackage().getName()).warning("Cannot find library manifest!\n");
}
}
catch (IOException e) {
Logger.getLogger(GlobalScreen.class.getPackage().getName()).severe(e.getMessage());
}
try {
// The temp file for this instance of the library.
File libFile;
// If we were unable to extract a library version from the manifest.
if (libNativeVersion != null) {
libFile = new File(System.getProperty("java.io.tmpdir"), libNativePrefix + libNativeVersion + libNativeSuffix);
}
else {
libFile = File.createTempFile(libNativePrefix, libNativeSuffix);
}
byte[] buffer = new byte[4 * 1024];
int size;
// Check and see if a copy of the native lib already exists.
FileOutputStream libOutputStream = new FileOutputStream(libFile);
// Setup a digest...
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
DigestInputStream digestInputStream = new DigestInputStream(libInputStream, sha1);
// Read from the digest stream and write to the file steam.
while ((size = digestInputStream.read(buffer)) != -1) {
libOutputStream.write(buffer, 0, size);
}
// Close all the streams.
digestInputStream.close();
libInputStream.close();
libOutputStream.close();
// Convert the digest from byte[] to hex string.
String sha1Sum = new BigInteger(1, sha1.digest()).toString(16).toUpperCase();
if (libNativeVersion == null) {
// Use the sha1 sum as a version finger print.
libNativeVersion = sha1Sum;
// Better late than never.
File newFile = new File(System.getProperty("java.io.tmpdir"), libNativePrefix + libNativeVersion + libNativeSuffix);
if (libFile.renameTo(newFile)) {
libFile = newFile;
}
}
// Set the library version property.
System.setProperty("jnativehook.lib.version", libNativeVersion);
// Load the native library.
System.load(libFile.getPath());
Logger.getLogger(GlobalScreen.class.getPackage().getName())
.info("Library extracted successfully: " + libFile.getPath() + " (0x" + sha1Sum + ").\n");
}
catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
else {
Logger.getLogger(GlobalScreen.class.getPackage().getName())
.severe("Unable to extract the native library " + libResourcePath.toString().toLowerCase(Locale.English) + libNativeName + "!\n");
throw new UnsatisfiedLinkError();
}
}
}
回答by MrSnowflake
I wanted to offer an alternative solution, because @Java42's answer is not portable (JVM dependant) and doesn't work starting from Oracle's/OpenJDK's Java 12.
我想提供一个替代解决方案,因为@Java42 的答案不可移植(依赖于 JVM)并且从 Oracle 的/OpenJDK 的 Java 12 开始不起作用。
You should use your own custom ClassLoader
implementation. In the ClassLoader
there's a method findLibary(String libname)
. This method returns the full path to the library to load. This can be used to load libraries in arbitrary places.
您应该使用自己的自定义ClassLoader
实现。在ClassLoader
有一个方法findLibary(String libname)
。此方法返回要加载的库的完整路径。这可用于在任意位置加载库。
public class MyClassLoader extends ClassLoader {
@Override
protected String findLibrary(String libname) {
return "/actual/path/to/library.so";
// or return null if unknown, then the path will be searched
}
}
Next step is to have you custom ClassLoader
used by the JVM. So set it very soon in your code as the current threads contextClassLoader
:
下一步是让ClassLoader
JVM自定义使用。因此,请尽快在您的代码中将其设置为当前线程contextClassLoader
:
public static void main(String[] args) {
Thread.currentThread().setContextClassLoader(new StarterClassLoader());
// ... your code
}