Search code examples
javagrailsjava-native-interfaceswigunsatisfiedlinkerror

UnsatisfiedLinkError when using a JNI native library from Grails application


I have an application where I need to use a Native Library: libfoo.so

My code is as follows:

Accessor.java:

public class Accessor {        
    static {
        String path = "/usr/lib/libfoo.so";
        System.load(path);
    }
    ...
}

This works perfectly fine when I deploy my war file in a standalone tomcat server.

The problem is when I try to run the embedded tomcat server when you run:

grails run-app

I get an UnsatisfiedLinkError:

Caused by UnsatisfiedLinkError: com.foo.bar.GFS_MALJNI.new_Accessor__SWIG_0(Ljava/lang/String;I)J
->>   39 | <init>    in com.foo.bar.Accessor 

Interestingly enough, if I change my BuildConfig.groovy file to fork mode, it also works.

BuildConfig.groovy:

grails.project.fork = [
   run: [maxMemory:1024, minMemory:64, debug:false, maxPerm:256]
]

I do not want to run it in fork mode.


Solution

  • I noticed that two different class loaders are being used.

    In the non-forked mode, this class loader was being used: java.net.URLClassLoader

    In the forked mode, this class loader was being used: groovy.lang.GroovyClassLoader

    The native library works correctly in the forked mode, so I needed to come up with a hack to load the library with the GroovyClassLoader in the non-forked mode.

    This is how System.load is defined in the JDK Source:

    System.java:

    public final class System {
        ...
        public static void load(String filename) {
            Runtime.getRuntime().load0(getCallerClass(), filename);
        }
        ...
    }
    

    It's calling load0 with the classloader and filename. The obvious solution is to call load0 with your own classloader, but you can't call it since it is package-protected.

    When you write code in groovy, you have access to packge-protected and private methods/variables.

    I can specify my own classloader and load the library, as such:

    class Accessor {        
        static {
            String path = "/usr/lib/libfoo.so"
            //System.load(path);
            Runtime.getRuntime().load0(groovy.lang.GroovyClassLoader.class, path)
        }
        ...
    }
    

    I just tried it, and it's working in non-forked mode.