Java Web Start - load native dependency with another native dependency
java, java-native-interface, java-web-start
Solution
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].
Problem
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?