Search code examples
javajvmjava-native-interfacejlink

Embedding a Java application packaged as a jlink image in a macOS app


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?


Solution

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