2.1 JNI with C
Step 1: Write a Java Class that uses C Codes - HelloJNI.java
public class HelloJNI {
static {
System.loadLibrary("hello"); // Load native library at runtime
// hello.dll (Windows) or libhello.so (Unixes)
}
// Declare a native method sayHello() that receives nothing and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args) {
new HelloJNI().sayHello(); // invoke the native method
}
}
The static initializer invokes System.loadLibrary() to load the native library "Hello" (which contains the native method sayHello()) during the class loading. It will be mapped to "hello.dll" in Windows; or "libhello.so" in Unixes. This library shall be included in Java's library path (kept in Java system variable java.library.path); otherwise, the program will throw a UnsatisfiedLinkError. You could include the library into Java Library's path via VM argument -Djava.library.path=path_to_lib.
Next, we declare the method sayHello() as a native instance method, via keyword native, which denotes that this method is implemented in another language. A native method does not contain a body. The sayHello() is contained in the native library loaded.
The main() method allocate an instance of HelloJNI and invoke the native method sayHello().
Compile the "HelloJNI.java" into "HelloJNI.class".
> javac HelloJNI.java
Step 2: Create the C/C++ Header file - HelloJNI.h
Run javah utility on the class file to create a header file for C/C++ programs:
> javah HelloJNI
The output is HelloJNI.h as follows:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
The header declares a C function Java_HelloJNI_sayHello as follows:
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
The naming convention for C function is Java_{package_and_classname}_{function_name}(JNI arguments). The dot in package name shall be replaced by underscore.
The arguments:
JNIEnv*: reference to JNI environment, which lets you access all the JNI fucntions.
jobject: reference to "this" Java object.
We are not using these arguments in the hello-world example, but will be using them later. Ignore the macros JNIEXPORT and JNICALL for the time being.
The extern "C" is recognized by C++ compiler only. It notifies the C++ compiler that these functions are to be compiled using C's function naming protocol (instead of C++ naming protocol). C and C++ have different function naming protocols as C++ support function overloading and uses a name mangling scheme to differentiate the overloaded functions. Read "Name Mangling".
Step 3: C Implementation - HelloJNI.c
#include
#include
#include "HelloJNI.h"
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
Save the C program as "HelloJNI.c".
The header "jni.h" is available under the "\include" and "\include\win32" directories, where is your JDK installed directory (e.g., "c:\program files\java\jdk1.7.0").
The C function simply prints the message "Hello world!" to the console.
Compile the C program - this depends on the C compiler you used.
For MinGW GCC in Windows
> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
// Define and Set environment variable JAVA_HOME to JDK installed directory
// I recommend that you set JAVA_HOME permanently, via "Control Panel" ⇒ "System" ⇒ "Environment Variables"
> echo %JAVA_HOME%
// In Windows, you can refer a environment variable by adding % prefix and suffix
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c
// Compile HellJNI.c into shared library hello.dll
The compiler options used are:
-Wl: The -Wl to pass linker option --add-stdcall-alias to prevent UnsatisfiedLinkError (symbols with a stdcall suffix (@nn) will be exported as-is and also with the suffix stripped). (Some people suggested to use -Wl,--kill-at.)
-I: for specifying the header files directories. In this case "jni.h" (in "\include") and "jni_md.h" (in "\include\win32"), where denotes the JDK installed directory. Enclosed the directory in double quotes if it contains spaces.
-shared: to generate share library.
-o: for setting the output filename "hello.dll".
You can also compile and link in two steps:
// Compile-only with -c flag. Output is HElloJNI.o
> gcc -c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.c
// Link into shared library "hello.dll"
> gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o
Try nm (which list all the symbols) on the shared library produced to look for the sayHello() function. Take note the GCC added prefix _ and suffix @8 (the number of bytes of parameters). Check for the function name Java_HelloJNI_sayHello with type "T" (defined).
> nm hello.dll | grep say
624011d8 T _Java_HelloJNI_sayHello@8
For Cygwin GCC in Windows
You need to define the type __int64 as "long long" via option -D _int64="long long".
For gcc-3, include option -mno-cygwin to build DLL files which are not dependent upon the Cygwin DLL.
> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias
-I"\include" -I"\include\win32" -shared -o hello.dll HelloJNI.c
For gcc-4: I still cannot find the correct compiler option (-mno-cygwin is not supported). The Java program hangs!
Step 4: Run the Java Program
> java HelloJNI
or
> java -Djava.library.path=. HelloJNI
You may need to specify the library path of the "hello.dll" via VM option -Djava.library.path=
, as shown above.
Java programming with JNI