Comment regrouper une bibliothèque native et une bibliothèque JNI dans un JAR?

101

La bibliothèque en question est le Cabinet de Tokyo .

Je veux avoir la bibliothèque native, la bibliothèque JNI et toutes les classes d'API Java dans un fichier JAR pour éviter les maux de tête de redistribution.

Il semble y avoir une tentative sur GitHub , mais

  1. Il n'inclut pas la bibliothèque native réelle, seulement la bibliothèque JNI.
  2. Il semble être spécifique au plugin de dépendances natif de Leiningen (il ne fonctionnera pas comme redistribuable).

La question est la suivante: puis-je regrouper tout dans un JAR et le redistribuer? Si oui, comment?

PS: Oui, je me rends compte que cela peut avoir des implications de portabilité.

Alex B
la source

Réponses:

54

Il est possible de créer un fichier JAR unique avec toutes les dépendances, y compris les bibliothèques JNI natives pour une ou plusieurs plates-formes. Le mécanisme de base consiste à utiliser System.load (File) pour charger la bibliothèque au lieu de System.loadLibrary (String) typique qui recherche la propriété système java.library.path. Cette méthode rend l'installation beaucoup plus simple car l'utilisateur n'a pas à installer la bibliothèque JNI sur son système, au détriment, cependant, que toutes les plates-formes peuvent ne pas être prises en charge car la bibliothèque spécifique pour une plate-forme peut ne pas être incluse dans le fichier JAR unique .

Le processus est le suivant:

  • inclure les bibliothèques JNI natives dans le fichier JAR à un emplacement spécifique à la plate-forme, par exemple à NATIVE / $ {os.arch} / $ {os.name} /libname.lib
  • créer du code dans un initialiseur statique de la classe principale pour
    • calcule os.arch et os.name actuels
    • recherchez la bibliothèque dans le fichier JAR à l'emplacement prédéfini à l'aide de Class.getResource (String)
    • s'il existe, extrayez-le dans un fichier temporaire et chargez-le avec System.load (File).

J'ai ajouté une fonctionnalité pour faire cela pour jzmq, les liaisons Java de ZeroMQ (plug sans vergogne). Le code peut être trouvé ici . Le code jzmq utilise une solution hybride afin que si une bibliothèque intégrée ne peut pas être chargée, le code reviendra à la recherche de la bibliothèque JNI le long du chemin java.library.path.

kwo
la source
1
J'aime cela! Cela évite beaucoup de problèmes pour l'intégration, et vous pouvez toujours revenir à l '«ancienne» méthode avec System.loadLibrary () en cas d'échec. Je pense que je vais commencer à utiliser ça. Merci! :)
Matthieu
1
Que dois-je faire si ma DLL de bibliothèque a une dépendance à une autre DLL? Je reçois toujours un UnsatisfiedLinkErrorcomme chargement de la première dll veut implicitement charger la dernière, mais je ne peux pas la trouver car elle est cachée dans le fichier jar. Le chargement de la dernière DLL en premier n'aide pas non plus.
fabb
Les autres dépendants DLLdoivent être connus à l'avance et leur emplacement doit être ajouté dans la PATHvariable d'environnement.
truthadjustr
Fonctionne très bien! Si quelqu'un ne le voit pas, déposez la classe EmbeddedLibraryTools dans votre projet et modifiez-la en conséquence.
Jon La Marr
41

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

est un excellent article, qui résout mon problème.

Dans mon cas, j'ai le code suivant pour initialiser la bibliothèque:

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);
        }
    }
}
Davs
la source
26
utilisez NativeUtils.loadLibraryFromJar ("/ natives /" + System.mapLibraryName ("crypt")); pourrait être mieux
BlackJoker
1
Bonjour quand j'essaye d'utiliser la classe NativeUtils et que j'essaye de déposer le fichier .so dans le libs / armeabi / libmyname.so, j'obtiens l'exception comme java.lang.ExceptionInInitializerError, causée par: java.io.FileNotFoundException: Le fichier /native/libhellojni.so est introuvable dans le JAR. Veuillez me faire savoir pourquoi je reçois l'exception. Merci
Ganesh
16

Jetez un œil à One-JAR . Il encapsulera votre application dans un seul fichier jar avec un chargeur de classe spécialisé qui gère entre autres les "jars dans les jars".

Il gère les bibliothèques natives (JNI) en les décompressant dans un dossier de travail temporaire selon les besoins.

(Avertissement: je n'ai jamais utilisé One-JAR, je n'en ai pas encore eu besoin, je l'ai juste mis en signet pour un jour de pluie.)

Evan
la source
Je ne suis pas un programmeur Java, une idée si je dois envelopper l'application elle-même? Comment cela fonctionnerait-il en combinaison avec un chargeur de classe existant? Pour clarifier, je vais l'utiliser depuis Clojure et je veux pouvoir charger un JAR en tant que bibliothèque, plutôt qu'en tant qu'application.
Alex B
Ahhh, ça ne conviendrait probablement pas que. Pensez que vous serez coincé avec la distribution de vos bibliothèques natives en dehors du fichier jar, avec des instructions pour les mettre dans le chemin de la bibliothèque de l'application.
Evan
9

1) Incluez la bibliothèque native dans votre JAR en tant que ressource. Par exemple. avec Maven ou Gradle et la mise en page standard du projet, placez la bibliothèque native dans le main/resourcesrépertoire.

2) Quelque part dans les initialiseurs statiques de classes Java, liés à cette bibliothèque, mettez le code comme suit:

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());
Leventov
la source
Cette solution fonctionne également au cas où vous voudriez déployer quelque chose sur un serveur d'applications comme wildfly ET la meilleure partie est qu'il est capable de décharger correctement l'application!
Hash du
C'est la meilleure solution pour éviter l'exception chargée de la «bibliothèque native déjà».
Krithika Vittal le
freak ........... ça marche
Juhan il y a
5

JarClassLoader est un chargeur de classe pour charger des classes, des bibliothèques natives et des ressources à partir d'un seul JAR monstre et à partir de JAR à l'intérieur du JAR monstre.

tekumara
la source
1

Vous devrez probablement dissocier la bibliothèque native du système de fichiers local. Autant que je sache, le morceau de code qui effectue le chargement natif regarde le système de fichiers.

Ce code devrait vous aider à démarrer (je ne l'ai pas regardé depuis un moment, et c'est dans un but différent, mais devrait faire l'affaire, et je suis assez occupé pour le moment, mais si vous avez des questions, laissez un commentaire et je répondrai dès que possible).

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();
            }
        }
    }
}
TofuBière
la source
1
Si vous avez besoin de décompresser une ressource spécifique, je vous recommande de consulter le projet github.com/zeroturnaround/zt-zip
Neeme Praks
zt-zip ressemble à une API décente.
TofuBeer