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

发布于 2024-09-03 03:43:17 字数 493 浏览 14 评论 0原文

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

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

似乎有 GitHub 上的尝试,但

  1. 它不包含实际的本机库,仅包含 JNI图书馆。
  2. 它似乎特定于 Leiningen 的本机依赖项插件(它不能作为可再发行组件使用)。

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

PS:是的,我意识到这可能会对可移植性产生影响。

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.

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

  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).

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

泅渡 2024-09-10 03:43:17

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

该过程如下:

  • 将本机 JNI 库包含在 JAR 文件中特定于平台的位置,例如在 NATIVE/${os.arch}/${os.name}/libname.lib
  • 在静态文件中创建代码主类的初始化器为
    • 计算当前的 os.arch 和 os.name
    • 使用 Class.getResource(String) 在 JAR 文件中的预定义位置查找库
    • 如果存在,将其提取到临时文件并使用 System.load(File) 加载。

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

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.

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).

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.

晨与橙与城 2024-09-10 03:43:17

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

是一篇很棒的文章,它解决了我的问题。

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

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

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);
        }
    }
}
写下不归期 2024-09-10 03:43:17

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

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

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

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.

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

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

凌乱心跳 2024-09-10 03:43:17

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

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/resources directory.

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

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());
坠似风落 2024-09-10 03:43:17

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

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

姜生凉生 2024-09-10 03:43:17

Kotlin 的解决方案:

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

    tasks.withType; {
        显现 {
            属性(“主类”到“MainKt”)
        }
    
        val libs = setOf("kotlin-stdlib-1.4.0.jar")
    
        来自(configurations.runtimeClasspath.get()
            .filter { 库中的 it.name }
            .map { zipTree(it) })
    
        来自(“librust_kotlin.dylib”)
    }
    
  • main 方法:将库复制到临时文件以使用绝对路径加载它

     with(createTempFile()) {
         退出时删除()
         val bytes = My::class.java.getResource("librust_kotlin.dylib")
             .readBytes()
    
         输出流().write(字节)
         系统.负载(路径)
     }
    

Solution for Kotlin:

  • build.gradle.dsl: copy kotlin runtime (kotlin-stdlib-1.4.0.jar) and native library (librust_kotlin.dylib) to 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)
     }
    
梦归所梦 2024-09-10 03:43:17

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

这段代码应该可以帮助您开始(我已经有一段时间没有看过它了,它是出于不同的目的,但应该可以解决问题,而且我现在很忙,但如果您有疑问,请发表评论)我会尽快回复)。

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

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();
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文