如何在 JAR 中捆绑本机库和 JNI 库?

问题图书馆是 东京内阁

我希望将本机库、 JNI 库和所有 JavaAPI 类放在一个 JAR 文件中,以避免重新分发的麻烦。

似乎有 在 GitHub 上的一次尝试,但是

  1. 它不包括实际的本机库,只包括 JNI 库。
  2. 它似乎是特定于 雷宁根的本地依赖项插件(它不能作为可再发布的插件工作)。

问题是,我是否可以将所有内容打包到一个 JAR 中并重新分发它? 如果可以,怎么做?

附注: 是的,我意识到它可能具有可移植性的含义。

98636 次浏览

You will probably have to unjar the native library to the local file system. As far as I know the bit of code that does the native loading looks at the file system.

这段代码应该可以帮助你开始(我有一段时间没有看它了,这是为了一个不同的目的,但应该可以起到作用,我现在很忙,但是如果你有问题,请留言,我会尽快回答)。

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;




public class FileUtils
{
public static String getFileName(final Class<?>  owner,
final String    name)
throws URISyntaxException,
ZipException,
IOException
{
String    fileName;
final URI uri;


try
{
final String external;
final String decoded;
final int    pos;


uri      = getResourceAsURI(owner.getPackage().getName().replaceAll("\\.", "/") + "/" + name, owner);
external = uri.toURL().toExternalForm();
decoded  = external; // URLDecoder.decode(external, "UTF-8");
pos      = decoded.indexOf(":/");
fileName = decoded.substring(pos + 1);
}
catch(final FileNotFoundException ex)
{
fileName = null;
}


if(fileName == null || !(new File(fileName).exists()))
{
fileName = getFileNameX(owner, name);
}


return (fileName);
}


private static String getFileNameX(final Class<?> clazz, final String name)
throws UnsupportedEncodingException
{
final URL    url;
final String fileName;


url = clazz.getResource(name);


if(url == null)
{
fileName = name;
}
else
{
final String decoded;
final int    pos;


decoded  = URLDecoder.decode(url.toExternalForm(), "UTF-8");
pos      = decoded.indexOf(":/");
fileName = decoded.substring(pos + 1);
}


return (fileName);
}


private static URI getResourceAsURI(final String    resourceName,
final Class<?> clazz)
throws URISyntaxException,
ZipException,
IOException
{
final URI uri;
final URI resourceURI;


uri         = getJarURI(clazz);
resourceURI = getFile(uri, resourceName);


return (resourceURI);
}


private static URI getJarURI(final Class<?> clazz)
throws URISyntaxException
{
final ProtectionDomain domain;
final CodeSource       source;
final URL              url;
final URI              uri;


domain = clazz.getProtectionDomain();
source = domain.getCodeSource();
url    = source.getLocation();
uri    = url.toURI();


return (uri);
}


private static URI getFile(final URI    where,
final String fileName)
throws ZipException,
IOException
{
final File location;
final URI  fileURI;


location = new File(where);


// not in a JAR, just return the path on disk
if(location.isDirectory())
{
fileURI = URI.create(where.toString() + fileName);
}
else
{
final ZipFile zipFile;


zipFile = new ZipFile(location);


try
{
fileURI = extract(zipFile, fileName);
}
finally
{
zipFile.close();
}
}


return (fileURI);
}


private static URI extract(final ZipFile zipFile,
final String  fileName)
throws IOException
{
final File         tempFile;
final ZipEntry     entry;
final InputStream  zipStream;
OutputStream       fileStream;


tempFile = File.createTempFile(fileName.replace("/", ""), Long.toString(System.currentTimeMillis()));
tempFile.deleteOnExit();
entry    = zipFile.getEntry(fileName);


if(entry == null)
{
throw new FileNotFoundException("cannot find file: " + fileName + " in archive: " + zipFile.getName());
}


zipStream  = zipFile.getInputStream(entry);
fileStream = null;


try
{
final byte[] buf;
int          i;


fileStream = new FileOutputStream(tempFile);
buf        = new byte[1024];
i          = 0;


while((i = zipStream.read(buf)) != -1)
{
fileStream.write(buf, 0, i);
}
}
finally
{
close(zipStream);
close(fileStream);
}


return (tempFile.toURI());
}


private static void close(final Closeable stream)
{
if(stream != null)
{
try
{
stream.close();
}
catch(final IOException ex)
{
ex.printStackTrace();
}
}
}
}

看看 一罐。它将用一个专门的类装入器将您的应用程序封装在一个 jar 文件中,该类装入器处理“ jar 中的 jar”和其他事情。

It 处理本机(JNI)库 by unpacking them to a temporary working folder as required.

(免责声明: 我从未使用过 One-JAR,目前还不需要,只是将其加入书签以备不时之需。)

JarClassLoader 是一个类装入器,用于从单个巨型 JAR 和巨型 JAR 内部的 JAR 装入类、本机库和资源。

可以为一个或多个平台创建一个包含所有依赖项(包括本机 JNI 库)的单个 JAR 文件。基本机制是使用 System.load (File)加载库,而不是使用搜索 java.library. path 系统属性的典型 System.loadLibrary (String)。这种方法使得安装变得更加简单,因为用户不必在他的系统上安装 JNI 库,但是,代价是不支持所有平台,因为一个平台的特定库可能不包含在单个 JAR 文件中。

程序如下:

  • include the native JNI libraries in the JAR file at a location specific to the platform, for example at NATIVE/${os.arch}/${os.name}/libname.lib
  • 在主类的静态初始值设定项中创建代码,以
    • 计算当前的 os.arch 和 os.name
    • 使用 Class.getResource (String)在预定义的位置查找 JAR 文件中的库
    • 如果存在,将其解压缩到一个临时文件中,并用 System.load (File)加载它。

我为 jzmq 添加了这样的功能,这是 ZeroMQ (无耻的插件)的 Java 绑定。代码可以找到 给你。Jzmq 代码使用混合解决方案,这样,如果无法加载嵌入式库,代码将恢复到沿着 java.library. path 搜索 JNI 库。

Https://www.adamheinrich.com/blog/2012/12/how-to-load-native-jni-library-from-jar/

is great article, which solves my issue ..

在我的例子中,我得到了以下初始化库的代码:

static {
try {
System.loadLibrary("crypt"); // used for tests. This library in classpath only
} catch (UnsatisfiedLinkError e) {
try {
NativeUtils.loadLibraryFromJar("/natives/crypt.dll"); // during runtime. .DLL within .JAR
} catch (IOException e1) {
throw new RuntimeException(e1);
}
}
}

1)将本机库作为资源包含到您的 JAR 中。例如,使用 Maven 或 Gradle,以及标准的项目布局,将本机库放入 main/resources目录。

2)在 Java 类的静态初始化程序中,与这个库相关的代码放置如下:

String libName = "myNativeLib.so"; // The name of the file in resources/ dir
URL url = MyClass.class.getResource("/" + libName);
File tmpDir = Files.createTempDirectory("my-native-lib").toFile();
tmpDir.deleteOnExit();
File nativeLibTmpFile = new File(tmpDir, libName);
nativeLibTmpFile.deleteOnExit();
try (InputStream in = url.openStream()) {
Files.copy(in, nativeLibTmpFile.toPath());
}
System.load(nativeLibTmpFile.getAbsolutePath());

科特林的解决方案:

  • build.gradle.dsl: 将 kotlin 运行时(kotlin-stdlib-1.4.0.JAR)和本机库(library _ kotlin. dylib)复制到 JAR

    tasks.withType<Jar> {
    manifest {
    attributes("Main-Class" to "MainKt")
    }
    
    
    val libs = setOf("kotlin-stdlib-1.4.0.jar")
    
    
    from(configurations.runtimeClasspath.get()
    .filter { it.name in libs }
    .map { zipTree(it) })
    
    
    from("librust_kotlin.dylib")
    }
    
  • main method: copy library to a temporary file to load it using absolute path

     with(createTempFile()) {
    deleteOnExit()
    val bytes = My::class.java.getResource("librust_kotlin.dylib")
    .readBytes()
    
    
    outputStream().write(bytes)
    System.load(path)
    }