Showing posts with label JNI. Show all posts
Showing posts with label JNI. Show all posts

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.