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. Settings for linux and windows differ in some details, so these differences will be explicitely marked.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Creating a Java Project

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

public class JNITest {

     static {
         // load library
         if (System.getProperty("os.name").startsWith("Windows"))
             // windows
             System.loadLibrary("libJNI_Library_windows");

         else
             // linux
             System.loadLibrary("JNI_Library_linux");
     }

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

     // native method signature
     public native void hello(String name);
}
Line 9/13 will trigger loading of the shared library.
Line 21 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". As Project Type select Shared Library/Empty Project. This will provide reasonable defaults for our build. Create a source folder src within that project.

The header file for the shared functions 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:

Location: C:\Program Files\Java\jdk1.8.0_31\bin\javah.exe
Working Directory: ${workspace_loc:/com.codeandme.jni/bin}
Arguments: -jni -d "${workspace_loc:/JNI_Library_windows/src}" com.codeandme.jni.JNITest

 
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 the target folder. Otherwise refresh manually after file creation.
Open the project properties of JNI_Library and go to C/C++ General/Paths and Symbols. Select [All Configurations] from the Configuration combo at the top of the dialog.
On the Includes tab add following folders from your JDK to the include paths for GNU C++.

Windows:
  • include
  • include/win32
Linux:
  • include
  • include/linux 

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

#include "com_codeandme_jni_JNITest.h"

using namespace std;

JNIEXPORT void JNICALL Java_com_codeandme_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);
}

Finally we need to adjust our build settings a bit. Open the project properties and go to C/C++ Build/Settings. Switch to the Tool Settings tab:

Windows:
  • GCC C++ Compiler/Miscellaneous: enable Position independent code (-fPIC)
  • GCC C++ Linker/Shared Library Settings: enable Shared (-shared)
  • MinGW C++ Linker/Miscellaneous: add -Wl,--add-stdcall-alias
Linux:
  • GCC C++ Compiler/Miscellaneous: enable Position independent code (-fPIC)
  • GCC C++ Linker/Shared Library Settings: enable Shared (-shared)
The build result needs to be copied over to our java project. We can automate this step with a simple copy command as a Post-Build step on the Build Steps tab:

Windows:
  • xcopy "${BuildArtifactFilePrefix}${BuildArtifactFileName}" "${workspace_loc:/com.codeandme.jni/resources/}" /Y
Linux:
  • cp "${BuildArtifactFilePrefix}${BuildArtifactFileName}" "${workspace_loc:/com.codeandme.jni/resources/}"

This will automatically copy our build product to the Java Project we created at step 1.

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.codeandme.jni/resources/}"
to VM arguments.

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

Further reading

It is also possible to have asynchronous callbacks from C to Java. An example how to do this is available on github.

24 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
  5. Great Tutorial, thank you! Just a side note: in my case (windows & mingw) I had to add the addional

    -static-libgcc -static-libstdc++

    as linker flags in order to avoid an
    "UnsatisfiedLinkError: Can't find dependent libraries"

    ReplyDelete
    Replies
    1. Thank you! I'm using (windows & mingw) and I also had to use that.
      So for anyone else in the same situation the full list is:

      -Wl,--add-stdcall-alias, -static-libgcc, -static-libstdc++

      Delete
  6. Great tutorial Thank you!! :)

    BTW:
    In the Java file you have:
    System.loadLibrary("JNI Library");

    However the library that is copied into the Java resource file was name libJNI Test.dll, and thus the correct line should be:
    System.loadLibrary("libJNI Library");

    ReplyDelete
    Replies
    1. depending on the operating system you are using, you may be right. Please see my response to the 2nd post

      Delete
    2. Oh my bad, I didn't realized you mentioned that already. Thanks again! This was by far the best Eclipse + JNI tutorial that I found.

      Delete
  7. Hey, thank you for your tutorial. But I have a problem.

    xcopy "${BuildArtifactFilePrefix}${BuildArtifactFileName}" "${workspace_loc:/JNI Test/resources/}" /Y

    This command is not copy our build project. How can I fix that.

    ReplyDelete
    Replies
    1. hi, its a simple doc command, check that all files and target locations are there

      Delete
    2. What he means is that the xcopy fails because the resources folder is not created.

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. After another try ... getting these errors...

    The Os Is WINDOWS----calling native libraray
    Exception in thread "main" java.lang.UnsatisfiedLinkError: C:\Users\Lenovo\WorkSpaceForJNI\com.codeandme.jni\lib\libJNI_Library.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(Unknown Source)
    at java.lang.ClassLoader.loadLibrary(Unknown Source)
    at java.lang.Runtime.loadLibrary0(Unknown Source)
    at java.lang.System.loadLibrary(Unknown Source)
    at com.codeandme.jni.JNITest.(JNITest.java:9)

    Why this is comming?

    ReplyDelete
    Replies
    1. the error message is clear: you are building a 32 bit library and tr to run it on a 64 bit environment. you need to adjust your library build settings to 64 bit. How to do this is beyond this tutorial

      Delete
    2. Hi Christian..
      Sorry for the late reply...
      And Yes, you were right, I simply replaced the MinGW32 to MinGW64 before going for the build... and it worked fine...
      Thanks for that wonderful piece of code..

      Hope you can add something about "How to run a piece of code in a remote server through eclipse installed in a local desktop"... Thanks in advance...

      Delete
    3. Where to set pls for this issue for windows ?

      Delete
  11. how to fix error :
    # A fatal error has been detected by the Java Runtime Environment:
    #
    # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00000003f79fd55d, pid=7040, tid=7064
    #
    # JRE version: Java(TM) SE Runtime Environment (7.0_79-b15) (build 1.7.0_79-b15)
    # Java VM: Java HotSpot(TM) 64-Bit Server VM (24.79-b02 mixed mode windows-amd64 compressed oops)
    # Problematic frame:
    # C [cygstdc++-6.dll+0x6d55d]

    ReplyDelete
    Replies
    1. your dll did something wrong, sorry but I cant help you here

      Delete
  12. Useful article, thank you for sharing the article!!!

    Website bloggiaidap247.com và website blogcothebanchuabiet.com giúp bạn giải đáp mọi thắc mắc.

    ReplyDelete