aeshirey
aeshirey

Reputation: 11

Native library loads, but calling native function throws UnsatisfiedLinkError

I built a native library, libmynative.so, that exposes a single, straightforward function:

public class MyWrapper {
    public static native double MyCalculation(double a, double b);

    static {
        System.loadLibrary("mynative");
    }
}

This works fine when I compile MyWrapper.java, have the .so and .class files together, and run a simple test with java -Djava.library.path=. MyWrapper (when this class has a main method).

But I want to package both the wrapper and native library into a .jar file and use it by having the wrapper extract the .so file and load it from a temp directory at runtime. My code is basically:

public class MyWrapper {
    public static native double MyCalculation(double a, double b);

    private static File tempDir = null;

    static {
        try {
           tempDir = Files.createTempDirectory("foo").toFile();
           tempDir.deleteOnExit();
        } catch (IOException e) {
           System.out.println("Couldn't create temporary directory");
        }

        try {
            InputStream inStream = OpenDP.class.getResourceAsStream("/libmynative.so");

            // Create the temp file in the filesystem
            File temp = new File(tempDir.getPath() + "/libmynative.so");
            temp.createNewFile();
            temp.deleteOnExit();
            FileOutputStream outStream = new FileOutputStream(temp);

            // Copy from the .jar to the filesystem
            try {
                byte[] buffer = new byte[256 * 1024];
                int bytesRead;
                while ((bytesRead = inStream.read(buffer)) >= 0) {
                    outStream.write(buffer, 0, bytesRead);
                }
            } finally {
                outStream.close();
                inStream.close();
            }

            // Try to load library from extracted native resources
            String p = tempDir.getAbsolutePath() + "/libmynative.so";
            System.load(p);
        } catch (Exception e) {
            throw new UnsatisfiedLinkError(e.getMessage());
        }
    }

I can call my native function in a standalone app, so the function signature is fine.

I build a jar file with MyWrapper.class in its package directory and libmynative.so in the root. Then I have a simple test project that I run (using java -cp ".:MyJar.jar" MyTestProject):

import com.myorganization.MyWrapper;
public static void main(String[] args) {
    System.out.println(MyWrapper.MyCalculation(1.0, 2.0));
}

This fails with:

Exception in thread "main" java.lang.UnsatisfiedLinkError: com.myorganization.MyWrapper.MyCalculation(DD)D
        at com.myorganization.MyWrapper.MyCalculation(NativeMethod)
        ...

I've verified that the System.load call doesn't throw an exception; the problem occurs when the function is invoked. My best guess, not being an expert here, is that there's an issue with the java.library.path which isn't set? How can I debug what's going on here? Or is there a simple answer?

Upvotes: 1

Views: 608

Answers (1)

Jorn Vernee
Jorn Vernee

Reputation: 33895

It is likely that the name of the function that Java expects is not the name of the function inside the library.

You can use the -Xlog:library=info VM flag (JDK 15+) to see which symbol the JVM is actually trying to load. Since you're getting an UnsatisfiedLinkError it should print something like this at the end:

[0.622s][info][library] Failed to find Java_my_package_MyWrapper_MyCalculation in library with handle 0x00007ff8a24f0000

Then, you can use nm to make sure that that symbol is actually in the library:

nm libmynative.so | grep Java_my_package_MyWrapper_MyCalculation

And I'm guessing you'll find that it isn't.

The solution in that case is to change the name of the function in the library to be what Java expects. One way to do that is to regenerate the header file with javah or javac -h, which will have the right name, and then implement the function from that header file.

Upvotes: 2

Related Questions