Developer

Go native with Java Native Interface

The JNI can greatly improve Java performance by allowing you to write parts of your application as natively compiled code. We've compiled all you need to know to get started with the standard.


A part of the Java platform since Java 1.1, the Java Native Interface (JNI) standard allows for interoperation between Java code and code written in other languages. The JNI was primarily designed for natively compiled languages, specifically C and C++, but there isn’t really anything to prevent you from using other languages as long as the required calling conventions are supported. Let's look at some of the basics of employing JNI using a very simple example.

Get the code
Download the source code for the examples in this article here.

Using Java to interoperate with natively compiled code usually removes the portability benefits Java brings to the table. However, there are cases when doing so is acceptable, even required, such as when interfacing with legacy libraries, interfacing with hardware or the operating system, or even just improving performance. Writing to the JNI standard does at least guarantee that the native code will work with any JVM implementation.

Getting started
Using the JNI isn't difficult once you get used to it. Since native methods will be implemented in another language, they don't have a method body. However, any native code must be declared with the native keyword and must be a member of a Java class. Listing A illustrates a simple class that declares a single, native, static method called sum.

After you have your Java class, you must next write the native code. The native method signature must provide a header file that satisfies some specific rules, so it's usually easier to generate it using the javah tool instead of building it by hand. You run javah against a compiled Java .class file, and it generates a suitable C/C++ header file for you. Listing B contains the generated header file for the Test1 found in Listing A. Notice that a single C/C++ function header has been created named Java_Test1_sum.

Implementing the native method
Once you have the header file, you just need to write your native method to match the generated function header, as I’ve done in Listing C. Notice that all native methods receive as the first parameter a pointer to the structure JNIEnv. This structure is used to make calls to JNI functions, which I’ll discuss further in another section. The meaning of the second parameter, jclass, depends on whether the method is static or instance. In the former case, jclass represents a reference to the class object, while in the latter, it’s a reference to the object for which the method was invoked. The final two jint arguments represent the int arguments for the Java method.

Return and parameter types are mapped to native C/C++ types according to some well-established rules of equivalency, as shown in Table A. Some types, such as the two jint arguments in Listing B, can be manipulated directly in the native code, while others may be manipulated only through JNI calls.
Table A
Java Type Native Type Description
boolean jboolean Unsigned 8-bit integer C/C++ type
byte jbyte Signed 8-bit integer C/C++ type
char jchar Unsigned 16-bit integer C/C++ type
short jshort Signed 16-bit integer C/C++ type
int jint Signed 32-bit integer C/C++ type
long jlong Signed 64-bit integer C/C++ type
float jfloat 32-bit floating point C/C++ type
double jdouble 64-bit floating point C/C++ type
Object jobject Any Java object, or a type that doesn’t have a corresponding native type
Class jclass Class objects
String jstring String objects
Object[] jobjectArray Array of objects of any type
boolean[] jbooleanArray Array of booleans
byte[] jbyteArray Array of bytes
char[] jcharArray Array of chars
short[] jshortArray Array of shorts
int[] jintArray Array of ints
long[] jlongArray Array of longs
float[] jfloatArray Array of floats
double[] jdoubleArray Array of doubles
JNI type mappings

The final step is to compile the native method into a shared library (i.e., a .so file in UNIX systems or a .dll file in Windows). Before calling the method in Java, the shared library must be loaded using the System.loadLibrary method. The most common approach is to do this in the static initializer for the class.

Accessing JNI features from native code
The example I just walked through is very simple and doesn't serve any useful purpose except to illustrate how to write a JNI method. Now let’s look at something a bit more advanced, working with nonprimitive types through the JNIEnv structure.

JNI provides many features in the form of functions that your native code can call through the pointer to the JNIEnv structure; these are passed as the first parameter to every native method. Calls to JNI functions take the following format (assuming env is a pointer to JNIEnv):
 
//C format:
(*env)-><jni function>( env, <parameters> )
//C++ format:
env-><jni function>( < parameters> )

 

I’ll be presenting the rest of my examples in this article using the C++ format.

Working with arrays
One of the capabilities JNI provides through JNIEnv is the ability to manipulate Java arrays. Two sets of functions are provided: one for manipulating arrays of primitive Java types and another for arrays of objects.

For the sake of speed, arrays of primitive Java types are exposed to native code as pointers to native types, so they can be accessed as regular arrays. Depending on the implementation, the pointer can be a pointer to the actual Java array or to a copy. Either way, the layout of the array is guaranteed to match one of the native types.

To access a Java primitive type array, you use one of the GetXXXArrayElements functions found in Table B, where XXX corresponds to the type of the array. The function takes the Java array as parameter and returns a pointer to an array of the corresponding native type.
Table B
Function Java Array Type Native Type
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
JNI array access functions

When you're done with the array, be sure to call the corresponding ReleaseXXXArrayElements function, passing as parameters the original Java array and the pointer returned by GetXXXArrayElements. If necessary, the release function will copy any changes you made (so they will be reflected in the Java array) and release all resources associated with the array.

To work with an array of Java objects, you’d use the GetObjectArrayElement and SetObjectArrayElement functions to respectively get and set an element of the array. The GetArrayLength function returns the length of any array.

Listing D contains a simple class illustrating the use of native code with Java arrays. The native implementation iterates over an int array and returns the sum of all the elements. For simplicity, this listing contains both the Java and native implementations; I’ve omitted the header file since this can be easily generated by javah.

Working with objects
Another feature JNI provides is the ability to work with Java objects from native code. Using appropriate JNI functions, you can create Java objects, get and set static and instance fields, and call both static and instance methods. JNI identifies fields and methods by ID, and a field or method ID is a required argument for any functions that deal with fields or methods.

Table C lists the JNI functions used to get the IDs of static and instance fields and methods. Each function receives (as arguments) the field or method’s class, its name, and signature, and it returns a corresponding jfieldID or jmethodID.
Table C
Function Description
GetFieldID Gets the ID of an instance field.
GetStaticFieldID Gets the ID of a static field.
GetMethodID Gets the ID of an instance method.
GetStaticMethodID Gets the ID of a static method.
Field and method ID functions

The class can be obtained through the method GetObjectClass if you have an object instance of that class, or through FindClass if you don’t. The signature is a string derived from the field’s type or method’s arguments and return type, as shown in Table D.
Table D
Java Type Signature
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
objects Lfully-qualified-class-name;
arrays [array-type
methods (argument-types)return-type
Determining field and method signatures

Once you have the class and method or field ID, you can save it for later use, so there’s no need to repeat the obtainment process again.

There are separate functions for accessing fields and methods. Instance fields are accessed using one of the GetXXXField variants appropriate for the field’s type. The GetStaticXXXField functions are used for static fields. To set field values, you use the SetXXXField and SetStaticXXXField functions. Table E contains a full listing of field access functions.
Table E
Java Type Method
boolean GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField
byte GetByteField, GetStaticByteField, SetByteField, SetStaticByteField
char GetCharField, GetStaticCharField, SetCharField, SetStaticCharField
short GetShortField, GetStaticShortField, SetShortField, SetStaticShortField
int GetIntField, GetStaticIntField, SetIntField, SetStaticIntField
long GetLongField, GetStaticLongField, SetLongField, SetStaticLongField
float GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField
double GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField
object GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField
Field access functions

Method access, on the other hand, is granted via the functions CallXXXMethod and CallStaticXXXMethod, where XXX indicated the method’s return type. These functions have variations that allow the arguments to be passed as an array (CallXXXMethodA and CallStaticXXXMethodA) or as a variable size list (CallXXXMethodV and CallStaticXXXMethodV). See Table F for a complete list.
Table F
Return Type Function
boolean CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV
byte CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV
char CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV
short CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV
int CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV
long CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV
float CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV
double CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV
void CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV
object CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV
Method access functions

The code in Listing E illustrates calling methods from native code. The native method printRandom gets the method ID of the static method Math.random and calls it several times, printing the result. The same process applies for instance methods.

The JNI is a powerful tool that when used with care can extend the Java language without heavily compromising portability. I’ve touched just the surface here to give you a glimpse of its capabilities and possibilities. I encourage you to check out the JNI Specification, which is part of the JDK documentation, for the complete API. I also recommend the Java tutorial, which you can find here.

 

Editor's Picks