MindView Inc.
[ Viewing Hints ] [ Revision History ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 2nd edition, Revision 12

©2000 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

B: The Java Native Interface (JNI)

The material in this appendix was contributed by and used with the permission of Andrea Provaglio (www.AndreaProvaglio.com).

The Java language and its standard API are rich enough to write full-fledged applications. But in some cases you must call non-Java code; for example, if you want to access operating-system-specific features, interface with special hardware devices, reuse a preexisting, non-Java code base, or implement time-critical sections of code.

Interfacing with non-Java code requires dedicated support in the compiler and in the Virtual Machine, and additional tools to map the Java code to the non-Java code. The standard solution for calling non-Java code that is provided by JavaSoft is called the Java Native Interface, which will be introduced in this appendix. This is not an in-depth treatment, and in some cases you’re assumed to have partial knowledge of the related concepts and techniques. [ Add Comment ]

JNI is a fairly rich programming interface that allows you to call native methods from a Java application. It was added in Java 1.1, maintaining a certain degree of compatibility with its Java 1.0 equivalent: the native method interface (NMI). NMI has design characteristics that make it unsuitable for adoption across all virtual machines. For this reason, future versions of the language might no longer support NMI, and it will not be covered here. [ Add Comment ]

Currently, JNI is designed to interface with native methods written only in C or C++. Using JNI, your native methods can:

Thus, virtually everything you can do with classes and objects in ordinary Java you can also do in native methods. [ Add Comment ]

Calling a native method

We’ll start with a simple example: a Java program that calls a native method, which in turn calls the standard C library function printf( ). [ Add Comment ]

The first step is to write the Java code declaring a native method and its arguments:

//: appendixb:ShowMessage.java
public class ShowMessage {
  private native void ShowMessage(String msg);
  static {
    System.loadLibrary("MsgImpl");
    // Linux hack, if you can't get your library
    // path set in your environment:
    // System.load(
    //  "/home/bruce/tij2/appendixb/MsgImpl.so");
  }
  public static void main(String[] args) {
    ShowMessage app = new ShowMessage();
    app.ShowMessage("Generated with JNI");
  }
} ///:~

The native method declaration is followed by a static block that calls System.loadLibrary( ) (which you could call at any time, but this style is more appropriate). System.loadLibrary( ) loads a DLL in memory and links to it. The DLL must be in your system library path. The file name extension is automatically added by the JVM depending on the platform. [ Add Comment ]

In the above code you can also see a call to the System.load( ) method, which is commented out. The path specified here is an absolute path, rather than relying on an environment variable. Using an environment variable is naturally the better and more portable solution, but if you can’t figure that out you can comment out the loadLibrary( ) call and uncomment this one, adjusting the path to your own directory. [ Add Comment ]

The header file generator: javah

Now compile your Java source file and run javah on the resulting .class file, specifying the —jni switch (this is done automatically for you by the makefile in the source code distribution for this book): [ Add Comment ]

javah —jni ShowMessage

javah reads the Java class file and for each native method declaration it generates a function prototype in a C or C++ header file. Here’s the output: the ShowMessage.h source file (edited slightly to fit into this book): [ Add Comment ]

/* DO NOT EDIT THIS FILE 
   - it is machine generated */
#include <jni.h>
/* Header for class ShowMessage */

#ifndef _Included_ShowMessage
#define _Included_ShowMessage
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ShowMessage
 * Method:    ShowMessage
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL 
Java_ShowMessage_ShowMessage
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

As you can see by the #ifdef __cplusplus preprocessor directive, this file can be compiled either by a C or a C++ compiler. The first #include directive includes jni.h, a header file that, among other things, defines the types that you can see used in the rest of the file. JNIEXPORT and JNICALL are macros that expand to match platform-specific directives. JNIEnv, jobject and jstring are JNI data type definitions, which will be explained shortly. [ Add Comment ]

Name mangling and function signatures

JNI imposes a naming convention (called name mangling) on native methods. This is important, since it’s part of the mechanism by which the virtual machine links Java calls to native methods. Basically, all native methods start with the word “Java,” followed by the name of the class in which the Java native declaration appears, followed by the name of the Java method. The underscore character is used as a separator. If the Java native method is overloaded, then the function signature is appended to the name as well; you can see the native signature in the comments preceding the prototype. For more information about name mangling and native method signatures, please refer to the JNI documentation. [ Add Comment ]

Implementing your DLL

At this point, all you have to do is write a C or C++ source code file that includes the javah-generated header file and implements the native method, then compile it and generate a dynamic link library. This part is platform-dependent. The code below is compiled and linked into a file called MsgImpl.dll for Windows or MsgImpl.so for Unix/Linux (the makefile packaged with the code listings contains the commands to do this—it is available on the CD ROM bound into this book, or as a free download from www.BruceEckel.com): [ Add Comment ]

//: appendixb:MsgImpl.cpp
//# Tested with VC++ & BC++. Include path must 
//# be adjusted to find the JNI headers. See 
//# the makefile for this chapter (in the 
//# downloadable source code) for an example.
#include <jni.h>
#include <stdio.h>
#include "ShowMessage.h"

extern "C" JNIEXPORT void JNICALL 
Java_ShowMessage_ShowMessage(JNIEnv* env, 
jobject, jstring jMsg) {
  const char* msg=env->GetStringUTFChars(jMsg,0);
  printf("Thinking in Java, JNI: %s\n", msg);
  env->ReleaseStringUTFChars(jMsg, msg);
} ///:~

The arguments that are passed into the native method are the gateway back into Java. The first, of type JNIEnv, contains all the hooks that allow you to call back into the JVM. (We’ll look at this in the next section.) The second argument has a different meaning depending on the type of method. For non-static methods like the example above, the second argument is the equivalent of the “this” pointer in C++ and similar to this in Java: it’s a reference to the object that called the native method. For static methods, it’s a reference to the Class object where the method is implemented. [ Add Comment ]

The remaining arguments represent the Java objects passed into the native method call. Primitives are also passed in this way, but they come in by value. [ Add Comment ]

In the following sections we’ll explain this code by looking at the ways that you access and control the JVM from inside a native method. [ Add Comment ]

Accessing JNI functions:
the JNIEnv argument

JNI functions are those that the programmer uses to interact with the JVM from inside a native method. As you can see in the example above, every JNI native method receives a special argument as its first parameter: the JNIEnv argument, which is a pointer to a special JNI data structure of type JNIEnv_. One element of the JNI data structure is a pointer to an array generated by the JVM. Each element of this array is a pointer to a JNI function. The JNI functions can be called from the native method by dereferencing these pointers (it’s simpler than it sounds). Every JVM provides its own implementation of the JNI functions, but their addresses will always be at predefined offsets. [ Add Comment ]

Through the JNIEnv argument, the programmer has access to a large set of functions. These functions can be grouped into the following categories:

The number of JNI functions is quite large and won’t be covered here. Instead, I’ll show the rationale behind the use of these functions. For more detailed information, consult your compiler’s JNI documentation. [ Add Comment ]

If you take a look at the jni.h header file, you’ll see that inside the #ifdef __cplusplus preprocessor conditional, the JNIEnv_ structure is defined as a class when compiled by a C++ compiler. This class contains a number of inline functions that let you access the JNI functions with an easy and familiar syntax. For example, the line of C++ code in the preceding example:

env->ReleaseStringUTFChars(jMsg, msg);

could also be called from C like this: [ Add Comment ]

(*env)->ReleaseStringUTFChars(env, jMsg, msg);

You’ll notice that the C style is (naturally) more complicated—you need a double dereferencing of the env pointer, and you must also pass the same pointer as the first parameter to the JNI function call. The examples in this appendix use the C++ style. [ Add Comment ]

Accessing Java Strings

As an example of accessing a JNI function, consider the code in MsgImpl.cpp. Here, the JNIEnv argument env is used to access a Java String. Java Strings are in Unicode format, so if you receive one and want to pass it to a non-Unicode function (printf( ), for example), you must first convert it into ASCII characters with the JNI function GetStringUTFChars( ). This function takes a Java String and converts it to UTF-8 characters. (These are 8 bits wide to hold ASCII values or 16 bits wide to hold Unicode. If the content of the original string was composed only of ASCII, the resulting string will be ASCII as well.) [ Add Comment ]

GetStringUTFChars( ) is one of the member functions in JNIEnv. To access the JNI function, we use the typical C++ syntax for calling a member function though a pointer. You use the form above to access all of the JNI functions. [ Add Comment ]

Passing and using Java objects

In the previous example we passed a String to the native method. You can also pass Java objects of your own creation to a native method. Inside your native method, you can access the fields and methods of the object that was received. [ Add Comment ]

To pass objects, use the ordinary Java syntax when declaring the native method. In the example below, MyJavaClass has one public field and one public method. The class UseObjects declares a native method that takes an object of class MyJavaClass. To see if the native method manipulates its argument, the public field of the argument is set, the native method is called, and then the value of the public field is printed.

//: appendixb:UseObjects.java
class MyJavaClass {
  public int aValue;
  public void divByTwo() { aValue /= 2; }
}

public class UseObjects {
  private native void 
    changeObject(MyJavaClass obj);
  static {
    System.loadLibrary("UseObjImpl");
    // Linux hack, if you can't get your library
    // path set in your environment:
    // System.load(
    //"/home/bruce/tij2/appendixb/UseObjImpl.so");
  }
  public static void main(String[] args) {
    UseObjects app = new UseObjects();
    MyJavaClass anObj = new MyJavaClass();
    anObj.aValue = 2;
    app.changeObject(anObj);
    System.out.println("Java: " + anObj.aValue);
  }
} ///:~

After compiling the code and running javah, you can implement the native method. In the example below, once the field and method ID are obtained, they are accessed through JNI functions. [ Add Comment ]

//: appendixb:UseObjImpl.cpp
//# Tested with VC++ & BC++. Include path must 
//# be adjusted to find the JNI headers. See 
//# the makefile for this chapter (in the 
//# downloadable source code) for an example.
#include <jni.h>
extern "C" JNIEXPORT void JNICALL
Java_UseObjects_changeObject(
JNIEnv* env, jobject, jobject obj) {
  jclass cls = env->GetObjectClass(obj);
  jfieldID fid = env->GetFieldID(
    cls, "aValue", "I");
  jmethodID mid = env->GetMethodID(
    cls, "divByTwo", "()V");
  int value = env->GetIntField(obj, fid);
  printf("Native: %d\n", value);
  env->SetIntField(obj, fid, 6);
  env->CallVoidMethod(obj, mid);
  value = env->GetIntField(obj, fid);
  printf("Native: %d\n", value);
} ///:~

Ignoring the “this” equivalent, the C++ function receives a jobject, which is the native side of the Java object reference we pass from the Java code. We simply read aValue, print it out, change the value, call the object’s divByTwo( ) method, and print the value out again. [ Add Comment ]

To access a Java field or method, you must first obtain its identifier using GetFieldID( ) for fields and GetMethodID( ) for methods. These functions take the class object, a string containing the element name, and a string that gives type information: the data type of the field, or signature information for a method (details can be found in the JNI documentation). These functions return an identifier that you use to access the element. This approach might seem convoluted, but your native method has no knowledge of the internal layout of the Java object. Instead, it must access fields and methods through indexes returned by the JVM. This allows different JVMs to implement different internal object layouts with no impact on your native methods. [ Add Comment ]

If you run the Java program, you’ll see that the object that’s passed from the Java side is manipulated by your native method. But what exactly is passed? A pointer or a Java reference? And what is the garbage collector doing during native method calls? [ Add Comment ]

The garbage collector continues to operate during native method execution, but it’s guaranteed that your objects will not be garbage-collected during a native method call. To ensure this, local references are created before, and destroyed right after, the native method call. Since their lifetime wraps the call, you know that the objects will be valid throughout the native method call. [ Add Comment ]

Since these references are created and subsequently destroyed every time the function is called, you cannot make local copies in your native methods, in static variables. If you want a reference that lasts across function invocations, you need a global reference. Global references are not created by the JVM, but the programmer can make a global reference out of a local one by calling specific JNI functions. When you create a global reference, you become responsible for the lifetime of the referenced object. The global reference (and the object it refers to) will be in memory until the programmer explicitly frees the reference with the appropriate JNI function. It’s similar to malloc( ) and free( ) in C. [ Add Comment ]

JNI and Java exceptions

With JNI, Java exceptions can be thrown, caught, printed, and rethrown just as they are inside a Java program. But it’s up to the programmer to call dedicated JNI functions to deal with exceptions. Here are the JNI functions for exception handling: [ Add Comment ]

Among these, you can’t ignore ExceptionOccurred( ) and ExceptionClear( ). Most JNI functions can generate exceptions, and there is no language feature that you can use in place of a Java try block, so you must call ExceptionOccurred( ) after each JNI function call to see if an exception was thrown. If you detect an exception, you may choose to handle it (and possibly rethrow it). You must make certain, however, that the exception is eventually cleared. This can be done in your function using ExceptionClear( ) or in some other function if the exception is rethrown, but it must be done. [ Add Comment ]

You must ensure that the exception is cleared, because otherwise the results will be unpredictable if you call a JNI function while an exception is pending. There are few JNI functions that are safe to call during an exception; among these, of course, are all the exception handling functions. [ Add Comment ]

JNI and threading

Since Java is a multithreaded language, several threads can call a native method concurrently. (The native method might be suspended in the middle of its operation when a second thread calls it.) It’s entirely up to the programmer to guarantee that the native call is thread-safe; i.e., it does not modify shared data in an unmonitored way. Basically, you have two options: declare the native method as synchronized, or implement some other strategy within the native method to ensure correct, concurrent data manipulation. [ Add Comment ]

Also, you should never pass the JNIEnv pointer across threads, since the internal structure it points to is allocated on a per-thread basis and contains information that makes sense only in that particular thread. [ Add Comment ]

Using a preexisting code base

The easiest way to implement JNI native methods is to start writing native method prototypes in a Java class, compile that class, and run the .class file through javah. But what if you have a large, preexisting code base that you want to call from Java? Renaming all the functions in your DLLs to match the JNI name mangling convention is not a viable solution. The best approach is to write a wrapper DLL “outside” your original code base. The Java code calls functions in this new DLL, which in turn calls your original DLL functions. This solution is not just a work-around; in most cases you must do this anyway because you must call JNI functions on the object references before you can use them. [ Add Comment ]

Additional information

You can find further introductory material, including a C (rather than C++) example and discussion of Microsoft issues, in Appendix A of the first edition of this book, which can be found on the CD ROM bound in with this book, or in a free download from www.BruceEckel.com. More extensive information is available at java.sun.com (in the search engine, select “training & tutorials” for keywords “native methods”). Chapter 11 of Core Java 2, Volume II, by Horstmann & Cornell (Prentice-Hall, 2000) gives excellent coverage of native methods. [ Add Comment ]

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:05/21/2001