dhilst

JNI Hello World

Hi every body. This is my first post. I hope you guys like. In this first post I will show how to declare, implement, compile and call a native Java methods in Linux.

The first thing we need is a class with a native method. I chose a static one for sake of simplicity. We’ll see instance methods later on anoter post.

Our class will look like this:

public class HelloJNIWorld {
	static {
		System.loadLibrary("hellonativeworld");
	}

	static native void helloWorld();

	public static void main(String[] args) {
		helloWorld();
	}
}

A litle bit of explanation doesn’t hurt any body right? So here we go… This first block load our native code. In linux this will try to load a library called “libhelloworld.so”. More on this later.

static {
	System.loadLibrary("hellonativeworld");
}

This block will load our native code as soon as our class get loaded by class loader.

The next line declares our native method. This method will be implemented at libhellonativeworld.so.

public native void helloWorld();

And after that we call it at our main:

public static void main(String[] args) {
	helloWorld();
}

Now we need to compiled it. Nothing new here:

javac HelloJNIWorld.java

If you try to call this class you’ll get an error. Let’s take a look:

[geckos@localhost jnihw01]$ java HelloJNIWorld
Exception in thread "main" java.lang.UnsatisfiedLinkError: no hellonativeworld in java.library.path
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1889)
	at java.lang.Runtime.loadLibrary0(Runtime.java:849)
	at java.lang.System.loadLibrary(System.java:1088)
	at HelloJNIWorld.<clinit>(HelloJNIWorld.java:3)
[geckos@localhost jnihw01]$

This means that java can’t find libhellonativeworld.so. We’ll create it soon but first we need to create a header for our library. This header is created by javah tool. This tool is called against a compiled class. This is why we compile our class before going furter to native code. The header can be generated with this command:

javah HelloJNIWorld

This command will generate the HelloJNIWorld.h header file. This header will look like this:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNIWorld */

#ifndef _Included_HelloJNIWorld
#define _Included_HelloJNIWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNIWorld
 * Method:    helloWorld
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNIWorld_helloWorld
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

As said at first line of the file, you should not edit this file. This is because generating it again will override your modifications. You can see that it declares our native method with some weird naming. The naming pattern is explaned at JNI’s documentation . You don’t have to botter to that namming pattern since javah tool deals with it for you. So the next step is defining our native method. To do this we will create a new file, named libhellonativeworld.c. At this file goes the body of our native method, function, what ever … You should include the header generated by javah and copy the prototype that was created for you, this will save you from typos, remeber to name the parameters :).

Our method will simply print a message, but from native code. So here it is:

#include "HelloJNIWorld.h"

JNIEXPORT void JNICALL Java_HelloJNIWorld_helloWorld
  (JNIEnv *env, jclass cls)
{
	puts("Hello native world");
}

Okay now its time to copile our native code, but first there is someting that need to be known. The header generated by javah include two headers. That headers can be found at JAVA_HOME/include and JAVA_HOME/include/linux, if you’re running in linux as I expect you to be. Well, I’m running Fedora 24 and here I can found it at /etc/alternatives/java_sdk. I will declare JAVA_HOME to point there. This can be done like this export JAVA_HOME=/etc/alternatives/java_sdk. After this compiling is a simple matter of:

cc -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -fPIC -shared -o libhellonativeworld.so libhellonativeworld.c

Now we have everything we need. Let’s try to run our native code:

$ java HelloJNIWorld
Exception in thread "main" java.lang.UnsatisfiedLinkError: no hellonativeworld in java.library.path
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
	at java.lang.Runtime.loadLibrary0(Runtime.java:870)
	at java.lang.System.loadLibrary(System.java:1122)
	at HelloJNIWorld.<clinit>(HelloJNIWorld.java:3)
$ 

Why this error again!? Well there is one little step missing. We have libhellonativeworld.so but java can’t find it. There is a bunch of ways to do this. You can move that library to some standard folder like /usr/lib. You can declare LD_LIBRARY_PATH to the folder where it resides, or you can use java.library.path property at command line. Let’s try the last:

$ java -Djava.library.path=. HelloJNIWorld
Hello native world

There you go. Last but not least. Doing all that commands by hand can be tedious so let’s create a Makefile to hold all that mess. It’ll look like this:

JAVA_HOME ?= /etc/alternatives/java_sdk
CFLAGS += -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux

all: libhellonativeworld.so

libhellonativeworld.so: libhellonativeworld.c HelloJNIWorld.h

HelloJNIWorld.h: HelloJNIWorld.class

HelloJNIWorld.class: HelloJNIWorld.java

%.so: %.c
	$(CC) $(CFLAGS) $(LDFLAGS) -fPIC -shared -o $@ $<

%.h: %.class
	javah -cp . $(<:.class=)

%.class: %.java
	javac -cp . $<

Pretty crypt doesn’t? These are called “pattern rules” and are topic for another post. I hope that this post help you to have an “in mind” road map about how native code is handled by Java. Everything you need to know can be found at documentation. All the code can be found at here

Cheers!