JAR内のネイティブライブラリとJNIライブラリをバンドルする方法


101

問題の図書館は東京内閣です。

再配布の問題を回避するために、ネイティブライブラリ、JNIライブラリ、およびすべてのJava APIクラスを1つのJARファイルに含めたいです。

GitHubでこれが試みられているようですが、

  1. 実際のネイティブライブラリは含まれず、JNIライブラリのみが含まれます。
  2. これは、Leiningenのネイティブ依存プラグインに固有のようです(再配布可能としては機能しません)。

問題は、すべてを1つのJARにバンドルして再配布できるかどうかです。はいの場合、どのように?

PS:はい、移植性に影響する可能性があることを理解しています。

回答:


54

1つ以上のプラットフォームのネイティブJNIライブラリを含むすべての依存関係を持つ単一のJARファイルを作成することが可能です。基本的なメカニズムは、java.library.pathシステムプロパティを検索する一般的なSystem.loadLibrary(String)の代わりに、System.load(File)を使用してライブラリをロードすることです。この方法では、ユーザーが自分のシステムにJNIライブラリをインストールする必要がないため、インストールがはるかに簡単になります。 。

プロセスは次のとおりです。

  • ネイティブのJNIライブラリをJARファイルのプラットフォーム固有の場所に含めます(例:NATIVE / $ {os.arch} / $ {os.name} /libname.lib)
  • メインクラスの静的初期化子にコードを作成して、
    • 現在のos.archとos.nameを計算します
    • Class.getResource(String)を使用して、事前定義された場所のJARファイルでライブラリを探します。
    • 存在する場合は、一時ファイルに抽出し、System.load(File)でロードします。

ZeroMQ(恥知らずなプラグイン)のJavaバインディングであるjzmqにこれを行う機能を追加しました。コードはここにあります。jzmqコードはハイブリッドソリューションを使用するため、埋め込みライブラリをロードできない場合、コードはjava.library.pathに沿ってJNIライブラリを検索するように戻ります。


1
大好きです!統合の手間を大幅に節約でき、System.loadLibrary()が失敗した場合はいつでも「古い」方法に戻すことができます。私はそれを使い始めると思います。ありがとう!:)
Matthieu 2013年

1
ライブラリdllが別のdllに依存している場合はどうすればよいですか?UnsatisfiedLinkError前者のdllをロードすると暗黙的に後者がロードされるため、常にを取得しますが、jarに隠されているため、それを見つけることができません。また、後者のdllを最初にロードしても解決しません。
2013年

他の依存関係DLLは事前に把握しておく必要があり、その場所はPATH環境変数に追加する必要があります。
TruthAdjuster

よく働く!これに出くわす誰かに明らかでない場合は、EmbeddedLibraryToolsクラスをプロジェクトにドロップし、それに応じて変更します。
Jon La Marr

41

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

26
NativeUtils.loadLibraryFromJar( "/ natives /" + System.mapLibraryName( "crypt"));を使用します。より良いかもしれない
BlackJoker 2013年

1
こんにちは、NativeUtilsクラスを使用してlibs / armeabi / libmyname.so内に.soファイルを配置しようとすると、java.lang.ExceptionInInitializerErrorのような例外が発生します。原因:java.io.FileNotFoundException:ファイル/native/libhellojni.soがJAR内に見つかりませんでした。例外が発生する理由を教えてください。ありがとう
ガネーシュ2015年

16

見てみましょうワンJARを。特に、「jar内のjar」を処理する特殊なクラスローダーを使用して、アプリケーションを単一のjarファイルにラップします。

必要に応じて、ネイティブ(JNI)ライブラリーを一時作業フォルダーにアンパックして処理します。

(免責事項:私はOne-JARを使用したことがなく、まだ使用する必要がありませんでした。雨の日のためにブックマークを付けただけです。)


私があれば、私は、Javaプログラマ、どんな考えないんだけど持っているアプリケーション自体をラップしますか?これを既存のクラスローダーと組み合わせるとどうなりますか?明確にするために、私はClojureからそれを使用し、JARをアプリケーションとしてではなくライブラリとしてロードできるようにしたいと思います。
Alex B

ああ、それはおそらくより適切ではないでしょう。ネイティブライブラリをjarファイルの外に配布し、それらをアプリケーションのライブラリパスに配置する手順を説明するのに困っていると思います。
Evan

8

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

このソリューションは、wildflyなどのアプリケーションサーバーに何かをデプロイする場合にも機能します。最も優れた点は、アプリケーションを適切にアンロードできることです。
ハッシュ

これは、「ネイティブライブラリがすでに」ロードされた例外を回避するための最良のソリューションです。
Krithika Vittal

5

JarClassLoaderは、単一のモンスターJARおよびモンスターJAR内のJARからクラス、ネイティブライブラリ、およびリソースをロードするクラスローダーです。


1

おそらく、ネイティブライブラリをローカルファイルシステムに解凍する必要があります。私が知る限り、ネイティブロードを実行するコードはファイルシステムを調べます。

このコードはあなたが始めるのに役立つはずです(私はしばらくそれを見ていないので、それは別の目的のためですが、トリックを実行するはずです、そして私は現在かなり忙しいですが、質問がある場合はコメントを残してくださいできるだけ早くお答えします)。

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

1
特定のリソースを解凍する必要がある場合は、github.com / zeroturnaround / zt
Neeme Praks

zt-zipはまともなAPIのように見えます。
TofuBeer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.