Tuesday, June 11, 2013

Call C functions from Java code (JNI-Java Native Interface).

Problem Statement

  • Call C-native function from java code.
  • Pass a message string from Java code to native function.
  • Native function prints this message.
  • Native function returns the same message to calling java method.
  • java method prints this returned string.

Environment

  • Java SDK 1.7.0
  • Visual studio 10.0 as C compiler.

Steps to be followed:

  1. Write a java class (say HelloWorld.java) that declares the native method.
  2. Compile this java class(HelloWorld.class).
  3. Create a C header file using javah tool (HelloWorld.h).
  4. Implement the native method in C (HelloWorld.c).
  5. Compile this C implementaion and create a HelloWorld.dll file.
  6. Run the HelloWorld using java runtime interpreter.

Step 1 : Write HelloWorld.java 


public class HelloWorld {   
     private native String print(String msg);
     public static void main(String[] args) {
         System.out.println("C function says " + new HelloWorld().print("JAVA"));       
     }
     static {
         System.loadLibrary("HelloWorld");
     }


Step 2 : Compile HelloWorld.java

 

Run javac HelloWorld.java in command line. This generates HelloWorld.class file


Step 3: Generate C-header  file

 

Run javah -jni HelloWorld in command line. This generates HelloWorld.h file. This is a C-Header file contains the functions signature for native method. We will later see the content of this file.

Step 4: Implement the native method in HelloWorld.c

 

 #include "jni.h"
 #include <stdio.h>
 #include "HelloWorld.h"

 JNIEXPORT jstring JNICALL
 Java_HelloWorld_print(JNIEnv *env, jobject obj, jstring msg)
 {
     const jbyte *str;
     printf("Inside C funcion!\n");   
     str = (*env)->GetStringUTFChars(env, msg, NULL);
     printf("Msg from Java layer %s\n",str);   
     return msg;   
 }


Step 5: Compile this class file to create a HelloWorld.dll file

 

Here C code is compiled using Visual studio. Other compilers (like gcc) might have a different syntax for compiling. 

  • Run vcvars.bat in command line to set up environment for visual studio tools.

  • Run cl -I"C:\Program Files\Java\jdk1.6.0_32\include" -I"C:\Program Files\Java\jdk1.6.0_32\include\win32" -MD -LD HelloWorld.c -FeHelloWorld.dll . This generates a HelloWorld.dll file. -LD option instructs compiler to gerenate a .dll file in place of .exe file.

Step 6: Run HelloWorld using java runtime interpreter

 

Run java HelloWorld. 

 

 

Behind the scene

 

Let us start by inspecting HelloWorld.h . Open this file in some editor. Following are the important points to be noticed.
  • This file includes jni.h . JNI provides the functionality to call native code and provide the types that is used in this header file. jni.h must be explicitly called included during compile time.
  •  This file contains prototype function for HelloWorld that's called in Java code. "Java_HelloWorld_print" is the prototype function. Naming convention is Java_<class_name>_<method_name>. This is also known as name mangling. This is important as using this java virtual machine links java calls to native method.
  •  First two arguments of this prototype function is always present even when no argument is passed to java call. The third argument in our case (and so on) is the argument which is passed to java method. in our case we have passed String to print method which gets mapped to jstring in Java_HelloWorld_print function.
  • Now coming to first argument JNIEnv * . This is a pointer known as interface pointer . This interface pointer points to a pointer which again points a structure of pointers. This structure of pointers contains pointer to interface functions (or JNI functions). These functions are accessed as (*env)->GetStringUTFChars(env, msg, NULL), where GetStringUTFChars is the JNI function.

Now open HelloWorld.c in an editor. Following are the important point to be noticed.
  • This C-file must include the generated header file, in our case HelloWorld.h
  • It must includes jni.h
  • print method's signature should be identical as defined in generated header file. This function returns jstring (String in java) to the calling Java method and receives a jstring (String in java) from the java call.
Our problem statement requires that native function must print the message (java String) passed to it by the java method.  String representation is bit different in Java and C. In C a string is represented by array of characters terminated by NULL character.

So to print a java string in C function we will use (*env)->GetStringUTFChars(env, msg, NULL)  JNI function. This returns a jbyte * ,using this pointer we can print the string in native C. This native function returns jstring type message to the calling java method.