Sunday, August 22, 2010

Video tutorial for JNI


JNI (Java Native Interface) allows the communication between a JAVA program and a native program (C, C++). The objective of this first blog post is to give you a quick introduction to this technology. Hopefully, after reading this post and watching the video, you should be able to:
  • set up your own environment with Eclipse and Visual Studio
  • create a program using JNI
  • debug while switching between JAVA and native code
  • avoid memory leaks
  • understand the risk of JVM crash





I used Eclipse and Microsoft Visual Studio for this demonstration. Eclipse is a popular development IDE for JAVA. You can download it for free on http://www.eclipse.org/. Visual Studio Express is the free version of Visual Studio and it is available here: http://www.microsoft.com/express/.

The example is using two classes Bean and JniExample. JniExample loads the native library (JniExampleLibrary.dll) and exposes the native methods implemented in the DLL. The bean is used to demonstrate how to exchange data through JNI.

We used javah.exe to generate the header file declaring the native methods in the DLL as defined in JniExample.java. The command line is:

javah -d C:\codebazaar\JniExample\cpp\JniExample codebazaar.example.JniExample
We also used javap.exe to generate the method signatures. The command line is:
javap -s codebazaar.example.JniExample
As your program may evolve, I recommend you to save these commands as external tools in eclipse.

To debug the library and the JAVA, in Visual Studio go to Debug, select Attach to Process, find javaw.exe and click on the Attach button.

As in any native code, you need to pay a special attention to memory leak. In the case of JNI, the JVM takes care of most of the job. Any instance passed to or returned by the DLL will be freed by the JVM. You need to free a buffer only if it has been allocated outside the JVM.

Finally, we demonstrated how to get the JVM crash... That was the easy part :D If you are considering communicating with a native library from your web application, you need to realize that a problem in the native library might take the whole server down. As this is not great in term of avaibility, you may consider running the native library on a separate web server.

I uploaded all the source code used. But you can view it directly from here:
package codebazaar.example;

public class Bean {
//public members
public String dataString; 
public byte[] dataByteArray;

public Bean() {}

//getters and setters
public String getDataString() {
return dataString;
}

public void setDataString(String dataString) {
this.dataString = dataString;
}

public byte[] getDataByteArray() {
return dataByteArray;
}

public void setDataByteArray(byte[] dataByteArray) {
this.dataByteArray = dataByteArray;
} 

@Override
public String toString(){
String ret = "string = " + dataString;

ret += " / byteArray =";
if ( dataByteArray != null ) {
for ( byte b : dataByteArray) {
ret += " " + b;
}
}

return  ret; 
}
}

package codebazaar.example;

public class JniExample {
static {
System.loadLibrary("JniExampleLibrary");
}

// native methods
/**
* this method calls printSomething
*/
public native void callJavaMethod();

/**
* this method creates an instance of Bean and returns it
*/
public native Bean createAndReturnBean();

/**
* this method takes an instance of Bean as parameter and changes the value of its members
*/
public native void modifyBean(Bean bean);

/**
* this method will the JVM when invoked 
*/
public native void crashTheJvm();

public void printSomething() {
System.out.println("Thanks for watching this video");
}

public void runExample1() {
System.out.println("starting runExample1...");
callJavaMethod();
}

public void runExample2() {
System.out.println("starting runExample2...");

Bean bean = createAndReturnBean();
System.out.println("returned= " + bean.toString());
}

public void runExample3() {
System.out.println("starting runExample3...");

Bean bean = new Bean();
bean.setDataString("hello");

byte[] byteArray = new byte[] { 0x01, 0x02, 0x03 };
bean.setDataByteArray(byteArray);

System.out.println("before: " + bean.toString());
modifyBean(bean);
System.out.println("after: " + bean.toString());
}

public void runExample4() {
System.out.println("starting runExample4...");
crashTheJvm();
}
}


package codebazaar.example;

public class Main {

/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Starting JavaMain...");

JniExample jniExample = new JniExample();
jniExample.runExample1();
jniExample.runExample2();
jniExample.runExample3();
jniExample.runExample4();
}

}


/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class codebazaar_example_JniExample */

#ifndef _Included_codebazaar_example_JniExample
#define _Included_codebazaar_example_JniExample
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     codebazaar_example_JniExample
* Method:    callJavaMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_callJavaMethod
(JNIEnv *, jobject);

/*
* Class:     codebazaar_example_JniExample
* Method:    createAndReturnBean
* Signature: ()Lcodebazaar/example/Bean;
*/
JNIEXPORT jobject JNICALL Java_codebazaar_example_JniExample_createAndReturnBean
(JNIEnv *, jobject);

/*
* Class:     codebazaar_example_JniExample
* Method:    modifyBean
* Signature: (Lcodebazaar/example/Bean;)V
*/
JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_modifyBean
(JNIEnv *, jobject, jobject);

/*
* Class:     codebazaar_example_JniExample
* Method:    crashTheJvm
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_crashTheJvm
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


#include "codebazaar_example_JniExample.h"
#include 

JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_callJavaMethod (JNIEnv * env, jobject obj) {
jclass jniExampleCls = env->GetObjectClass(obj);

jmethodID mid = env->GetMethodID(jniExampleCls, "printSomething", "()V");

env->CallVoidMethod(obj, mid);
}

JNIEXPORT jobject JNICALL Java_codebazaar_example_JniExample_createAndReturnBean  (JNIEnv *env, jobject obj) {
jclass beanClass = env->FindClass("codebazaar/example/Bean");
jmethodID constructorMethodId = env->GetMethodID(beanClass, "", "()V"); 
jobject bean = env->NewObject(beanClass, constructorMethodId);

jstring newString = env->NewStringUTF("this bean has been created in Java_codebazaar_example_JniExample_createAndReturnBean");

jmethodID setStringMid = env->GetMethodID(beanClass, "setDataString", "(Ljava/lang/String;)V");

env->CallVoidMethod(bean, setStringMid, newString );

return bean;
}

JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_modifyBean  (JNIEnv *env, jobject obj, jobject bean) {
jclass beanCls = env->GetObjectClass(bean);

jmethodID setStringMid = env->GetMethodID(beanCls, "setDataString", "(Ljava/lang/String;)V");

// set the bean member "dataString"
jstring newString = env->NewStringUTF("world");
env->CallVoidMethod(bean, setStringMid, newString );

// set the bean member "dataByteArray"
jbyteArray newByteArray = env->NewByteArray(5);
jbyte buffer[5] = {5,4,3,2,1};
env->SetByteArrayRegion( newByteArray, 0, 5, buffer);

jmethodID setByteArrayMid = env->GetMethodID(beanCls, "setDataByteArray", "([B)V");

env->CallVoidMethod(bean, setByteArrayMid, newByteArray );
}


JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_crashTheJvm (JNIEnv *env, jobject obj) {
//stack overflow - http://en.wikipedia.org/wiki/Stack_overflow
double x[1000000];
}



UPDATE 04/02/11:
If you encounter the following error:
java.lang.UnsatisfiedLinkError: ${path here}/library.dll: This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem
I would recommend you to use Dependency Walker to verify your DLL can find all its dependencies. If you are missing msvcr90.dll, you may be missing the Microsoft Redistributable Merge Modules.

21 comments:

appleboy said...

hi i followed all the steps as in this tutorial and generated all necessary files but when i run the main it show an error..:(

Starting JavaMain...
Exception in thread "main" java.lang.UnsatisfiedLinkError: no JniExampleLibrary in java.library.path
at java.lang.ClassLoader.loadLibrary(Unknown Source)
at java.lang.Runtime.loadLibrary0(Unknown Source)
at java.lang.System.loadLibrary(Unknown Source)
at codebazaar.example.JniExample.(JniExample.java:5)
at codebazaar.example.Main.main(Main.java:11)

please help to fix this

Matthias Büchner said...

@appleboy
Are you running the example from Eclipse? If yes, can you double-check you correctly setup the environment variable PATH as explained in the video at 11:36? I set the environment variable PATH to %PATH%;c:\YOURPATHGOESHERE\ where YOURPATHGOESHERE is the directory containing JniExampleLibrary.dll.

If you are running the example for a command prompt, you can check the PATH with the command echo %PATH%

Good luck!

Anonymous said...

Hi! How can read in JNI a Vector (Java) which was passed as a parameter??

Matthias Büchner said...

When it comes to JNI, I would recommend to stick with primitives and simple classes. But you don't always have the choice. Here is how you can modify the code sample provided in the tutorial to pass a Vector to the DLL.

1/ In JniExample.java, add the following code:
public native void readVector(Vector vector);
public void runExampleWithVector() {
Vector vector = new Vector();
vector.add( new Integer(10) );
vector.add( new Integer(20) );
vector.add( new Integer(30) );

readVector( vector );
}

2/ Run the following command to the the new header file
javah -d C:\codebazaar\JniExample\cpp\JniExample codebazaar.example.JniExample
You can notive there is a new signature:
/*
* Class: codebazaar_example_JniExample
* Method: readVector
* Signature: (Ljava/util/Vector;)V
*/
JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_readVector
(JNIEnv *, jobject, jobject);

3/ In JniExampleLibrary.cpp, implement this method:
JNIEXPORT void JNICALL Java_codebazaar_example_JniExample_readVector (JNIEnv *env, jobject obj, jobject vector) {

jclass vectorCls = env->GetObjectClass(vector);
jmethodID elementAtMid = env->GetMethodID(vectorCls, "elementAt", "(I)Ljava/lang/Object;");
jobject integerElement = env->CallObjectMethod(vector, elementAtMid, 0);

jclass integerCls = env->GetObjectClass(integerElement);
jmethodID intValuetMid = env->GetMethodID(integerCls, "intValue", "()I");
int intValue = env->CallIntMethod(integerElement, intValuetMid);

printf("vector[0] = %d", intValue);
}

4/ Compile the DLL. You can check with Dependency Walker (http://www.dependencywalker.com/) that the new method is present.

5/ Call runExampleWithVector() from the main method in Main.java.
You can see in the log the DLL was able to read the first element in the Vector object:
vector[0] = 10

harzemli said...

firstly sorry for my bad english.i want to use ArrayList in Java code but i dont know how i handle this data structure or like this.how do i use these.if i cant use this what do you suggest to me for solve this problem.i may use object array but some arrays is dynamic and these are prolem for me.please help me.

Anonymous said...

Hi,

I resolved the UnsatisfiedLinkError by adding the library path here in Eclipse (no need to create the PATH variable):

Window->Preferences->Java->Installed JREs. Then choose your current JRE(JDK) and click Edit. Fill Default VM Arguments: -Djava.library.path=

Matthias Büchner said...

Yes, this is another way to set the library path! Thanks for sharing.

@harzemli Sorry I haven't answer your question yet. I will try to build an example with ArrayList for you shortly.

barik said...

Hello Mathias can you post a shorter one? because it is easier to understand if the tutorials is short and simple. and i have a request can you post java jni a global keyboard hook?

Mom & Dad Joslin said...

I am trying to use items from the ddk within the dll (like winusb). I am running into problems with VS when I point to the required header files in the ddk complaining about duplicated items (it appears to be getting confused between the SDK and DDK itemss). I can tell it to ignore default libraries and then point to DDK items until all the unreolved errors are resolved. However, at that point, the Java app (System.loadLibrary) refuses to recognize the dll. It complains about no library in the java.library.path. I double checked the java.library.path and the dll is in one of the directories that that path contains.

I am using VS 2008 and trying to do this in 64-bit (Vista and Windows 7). It is being built as x64. I am running a 64-bit JDK/JVM (Sun jdk1.6.0_22 64-bit server).

Any direction would be appreciated.

Thanks.

Matthias Büchner said...

@Mom & Dad Jolin, did you write a JNI DLL wrapper for winusb? Load the DDL with Dependency Walker (http://www.dependencywalker.com/) and make sure you can see the JNI stubs. The method should be named with the pattern : "package_class_method"

Also, make sure the DLL is compiled for a 64-bit machine.

aries2004 said...

I found your vector example very useful. could you please post a sample on the how to add more elements to the vector.

Matthias Büchner said...

I was asked how to use JNI to communicate between a Java application and a C# DLL. I found this tutorial: http://www.codeproject.com/KB/cross-platform/javacsharp.aspx. I haven't tried it but it looks good.

sats said...

Hi,
I used these steps and it was very useful. Do you know any yby which we can detect memory leaks in the DLL by this procedure?

Thanks,
Sathish S

sats said...

Hi,
I used these steps and it was very useful. Do you know any tools which we use to detect memory leaks in the DLL by this procedure?

Thanks,
Sathish S

Matthias Büchner said...

Hi sats!

This is a very late answer, sorry. I never used such a tool. I used Visual VM (http://visualvm.java.net/) and Your Kit (http://www.yourkit.com/) to monitor a JVM but their use might be limited when used to monitor an application using JNI.

.Net provides a very useful integration with Perfmon (Performance Monitor) which helped me monitor the memory usage very efficiently.

newDev said...

Great Tutorial. im a newbie to JNI and facing this error while compiling on eclipse
"Can't load IA 32-bit .dll on a AMD 64-bit platform"

newDev said...

Never-mind, got it fixed..

Guru said...

First of all thanks to u...this tutorial is very useful...

i followed the way u specified in your video. but at-last, when i run it from eclipse i got the following exception:

Starting JavaMain...
starting runExample1...
Exception in thread "main" java.lang.UnsatisfiedLinkError: com.cts.JniExample.callJavaMethod()V
at com.cts.JniExample.callJavaMethod(Native Method)
at com.cts.JniExample.runExample1(JniExample.java:34)
at com.cts.Main.main(Main.java:14)

can you plz help to fix this...

JNI_Roadkill said...

Hey Matthias,
Thanks for the great tutorial. I still have an advanced problem. I am interfacing my Java appln with a Visual C++ COM object. (Eclipse and VS 2010) The COM object calls a default event-handler function when an event occurs. That event handler function does not know anything about the JNIEnv pointer or the jobject. I considered making global references for JNIEnv and jobject but I am not sure if it would work well in a multi-threaded environment.
I can post code excerpts if that would help. Look forward to your suggestion. Thanks.

Francis orlando said...

This is very great and helpful. Thank you.

Anonymous said...

Thank you so much for this excellent example

It was a huge help.