Introducing Java Native Interface (JNI) – Foreign (Function) Memory API


137. Introducing Java Native Interface (JNI)

Java Native Interface (JNI) was the first Java API meant to act as a bridge between JVM bytecode and the native code written in another programming language (typically C/C++).Let’s suppose that we plan to call via JNI a C function on a Windows 10, 64-bit machine.For instance, let’s consider that we have a C function for summing two integers called sumTwoInt(int x, int y). This function is defined in a C shared library named math.dll. Calling such functions from Java (generally speaking, functions implemented by native shared libraries) starts by loading the proper shared native library via System.loadLibrary(String library). Next, we declare the C function in Java via the native keyword. Finally, we call it in the following code:

package modern.challenge;
public class Main {
  static {      
    System.loadLibrary(“math”);
  }
  private native long sumTwoInt(int x, int y);
  public static void main(String[] args) {
    long result = new Main().sumTwoInt(3, 9);
    System.out.println(“Result: ” + result);
  }  
}

Next, we focus on C implementation. We need the header file (.h file) and the source file that implements this method (.cpp file).

Generating the header (.h) file

The header file (definition of the method) can be obtained by running javac with the –h option against our Main.java source as in the following figure (before JDK 9, use javah):

Figure 7.1 – Running javac –h to compile source code and generate .h file

Or, as plain text:

C:\SBPBP\GitHub\Java-Coding-Problems-Second-Edition\Chapter07\P137_EngagingJNI>javac –h src/main/java/modern/challenge/cpp –d target/classes src/main/java/modern/challenge/Main.java

This command compiles our code (Main.java) and places the resulting class in the target/classes folder. In addition, this command generates the C header modern_challenge_Main.h in jni/cpp. The important code of this file is listed here:

/*
 * Class:     modern_challenge_Main
 * Method:    sumTwoInt
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_modern_challenge_Main_sumTwoInt
  (JNIEnv *, jobject, jint, jint);

The function name was generated as Java_modern_challenge_Main_sumTwoInt. Moreover, we have here the following artifacts:

JNIEXPORT – the function is marked as exportable

JNICALL – sustains JNIEXPORT to guarantee that the function can be foud by JNI

JNIEnv – represents a pointer to the JNI environment for accessing JNI functions

jobject – represents a reference to this Java object

Implementing the modern_challenge_Main.cpp

Next, we provide the C implementation in src/main/java/modern/challenge/cpp as follows:

#include <iostream>
#include “modern_challenge_Main.h”
JNIEXPORT jlong JNICALL Java_modern_challenge_Main_sumTwoInt
  (JNIEnv* env, jobject thisObject, jint x, jint y) {
    std::cout << “C++: The received arguments are : “
      << x << ” and ” << y << std::endl;
  return (long)x + (long)y;
}

This is a simple snippet of C code that prints a message and returns x + y as a long result.

Compiling the C source code

So far, we have the C source code (the .cpp file) and the generated header (.h). Next, we have to compile the C source code and for this, we need a C compiler. There are many options, like Cygwin, MinGW, and so on.We decided to install MinGW (https://sourceforge.net/projects/mingw-w64/) for 64-bit platforms and use the G++ compiler.Having G++ in our hands, we have to trigger a specific command for compiling the C code as in the following figure:

Figure 7.2 – Compiling the C source code

Or, as plain text:

C:\SBPBP\GitHub\Java-Coding-Problems-Second-Edition\Chapter07\P137_EngagingJNI>g++ -c                    “-I%JAVA_HOME%\include” “-I%JAVA_HOME%\include\win32” src/main/java/modern/challenge/cpp/modern_challenge_Main.cpp  –o jni/cpp/modern_challenge_Main.o

Next, we have to pack everything in math.dll.

Generating the native shared library

It is time to create the native shared library, math.dll. For this, we use G++ again as in the following figure:

Figure 7.3 – Creating the math.dll

Or, as plain text:

C:\SBPBP\GitHub\Java-Coding-Problems-Second-Edition\Chapter07\P137_EngagingJNI>g++ -shared –o jni/cpp/math.dll jni/cpp/modern_challenge_Main.o –static –m64 –Wl,–add-stdcall-alias                  

Notice that we have used the –static option. This option instructs G++ to add in math.dll all dependencies. If you dislike this approach then you may need to manually add the dependencies in order to avoid java.lang.UnsatisfiedLinkError errors. To find out the missing dependencies you can use a DLL Dependency Walker such as this one: https://github.com/lucasg/Dependencies.

Finally, run the code

Finally, we can run the code. Keep your fingers crossed and execute the command from the following figure:

Figure 7.4 – Executing the Java code

Or, as plain text:

C:\SBPBP\GitHub\Java-Coding-Problems-Second-Edition\Chapter07\P137_EngagingJNI>java
–Djava.library.path=jni/cpp –classpath target/classes modern.challenge.Main

Notice that we should set the library path, otherwise, Java will not be able to load math.dll. If everything worked fine then you should see the output from this figure.Well, as you can easily conclude, JNI is not easy to use. Imagine doing all this work for an entire C library like Tensorflow which has 200+ functions. Besides being hard to use, JNI also faces a lot of shortcomings such as: error-prone, hard to maintain, brittle, has poor exception support, JNI errors can crush JVM, has a maximum off-heap of 2 GB allocated via ByteBuffer which cannot be directly free (we have to wait for the Garbage Collector to do it), and much more.Having this in mind, the community comes with other approaches that we will discuss in the next problems.

Leave a Reply

Your email address will not be published. Required fields are marked *