Search code examples
javacswiftjava-native-interface

How to load a JNI .dylib file with a dependency without getting an UnsatifiedLinkError?


Goal: Link Java to Swift

Problem: I get an UnsatisfiedLinkError when trying to load a JNI .dylib file that is linked with a Swift .dylib file when calling System#loadLibrary(String).

Expected Behavior: The dependency of the Java .dylib would be automatically loaded or the call to System.loadLibrary("SwiftCode") would load the dependency (the only solution I could come up with).

Note: I am combining this github tutorial and this Medium article to create my JNI .dylib file and this tutorial to create my Swift .dylib file.

Full stacktrace:

Exception in thread "main" java.lang.UnsatisfiedLinkError: /Users/hillmacbookpro/IdeaProjects/JavaToSwift/src/native/libSwiftHelloWorld.dylib: dlopen(/Users/hillmacbookpro/IdeaProjects/JavaToSwift/src/native/libSwiftHelloWorld.dylib, 1): Library not loaded: libSwiftCode.dylib
  Referenced from: /Users/hillmacbookpro/IdeaProjects/JavaToSwift/src/native/libSwiftHelloWorld.dylib
  Reason: image not found
    at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method)
    at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2408)
    at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2465)
    at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2662)
    at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2627)
    at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:829)
    at java.base/java.lang.System.loadLibrary(System.java:1833)
    at helloworld.SwiftHelloWorld.<clinit>(SwiftHelloWorld.java:7)

File Structure:

src
  - helloworld
      - SwiftHelloWorld.java
  - native
      - libSwiftHelloWorld.dylib
      - libSwiftCode.dylib

MCVE:

SwiftHelloWorld.java:

package helloworld;

public class SwiftHelloWorld {

    static {
        System.loadLibrary("SwiftCode"); // loading SwiftHelloWorld's dependency first
        System.loadLibrary("SwiftHelloWorld"); // exception thrown here, can't find dependency?
    }

    public static native void printHelloWorldImpl();

    public static void main(final String[] args) {
        printHelloWorldImpl();
    }

}

libSwiftHelloWorld.dylib: Created using these two terminal commands:

export JAVA_HOME="$(/usr/libexec/java_home -v 11)"; gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin/" -o libSwiftHelloWorld.dylib -dynamiclib helloworld_SwiftHelloWorld.c libSwiftCode.dylib

and these files:

  • helloworld_SwiftHelloWorld.c:

    #include <jni.h>
    #include <stdio.h>
    #include "helloworld_SwiftHelloWorld.h"
    #include "helloworld_SwiftHelloWorld_swift.h"
    
    JNIEXPORT void JNICALL Java_helloworld_SwiftHelloWorld_printHelloWorldImpl
      (JNIEnv *env, jclass clazz) {
    
        int result = swiftHelloWorld(42);
        printf("%s%i%s", "Hello World from JNI! ", result, "\n");
    
    }
    
  • helloworld_SwiftHelloWorld.h: The auto-generated JNI header from javac -h native/ helloworld/SwiftHelloWorld.java

  • helloworld_SwiftHelloWorld_swift.h:

    int swiftHelloWorld(int);
    

libSwiftCode.dylib: Created using swiftc SwiftCode.swift -emit-library -o libSwiftCode.dylib and the SwiftCode.swift file:

import Foundation

// force the function to have a name callable by the c code
@_silgen_name("swiftHelloWorld")
public func swiftHelloWorld(number: Int) -> Int {
    print("Hello world from Swift: \(number)")
    return 69
}

Related Questions:

  • Java JNI linking multiple libraries, he/she gets an UnsatifiedLinkError but with the discription undefined symbol: ... (unanswered)

  • Anything tagged with JNI and containing UnsatifiedLinkError

Other Notes: I am setting the Java virtual machine's options to -Djava.library.path=src/native, the parent directory of both of the .dylib files. I am using macOS.

Edit: I have tried preceding the command to compile SwiftCode.swift with xcrun as seen in this article (under "Compile Swift code") but I still get the same error.


Solution

  • macOS ld builds a library dependency's path into the binary. The loader loading libSwiftHelloWorld.dylib will only find libSwiftCode.dylib if the latter is in the current directory. Loading the dependency in Java doesn't work because for the loader it's a different library.

    You can change the built-in path for libSwiftCode.dylib with the -install_name argument (i.e. swiftc ... -Xlinker -install_name -Xlinker <your path>). If you rebuild libSwiftHelloWorld.dylib afterwards it will reference the path that you gave.