I'm a new OpenJDK user previously using Oracle's JDKs and have only just now been able to migrate a lot of our apps past Java 8. I'm experiencing trouble with deploying standalone java applications using a Jpackage Wix installer when the apps rely on a series of native dlls.
For the sake of simplicity I am deploying an app that loads a dll which in turn loads another dependent dll. Both dlls reside in the same folder. My java app has a third party library which loads the first dll but not the second or any further down the chain. I do not have access to this code to change what is explicitly loaded.
I'm passing the 'nativelibs' directory at launch time in the -Djava.library.path JVM property on the command line. The first library is found with no trouble but when lib1.dll attempts to load its own dependency dll lib1-dependency.dll the jvm (OpenJDK 19) throws an exception stating that it cannot be found. The nativelibs folder is not on the system path (Windows 10 64 bit).
I've also tried flattening the nativelibs directory so that all of the .dll libraries are in the same directory as the parent executable (appinstalldir) after finding OpenJDK bug 8213772 which appears to be in the 'open' bug state for over half a decade. The behavior is the same which is shocking to me. It has always been my understanding up to this point that windows will attempt to search the current process' working directory for dependent libraries at some point during the library search order procedure but this doesn't appear to be happening.
I used Dependencies application to check for library dependency load errors but none are found and the library loads fine showing that it was able to successfully load the dependent library from the same directory as lib1.dll. My java runtime is 32 bit and the dlls are all 32 bit as well. I know this because the app works fine if I DO modify the user's path and put the nativelibs directory on it.
How on Earth am I supposed to deploy my application without shoehorning a modification to the user's path environment variable? I do not want to modify the customer's path environment variable because what if there are other copies of the same library already on their path but they are 64 bit versions instead of the 32 bit version I am deploying? That would lead to more library load errors depending on the order of directories on their path...
Has this behavior (Windows won't search the process's working directory for a library) always been the case? Is this truly an OpenJDK bug that hasn't been resolved yet? That seems too devastating to be the case.
The point is that what I'm trying to do is not rocket science and it seems excessively difficult to deploy a set of native libraries as a part of my deployment.
What am I doing wrong?
From experimenting with Java 21.0.2 on Windows 10, I found three solutions, other than the one described in your answer, that worked for me.
jlink
At least on Windows, this means putting the DLLs in the bin
directory. There are a few ways you can do this.
Package the DLLs in a JMOD file via the jmod
tool, then point jlink
/ jpackage
at it when creating the custom run-time image.
Create the run-time image via jlink
, copy the DLLs into the bin
directory manually, then point jpackage
at said run-time image.
Create the run-time image and application image in one step via jpackage
and then copy the DLLs into the runtime/bin
directory manually.
The last two are essentially the same thing, it just depends on when you want to copy the DLLs and whether you create the run-time image separately via jlink
or have jpackage
execute jlink
for you.
The JMOD approach is the cleanest one, in my opinion. I also believe it is the least likely to break if things change about how run-time images are structured/behave. However, creating a JMOD file does require you to have a module-info descriptor. If your code is already modular then this is not much of a change (just make sure to package your code in the JMOD file along with the DLLs). Otherwise, perhaps you could create a "dummy" empty module that is only used to package the DLLs in a JMOD file.
There is no need to set the java.library.path
system property in this case.
jpackage
via --input
You are already trying to do this based on information provided in your question. However, the problem appears to be caused by the DLLs being nested under the nativelibs
directory. Putting the DLLs directly under the app
directory solves the problem. In other words, pass nativelibs
to --input
, not the parent directory of nativelibs
.
Do not set the java.library.path
system property in this case.
If you have lib1.dll
and lib2.dll
, where the former depends on the latter, and you point to them via the java.library.path
system property, then load lib2.dll
first, followed by lib1.dll
. Note this solution is likely to become more and more nightmarish the more complex the DLL dependency graph gets, but for just two DLLs this may be workable for you.