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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2937406/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 14:39:03  来源:igfitidea点击:

How to bundle a native library and a JNI library inside a JAR?

javajarclojurejava-native-interfacetokyo-cabinet

提问by Alex B

The library in question is Tokyo Cabinet.

有问题的图书馆是东京内阁

I want is to have the native library, JNI library, and all Java API classes in one JAR file to avoid redistribution headaches.

我想要在一个 JAR 文件中包含本机库、JNI 库和所有 Java API 类,以避免重新分发的麻烦。

There seems to be an attempt at this at GitHub, but

似乎在 GitHub 上进行了尝试,但是

  1. It does not include the actual native library, only JNI library.
  2. It seems to be specific to Leiningen's native dependencies plugin (it won't work as a redistributable).
  1. 它不包括实际的本机库,仅包括 JNI 库。
  2. 它似乎特定于Leiningen的本机依赖项插件(它不能作为可再发行组件工作)。

The question is, can I bundle everything in one JAR and redistribute it? If yes, how?

问题是,我可以将所有内容捆绑在一个 JAR 中并重新分发吗?如果是,如何?

P.S.: Yes, I realize it may have portability implications.

PS:是的,我意识到它可能会影响可移植性。

采纳答案by kwo

It is possible to create a single JAR file with all dependencies including the native JNI libraries for one or more platforms. The basic mechanism is to use System.load(File) to load the library instead of the typical System.loadLibrary(String) which searches the java.library.path system property. This method makes installation much simpler as the user does not have to install the JNI library on his system, at the expense, however, that all platforms might not be supported as the specific library for a platform might not be included in the single JAR file.

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

The process is as follows:

过程如下:

  • 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
  • create code in a static initializier of the main class to
    • calc the current os.arch and os.name
    • look for the library in the JAR file at the predefined location using Class.getResource(String)
    • if it exists, extract it to a temp file and load it with System.load(File).
  • 在特定于平台的位置的 JAR 文件中包含本机 JNI 库,例如在 NATIVE/${os.arch}/${os.name}/libname.lib
  • 在主类的静态初始值设定项中创建代码以
    • 计算当前的 os.arch 和 os.name
    • 使用 Class.getResource(String) 在预定义位置的 JAR 文件中查找库
    • 如果存在,将其解压缩到临时文件并使用 System.load(File) 加载它。

I added functionality to do this for jzmq, the Java bindings of ZeroMQ (shameless plug). The code can be found here. The jzmq code uses a hybrid solution so that if an embedded library cannot be loaded, the code will revert to searching for the JNI library along the java.library.path.

我添加了为 jzmq(ZeroMQ(无耻插件)的 Java 绑定)执行此操作的功能。代码可以在这里找到。jzmq 代码使用混合解决方案,因此如果无法加载嵌入式库,代码将恢复为沿 java.library.path 搜索 JNI 库。

回答by TofuBeer

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.

您可能必须将本机库解压到本地文件系统。据我所知,执行本机加载的代码位查看文件系统。

This code should help get you started (I haven't looked at it in a while, and it is for a different purpose but should do the trick, and I am pretty busy at the moment, but if you have questions just leave a comment and I'll answer as soon as I can).

这段代码应该可以帮助您入门(我有一段时间没有看过它,它的目的不同,但应该可以解决问题,而且我目前很忙,但如果您有任何疑问,请发表评论我会尽快回复)。

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

回答by Evan

Take a look at One-JAR. It will wrap your application up in a single jar file with a specialised class loader which handles "jars within jars" among other things.

看看One-JAR。它将您的应用程序包装在一个带有专门的类加载器的 jar 文件中,该类加载器处理“jars 中的 jars”等。

It handles native (JNI) librariesby unpacking them to a temporary working folder as required.

它通过根据需要将原生 (JNI) 库解压缩到临时工作文件夹来处理它们。

(Disclaimer: I've never used One-JAR, haven't needed to as yet, just had it bookmarked for a rainy day.)

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

回答by tekumara

JarClassLoaderis a class loader to load classes, native libraries and resources from a single monster JAR and from JARs inside the monster JAR.

JarClassLoader是一个类加载器,用于从单个怪物 JAR 和怪物 JAR 内的 JAR 加载类、本机库和资源。

回答by davs

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

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

is great article, which solves my issue ..

很棒的文章,解决了我的问题..

In my case I've got the following code for initialize the library:

就我而言,我有以下用于初始化库的代码:

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);
        }
    }
}

回答by leventov

1) Include the native library into your JAR as a Resource. E. g. with Maven or Gradle, and the standard project layout, put the native library into main/resourcesdirectory.

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

2) Somewhere in static initializers of Java classes, related to this library, put the code like the following:

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());