JNI Tutorial

February 12, 2020

Rate this post

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)
     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 */
 5. #ifndef _Included_Example1
 6. #define _Included_Example1
 7. #ifdef __cplusplus
 8. extern "C" {
 9. #endif
11. JNIEXPORT jint JNICALL Java_Example1_intMethodName
12.   (JNIEnv *, jobject, jint);
14. JNIEXPORT jboolean JNICALL Java_Example1_booleanMethodName
15.   (JNIEnv *, jobject, jboolean);
17. JNIEXPORT jstring JNICALL Java_Example1_stringMethodName
18.  (JNIEnv *, jobject, jstring);
20. JNIEXPORT jint JNICALL Java_Example1_intArrayMethodName
21.  (JNIEnv *, jobject, jintArray);
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>
 4. JNIEXPORT jint JNICALL Java_Example1_intMethodName
 5.   (JNIEnv *env, jobject obj, jint num) {
 6.    return num * num;
 7. }
 9. JNIEXPORT jboolean JNICALL Java_Example1_booleanMethodName
10.   (JNIEnv *env, jobject obj, jboolean boolean) {
11.   return !boolean;
12. }
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. }
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. }
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 



Eclipse Java Tutorial