Lookup Logic For Native Libraries in Java

If you want to do something in Java, there’s certainly Java library which helps you with to achieve your goal. However, for some stuff (3D rendering, accessing I/O devices) the Java API’s are not enough and you need to interact directly with the target platform.  In such cases, you need to use the Java Native Interface and provide a native library. This also means that you need to ship your application with additional native libraries. Here’s where the issues start.

Java and Native Libraries

Java and Native Libraries

When you use native-libraries, the JVM needs to load those. By default the JVM loads those libraries from a few standard system paths. Of course you don’t want to touch those. The other alternative is to specify an look-up path with the JVM-argument ‘java.library.path’. This works most of the time, but still has its issues! First, it doesn’t work with a simple jar-launcher! In order to specify this JVM-argument, you need to add a launch-script. The second issue has to with packaging your application. Imagine that you ship your application for different OS, like Linux, Windows 32- and 64-Bit etc. The native binaries are named the same, but are actually for different versions. For example there are two versions of ‘coolLibrary.dll’ one for 32-Bit, one for 64-Bit. You cannot have both in the same directory. You somehow need to decide at runtime which one you pick.

So what you want is to be more flexible how to resolve a native library. And you can! Thanks to our friend ClassLoader. Basically you can overwrite the ClassLoader.findLibrary()-method and implement you own resolving strategy. For example:

public class NativeLibPathClassLoader extends URLClassLoader{
    private final static String LIB_PATH = "./native";
    public NativeLibPathClassLoader(URL[] urls) {
        super(urls, Thread.currentThread().getContextClassLoader());
    }

    @Override
    protected String findLibrary(String libname) {
        // Implement your fancy library name resolving mechanism:
        String parentResolvedPath = super.findLibrary(libname);
        if(null==parentResolvedPath){
            final File nativeLibDir = new File(LIB_PATH);
            final File libraryPath = new File(nativeLibDir, getCPUArchPath());
            final File library = new File(libraryPath,System.mapLibraryName(libname));
            if(library.exists()){
                return library.getAbsolutePath();
            }
        }
        return null;
    }

    private String getCPUArchPath() {
        String arch = System.getProperty("os.arch");
        if(arch.contains("64")){
            return "amd64";
        } else{
            return "x86";
        }
    }
}

Of course, this class-loader should be the ‘root’ class loader of your application. This means this class loader should be responsible for loading all application-classes. To do this, I usually create a small ‘boot’-application, which starts the real application. Basically what it does is to instantiate the NativeLibPathClassLoader, pass it the location of the application with dependencies and then load the real application. Not that your ‘real’ application shouldn’t be on the regular class-path. Otherwise everything is loaded by the system class loader:

public class BootsTrapper {
    private final static String APPLICATION_PATH = "./application";
    private final static String APPLICATION_MAIN = "info.gamlor.application.ApplicationMain";

    public static void main(String[] args) {
        final File file = new File(APPLICATION_PATH);
        try {
            tryStartUp(file,args);
        } catch (Exception e) {
            System.err.println("Unexpected exception'" + file + "'.");
            e.printStackTrace(System.err);
        }
    }

    private static void tryStartUp(File applicationRoot,String[] arguments) throws Exception {
        if (applicationRoot.exists()) {
            final URL[] applicationJars = enumerateLibs(applicationRoot);

            // Here we setup our native library class loader
            final NativeLibPathClassLoader classLoader = new NativeLibPathClassLoader(applicationJars);
            Thread.currentThread().setContextClassLoader(classLoader);
            // and load the real-applicaiton with it
            final Class<?> bootClass = classLoader.loadClass(APPLICATION_MAIN);
            final Method mainMethod = bootClass.getMethod("main", arguments.getClass());
            mainMethod.invoke(null,new Object[]{arguments});
            
        } else {
            System.err.println("Couldn't find application-jars in '" + applicationRoot + "'.");
        }
    }

    private static URL[] enumerateLibs(File file) throws MalformedURLException {
        final File[] files = file.listFiles();
        final List<URL> urls = new ArrayList<URL>();
        for (File jarFile : files) {
            urls.add(jarFile.toURI().toURL());
        }
        return urls.toArray(new URL[urls.size()]);
    }
}

This is how I start up Java Desktop Applications which need native libraries. It works wonderful and it solves the annoying ‘java.library.path’-path issue.

Tagged on: