July 27, 2009

Simple JNI on Ubuntu 9.04

One can find quite a few tutorials on how to do JNI on Solaris and Windows. Also, C++ is not so frequent in those examples. For that reason I am doing two simple examples here. For any of the following you will need Sun JDK (I will return to that later). The first one passes string from Java to native method and prints personalized Hello World.

class HelloWorld
{ public native void sayHi(String name); static { System.loadLibrary("nativehw"); } public static void main(String[] args) { HelloWorld h = new HelloWorld(); h.sayHi("Night Rider"); }
}

What works for Solaris certainly works for Linux. We announce our intention to use native method sayHi from nativehw library. We compile our class:

javac HelloWorld.java

Then we generate header for native method:

javah -jni HelloWorld

Looking at method declaration in HelloWorld we write implementation:

#include
#include "HelloWorld.h"

using namespace std;

JNIEXPORT void JNICALL Java_HelloWorld_sayHi
(JNIEnv *env, jobject o, jstring s)
{
const char *str = env->GetStringUTFChars(s, 0); cout << "Hello " << str << endl;
env->ReleaseStringUTFChars(s, str); }

There is no garbage collector in C++ world, so we need to do some cleanup. We save this as HelloWorld.cpp and compile:

g++ -shared -I/usr/lib/j2sdk1.6-sun/include -I/usr/lib/j2sdk1.6-sun/include/linux HelloWorld.cpp -o libnativehw.so 

Path to your include directory may be different. Note lib in front of nativehw. We run the example like this:

java -Djava.library.path=. HelloWorld

The output is as expected - "Hello Night Rider".

The second example will be division with some exception handling. Again we start with Java code:

class MoreStuff {
 public native double div(double x, double y) throws Exception;
 static {
 System.loadLibrary("whatever");
 }
 public static void main(String[] args) {
 MoreStuff m = new MoreStuff();
 try{
 System.out.println("7.0 / 2.0 = " + m.div(7.0, 2.0));
 System.out.println("0.0 / 0.0 = " + m.div(0.0, 0.0));
 }catch(Exception e){
 System.out.println(e.getMessage());
 }
 }
}

Compile and create header:

javac MoreStuff.java
javah -jni MoreStuff

Implementation is:

JNIEXPORT jdouble JNICALL Java_MoreStuff_div
 (JNIEnv *env, jobject o, jdouble x, jdouble y){
 if(0.0 == y){
 jclass e = env->FindClass("java/lang/IllegalArgumentException");
 if (e == 0) {
 return -3.14;
 }
 env->ThrowNew(e,"Zero argument exception.");
 env->DeleteLocalRef(e);
 }
 return x/y;
}

More robust exception handling for failure to find class is recommended.

g++ -shared -I/usr/lib/j2sdk1.6-sun/include -I/usr/lib/j2sdk1.6-sun/include/linux MoreStuff.cpp -o libwhatever.so

And here is execution together with output:

java -Djava.library.path=.
MoreStuff 7.0 / 2.0 = 3.5
Zero argument exception.

In order to justify crossing boundaries and writing part of implementation in C++, one should have a good reason to do so. String search or big integer operations may be good candidates. Here are two good examples of such JNI based interfaces:
http://bitbucket.org/dfdeshom/gmp-java/http://johannburkard.de/software/stringsearch/ 

To get the first to compile one may need to edit JDKHOME/include/jni.h, include directive:

#include "jni_md.h"

should be

#include "linux/jni_md.h"

At least that was the case with my custom Sun JDK. What my custom JDK looks like is described here . While jni.h is part of OpenJDK source I didn't manage to locate it in OpenJDK installed on Ubuntu 8.04.

Click Here!