home / blog

JNI helloworld

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");
}
This entry was posted in geek and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.