Code in Java, Execute as C++.. How To Call Java Method From C++ Using... | by Dwi Prasetyo Adi Nugroho | Towards Data Science

Java and C++ remain two of the most popular programming languages. The two languages have different designs and characteristics. Depending on the problem, one might work better than the other. However, at some point, we need to integrate these languages, e.g. calling a method written in Java to your C++ code. The need to integrate Java and C++ is not something new. In fact, we can find a tutorial dated back to 1996. Since then, the two languages have developed remarkably.
In this tutorial, we are going to learn how to call a java method from a C++ using GraalVM native image. If this is the first time you heard about GraalVM, you might be interested in checking out their homepage. GraalVM is a virtual machine that you can use to run a Java application. It’s based on JVM, but with additional features such as ahead of time compilation, fewer memory footprints, and other advanced optimizations. Additionally, GraalVM provides a native image plugin that you can use to compile a Java application to a native application. That means your application can run without requiring a JVM installed and running. Interestingly, you can also use this native image plugin to compile your Java application into a shared library and load it from your C++ code. This comes very handily when you code mainly written in C++ but you want to use an existing Java library on some part of it.
Before diving into the tutorial, let’s have a look at how this thing works. Basically, what you will do are the following:
Write your method in Java.Compile the Java code to C++ shared library (yourlib.so) and header (yourlib.h) file using GraalVM.Load the library and header to your C++ project.
Write your method in Java.
Compile the Java code to C++ shared library (yourlib.so) and header (yourlib.h) file using GraalVM.
Load the library and header to your C++ project.
In this example, we will simulate a use case where you want to use a function from a third-party Java library on your C++ project. Particularly, we are interested to use one of the math functions from Google’s Guava library.
First, we need to set up a maven project for our java method. Building our project in maven helps us to import the dependencies as well as utilizing the GraalVM native image plugin to create the shared library. Once the project is created, add the dependencies to Guava library (or any library that you need) and the GraalVM library to your pom.xml file:
<dependencies>    <!-- https://mvnrepository.com/artifact/org.graalvm.nativeimage/svm -->    <dependency>        <groupId>org.graalvm.nativeimage</groupId>        <artifactId>svm</artifactId>        <version>19.3.1</version>    </dependency>    <dependency>        <groupId>com.google.guava</groupId>        <artifactId>guava</artifactId>        <version>28.2-jre</version>    </dependency></dependencies>
Next, add the following plugin to build your java code to C++ shared library:
<build>    <finalName>libmymath</finalName>    <plugins>        <plugin>            <groupId>com.oracle.substratevm</groupId>            <artifactId>native-image-maven-plugin</artifactId>            <version>20.0.0</version>            <executions>                <execution>                    <goals>                        <goal>native-image</goal>                    </goals>                    <phase>package</phase>                </execution>            </executions>            <configuration>                <buildArgs>--shared -H:Name=libmymath</buildArgs>            </configuration>        </plugin>    </plugins></build>
Let’s have a look at the bold parts. On the <finalName> tag, put a name that you want as the name of your library. Note the version that you specify on the <version> tag as you will need it later. On the <buildArgs>, we specify the shared and name parameter to instruct the GraalVM native image to create a shared library instead of a native executable. Put the same name as finalName on the Name parameter.
Now let’s prepare our Java method. Given an integer input x, we are interested to calculate the nearest power of 2 greater x. For example, if x is 14 it will return 16, as 16 is the nearest power of 2 greater than 14. Below is the snippet of our function. Note that we need to put an additional first parameter thread of type IsolateThread to bridge our Java and C++ execution. This parameter has to be in the first position. We also need to specify the name of the method in the CEntryPoint annotation. This is the method name that we will refer to in our C++ code. You can check the complete java project in this repository.
import com.google.common.math.IntMath;import org.graalvm.nativeimage.IsolateThread;import org.graalvm.nativeimage.c.function.CEntryPoint;public class MyMath {    @CEntryPoint (name = "ceilingPowerOfTwo")    public static int ceilingPowerOfTwo(IsolateThread thread, int x) {       return IntMath.ceilingPowerOfTwo(x);    }}
Having our project set, we now need to run mvn package to compile our Java method to a shared library. However, as this functionality only available on GraalVM, we will need to set up the GraalVM and point our JAVA_HOME to the GraalVM binary. Therefore, you will need to do the following:
Download GraalVM. Make sure you pick the correct architecture, java version, and the GraalVM version that you specify in the pom.xml. Specifically, this example project uses GraalVM 20.0.0 for Java 8.Install the native-image extension by using the gu binary. In a Unix environment, you can do that using: <path_to_graalvm_directory>graalvm-ce-java8–20.0.0 2bin/gu install native-imageUpdate JAVA_HOME to point to the downloaded GraalVM directory. In a Unix environment, you can use export JAVA_HOME=<path_to_graalvm_directory/graalvm-ce-java8–20.0.0 2/>.
Download GraalVM. Make sure you pick the correct architecture, java version, and the GraalVM version that you specify in the pom.xml. Specifically, this example project uses GraalVM 20.0.0 for Java 8.
Install the native-image extension by using the gu binary. In a Unix environment, you can do that using: <path_to_graalvm_directory>graalvm-ce-java8–20.0.0 2bin/gu install native-image
Update JAVA_HOME to point to the downloaded GraalVM directory. In a Unix environment, you can use export JAVA_HOME=<path_to_graalvm_directory/graalvm-ce-java8–20.0.0 2/>.
Now you can call the mvn package install to build the shared library. It will generate several files: graal_isolate.h, graal_isolate_dynamic.h, libmymath.h, libmymath_dynamic.h, and libmymath.so (or in other formats in other OS). Aside from our own library, it will also generate Graal VM C++ libraries.
Let’s now use our shared library in the following simple C++ project. Put the generated file inside the directory of this C++ project. For example, you can create an include folder and put these files inside this folder.
#include <iostream>#include <libmymath.h>int main() {    graal_isolate_t *isolate = NULL;    graal_isolatethread_t *thread = NULL;    if (graal_create_isolate(NULL, &isolate, &thread) != 0) {        fprintf(stderr, "initialization error\n");        return 1;    }    printf("Result> %d\n",ceilingPowerOfTwo(thread, 14));    return 0;}
Compile the C++ code using the following command and try to run it.
g++ ceilingPowerOfTwoCpp.cpp -L includes/ -I includes/ -lmymath -o ceilingPowerOfTwoCpp
Note: you might need to set the includes directory to LD_LIBRARY_PATH using:
export LD_LIBRARY_PATH=<path_to_includes_directory>
Interoperability is an inevitable requirement in the world where programming languages are growing concurrently. The shared library feature of GraalVM Native Image provides an easy way to embed Java method to a C++ program, which enables the writing of complex user-defined functions and the use of the third-party library. A promising example might be on the development of a data processing framework that aims both high performance and high-level APIs.
If you want to read more about GraalVM, head on to their home page. The full code for this tutorial is available in this repository.