Calling Java Code from C/C++ Transparently

338 VIEWS

·

I recently needed to create a C/C++ API for some existing Java code. The first thing that came to mind was to use the Java Native Interface (JNI). However, that can get ugly, mainly because JNI was designed to do the reverse (call C code from Java) and because it adds additional compilation steps. For example, you need to generate the JNI layer using the JDK’s javah tool, implement and compile the native C/C++ side of things, and then keep it all in sync as the Java code changes. Fortunately, there is an alternative.

Inside Java Native Access (JNA)

The JNA library is written in Java, and provides Java applications easy access to native code using 100% pure Java. JNA implements a small, built-in, multi-purpose JNI library stub layer to dynamically invoke native code, similar to Java’s reflection. As a result, JNA hides all the ugly details of JNI, and instead allows you to call directly into native code using natural Java syntax. Overall, it removes the overhead of configuring and building JNI code for multiple platforms (e.g. Windows, Mac OS X, Linux, Solaris, and so on), which is a huge time saver.

JNA has been around for awhile. It’s open source, and available in Git, through the GNU LGPL license. Additionally, JNA is used by popular third-party software, such as NetBeans, IntelliJ, Apache Cassandra, GStreamer, and others. JNA also supports multiple platform architectures as well (e.g. Windows, Unix, x86, and ARM).

JNA Example: From Java to C/C++

To begin, let’s explore a simple JNA example of Java calling into native code. The example starts with a simple C++ class (see Listing 1) that, for demonstration purposes, performs a variety of functions:

  1. Return the result of a calculation on an integer passed as a parameter: calc(int i)
  2. Return a text string in response to a request: getName()
  3. Log a text string passed into the native code as a parameter: log(string s)
  4. Call back into the Java code via a callback definition: callme(callback func)
Listing 1 – A native class definition that performs a variety of common functions.

All of the methods described above are defined in a C++ header file, along with the callback definition. In this case, the callback will provide an integer value to the caller at some time in the future, perhaps multiple times (typical callback pattern). The actual C++ code is very normal and very boring (see Listing 2), which is good in this case as it illustrates that using JNA requires no changes to the native code.

Listing 2 – The native implementation of the MyLibClass class.

Before we use the native class MyLibClass in a Java application, let’s look at a very simple example of calling native code from Java, as shown in Listing 3.

Listing 3 – Simple native code invocation from Java.

First, this Java code uses JNA’s Library class to load a native library via a nested interface named CLibrary. In this case, the library is the standard C library. The JNA code is written so that it will work on Windows and Unix platforms equally. It also defines Java methods that match the native methods you wish to call from Java. The one line of code in the Java main() routine illustrates how simple and easy it is to call into native code using JNA.

The result is 100% pure Java code, which is mostly straightforward and readable. In this example, the JNA calling technique is similar to Java’s reflection, or any type of indirect method invocation. However, there can be a slight performance penalty per call when compared to native or plain Java method calls. To address this, JNA defines a technique called Direct Method Mapping to eliminate this penalty. Listing 4 contains the Java code that calls the native class MyLibClass (defined in Listing 1) with this optimization.

Listing 4 – Using JNA Direct Method Invocation to optimally call native code from Java.

There are two key concepts defined in this code: the use of JNA’s Direct Method Invocation, and the syntax to define callbacks. The static class MyCLib defines Java methods for each of the matching native MyLibClass methods we wish to call (which happens to be all of them in this case). The syntax is simple: Java primitives match C/C++ types, and the Java String class matches the C++ string class.

For the callback used in the method callme(), we start with a Java interface that defines a method named invoke, which accepts the same parameters as the native callback method. This interface—named callback in this example, although you can change that—extends the JNA Callback class, and is then defined as the parameter func in the Java version of the callme() method. To complete the JNA callback pattern, the code implements the func object’s invoke method, which will be called when the native code follows through with the callback. In this example, the callback code simply stores the given value.

The static method main() calls the native code from Java, which includes the callme method where the callback is provided. Finally, the result of the value given back from the native code from the callback is logged. Admittedly, there is some mental bookkeeping involved with this JNA code, especially when callbacks are involved, but the end result as shown in main() is straightforward.

All of the code for the samples above are available in Git for download here:

  1. MyCLib
  2. JNATest

Calling Java from C/C++ Using JNA

The JNA callback pattern is the key to allow native code to transparently call Java code. By transparently, I mean that no special C/C++ code is needed to make this work and, in fact, the native developer doesn’t have to know that Java is even being invoked. To make this work, each native API method is routed back to the Java implementation through a matching JNA callback. This approach supports passing parameters, and routing each method’s return value back through as the callback parameters and return value, respectively. Let’s follow this process visually.

First, the Java interface MyJavaAPI is defined and implemented (see Figure 1). The methods and functionality are similar to those explored in the earlier example.


Figure 1 – A normal Java interface and implementation.

To make this code invokable from the native C/C++ side, JNA callbacks are defined for each API call, along with Java methods to register the callbacks from the native side (see Figure 2).

Figure 2 – The JNA code for callbacks that match each Java API method.

Next, a matching native library is written to match the methods in the Java API one-for-one. You can even create a tool that generates this automatically from the Java API. The Java JNA code is updated to call into this native code, providing the JNA callback methods (see Figure 3).

Figure 3 – The JNA code loads the matching native library and provides the callbacks.

Next, the native library code is updated to implement the API methods, which (through the JNA callback technique) then call into the matching Java code. Each native method checks that the JVM has been launched, and if it hasn’t, it will be completed before invoking the method call through the callback (see Figure 4).

Figure 4 – Each native method calls the matching Java API through the JNA callbacks.

With that, the native-to-Java invocation machinery is in place. All of this is put in motion once the native developer loads the C/C++ library code and invokes one of the methods. At that point, the JVM is loaded, the callbacks are registered, and each of the Java calls are routed through the matching callbacks. The diagram in Figure 5 shows the process in action as each C++ call propagates through the layers.


Figure 5 – The C++ application calls the C++ version of the Java API through the layers.

Following the arrows, you can see how all of the components are involved. Showing it this way can make it seem more involved than it really is, but it helps to understand the process in detail. To support C as well as C++ applications, the C++ library defers its calls to the C implementation (via the functions prefixed with extern “C”). C applications can call the C layer directly, as shown in Figure 6.

Figure 6 – Both C and C++ applications are supported in this JNA pattern.

Executing this sample code results in the following output:

You can follow the steps of processing via the logging inserted throughout the code. As mentioned above, both the native and JNA layers that match the Java API code could conceivably be generated automatically by a tool that analyzes the Java interface. For now this is left as an exercise for the reader.

Download the four sample projects from Git here:

  1. MyJavaLibrary
  2. MyJavaLibrary_JNA
  3. MyJavaLibrary_C
  4. MyTestCApp

Launching the JVM from Native Code

A critical step to make the calls from native to Java code transparent to C/C++ developers is to launch the JVM automatically from the native side. To do so, the JVM launch code is contained within a single header file JNABootstrap.h, summarized in Listing 5.

Listing 5 – The code to launch the JVM and load the JNA code automatically.

Note that many of the details have been left out of the code above for brevity. Overall, the create_vm method uses the paths to the JDK installation, the Java API, the JNA code, and the native implementation to create the JVM with everything it needs to load all of these components. Once it loads the JVM, it loads the JNA code and finds the constructor for the main class, which it loads. With the JVM running and the JNA class loaded, calls into the native library will be successfully routed to the Java implementation—all of which happens under the hood.

Other Important Details

For Java to call a native library, with or without JNA, you need to provide the JVM the path to it. When invoking a Java application, you do this on the command line via the JVM: -Djava.library.path=… parameter. However, since this process begins when a native application is invoked, this isn’t possible. Therefore, you need to define the corresponding environment variable LD_LIBRARY_PATH to contain the path to the native library that matches the Java API, as shown here:

The JVM uses this environment variable to load the library.

Additionally, on the native side, the C/C++ library version of the Java API that you generate, once compiled, must be in the path for your native application to find. An easy way to ensure this is to copy the library to the same directory as your application. For instance, the script I use to run my native test application, as shown below, copies the latest version of the native library file to the current directory each time.

Conclusion

JNA allows you to easily call native code from Java using nothing but pure Java—no custom JNI code is needed. However, using JNA’s callback technique along with some glue code to automatically and transparently invoke the JVM from a native application, you can easily call Java code from a C/C++ application. With this approach, the native coder uses a thin native definition of the Java code being invoked. Under the hood, the JVM is launched, the Java API is loaded, and all callbacks are registered automatically. All of this works while C/C++ developers write nothing but native code, and Java developers write nothing but Java code.

Do you think you can beat this Sweet post?

If so, you may have what it takes to become a Sweetcode contributor... Learn More.

Eric Bruno is a contributing editor to multiple publications with more than 20 years of experience in the information technology community. He is a highly requested speaker and writer for topics spanning the technology spectrum, from mobile to the data center.


Discussion

Click on a tab to select how you'd like to leave your comment

Leave a Comment

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

Menu