Calling C++ native code from Java

Recently, I faced a lot of troubles in finding a good article that completely explains the process of calling a C++ native function from Java. I did some experiments and I finally succeeded. I will be sharing the instructions step-by-step.

In this article, I will be taking a very basic example of taking an array of numbers as input in Java, computing their sum using a native C++ code and then printing the output back to the console using Java. I would be keeping the article concise and would encourage you to explore the official documentation in case you run into some issues. Also, I will be using Windows and GCC compilers. A 32-bit C++ compiler is compatible with 32-bit Java compiler and a 64-bit C++ compiler is compatible with 64-bit Java compiler. MinGW doesn't publish x64 versions of GCC compilers for Windows (it is only available for Linux). So, in case you are using Windows make sure that you install 32-bit Java version. I am also assuming that you are using JDK 8 or some above version. All the mentioned commands must be run from the same directory unless specified otherwise.

Let's begin.

1. Create a C++ shared library

We will create a shared library that is written in C++ along with a header file which will only contain the signature of the native method. Remember, on Windows the shared libraries have .dll extension by convention and on Linux the shared libraries have .so extension.

To compile:-

> g++ -shared -fPIC NativeSumCalculator.cpp -o NativeSumCalculator.dll

-shared flag indicates generating a shared library that is dynamically loaded during runtime (if this library is supposed to be in a separate directory, then add the directory to the PATH) and -fPIC generates a position independent code. I am using .dll extension on Windows. Use .so extension on Linux (By default, Java searches for .dll file on Windows and .so on Linux when System.loadLibrary() is used. We would be using System.load() instead of System.loadLibrary(), which takes an absolute path to avoid confusion).

Once compiled, you may delete the C++ file ( and not the header file) as by doing this, we would be in a position that we only would be having the shared library that calculates the sum. Generally, this is always the case. The native code is in the form of a shared library with available method signature in a header file and we are supposed to write a bridge and a wrapper to call such methods.

Important: Move this shared library to one of the Java library locations. Use the command below to find out the location. Java will search for dependencies in these locations.

> java -XshowSettings:properties -version

2. Write the Java code and generate the JNI headers

Our Java code has a single class SumCalculator with a native method which when called would be resolved to the C++ method. Compile the Java code.

> javac SumCalculator.java

You will get a class file. Use the javah tool (available with JDK) to generate the JNI header file. This JNI header file is a C++ header file and would be implemented in C++.

> javah SumCalculator

Note: From JDK 8 and above, the compilation and generation of JNI header file has been combined in a single command. -h tells the directory to place the generated header file. We would be placing it in the header file in the current working directory.

> javac SumCalculator.java -h .

3. Implement the header file in C++

A SumCalculator.h header file would be generated after the above step. The contents of the file would be as follows:

The above header file would be implemented by the following C++ file. This C++ code would convert the Java types to C++ types, call the native library function and return the result back in the form of Java types. You may look at the official documentation for details regarding the conversion of Java and C++ types.

Compiling this C++ file is a bit tricky.

> g++ -shared -fPIC NativeBridge.cpp -o NativeBridge.dll -L. -l:NativeSumCalculator.dll -I "C:\Program Files\Java\jdk-12.0.1\include" -I "C:\Program Files\Java\jdk-12.0.1\include\win32"

-L flag tells the compiler about the directory containing the shared library (please specify the directory in case the shared library is not in the current working location). -l (small l) flag tells the compiler about the full name of the shared library file. -I (capital i) flag adds two C++ headers include folders which contain jni.h and jni_md.h. These header files contain information about the Java types. Replace the C++ include folders with the folder containing the header files.

4. Load the library and run the Java program

We will be modifying the Java program to load the library by adding a static line. Please note that System.load() takes an absolute path to the library. The dependent shared library (NativeSumCalculator.dll) would be automatically loaded as it is in Java library location. Once loaded, re-compile the program and run it. The output would be displayed.


> javac SumCalculator.java
> java SumCalculator

Conclusion

That's it. Now, you are able to run native C++ code from Java. Such calls can be highly effective in large computations were JVMs generally prove to be slower than running a native version of the code. You might run into the very famous UnsatisfiedLinkError for sure. In that case, please ensure that you had followed the article properly. You may mention your problem in the comments and I will try my best to resolve them.

Comments

Popular posts from this blog

DDoS Attack on Bitotsav '19 Website

Setting up Machine Learning environment on High Performance Computing Server

Architecture of High Performance Computing Server at BIT Mesra