Monday, September 5, 2011

JNI made easy

Using JNI can sometimes be tricky. Getting all the settings right can be a nightmare when done for the first time.
In this tutorial I will use Eclipse with CDT (using MinGW/gcc) to create a simple hello world example. Therefore we will create a Java program, that calls native code from a shared library.

Source code for this tutorial is available on googlecode as a single zip archive or you can checkout the SVN projects directly.


Step 1: Creating a Java Project

First create a new Java Project called "JNI Test". Create a class com.example.jni.JNITest with following content:
package com.example.jni;

public class JNITest {

    static {
        // load library
        System.loadLibrary("JNI Library");
    }

    public static void main(final String[] args) {
        new JNITest().hello("world");
    }

    // native method signature
    public native void hello(String name);
}
Line 7 will trigger loading of the shared library.
Line 15 declares an external method which is implemented in the native library.

Create a new folder called resources under your project root. This will be the location for our JNI library.


Step 2: Create C++ Project

Now we need to create the library code. Therefore create a new C++ Project called "JNI Library". Create a source folder src within that project. The function header for the shared function needs to follow a very specific coding. This is generated by javah, a helper program of your JDK. We will do this by using an external tool helper.
Use Run -> External Tools -> External Tools Configurations... and create a new Program launcher.

This launcher will create a header file within the src folder of our C++ Project. On linux you will have to change the Location to /usr/bin/javah. On the refresh tab you can enable refreshing of this folder. Otherwise refresh manually after file creation.

Open the project properties of JNI Library and go to C/C++ General/Paths and Symbols. On the Includes tab add include and include/win32 folders of your JDK to the include paths for GNU C++.

On linux you need to includethe folders include and include/linux.

 
Now we can add our implementation in C++.
Create a new source file within src folder named com_example_jni_JNITest.cpp. Add following content to the file
#include  <iostream>

#include "com_example_jni_JNITest.h"

using namespace std;

JNIEXPORT void JNICALL Java_com_example_jni_JNITest_hello
    (JNIEnv *env, jobject jthis, jstring data) {

    jboolean iscopy;
    const char *charData = env->GetStringUTFChars(data, &iscopy);
    cout << "Hello " << charData << endl;
    env->ReleaseStringUTFChars(data, charData);
}
JNI needs a shared library to access, so we need to customize build settings a bit. Open the project properties and go to C/C++ Build/Settings. On the Tool Settings tab we need to add some linker flags. Add
-Wl,--add-stdcall-alias
at MinGW C++ Linker/Miscellaneous.

Check that Shared(-shared) is enabled at MinGW C++ Linker/Shared Library Settings.

On the Build Steps tab add following Post-Build step:
xcopy "${BuildArtifactFilePrefix}${BuildArtifactFileName}" "${workspace_loc:/JNI Test/resources/}" /Y


This will automatically copy our build product to the Java Project we created at step 1.
Finally go to Build Arifact tab and select Shared Library as Artifact Type.



Save all settings and build your library. It should be copied over to your Java Project. Refresh your workspace (F5) to see the newly created file.

Step 3: Put it all together

We are almost done. As a final step we need to add the resource folder to the library path of our run target.  Launch your Java program to create a launch target for it. You will get an Exception, don't worry, we'll fix that immediately. Go to Run -> Run Configurations... and find your Java run target (typically named JNITest). On the Arguments tab add

-Djava.library.path="${workspace_loc}/com.example.jni/resources;${env_var:PATH}"
to VM arguments.

Now launch your Java program and you should get a "Hello world" on your console view.

5 comments:

  1. Thanks a lot for this detailed instruction.
    It was a pain for me to get JNI running with CDT and MinGW. There are much tutorials and instructions out there on the web, but most of them are out of date. And there are many explanations about this ******* java.lang.UnsatisfiedLinkError - too much caused by too much several reasons....
    I hate the heterogeneous C++ build process that is more complicated to me than programming itself.

    ReplyDelete
  2. Excellent Article ! But there should be one fix associated with that,
    /* CODE */
    package com.example.jni;

    public class JNITest {

    static {
    // load library
    System.loadLibrary("resources/libJNI Library");
    }

    public static void main(final String[] args) {
    new JNITest().hello("world");
    }

    // native method signature
    public native void hello(String name);
    }

    ReplyDelete
    Replies
    1. Actually this is a windows/linux issue (don't know how this is on mac):
      On windows a dll called libJNI.dll would be accessed with System.loadLibrary("libJNI");
      On linux libJNI.so would be accessed with ystem.loadLibrary("JNI");

      so I mixed windows and linux stuff a bit up there.

      Delete
  3. Dear Christian,

    Excellent, succinct, article. I'm determined to get it to work, but I can't figure out WHICH file is can't find when its building JNI Library (in the console log in full below the error is reported as "g++: CreateProcess: No such file or directory)"

    CDT Build Console log:
    ---------------------------------------------------------------------------------
    13:51:09 **** Incremental Build of configuration Debug for project JNI Library ****
    Info: Internal Builder is used for build
    g++ "-IC:\\Program Files\\Java\\jdk1.7.0_06\\include\\win32" "-IC:\\Program Files\\Java\\jdk1.7.0_06\\include" "-IC:\\MinGW\\lib\\gcc\\mingw32\\4.7.0\\include\\c++" "-IC:\\MinGW\\lib\\gcc\\mingw32\\4.7.0\\include\\c++\\backward" "-IC:\\MinGW\\lib\\gcc\\mingw32\\4.7.0\\include\\c++\\mingw32" "-IC:\\MinGW\\include" -O0 -g3 -Wall -c -fmessage-length=0 -o "src\\com_example_jni_JNITest.o" "..\\src\\com_example_jni_JNITest.cpp"
    g++: CreateProcess: No such file or directory
    g++ -Wl,--add-stdcall-alias -shared -o "libJNI Library.dll" "src\\com_example_jni_JNITest.o"
    g++: src\com_example_jni_JNITest.o: No such file or directory
    xcopy "libJNI Library.dll" /Y
    File not found - libJNI Library.dll
    0 File(s) copied

    13:51:09 Build Finished (took 288ms)
    ----------------------------------------------------------------------------------

    Thanks for taking a look,

    Peter Schwenn

    ReplyDelete
  4. Woowwwwwwwwwwwwwwwwwwww!!!. Awesome... i was ruining from last two days to execute a sample hello world program..visual studio dll creation sucks since i dunno to create properly and no proper tutorial for that.. this tutorial made it easy with eclipse itself.. thanks to the author...

    love yaaaaa!

    ReplyDelete