Skip to main content

Calling C++ Native Code from Java

Recently, I encountered significant trouble finding a good article that completely explains the process of calling a C++ native function from Java. I conducted some experiments and finally succeeded. I will be sharing the instructions step-by-step.

In this article, I will present 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 will keep the article concise and encourage you to explore the official documentation if you encounter any issues.

I will be using Windows and GCC compilers for this demonstration. A 32-bit C++ compiler is compatible with a 32-bit Java compiler, and a 64-bit C++ compiler is compatible with a 64-bit Java compiler. MinGW does not publish x64 versions of GCC compilers for Windows (it is only available for Linux). Therefore, if you are using Windows, ensure that you install the 32-bit Java version. I am also assuming that you are using JDK 8 or a later version.

All the mentioned commands must be run from the same directory unless specified otherwise.

Let's begin.

How To?

1. Create a C++ shared library

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

To compile:-

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

The -shared flag indicates the generation of 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 LD_LIBRARY_PATH environment variable.) The -fPIC flag generates position-independent code. On Windows, use the .dll extension for shared libraries. On Linux, use the .so extension. By default, Java searches for .dll files on Windows and .so files on Linux when System.loadLibrary() is used. We will be using System.load() instead of System.loadLibrary(), which takes an absolute path to avoid confusion.

Once compiled, you may delete the cpp file (but not the header file). By doing this, we will be in a situation where we have only the shared library that calculates the sum. Generally, this is always the case. The native code exists in the form of a shared library with available method signatures 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 following command 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 that, when called, will be resolved to the C++ method. Compile the Java code.

> javac SumCalculator.java

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

> javah SumCalculator

Note: From JDK 8 and above, the compilation and generation of the JNI header file have been combined into a single command. The -h option specifies the directory to place the generated header file. We will be placing it 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 will be implemented by the following C++ file. This C++ code will convert Java types to C++ types, call the native library function, and then return the result back in the form of Java types. You may refer to 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"

The -L flag specifies the directory containing the shared library. (If the shared library is not located in the current working directory, you must specify the directory.) The -l flag (lowercase 'l') specifies the base name of the shared library file. The -I flag (uppercase 'I') is used to include directories that contain header files, such as jni.h and jni_md.h. These header files contain information about Java types. Replace the default C++ include folders with the actual folder containing your header files.

4. Load the library and run the Java program

We will modify 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) will be automatically loaded as it is located in the Java library location. Once loaded, recompile the program and run it. The output will 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 where JVMs generally prove to be slower than running a native version of the code. You might encounter the very famous UnsatisfiedLinkError for sure. In that case, please ensure that you have 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

Architecture of High Performance Computing Server at BIT Mesra

A High-Performance Computing (HPC) server was installed a few years back. It was a replacement for PARAM 10000, the supercomputer that is no longer available for use. Initially, the HPC was under the Department of Computer Science. The Department of Chemical Engineering and Biotechnology was the primary user of the HPC (mostly for simulation purposes), and so the administration decided to move it under the Central Instrumentation Facility (CIF). You need permission from the CIF to access the HPC. HPC is only available for research purposes, and you need to provide a good reason along with a proper recommendation from a professor to gain access to the HPC. The HPC is at least 20 times more powerful than the most powerful PC that anyone has on campus. Also, I recently checked the usage and realized that not even 10% of its power is being utilized. I hope this blog post will help you in understanding the core architecture of the HPC. Architecture The Architecture of High Performance Compu...

Setting up Machine Learning Environment on High Performance Computing Server

In the last article, I discussed the architecture of the HPC. If you have not read that article, I would recommend that you read it before proceeding with this one. Architecture of High-Performance Computing Server at BIT Mesra  The power of HPC can be utilized for its most important application in the field of computer science: Machine Learning. I am assuming that you already have obtained your SSH credentials to log on to the master node. Also, we will be setting up the environment in Python. Let's jump straight to the steps. How To? Step 1: Download and Install Anaconda on the Master Node Note that you are not the root user of the HPC. You are just a regular user, and therefore, administrative commands (such as sudo or su) will not work. Anaconda has made it much easier to install Python packages for non-root users, and we will be using Anaconda for setting up Python 3 and installing the required packages. Log in to the master node     > ssh be1005815@172.16.23.1 Go...