Як зв’язати рідну бібліотеку та бібліотеку JNI всередині JAR?


101

Бібліотека, про яку йдеться, - Кабінет Токіо .

Я хочу - мати рідну бібліотеку, бібліотеку JNI та всі класи Java API в одному файлі JAR, щоб уникнути головного болю при перерозподілі.

Здається, у GitHub є спроба цього , але

  1. Він не включає фактичну рідну бібліотеку, лише бібліотеку JNI.
  2. Це, здається, є специфічним для вбудованого модуля залежностей Leiningen (він не працюватиме як перерозподілений).

Питання полягає в тому, чи можу я об'єднати все в один JAR і перерозподілити його? Якщо так, то як?

PS: Так, я розумію, що це може мати наслідки переносимості.

Відповіді:


54

Можна створити один файл JAR з усіма залежностями, включаючи рідні бібліотеки JNI для однієї або декількох платформ. Основний механізм полягає у використанні System.load (File) для завантаження бібліотеки замість типової System.loadLibrary (String), яка здійснює пошук властивості системи java.library.path. Цей спосіб робить інсталяцію набагато простішою, оскільки користувачеві не потрібно встановлювати бібліотеку JNI у своїй системі за рахунок, однак, що всі платформи можуть не підтримуватися, оскільки певна бібліотека для платформи може бути не включена до одного файлу JAR .

Процес такий:

  • включити нативні бібліотеки JNI у файл JAR у місці, специфічному для платформи, наприклад у NATIVE / $ {os.arch} / $ {os.name} /libname.lib
  • створити код у статичному ініціалізаторі основного класу до
    • calc поточного os.arch та os.name
    • шукайте бібліотеку у файлі JAR у заздалегідь визначеному місці за допомогою Class.getResource (String)
    • якщо він існує, витягніть його до тимчасового файлу та завантажте його у System.load (File).

Я додав функціональність, щоб зробити це для jzmq, прив'язки Java ZeroMQ (безсоромний штекер). Код можна знайти тут . Код jzmq використовує гібридне рішення, так що якщо вбудована бібліотека не може бути завантажена, код повернеться до пошуку бібліотеки JNI по java.library.path.


1
Я це люблю! Це економить багато проблем для інтеграції, і ви завжди можете повернутися до "старого" способу за допомогою System.loadLibrary (), якщо вона не вдалася. Я думаю, я почну використовувати це. Дякую! :)
Матьє

1
Що робити, якщо в моїй бібліотеці dll залежить залежність від іншого dll? Я завжди отримую UnsatisfiedLinkErrorяк завантажувальний колишній dll неявно хоче завантажити другий, але не можу його знайти, оскільки він схований у банку. Також завантаження останнього dll спочатку не допомагає.
fabb

Інші утриманці DLLповинні бути відомі заздалегідь, а їх розташування слід додати до PATHзмінної середовища.
truthadjustr

Чудово працює! Якщо комусь це не зрозуміло, додайте клас EmbeddedLibraryTools до свого проекту та змініть його відповідно.
Джон Ла Марр

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 ("крипта")); могло бути краще
BlackJoker

1
Привіт, коли я намагаюся використовувати клас NativeUtils і намагаюся подати файл .so всередині libs / armeabi / libmyname.so, я отримую виняток, як java.lang.ExceptionInInitializerError, викликаний: java.io.FileNotFoundException: Файл /native/libhellojni.so не знайдено всередині JAR. Будь ласка, дайте мені знати, чому я отримую виняток. Дякую
Ганеш

16

Погляньте на One-JAR . Він додасть вашу програму в один файл jar із спеціалізованим завантажувачем класу, який між іншим обробляє "банки в банках".

Він обробляє рідні (JNI) бібліотеки , розпаковуючи їх у тимчасову робочу папку, як потрібно.

(Відмова: Я ніколи не використовував One-JAR, ще не потребував цього, просто мав закладки для дощового дня.)


Я не програміст Java, будь-яка ідея, якщо мені доведеться обгортати саму програму? Як це буде працювати в поєднанні з існуючим завантажувачем класів? Для уточнення я збираюся використовувати його з Clojure і хочу мати можливість завантажувати JAR як бібліотеку, а не як додаток.
Олексій Б

А-а-а, це, мабуть, не підходило б. Подумайте, вам доведеться застрягти в розповсюдженні вашої рідної бібліотеки (-ів) за межами файлу jar з інструкціями щодо їх розміщення в бібліотечному шляху програми.
Еван

8

1) Включіть рідну бібліотеку до своєї JAR як ресурс. E. g. за допомогою 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. І найкраще, що він здатний правильно завантажити програму!
Хеш

Це найкраще рішення, щоб уникнути завантаження винятку "рідна бібліотека".
Крітіка Віттал

5

JarClassLoader - це завантажувач класів для завантаження класів, рідних бібліотек та ресурсів з одного JAR монстра та з JARs всередині 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-zip
Neeme Praks

zt-zip виглядає як гідний API.
TofuBeer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.