Developer

Discover how the Java Native Interface works

Peter V. Mikhalenko offers an introduction on the Java Native Interface (JNI) and describes how native types are mapped to Java types and classes. Benefits of the JNI include its performance and its ability to preserve legacy code and add more functionality to your Java app.

The Java Native Interface (JNI) is a Java layer that allows Java code running in the Java Virtual Machine (JVM) to call and be called by native applications and libraries written in other languages, such as C, C++, and assembly. Developers use the JNI to write native methods to handle situations when an application cannot be written entirely in Java, such as when the standard Java class library does not support the platform-dependent features or program library. It is also used to modify an existing application, written in another programming language, to be accessible to Java applications.

Many of the standard library classes depend on the JNI to provide functionality to the developer and the user, e.g., I/O file reading and sound capabilities. The inclusion of performance- and platform-sensitive API implementations in the standard library allows all Java applications to access this functionality in a safe and platform-independent manner. Before resorting to using the JNI, developers should make sure the functionality is not already provided in the standard libraries. In this introductory article, I will cover how the JNI works and how native types are mapped to Java types and classes.

How the JNI works

In the JNI, native functions are implemented in a separate .c or .cpp file. (C++ provides a slightly cleaner interface with the JNI.) When the JVM invokes the function, it passes a JNIEnv pointer, a jobject pointer, and any Java arguments declared by the Java method. A JNI function may look like this:

JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobjectobj)
{
    //Method native implemenation
}

The env pointer is a structure that contains the interface to the JVM. It includes all of the functions necessary to interact with the JVM and to work with Java objects. Example JNI functions are converting native arrays to and from Java arrays, converting native strings to and from Java strings, instantiating objects, throwing exceptions, etc. Basically, you can use JNIEnv to do anything that Java does, albeit with considerably less ease.

To be more formal, native code accesses JVM features by calling JNI functions, which are available through an interfacepointer (this is a pointer to a pointer). This pointer points to an array of pointers, each of which points to an interface function. Every interface function is at a predefined offset inside the array. Native methods receive the JNI interface pointer as an argument. The JVM is guaranteed to pass the same interface pointer to a native method when it makes multiple calls to the native method from the same Java thread. However, a native method can be called from different Java threads, and therefore may receive different JNI interface pointers.

Native methods are loaded with the System.loadLibrary method. In the following example, the class initialization method loads a platform-specific native library in which the native method f is defined:

packagepkg;  
class Cls {
     native double f(inti, String s);
     static {
         System.loadLibrary(pkg_Cls");
     }
}

The argument to System.loadLibrary is a library name chosen arbitrarily by the programmer. The system follows a standard, platform-specific approach to convert the library name to a native library name. For example, a Solaris system converts the name pkg_Cls to libpkg_Cls.so, while a Win32 system converts the same pkg_Cls name to pkg_Cls.dll.

Get developer tips in your inbox
Delivered each Thursday, our free Java newsletter provides insight and hands-on tips you need to unlock the full potential of this programming language.
Automatically sign up today!

Dynamic linkers resolve entries based on their names. A native method name is concatenated from components, which include: the prefix Java_, a mangled fully-qualified class name, and a mangled method name.

Note: Microsoft's implementation of a JVM has a similar mechanism for calling native Windows code from Java called the Raw Native Interface (RNI).

Data type mapping

Primitive types, such as integers, characters, and so on, are copied between Java and native code. Arbitrary Java objects, on the other hand, are passed by reference.

This table shows the mapping of types between Java and native code. These types are interchangeable. You can use jint where you normally use an int, and vice-versa without any typecasting required. However, mapping between Java Strings and arrays to native strings and arrays is different. If you use a jstring where a char * would be, your code could crash the JVM. This is an example of how you should work correctly with strings:

JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobjectobj, jstringjavaString)
{
    //Get the native string from Java string
    const char *nativeString = env->GetStringUTFChars(env,javaString, 0);
    printf("%s", nativeString);
    env->ReleaseStringUTFChars(env,javaString, nativeString);
}

You always manipulate Java objects using the interface pointer env.

Conclusion

It is not particularly easy to implement the JNI in your application. However, its performance and its ability to preserve legacy code and add more functionality to your Java app may outweigh its challenges. For more detailed information, visit the JNI home page.

Peter V. Mikhalenko is a Sun certified professional who works for Deutsche Bank as business consultant.

Editor's Picks

Free Newsletters, In your Inbox