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?
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].
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With