Introduction to Java Native Interface: Establishing a bridge between Java and C/C++

Yiğit PIRILDAK
Mar 16, 2020 · 5 min read

JNI (Java Native Interface) is a foreign function interface that allows code running on JVM to call (or be called by) native applications. Using JNI, one can call methods written in C/C++ or even access assembly language.

JNI comes in handy when you need to do something that cannot be accomplished by Java alone (low-level code may be needed to talk directly to OS/Hardware which cannot be accomplished by Java alone). It’s also possible that your company developed some software that is now considered legacy and you need to continue development using Java. In that case, you can create an interface between the legacy code and Java so you can build upon it.

How It Works

Let’s examine a simple application that demonstrates the process. You can find the full source code here.

We will start by creating a java class and declare some native methods.

  • Native methods are declared using the native keyword.

TemperatureData is used as a simple data object and TemperatureScale is an enumeration.

Compile and create JNI headers

I created a Makefile to easily build and run the sample application. Let’s go over it:

First, we need to specify Java’s home directory (JAVA_HOME). Creating separate folders for everything is not necessary, but it’s a good idea to keep things organized, which saves a lot of time when cleaning a project.

At line 10, we use javac command to compile our Java code to bytecode (.class files) and create the JNI headers. -h option is given to specify where headers should go after building.

After performing this step, a header file will be created under MY_JNI_HEADERS folder (com_jni_example_TemperatureSampler.h).

You can see that our native getTemperature and getDetailedTemperature methods are present in this file. Naming of these methods follows this convention :

Java_{package_and_classname}_{function_name}(JNI_arguments)

At this point, all you need to do is create another C/C++ file and implement these methods. After doing that, line 11 of Makefile creates an object file from your cpp source, and line 12 converts it into a dynamic library.

Native Side Implementation

Simply import the header file, copy the method signature and fill it in. Let’s start with the simpler getTemperature method:

Primitive types don’t need special conversions, so simply returning the number from getTemperature method is fine. For complex types, we need to do a lot more work:

If you’re familiar with Java’s Reflection API, you’ll notice this is very similar to it.

JNI provides all necessary methods to get/convert Java objects using the JNIEnv pointer, which is passed to all JNI method calls.

[Line 4–5] We get a hold of TemperatureData class by calling FindClass method and then create an instance of TemperatureData using AllocObject.

[Line 8] Same as TemperatureData, we grab the TemperatureScale class. We don’t instantiate it since it’s an Enumeration, we will access its values later.

[Line 11–12–13] We get fields of TemperatureData by providing the class, field name and field’s type to GetFieldID method.

[Line 16–17] Enumeration values are considered static fields, so we have to access them using GetStaticFieldID method. Its signature is the same as GetFieldID.

[Line 29–30–31–32] Remember the TemperatureData that we instantiated at the beginning? Now that we have all its fields, we can set them using Set<Type>Field functions. Based on our field types, we only need to use SetObjectField and SetFloatField methods. After setting these values, we simply return the object.

As you can see, Java classes are referenced by using their full package names, starting with L (like Ljava/lang/String; for String). If you want to access an array, your type simply starts with [ ( [Ljava/lang/String for a String array )

Primitive types are easier, as they have their own simple definitions. Here’s a list of all of them:

Z = boolean
B = byte
S = short
I = int
J = long
F = float
D = double
C = char
L = any non-primitive(Object)
[Z = boolean array
[B = byte array
[S = short array
[I = int array
[J = long array
[F = float array
[D = double array
[C = char array
[L = any non-primitive(Object)array

How about calling a Java function from Native Side?

You might’ve noticed that I skipped lines 20–26 which demonstrates this. Now let’s take a look at it.

[Line 20–21–22] thisObject that is passed to JNI methods is actually the caller of native method. We can grab that object’s class using GetObjectClass method. After that, GetMethodID is used to grab one of its methods. Third parameter of GetMethodID describes the parameters and return value of the function. Since our getPreferredScale doesn’t have any parameters, inside parentheses are empty. We do have a return value however, which is TemperatureScale. Similar to how we grabbed field ids, we provide its full package name here.

If we had two parameters, let’s say (String, float), our method signature would look like this:

“(Ljava/lang/String;F)Lcom/jni/example/TemperatureScale;”

[Line 24–25] IsSameObject method is used to compare two enumeration values (it’s basically the equals method).

Things to Consider

Lastly, I want to talk a little bit about pitfalls of using JNI.

  • Your application loses platform portability. If you need to support different platforms, you will need to compile the native layers for each of those platforms.

References:

The Startup

Get smarter at building your thing. Join The Startup’s +794K followers.

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Yiğit PIRILDAK

Written by

A curious Software Engineer who is interested in Embedded Systems and ML. Wastes time by playing video games, watching TV Shows and reading fantasy novels.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +794K followers.

Yiğit PIRILDAK

Written by

A curious Software Engineer who is interested in Embedded Systems and ML. Wastes time by playing video games, watching TV Shows and reading fantasy novels.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +794K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store