I am trying to embed a Java application into a native Mac app. The Java application is packaged with jlink (Java 9+) including a stripped down JRE.
I'm trying to use the Java Invocation Interface to create the JVM and launch the main class, but the program crashes at the the point where JNI_CreateJavaVM
is called, because it can't find libjava.dylib
.
In my Xcode project, I have a "Copy Files" phase that copies the entire jlink image (the folder containing bin
, lib
, etc. directories) into the application bundle, into the Contents/image
directory.
$ ls -l Java\ Native\ Wrapper.app/Contents/image/
total 8
drwxr-xr-x 11 ahs staff 352 Jan 13 12:46 bin
drwxr-xr-x 6 ahs staff 192 Jan 13 12:46 conf
drwxr-xr-x 25 ahs staff 800 Jan 13 12:46 legal
drwxr-xr-x 52 ahs staff 1664 Jan 13 12:46 lib
-rw-r--r-- 1 ahs staff 717 Jan 13 12:46 release
The dylib file is there:
$ file Java\ Native\ Wrapper.app/Contents/image/lib/libjava.dylib
Java Native Wrapper.app/Contents/image/lib/libjava.dylib: Mach-O 64-bit dynamically linked shared library x86_64
My Objective-C code links against the JNI library in lib/jli/libjli.dylib
. Xcode automatically copies it into the Frameworks directory of the app bundle.
When I run the program, I get the following error on the console:
Error: could not find libjava.dylib
Failed to GetJREPath()
I added the image/lib
directory to the runtime search path. Inspecting the binary with otool
confirms it:
$ otool -l Java\ Native\ Wrapper
...
Load command 18
cmd LC_RPATH
cmdsize 48
path @executable_path/../Frameworks (offset 12)
Load command 19
cmd LC_RPATH
cmdsize 48
path @executable_path/../image/lib (offset 12)
...
I am really stumped here. I've tried copying the dylibs to the Frameworks directory, that didn't work.
What am I doing wrong?
It should work just fine. If I create user specific variable - JAVA_HOME
(User-Defined Settings
) and set it such way it points to jlink
based version of Java.
JAVA_HOME
: $(HOME)/tmp/jdk-13-stripped
and then, I make sure to add: $(JAVA_HOME)/jdk-13-stripped/lib
as a resource (it will end up inside Resources
dir of the final application) and then, I set following settings:
Run Search Paths: @executable_path/../Resources/lib/server
Header Search Paths: $(JAVA_HOME)/include and $(JAVA_HOME)/include/darwin
Library Search Paths: $(JAVA_HOME)/lib/server
Other Linker Flags: -L$(JAVA_HOME)/lib/server -ljvm
and then, I run the code
> ./CallJVM
Hello from Java
If I check which image was loaded, it is taken from the proper location
> lldb CallJVM
(lldb) image list
...
...
[ 7] .... .../Debug/CallJVM.app/Contents/Resources/lib/server/libjvm.dylib
Update
In case someone needs video tutorial related to this topic, here you go: Embedding JVM inside macOS application bundle (using XCode)