1

I have a custom JNI library that I've compiled on my Ubuntu 20.04 machine - lets call it JNI.so. I also have 2 different Spring Boot webapps that I'm deploying onto my Tomcat 9 server - both of which need access to this library file. Following the instructions here I have tried both adding a JniLifecycleListener to my server.xml configuration file as well as programatically calling org.apache.tomcat.jni.Library.loadLibrary. Although my goal is to get two webapps accessing this native library, to simply debugging this problem I've restricted myself to just one webapp until I get up and running. Here's what my java class looks like:

package com.example.hellojni;

public class HelloJni {

    public native String  stringFromJNI();

    public String test() {
        return stringFromJNI();
    }
}

And here is the C code:

#include <string.h>
#include <jni.h>

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

Compiled with the following:

gcc -c -fPIC hello-jni.c -o hello-jni.o -I/usr/lib/jvm/java-8-openjdk-amd64/include/ -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux
gcc hello-jni.o -shared -o libJNI.so

I've also placed my JNI.so file in $CATALINA_HOME/shared/lib and added the following to my catalina.properties file (as specified here):

shared.loader="${catalina.base}/shared/lib", "${catalina.base}/shared/lib/*.jar"

With the JniLifecycleListener line in my server.xml file, I see the following log data when tomcat boots up:

17-Jun-2020 15:32:06.266 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [705] milliseconds

17-Jun-2020 15:32:06.267 INFO [main] org.apache.catalina.core.JniLifecycleListener.lifecycleEvent Loaded native library [JNI]

17-Jun-2020 15:32:06.329 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]

17-Jun-2020 15:32:06.331 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache T

However when I try and access the library from within a Spring Boot controller with the following code:

System.out.println(new HelloJni().test());

I get the following error:

java.lang.UnsatisfiedLinkError: com.example.hellojni.HelloJni.stringFromJNI()Ljava/lang/String;
at com.example.hellojni.HelloJni.stringFromJNI(Native Method) ~[classes/:0.0.1-SNAPSHOT]
at com.example.hellojni.HelloJni.test(HelloJni.java:9) ~[classes/:0.0.1-SNAPSHOT]
at com.company.MyApplication.controllers.DashboardController.dashboard(DashboardController.java:51) ~[classes/:0.0.1-SNAPSHOT]

Now, I feel like I've tried every solution I have been able to find online all with pretty much the same results. I tried scrapping using of the use of the JniLifecycleListener altogether and instead reverted back to this popular solution that appears to predate Tomcat 7 but with the exact same results. I've tried locating the .so file in $CATALINA_HOME/lib instead of shared/lib with similar results.

The only way I've been able to make any headway is by scrapping the JniLifecycleListener, calling plain System.loadLibrary() in a static block of code inside a JAR that I've packed up and dropped into shared/lib along with the .so file and then call into the native method call. However, this only works while IN a static method or that static section of code that loads the library. I guess at the very least it confirmed that my JNI libraries are written correctly.

Can anyone help me, I feel like I'm going crazy!

Community
  • 1
  • 1
user3062913
  • 353
  • 2
  • 10

2 Answers2

0

Your function is not visible in the library. The header file that javac -h creates for you declares JNIEXPORT for all native functions which for GNU compilers is expanded to visibility("default"). But you didn't include that header, so the linker doesn't export that function (and therefore your Java process can't find it).

user2543253
  • 2,143
  • 19
  • 20
0

I got exactly the same issue here when using ODA Java SDK. The dll files are indeed loaded using the JNILifeCirclelistener (as the log says) when tomcat starts, but the actual function is called, it always raises this error:

java.lang.UnsatisfiedLinkError: com.opendesign.core.GlobalsJNI.swig_module_init()V

As long as I remove JNILifeCirclelistener and use the System.loadLibrary(), the dll just works fine with JNI. My JVM, dll and tomcat are all 64-bit.

Very strange behaviour and I am not sure what can be the fix.

derloopkat
  • 6,232
  • 16
  • 38
  • 45