web-dev-qa-db-fra.com

Comment intégrer une bibliothèque native et une bibliothèque JNI dans un fichier JAR?

La bibliothèque en question est Cabinet de Tokyo .

Je souhaite disposer de la bibliothèque native, de la bibliothèque JNI et de toutes les Java de l'API dans un fichier JAR pour éviter les problèmes de redistribution.

Il semble y avoir ne tentative de ceci à GitHub , mais

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

La question est de savoir si je peux tout regrouper dans un fichier JAR et le redistribuer. Si oui comment?

P.S .: Oui, je me rends compte que cela peut avoir des implications sur la portabilité.

94
Alex B

Il est possible de créer un seul fichier JAR 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 à la place de System.loadLibrary (String), qui recherche la propriété système Java.library.path. Cette méthode simplifie beaucoup l'installation car l'utilisateur n'a pas à installer la bibliothèque JNI sur son système, mais toutes les plates-formes risquent de ne pas être prises en charge, car la bibliothèque spécifique d'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, à l'adresse NATIVE/$ {os.Arch}/$ {os.name}/nomlib.lib
  • créer du code dans un initialiseur statique de la classe principale à
    • calcule les 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é permettant de faire cela pour jzmq, les liaisons Java de ZeroMQ (plug sans vergogne)). Le code est disponible ici . Le code jzmq utilise une solution hybride que si une bibliothèque incorporée ne peut pas être chargée, le code reprendra la recherche de la bibliothèque JNI le long de Java.library.path.

41
kwo

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

Jetez un oeil à One-JAR . Il encapsulera votre application dans un fichier JAR unique avec un chargeur de classe spécialisé, qui gère notamment les "fichiers JAR dans les fichiers JAR".

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'ai pas encore eu besoin de le faire, je l'ai simplement mis en favori pour un jour de pluie.)

16
Evan

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

5
tekumara

1) Incluez la bibliothèque native dans votre fichier JAR en tant que ressource. Par exemple. avec Maven ou Gradle et la structure de projet standard, place la bibliothèque native dans main/resources répertoire.

2) Quelque part dans les initialiseurs statiques de Java classes, liées à 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());
3
leventov

Vous devrez probablement libérer la bibliothèque native du système de fichiers local. Autant que je sache, le code utilisé par le chargement natif concerne le système de fichiers.

Ce code devrait vous aider à démarrer (je ne l'ai pas lu depuis un moment et il a un but différent, mais devrait faire l'affaire, et je suis plutôt 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();
            }
        }
    }
}
1
TofuBeer