JNI is easy to use when you know how and frustrating when you don’t. I hope to demystify it and provide some pointers to when it goes wrong.
Define your native methods using the native modifier. In this example we’ll add two numbers together.
public class HelloWorld { public native int add(int a, int b); }
Now generate your .h header files from the Java.
javac HelloWorld.java javah -jni -classpath . HelloWorld
Implement your methods in a .c file
#include <jni.h> #include "HelloWorld.h" JNIEXPORT jint JNICALL Java_HelloWorld_add (JNIEnv * env, jobject obj, jint a, jint b) { return (jint)(a + b); }
Build your native library. This will vary between platforms.
# Linux gcc -shared HelloWorld.c -I/usr/lib/jvm/java-6-openjdk/include -o libhelloworld.so # MacOS 10.6 - uses .dylib extension gcc -shared HelloWorld.c -I-I/System/Library/Frameworks/JavaVM.framework/Headers -o libhelloworld.dylib # MacOS - older extension name "jnilib" gcc -shared HelloWorld.c -I-I/System/Library/Frameworks/JavaVM.framework/Headers -o libhelloworld.jnilib
Loading your library. This is traditionally done in a static block. The value you pass to loadLibrary() is the “base” library name – no lib prefix or .so, .dylib, .jnilib or .dll extensions.
public class HelloWorld { static { System.loadLibrary("helloworld"); } public native int add(int a, int b); public static void main(String [] args) { System.out.println(new HelloWorld().add(12,34)); } }
Running. The most important thing here is to let the VM know where your library is. There are several methods for this.
- Put the library in a known search path:
# MacOS cp libhelloworld.dylib /Library/Java//Extensions # Linux cp libhelloworld.so /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/ext/ # run as normal java -cp . HelloWorld 46
- Alter the VM arg java.library.path
java -Djava.library.path=. -cp . HelloWorld 46
- Alter the OS library loader
export LD_LIBRARY_PATH="." java -cp . HelloWorld 46
When things go wrong: can’t see library
java -cp . HelloWorld Exception in thread "main" java.lang.UnsatisfiedLinkError: no helloworld in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860) at java.lang.Runtime.loadLibrary0(Runtime.java:845) at java.lang.System.loadLibrary(System.java:1084) at HelloWorld.<:clinit>(HelloWorld.java:6)
When things go wrong: incorrectly named library
mv libhelloworld.dylib libhelloworld.dylib2 java -Djava.library.path=. -cp . HelloWorld Exception in thread "main" java.lang.UnsatisfiedLinkError: no helloworld in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860) at java.lang.Runtime.loadLibrary0(Runtime.java:845) at java.lang.System.loadLibrary(System.java:1084) at HelloWorld.<:clinit>(HelloWorld.java:6)
When things go wrong: Method name mismatch.
JNIEXPORT jint JNICALL Java_HelloWorld2_add <==== name changed to not match classname. (JNIEnv * env, jobject obj, jint a, jint b) { return (jint)(a + b); }
java -Djava.library.path=. -cp . HelloWorld Exception in thread "main" java.lang.UnsatisfiedLinkError: HelloWorld.add(II)I at HelloWorld.add(Native Method) at HelloWorld.main(HelloWorld.java:11)
When things go wrong: library depends on another library that can’t be found.
# My library now depends on libftd2xx ldd libhelloworld.so linux-vdso.so.1 => (0x00007fff5f9ff000) libftd2xx.so => /usr/lib/libftd2xx.so (0x00007f4338c68000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f43388a9000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f433868b000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4338487000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f433827f000) /lib64/ld-linux-x86-64.so.2 (0x00007f43390af000) # I make it unavailable sudo mv /usr/lib/libftd2xx.so /usr/lib/libftd2xx.so.hidden java -Djava.library.path=. -cp . HelloWorld Exception in thread "main" java.lang.UnsatisfiedLinkError: /data3/development/local/java/jni/libhelloworld.so: libftd2xx.so: cannot open shared object file: No such file or directory at java.lang.ClassLoader$NativeLibrary.load(Native Method) at java.lang.ClassLoader.loadLibrary1(ClassLoader.java:1935) at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1860) at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1850) at java.lang.Runtime.loadLibrary0(Runtime.java:845) at java.lang.System.loadLibrary(System.java:1084) at HelloWorld.<:clinit>(HelloWorld.java:4)
When things go wrong: library without execute permissions – windows only issue
Also useful for debugging there is also a System.load() call which takes an absolute path to the library.
static { System.load("/Users/adam/development/java/jni/libhelloworld.dylib"); }