Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Web Start - load native dependency with another native dependency

I am using Java Web Start to launch a Java application that depends on some third party native libraries. These native libraries then subsequently load another native library (commonLib) as their dependency using LoadLibrary/dlopen.

When not using Web Start, everything works as expected when the native libraries are located in the same directory.

Web Start, however, requires the native libraries to be packed in a jar file and referenced in the jnlp file, which I did:

  <!-- Windows OS -->
    <resources os="Windows">
        <nativelib href="native/native-windows.jar" />
    </resource>

  <!-- Linux OS -->
    <resources os="Linux">
        <nativelib href="native/native-linux.jar" />
    </resources>

  <!-- Mac OSX -->
    <resources os="Mac OS X">
        <nativelib href="native/native-osx.jar"/>   
    </resources>

The native libraries load fine but they fail to load their dependency commonLib - the C++ LoadLibrary/dlopen call fails because the file is present in some jar/cache folder not on the current library search path.

On Windows, I was able to solve this problem by pre-loading commonLib in Java before trying to load the JNI library, like so:

System.loadLibrary("commonLib");
System.loadLibrary("myNativeLib");

However, this approach doesn't work on OS X - dlopen in the native code fails. dlopen is apparently not smart enough not to try to load the library again if it is already loaded.

Is there a cross-platform way to pack and load native libraries that depend on other native libraries in Java Web Start?

like image 1000
Karel Petranek Avatar asked Jul 21 '13 20:07

Karel Petranek


1 Answers

I was able to find an (ugly) workaround. The trick is to pack the dependent libraries (commonLib) to a simple resource jar and add it to the jnlp file:

...
<resources os="Windows">
  <jar href="native/deps-windows.jar" />
</resources>
<resources os="Linux">
  <jar href="native/deps-linux.jar" />
</resources>
<resources os="Mac OS X">
  <jar href="native/deps-osx.jar" />
</resources>
...

Step two is to extract these resources using Java into a temporary directory:

String tmpDir = System.getProperty("java.io.tmpdir");
if (!tmpDir.endsWith("/") && !tmpDir.endsWith("\\"))
    tmpDir += "/";
String resource = "commonDir.dll"; // Adjust accordingly to current OS, omitted for brevity
InputStream is = loader.getResourceAsStream(resource);
if (is == null) {
    // Handle error - resource not found
    return;
}
try {
    FileOutputStream os = new FileOutputStream(tmpDir + resource);
    byte[] buffer = new byte[1024*1024];
    int len = is.read(buffer);
    while (len != -1) {
        os.write(buffer, 0, len);
        len = is.read(buffer);
    }
    os.close();
    is.close();
    System.out.println("Extracted " + resource + " to " + tmpDir);
} catch(IOException ex) {
    // Handle the exception - cannot write to temp directory
    System.out.println("Could not extract resource " + resource + " to " + tmpDir + ": " + ex.getMessage());
}

Step 3 is either to inform the native JNI library about the full path to the extracted dependency, or to temporarily set current directory to the temp directory tmpDir, load the JNI library and set it back. This is a problem by itself - in Java it is hard to change the current working directory. You may solve it by creating another small utility JNI library that does that from C though [1].

like image 103
Karel Petranek Avatar answered Oct 03 '22 08:10

Karel Petranek