Search code examples
javadllwindows-10wixjpackage

JPackage/OpenJDK - Can't Deploy App With JNI Dependencies


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.

directory structure

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.

DependeciesGUI.exe view of the failing dll

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?


Solution

  • 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.


    Include the DLLs in the Run-time Image Created by jlink

    At least on Windows, this means putting the DLLs in the bin directory. There are a few ways you can do this.

    1. Package the DLLs in a JMOD file via the jmod tool, then point jlink / jpackage at it when creating the custom run-time image.

    2. Create the run-time image via jlink, copy the DLLs into the bin directory manually, then point jpackage at said run-time image.

    3. 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.


    Include the DLLs in the Application Image Created by 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.


    Load the Dependency First

    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.