In today’s world most systems are architected as a set of services implemented in different programming languages deployed on multiple hardware platforms. Often it is required for a piece of software written in a different programming language than the module it needs to interface with. I have been in situations when modules written in C# or Java had the need to interface with code written using the C programming language (typically for performance, separations of concern reasons or to allow the Java program access functionality written in a different language).
The Java Native Interface (JNI) is a programming framework that enables Java code running in a Java Virtual Machine (JVM) to call and be called by native applications (programs specific to a hardware and operating system platform) and libraries written in other languages such as C, C++ and assembly.
JNI enables programmers to write native methods to handle situations when an application cannot be written entirely in the Java programming language, e.g. when the standard Java class library does not support the platform-specific features or program library. It is also used to modify an existing application—written in another programming language—to be accessible to Java applications. Many of the standard library classes depend on JNI to provide functionality to the developer and the user, e.g. file I/O and sound capabilities. Including performance- and platform-sensitive API implementations in the standard library allows all Java applications to access this functionality in a safe and platform-independent manner.
The JNI framework lets a native method use Java objects in the same way that Java code uses these objects. A native method can create Java objects and then inspect and use these objects to perform its tasks. A native method can also inspect and use objects created by Java application code.
On Windows platforms, Structured Exception Handling (SEH) may be employed to wrap native code in SEH try/catch blocks so as to capture machine (CPU/FPU) generated software interrupts (such as NULL pointer access violations and divide-by-zero operations), and to handle these situations before the interrupt is propagated back up into the JVM (i.e. Java side code), in all likelihood resulting in an unhandled exception.
JNI incurs considerable overhead and performance loss under certain circumstances:
- Function calls to JNI methods are expensive, especially when calling a method repeatedly.
- Native methods are not in-lined by the JVM, nor can the method be JIT compiled, as the method is already compiled.
- A Java array may be copied for access in native code, and later copied back. The cost can be linear in the size of the array.
- If the method is passed an object, or needs to make a callback, then the native method will likely be making its own calls to the JVM. Accessing Java fields, methods and types from the native code requires something similar to reflection. Signatures are specified in strings and queried from the JVM. This is both slow and error-prone.
- Java Strings are objects, have length and are encoded. Accessing or creating a string may require an O(n) copy.
In this blog entry I will develop a couple pieces of code that illustrate the use of JNI when a Java application needs to call a function written in C.
Using Visual Studio 2013 Professional Edition I wrote the MyCLib.c file:
// **** SYSTEM header files ****
#include “stdafx.h”
#include <stdio.h>
// **** MANDATORY JNI header file ****
#include “jni.h”
// **** APPLICATION header files ****
#include “MyCLib.h”
//void Hello ()
JNIEXPORT void JNICALL Java_com_canessa_john_jnitest_CallMyCLib_Hello (
JNIEnv *env,
jobject jObject
)
/*
****************************************************************************@@
– This is a method that displays a message to the console.
******************************************************************************
*/
{
//printf(“Hello <<< hello world !!!\n”);
printf(“Java_com_canessa_john_jnitest_CallMyCLib_Hello <<< hello world !!!\n”);
fflush(stdout);
}
At this point, please disregard the included jni.h file. We will deal with it later.
Note the two lines commented out. The original function was named Hello() and did not include arguments. When called a message was displayed indicating the original name of the function. This code was wrapped in a DLL (Dynamic Link Library) named: MyCLib.dll
The VS 2013 solution included a second project named TestLib. The purpose of this project was to verify the operation of the Hello() function.
A screen capture of original console follows:
main <<< before Hello() !!!
Hello <<< hello world !!!
main <<< after Hello() !!!
My machine is running a 64-bit version of Java. One must make sure that the VS solution is generated for 64-bit.
Wrote the following Java code using the Eclipse IDE:
package com.canessa.john.jnitest;
public class CallMyCLib {
// **** name for the C library ****
static final String externalLibrary = “MyCLib”;
// **** inform JVM of the C method that we will use ****
native void Hello();
// **** load the external C library holding the external method(s) ****
static {
System.loadLibrary(externalLibrary);
}
/**
* test code
* @param args
*/
static public void main(String[] args) {
// **** instantiate the default constructor ****
CallMyCLib cFunc = new CallMyCLib();
// **** make a call to at function in a C library ****
System.out.println(“main <<< before cFunc.Hello”);
cFunc.Hello();
System.out.println(“main <<< after cFunc.Hello”);
}
}
Note that the name of the external library matches the name of the DLL written in the previous step. The name of the method is Hello(). After instantiating the class, the method in the DLL written in C is invoked.
At this point we need to modify the original Hello() function in the DLL. To achieve this we need to execute from a command prompt the following command:
javah -d <outputdir> -classpath <classpath> <fully_qualified_class>
‘outputdir’ is the directory where the header file is generated
‘classpath’ contains an absolute path to the directory containing the root package
‘fully_qualified_class’ the name of the class containing native methods WITHOUT the .class extension
The command in our case follows:
C:\> javah -d C:\Users\John\workspace\JNITest\bin\com\canessa\john\jnitest -classpath C:\Users\John\workspace\JNITest\bin com.canessa.john.jnitest.CallMyCLib
The javah command generates the following file in the following directory:
C:\Users\John\workspace\JNITest\bin\com\canessa\john\jnitest> dir
Volume in drive C is OS
Volume Serial Number is 26E8-87B0
Directory of C:\Users\John\workspace\JNITest\bin\com\canessa\john\jnitest
12/20/2016 02:01 PM <DIR> .
12/20/2016 02:01 PM <DIR> ..
12/20/2016 02:00 PM 886 CallMyCLib.class
12/20/2016 02:01 PM 523 com_canessa_john_jnitest_CallMyCLib.h
2 File(s) 1,409 bytes
2 Dir(s) 760,758,013,952 bytes free
The contents of the com_canessa_john_jnitest_CallMyCLib.h file follows:
C:\Users\John\workspace\JNITest\bin\com\canessa\john\jnitest> type com_canessa_john_jnitest_CallMyCLib.h
/* DO NOT EDIT THIS FILE – it is machine generated */
#include <jni.h>
/* Header for class com_canessa_john_jnitest_CallMyCLib */
#ifndef _Included_com_canessa_john_jnitest_CallMyCLib
#define _Included_com_canessa_john_jnitest_CallMyCLib
#ifdef __cplusplus
extern “C” {
#endif
/*
* Class: com_canessa_john_jnitest_CallMyCLib
* Method: Hello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_canessa_john_jnitest_CallMyCLib_Hello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Of interest is the updated declaration of the Hello() function in the MyCLib.c source file. The original declaration is updated using the contents of this file.
We also need to add the jni.h header file to the MyCLib.c file. In actuality there are two files that need to be added. They are:
From the following folder:
C:\ProgramData\Java\jdk1.8.0_112\include\win32> dir
Volume in drive C is OS
Volume Serial Number is 26E8-87B0
Directory of C:\ProgramData\Java\jdk1.8.0_112\include\win32
11/24/2016 08:46 AM <DIR> .
11/24/2016 08:46 AM <DIR> ..
11/24/2016 08:46 AM <DIR> bridge
11/24/2016 08:46 AM 898 jawt_md.h
11/24/2016 08:46 AM 485 jni_md.h <=== ADD !!!
2 File(s) 1,383 bytes
3 Dir(s) 760,757,850,112 bytes free
From the following folder:
C:\ProgramData\Java\jdk1.8.0_112\include> dir
Volume in drive C is OS
Volume Serial Number is 26E8-87B0
Directory of C:\ProgramData\Java\jdk1.8.0_112\include
12/19/2016 01:37 PM <DIR> .
12/19/2016 01:37 PM <DIR> ..
11/24/2016 08:46 AM 20,128 classfile_constants.h
11/24/2016 08:46 AM 8,690 jawt.h
11/24/2016 08:46 AM 6,407 jdwpTransport.h
11/24/2016 08:46 AM 73,701 jni.h <=== ADD ME!!!
11/24/2016 08:46 AM 77,428 jvmti.h
11/24/2016 08:46 AM 3,774 jvmticmlr.h
11/24/2016 08:46 AM <DIR> win32
7 File(s) 190,613 bytes
3 Dir(s) 760,757,719,040 bytes free
For simplicity I copied jni_md.h (included by jni.h) into the folder holding the jni.h file as illustrated by:
C:\ProgramData\Java\jdk1.8.0_112\include> dir
Volume in drive C is OS
Volume Serial Number is 26E8-87B0
Directory of C:\ProgramData\Java\jdk1.8.0_112\include
12/19/2016 01:37 PM <DIR> .
12/19/2016 01:37 PM <DIR> ..
11/24/2016 08:46 AM 20,128 classfile_constants.h
11/24/2016 08:46 AM 8,690 jawt.h
11/24/2016 08:46 AM 6,407 jdwpTransport.h
11/24/2016 08:46 AM 73,701 jni.h <=== ADD ME!!!
11/24/2016 08:46 AM 485 jni_md.h <=== ADD ME!!!
11/24/2016 08:46 AM 77,428 jvmti.h
11/24/2016 08:46 AM 3,774 jvmticmlr.h
11/24/2016 08:46 AM <DIR> win32
7 File(s) 190,613 bytes
3 Dir(s) 760,757,637,120 bytes free
We now need to add the include path C:\ProgramData\Java\jdk1.8.0_112\include in the MyCLib project. Select: MyCLIb->Properties->Configuration Properties->C/C++->General->Additional Include Directories and add the specified path:
C:\Users\John\Documents\Visual Studio 2013\Projects\MyCLib\MyCLib;C:\ProgramData\Java\jdk1.8.0_112\include
Now we are ready to test the modified DLL file as illustrated by the output in the following console IDE screen capture:
main <<< before Hello() !!!
Java_com_canessa_john_jnitest_CallMyCLib_Hello <<< hello world !!!
main <<< after Hello() !!!
This example illustrated the use of JNI to make a function call in a C DLL from a Java application.
In the near future I will use the JNA (Java Native Access) to perform the same task. It is always good for developers to be able to perform the same tasks in different ways, each having some advantages over others.
If you have comments or questions regarding this entry or any other entry in this blog, please do not hesitate and send me a message. I will reply as soon as possible and will not use your name unless you explicitly indicate so.
John
john.canessa@gmail.com
Follow me on Twitter: @john_canessa