2

Is there a way in JNA to load multiple dependent libraries with Java?

I usually use Native.loadLibrary(...) to load one DLL. But I guess its not working this way because I assign this function call to the instance member.

gavioto
  • 1,695
  • 2
  • 17
  • 38
wurmi
  • 333
  • 5
  • 18
  • 1
    Multiple native libraries or multiple JNA library mappings from the same native library? – technomage Sep 16 '15 at 14:09
  • 1
    Multiple native libraries. They are completley different. One of the DLLs is depending somehow on the other.(I've got this from the Dependency Walker) – wurmi Sep 16 '15 at 14:37
  • 1
    Just make sure you load the one _without_ dependencies on the other first, either via JNA or System.loadLibrary(). – technomage Sep 16 '15 at 17:50
  • 1
    This is in fact the problem. Loading the DLLs with System.loadLibrary() causes an UnsatisfiedLinkError when I try to call some functions. And I dont know how to load multiple Libraries with JNA because I there is no static initializer allowed. Or am I totally wrong? – wurmi Sep 17 '15 at 06:07

3 Answers3

7

Let's say I have library foo and library bar. bar has a dependency on foo; it also has a dependency on baz, which we are not mapping with JNA:

public class Foo {
    public static final boolean LOADED;
    static {
        Native.register("foo");
        LOADED = true;
    }
    public static native void call_foo();
}

public class Bar {
    static {
        // Reference "Foo" so that it is loaded first
        if (Foo.LOADED) {
            System.loadLibrary("baz");
            // Or System.load("/path/to/libbaz.so")
            Native.register("bar");
        }
    }
    public static native void call_bar();
}

The call to System.load/loadLibrary will only be necessary if baz is neither on your library load path (PATH/LD_LIBRARY_PATH, for windows/linux respectively) nor in the same directory as bar (windows only).

EDIT

You can also do this via interface mapping:

public interface Foo extends Library {
    Foo INSTANCE = (Foo)Native.loadLibrary("foo");
}
public interface Bar extends Library {
    // Reference Foo prior to instantiating Bar, just be sure
    // to reference the Foo class prior to creating the Bar instance
    Foo FOO = Foo.INSTANCE;
    Bar INSTANCE = (Bar)Native.loadLibrary("bar");
}
technomage
  • 9,861
  • 2
  • 26
  • 40
0

Loading lib transient dependencies with JNA from JAR Resources.

My resources folder res:

res/
`-- linux-x86-64
    |-- libapi.so
    |-- libdependency.so

-

MyApiLibrary api = (MyApiLibrary) Native.loadLibrary("libapi.so", MyApiLibrary.class, options);

API Explodes: Caused by: java.lang.UnsatisfiedLinkError: Error loading shared library libdependency.so: No such file or directory

Can be solved by loading dependencies beforehand by hand:

import com.sun.jna.Library;

Native.loadLibrary("libdependency.so", Library.class);
MyApiLibrary api = (MyApiLibrary) Native.loadLibrary("libapi.so", MyApiLibrary.class, options);

Basically you have to build dependency tree in reverse, by hand, by yourself.


I recommend setting

java -Djna.debug_load=true -Djna.debug_load.jna=true

Furthermore, setting jna.library.path to Resource has no effect, because JNA extracts to filesystem, then it loads lib. Lib on filesystem can NOT access other libs within jar.

Context class loader classpath. Deployed native libraries may be installed on the classpath under ${os-prefix}/LIBRARY_FILENAME, where ${os-prefix} is the OS/Arch prefix returned by Platform.getNativeLibraryResourcePrefix(). If bundled in a jar file, the resource will be extracted to jna.tmpdir for loading, and later removed (but only if jna.nounpack is false or not set).

Javadoc

RTFM and happy coding. JNA v.4.1.0

0

I was in a similar situation, dealing with multiplatform and several dependent libraries, but needing to load only one. Here is my take.

Suppose you get a set 32/64 win/linux libraries with dependencies. Suppose you only need to have a JNA binding for libapi

You'll need to organize them into your jar like this:

linux-x86-64
    |-- libapi.so
    |-- libdependency.so
linux-x86
    |-- libapi.so
    |-- libdependency.so
win32-x86-64
    |-- libapi.dll
    |-- libdependency.dll
win32-x86
    |-- libapi.dll
    |-- libdependency.dll

You can:

  • determine if executing from a JAR file (avoids performing the operation when executing from your favorite IDE ; see How to get the path of a running JAR file?)

  • use JNA to determine your current executing platform

  • extract all appropriate library files into java temp folder (using elements from this answer: https://stackoverflow.com/a/58318009/7237062 (or related answers) should do the trick)

  • Tell JNA to look into the newly created temp folder

  • and voilà !

  • missing in code example is the directory cleanup at application shutdown, but I leave that to you

The main part should look like that:

MainClass.java

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Optional;
import java.util.jar.JarFile;

import com.sun.jna.Platform;

public class MainClass {
    private static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
    private static final String TEMP_DIR = System.getProperty(JAVA_IO_TMPDIR);
    private static final String JNA_LIBRARY_PATH = "jna.library.path";

    public static void main(String[] args) {
        // ...
        // path management here maybe suboptimal ... feel free to improve
        // from https://stackoverflow.com/questions/320542/how-to-get-the-path-of-a-running-jar-file
        URL current_jar_dir = Overview.class.getProtectionDomain().getCodeSource().getLocation();
        Path jar_path = Paths.get(current_jar_dir.toURI());
        String folderContainingJar = jar_path.getParent().toString();

        ResourceCopy r = new ResourceCopy(); // class from https://stackoverflow.com/a/58318009/7237062 
        Optional<JarFile> jar = r.jar(MainClass.class);
        if (jar.isPresent()) {
            try {
            System.out.println("JAR detected");
            File target_dir = new File(TEMP_DIR);
            System.out.println(String.format("Trying copy from %s %s to %s", jar.get().getName(), Platform.RESOURCE_PREFIX, target_dir));
            // perform dir copy
            r.copyResourceDirectory(jar.get(), Platform.RESOURCE_PREFIX, target_dir);
            // add created folders to JNA lib loading path
            System.setProperty(JNA_LIBRARY_PATH, target_dir.getCanonicalPath().toString());             
            } catch(Exception e) {
                e.printStackTrace(); // TODO: handle exception ?
            }
        } else {
            System.out.println("NO JAR");
        }
        // ...
    }

ResourceCopy.java (copy here for completeness; taken from https://stackoverflow.com/a/58318009)

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
 * A helper to copy resources from a JAR file into a directory. source :
 * https://stackoverflow.com/a/58318009
 */
public final class ResourceCopy {

    /**
     * URI prefix for JAR files.
     */
    private static final String JAR_URI_PREFIX = "jar:file:";

    /**
     * The default buffer size.
     */
    private static final int BUFFER_SIZE = 8 * 1024;

    /**
     * Copies a set of resources into a temporal directory, optionally
     * preserving the paths of the resources.
     * 
     * @param preserve
     *            Whether the files should be placed directly in the directory
     *            or the source path should be kept
     * @param paths
     *            The paths to the resources
     * @return The temporal directory
     * @throws IOException
     *             If there is an I/O error
     */
    public File copyResourcesToTempDir(final boolean preserve, final String... paths) throws IOException {
        final File parent = new File(System.getProperty("java.io.tmpdir"));
        File directory;
        do {
            directory = new File(parent, String.valueOf(System.nanoTime()));
        } while (!directory.mkdir());
        return this.copyResourcesToDir(directory, preserve, paths);
    }

    /**
     * Copies a set of resources into a directory, preserving the paths and
     * names of the resources.
     * 
     * @param directory
     *            The target directory
     * @param preserve
     *            Whether the files should be placed directly in the directory
     *            or the source path should be kept
     * @param paths
     *            The paths to the resources
     * @return The temporal directory
     * @throws IOException
     *             If there is an I/O error
     */
    public File copyResourcesToDir(final File directory, final boolean preserve, final String... paths)
            throws IOException {
        for (final String path : paths) {
            final File target;
            if (preserve) {
                target = new File(directory, path);
                target.getParentFile().mkdirs();
            } else {
                target = new File(directory, new File(path).getName());
            }
            this.writeToFile(Thread.currentThread().getContextClassLoader().getResourceAsStream(path), target);
        }
        return directory;
    }

    /**
     * Copies a resource directory from inside a JAR file to a target directory.
     * 
     * @param source
     *            The JAR file
     * @param path
     *            The path to the directory inside the JAR file
     * @param target
     *            The target directory
     * @throws IOException
     *             If there is an I/O error
     */
    public void copyResourceDirectory(final JarFile source, final String path, final File target) throws IOException {
        final Enumeration<JarEntry> entries = source.entries();
        final String newpath = String.format("%s/", path);
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            if (entry.getName().startsWith(newpath) && !entry.isDirectory()) {
                final File dest = new File(target, entry.getName().substring(newpath.length()));
                final File parent = dest.getParentFile();
                if (parent != null) {
                    parent.mkdirs();
                }
                this.writeToFile(source.getInputStream(entry), dest);
            }
        }
    }

    /**
     * The JAR file containing the given class.
     * 
     * @param clazz
     *            The class
     * @return The JAR file or null
     * @throws IOException
     *             If there is an I/O error
     */
    public Optional<JarFile> jar(final Class<?> clazz) throws IOException {
        final String path = String.format("/%s.class", clazz.getName().replace('.', '/'));
        final URL url = clazz.getResource(path);
        Optional<JarFile> optional = Optional.empty();
        if (url != null) {
            final String jar = url.toString();
            final int bang = jar.indexOf('!');
            if (jar.startsWith(ResourceCopy.JAR_URI_PREFIX) && bang != -1) {
                optional = Optional.of(new JarFile(jar.substring(ResourceCopy.JAR_URI_PREFIX.length(), bang)));
            }
        }
        return optional;
    }

    /**
     * Writes an input stream to a file.
     * 
     * @param input
     *            The input stream
     * @param target
     *            The target file
     * @throws IOException
     *             If there is an I/O error
     */
    private void writeToFile(final InputStream input, final File target) throws IOException {
        final OutputStream output = Files.newOutputStream(target.toPath());
        final byte[] buffer = new byte[ResourceCopy.BUFFER_SIZE];
        int length = input.read(buffer);
        while (length > 0) {
            output.write(buffer, 0, length);
            length = input.read(buffer);
        }
        input.close();
        output.close();
    }

}
LoneWanderer
  • 3,058
  • 1
  • 23
  • 41