Java Native Interface (JNI) is a home-grown programming interface that’s bundled together with the Java Software Development Kit (SDK). JNI is critical because it allows developers to use code libraries or snippets of codes written in other programming languages such as C++ and C. An essential part of the JNI is the Invocation API which places a Java virtual machine (JVM) into applications written in native language so that developers can call the java code while still inside the native code.
Most developers are aware of performance and memory issues in Java. To get around these problems, a programmer can use native language. As compared to Java, native code is up to 20 times faster when running in the interpreted mode. What’s more, using native code gives developers the chance to use previously incompatible hardware routines and low level operating systems.
What’s in this tutorial?
We will cover how to call native C/C++ code from inside a java app. To properly benefit from this tutorial, the following components and tools are needed.
- The Java compiler javac.exe which comes integrated into the SDK.
- The Java virtual machine (JVM) which is java.exe that also comes integrated into the SDK.
- The native method C generator javah.exe that also ships with the SDK.
- Native header files together with complete library files which are all used to define JNI.
- Lastly, the C and C++ compiler whose role is to create a shared library. Here you can use either Visual C++ if you are working in a windows environment and cc when on a UNIX system.
JNI Components
Java Native Interface has two main components that give it all its capabilities and attributes. These are:
- h: this is a C/C++ header file component that maps Java onto the native parts. Javah ensures this file is automatically added in the app header files.
- javah: this is the tool that builds header files in C-style so that Java method signatures can be converted into native functions.
With these components in place, one can now start coding. We shall first look at calling C/C++ codes from Java applications.
Calling native code from Java applications
JNI allows the developer to exercise their creativity and go around problems that cannot be addressed in Java but can be solved in a native language. In such an instance JNI becomes an invaluable tool to the programmer in a couple of situations such as:
- When you have legacy code libraries or code that you want to use inside a java program.
- When you need to insert time-sensitive code in a faster, low-level programming language.
- When you need to work with features that are platform dependent and are not supported in the Java class library.
Steps involved in calling C/C++ from Java code
Write the java code
This is the first step in this process. Here the developer is writing Java code to satisfy three components: declaring the native method which will be called later; loading the shared library which contains the native code; and finally calling the native methods. Using the following code, we will learn a few things:
public class Example1 { public native int intMethodName(int n); public native boolean booleanMethodName(boolean bool); public native String stringMethodName(String text); public native int intArrayMethodName(int[] intArray); public static void main(String[] args) { System.loadLibrary("Example1"); Example1 example = new Example1(); int square = example.intMethodName(5); boolean bool = example.booleanMethodName(true); String text = sample.stringMethodName("JAVA"); int sum = sample.intArrayMethodName( new int[]{1,1,2,3,5,8,13} ); System.out.println("intMethodName: " + square); System.out.println("booleanMethodName: " + bool); System.out.println("stringMethodName: " + text); System.out.println("intArrayMethodName: " + sum); } }
In lines 3 to 6, our native methods are declared. We must then load the shared libraries which is done on line 10 where finally the methods are called in lines 12 through 15.
Compiling the java code
The next step is compiling the java code to bytecode. We can do this by using the inbuilt javac java compiler which comes with the SDK. Here we use the following command to compile the code to bytecode:
javac Example1.java
Creating C/C++ header files
The third step is creating the native language header files which sources the native functions’ signatures. One way to do this is using the javah native tool which is a C stub generator that comes with the SDK. This tool is meant to create header files defining C-style functions for every native method found in the java code file. We shall use the following command:
javah Example1
After running this command, you should expect to see results such as these: let’s call the file Example1.h
1. /* DO NOT EDIT THIS FILE - it is machine generated */ 2. #include <jni.h> 3. /* Header for class Example1 */ 4. 5. #ifndef _Included_Example1 6. #define _Included_Example1 7. #ifdef __cplusplus 8. extern "C" { 9. #endif 10. 11. JNIEXPORT jint JNICALL Java_Example1_intMethodName 12. (JNIEnv *, jobject, jint); 13. 14. JNIEXPORT jboolean JNICALL Java_Example1_booleanMethodName 15. (JNIEnv *, jobject, jboolean); 16. 17. JNIEXPORT jstring JNICALL Java_Example1_stringMethodName 18. (JNIEnv *, jobject, jstring); 19. 20. JNIEXPORT jint JNICALL Java_Example1_intArrayMethodName 21. (JNIEnv *, jobject, jintArray); 22. 23. #ifdef __cplusplus 24. } 25. #endif 26. #endif
Writing the native code
In this section, we introduce the writing of C/C++. At this stage, the developer should note that all signatures must resemble the function declarations in Example1.h for the proper functioning of the program. Here are examples of implementations written in C and C++.
1. #include "Example1.h" 2. #include <string.h> 3. 4. JNIEXPORT jint JNICALL Java_Example1_intMethodName 5. (JNIEnv *env, jobject obj, jint num) { 6. return num * num; 7. } 8. 9. JNIEXPORT jboolean JNICALL Java_Example1_booleanMethodName 10. (JNIEnv *env, jobject obj, jboolean boolean) { 11. return !boolean; 12. } 13. 14. JNIEXPORT jstring JNICALL Java_Example1_stringMethodName 15. (JNIEnv *env, jobject obj, jstring string) { 16. const char *str = (*env)->GetStringUTFChars(env, string, 0); 17. char cap[128]; 18. strcpy(cap, str); 19. (*env)->ReleaseStringUTFChars(env, string, str); 20. return (*env)->NewStringUTF(env, strupr(cap)); 21. } 22. 23. JNIEXPORT jint JNICALL Java_Example1_intArrayMethodNam 24. (JNIEnv *env, jobject obj, jintArray array) { 25. int i, sum = 0; 26. jsize len = (*env)->GetArrayLength(env, array); 27. jint *body = (*env)->GetIntArrayElements(env, array, 0); 28. for (i=0; i<len; i++) 29. { sum += body[i]; 30. } 31. (*env)->ReleaseIntArrayElements(env, array, body, 0); 32. return sum; 33. } 34. 35. void main(){}
Creating a shared library
The native code is housed in the shared library. Most compilers can now create shared libraries. Commands differ from compiler to compiler, you will need to link in your headers from the JDK.
Running the java program
At this point, we ensure the code runs appropriately and performs the required task. Since all Java code executes in the Java virtual machine, we need the Java runtime environment. We can use the Java interpreter, java which is inbuilt in the SDK. The command is:
java Example1
Running the Example1.class program should display the following results:
PROMPT>java Example1 intMethodName: 25 booleanMethodName: false stringMethodName: JAVA intArrayMethodName: 33 PROMPT>
Sources
https://en.wikipedia.org/wiki/Java_Native_Interface
https://developer.android.com/training/articles/perf-jni.html
http://carfield.com.hk/document/java/tutorial/jni_tutorial.pdf
https://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
https://www.techrepublic.com/article/discover-how-the-java-native-interface-works/
https://www.protechtraining.com/content/java_fundamentals_tutorial-_java_native_interface_jni