Chapter 41
Extending the Reach of JavaThe Java Native Interface

by Govind Sashadri

The topic of native methods has been called as one of the "final frontiers" of Java, and rightfully so! That's only because this potent feature empowers the Java developer to potentially do just about anything that can be program-atically done on a computer--assuming that a Java implementation exists for it, of course. Native methods allow progammers to extend the reach of Java, by selectively implementing methods in other programming languages like C or C++, when the problem cannot be solved using Java alone.

Prior to the evolution of JDK 1.1, native methods was one of those foreboding, "rocket-science" Java topics that was seldom discussed and even less understood. And not without ample reason, either! Then, the very imple- mentation of the native method interface within JVMs had differed quite drastically, depending on the vendor implementing it. Due to myriad shortcomings, Sun's native method specification under JDK 1.02 was superseded by proprietary interfaces like Netscape's Java Runtime Interface (JRI), Microsoft's Raw Native Interface (RNI), and the Java/COM interface. The lack of a unified interface meant that the developer had to implement a separate native method binary for each JVM used, even though they may all have targeted the same platform!

Given the prevailing situation, it was none too surprising that the native method specification was thoroughly overhauled under the JDK 1.1. The new and improved version, called the Java Native Interface (JNI), simplifies matters by presenting a unified interface for incorporating native methods, irrespective of the JVM used, assuming that the vendor conforms to the JNI specification for supporting native methods. JNI was developed following extensive consultations between JavaSoft and other Java licensees, and bears very close resemblance to Netscape's JRI specification.


NOTE: There is still some confusion as to whether Microsoft will be adopting the JNI standard within their JVM implementation. So far, Microsoft has insisted that they will not be adopting JNI, but rather will support only the RNI and Java/COM interfaces.


The Case for "Going Native"

Java purists may consider incorporating C or C++ code within Java programs as an act of heresy! Why in the world would someone want to program in anything but Java, given the power and flexibility of the platform-independent JDK? The reasons are many:

Incorporating native methods may bring certain benefits--but there is certainly a price to be paid:


NOTE: Future versions of browsers may well have a fully customizable security manager, where the user can load applets implementing native methods on a selective basis. Sun's HotJava browser currently provides a customizable security manager.

JNI Highlights

As mentioned before, JVM's full compliance with the JDK 1.1 specification presents the same standard native method interface--the JN--irrespective of the platform. The highlights of the JNI are as follows:

Writing Native Methods

If you still think native methods are the way to go, then it's time to dive into the mysterious waters of the sea of JNI.


NOTE: All of the JNI examples given here are written with the Solaris operating environment in mind, and the native methods are implemented using "C." The examples should work fine on other platforms, as long as the shared library is created properly for that platform.


All native methods are implemented in JNI by closely following a basic six-step program.

Step OneWrite the Java Code

Looking at Listing 41.1 you see that the keyword native, within the declaration of the method greet (see below), indicates that it is implemented outside Java, in a different programming language. Additionally, the static block tells Java to load the shared library (libsayhello1.so, in the case of Solaris), within which you can find the actual implementation of the method at runtime.

Listing 41.1SayHello1.java--Native Methods Demo Program

public class SayHello1 {
     public native void greet();
   
     static {
     System.loadLibrary("sayhello1");
     }
          public static void main(String[] args) {
          new SayHello1().greet();
     }
}

The main method simply invokes a new instance of the class SayHello1 and invokes the native method greet. Notice that the native method is invoked just like any ordinary instance method.

Step Two--Compile the Java Code to a Class File

No surprises here. Simply compile the Java source file using the javac compiler as usual.

javac SayHello1.java

Step Three--Generate the JNI-Style Header File

Apply javah--the C header and stub file generator given to you as part of the JDK--on the compiled class file to generate the JNI header file for the class. JNI, unlike the native method interface specification under JDK 1.02, does not make use of stub files.

javah -jni SayHello1

This generates the corresponding header file, SayHello1.h, to the local directory (see Listing 41.2).


NOTE: The generated header file always has the naming convention SomeClassFile.h, where the class SomeClassFile contains the native method declaration, within your Java source.


Listing 41.2SayHello1.h--Generated Header File

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

#ifndef _Included_SayHello1
#define _Included_SayHello1
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     SayHello1
 * Method:    greet
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_SayHello1_greet
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Step Four--Implement the Native Method

The native method can now be developed, after making sure to include the generated header file within the native method implementation file. The native method prototypes can be taken from the header file, to simplify matters (see Listing 41.3).


TIP: There are no restrictions for naming the native method implementation file. But it is better to choose a file name that has some relationship to the actual Java file within which the native method is declared.


Listing 41.3MyHello1.c--Native Method Implementation Program

#include "SayHello1.h"
#include <studio.h>
JNIEXPORT# void JNICALL# Java_SayHello1_greet ( JNIEnv *env, jobject this) {
     printf("Hi folks! Welcome to the netherworld of native methods!\n");
}

Step Five--Create the Shared Library

This step varies quite a bit depending on the target platform, and requires the availability of a compiler that allows you to create a shared library. For Solaris, a shared object is created quite easily by using the appropriate compiler option. It is important that the path of the standard JDK 1.1 includes files that are properly denoted during the compilation process.

For example, the following command has the effect of creating the shared object libsayhello1.so on Solaris platforms.

cc -G MyHello1.c -I $JAVAHOME/include -I $JAVAHOME/include/solaris -o libsayhello1.so

For Windows 95 and Windows NT, you can create a DLL for the native implementation as follows:

cl MyHello1.c -I$JAVAHOME\include -I$JAVAHOME\include\win32 -Fesayhello1.dll -MD -LD -nologo $JAVAHOME\lib\javai.lib


NOTE: For Windows 95 and Windows NT, you can use any "C" or "C++" compiler that lets you create DLLs. You have to make sure that you include the path of the relevant JDK include files when you create the DLL.


Step Six--Run the Java Program

Running the application as:

java SayHello1

produces the output:

Hi folks! Welcome to the netherworld of native methods!


TIP: On Solaris platforms, please note that the environment variable LD_LIBRARY_PATH should contain the path of the newly created shared object.


Accessing Object Fields from Native Methods

JNI methods can easily access and even alter the member fields of the invoking object. This is done by using the various JNI accessor functions that are made available to programmers via the interface pointer that is passed by default as the first argument to every JNI method. The JNI interface pointer is of type JNIEnv. The second argument differs on the nature of the native method. For a non-static native method, the argument is a reference to the object, whereas for a static method, it is a reference to its Java class.

Accessing Java object members through the accessor functions is what ensures the portability of the native method implementation. Assuming the vendor of a VM implements the JNI, your native methods should work irrespective of how the Java objects are maintained internally.

Take a look at the function prototype in Listing 41.3.

JNIEXPORT# void JNICALL# Java_SayHello1_greet ( JNIEnv *env, jobject this)

You see that the first two arguments for this method are passed by default by the Java environment. The interface pointer env gives you access to the accessor functions and the object reference this refers to the instance that invoked the native method.

Listing 41.4 is a modification of Listing 41.1. Here, you have added a couple of public fields to class, and you see how they can be accessed and changed in Listing 41.5.

Listing 41.4SayHello2.java--Java Program to Demonstrate Object Access from Native Methods

public class SayHello2 {
  
          public String aPal = "Java Joe";
          public int age=0;
          public native void greet();
    
      
          static {
               System.loadLibrary("sayhello2");
          }
     
          public void howOld() {
               System.out.println(aPal + " is " + age + "years old today!");          
          }
          public static void main(String[] args) {
               SayHello2 mySayHello2 = new SayHello2();
		mySayHello2.greet();
		mySayHello2.howOld();
          }
     }

Listing 41.5MyHello2.c--Native Method Implementation Program to Demonstrate Object Access

#include "SayHello2.h"
#include <studio.h>
JNIEXPORT# void JNICALL# Java_SayHello2_greet ( JNIEnv *env, jobject this) {
	jfieldID jf;
	jclass jc;
	jobject jobj;
	const jbyte *pal;
       jint new_age=2;
       jc = (*env)->GetObjectClass(env, this);
       jf = (*env)->GetFieldID(env,jc,"aPal","Ljava/lang/String;");
       jobj = (*env)->GetObjectField(env,this,jf);
       pal = (*env)->GetStringUTFChars(env,jobj,0);
       printf("Hi %s! Welcome to the netherworld of native methods!\n", pal);
       jf = (*env)->GetFieldID(env,jc,"age","I");
       (*env)->SetIntField(env,this,jf,new_age);
}

In Listing 41.5, you see how you can not only access the value of a Java field, but can also reset Java field values from within the native method itself.

The Java class file and shared library are created as before, and on execution, you get the output:

Hi Java Joe! Welcome to the netherworld of native methods!
Java Joe is 2 years old today!


TROUBLESHOOTING:
Why am I getting all the exception errors?
If you get a ton of Java exception errors, the most likely cause could be an incorrect type signature for your GetMethodID() function call. Verify that the signature is correct and recompile your shared library.


Object fields are accessed and used within native methods by following these four steps:

  1. Get the class type of the invoking object.

  2. Get the field ID.

  3. Use the appropriate GetField()/SetField() accessor functions to retrieve/set the object field.

  4. Convert the retrieved object field as needed to use within the native method.

The GetObjectClass is used to determine the class of the object invoking the native method. You see that before the value of an object member field is accessed, you first need to obtain a fieldID for it. The GetFieldID accessor function needs to know the exact field name, as well as its type signature. Table 41.1 denotes the possible Java VM type signatures.
Table 41.1 Java VM Type Signatures
Type Signature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
Lfully-qualified-class; fully-qualified-class
[type type[]
(arg-types)ret-type method type


Since the accessed field is of type String, its type signature is Ljava/lang/String.

The JNI interface pointer provides many functions to access the actual member field, depending on its type.

The various GetField() functions available are shown in Table 41.2.
Table 41.2 Accessor Functions for Java Field Access
GetField Routine Name Native Type Java Type
GetObjectField() jobject Object
GetBooleanField() jboolean boolean
GetByteField() jbyte byte
GetCharField() jchar char
GetShortField() jshort short
GetIntField() jint int
GetLongField() jlong long
GetFloatField() jfloat float
GetDoubleField() jdouble double
You make use of the GetObjectField function since the target field accessed is a Java object. Also, you have to declare an equivalent native type to store the accessed Java field within the native method.

Lastly, you see that object fields can have their values altered by making use of the appropriate SetField() function. You make use of the SetIntField() function since the target field set is of type int. As before, it is important to declare the correct native type to store the value of the native field that is passed to the Java environment.

The various SetField() functions available are shown in Table 41.3.
Table 41.3 Accessor Functions for Setting Java Field Values
SetField Routine Name Native Type Java Type
SetObjectField() jobject Object
SetBooleanField() jboolean boolean
SetByteField() jbyte byte
SetCharField() jchar char
SetShortField() jshort short
SetIntField() jint int
SetLongField() jlong long
SetFloatField() jfloat float
SetDoubleField() jdouble double
The primitive types that can be used within native functions are denoted in Table 41.4.
Table 41.4 Primitive Types
Java Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

Accessing Java Methods from Native Methods

The process of invoking a Java method from within a native method is a little complicated. But, using the techniques demonstrated below, you will also be able to call other native methods from within a native method. In fact, a native method can even call itself recursively, if need be. Listing 41.6 is a good example of how a Java method can be invoked from a native method, with arguments passed to it. Listing 41.7 shows the "C" implementation and demonstrates the actual JNI syntax for accessing Java methods from native methods.

Listing 41.6 SayHello3.java--Java Program to Demonstrate Java Method Access from Native Methods

public class SayHello3 {
   
          public  String rectArea(int l, int b ) {
               return "The area of the rectangle is " + l*b + " units" ;
          }
          public native void calc();
          
          static {
               System.loadLibrary("sayhello3");
          }
    
          public static void main(String[] args) {
               new SayHello3().calc();
          }
     }

Listing 41.7 MyHello3.c--Native Method Implementation to Demonstrate Java Method Access

     #include "SayHello3.h"
     #include <studio.h>
     JNIEXPORT# void JNICALL# Java_SayHello3_greet ( JNIEnv *env, jobject this) {
          jmethodID jm;
          jclass jc;
          jobject jo;
          const jbyte *area;
          jint x,y ;
          x =20;
          y =20;
          jc = (*env)->GetObjectClass(env, this);
          jm = (*env)->GetMethodID(env,jc,"rectArea","(II)Ljava/lang/ String;",x,y);
          jo= (*env)->CallObjectMethod(env,this,jm,x,y);
          area = (*env)->GetStringUTFChars(env,jo,0);
          printf("%s\n",area);
    }

The Java class file and shared library is created as before, and on execution, you get the output:

The Area of the Rectangle is 400 units

Object methods can be called from within native methods by following these five steps:

  1. Get the class type of the invoking object.
  2. Get the method ID.
  3. Initialize the variables that need to be passed as parameters to the Java method.
  4. Use the appropriate CallMethod() accessor function to invoke the method.
  5. Convert the returned object field as needed for use within the native method.

Looking at the type signatures in Table 41.1, you can deduce that the type signature for the Java method rectArea() in Listing 41.6 is (II)Ljava/lang/String; This is because the method takes in two ints and returns back a String object.

The various CallMethod() accessor functions available are shown in Table 41.5.
Table 41.5 Accessor Functions to Invoke Java Methods from Native Methods
CallMethod Routine Name Native Type Java Type
CallVoidMethod() void void
CallObjectMethod() jobject Object
CallBooleanMethod() jboolean boolean
CallByteMethod() jbyte byte
CallCharMethod() jchar char
CallShortMethod() jshort short
CallIntMethod() jint int
CallLongMethod() jlong long
CallFloatMethod() jfloat float
CallDoubleMethod() jdouble double

Accessing Static Fields

Thus far you have seen how you've worked with functions that work on instance fields and methods. Static fields and methods of Java objects can be accessed in much the same way by using some of the special functions shown below.

GetStaticFieldID()--The signature of the field accessed can be obtained from Table 41.1. Once the signature has been obtained, the GetStaticFieldID() is used exactly as the GetFieldID() function.

GetStaticField()--Table 41.6 shows the various GetStaticField() functions that can be used to access static fields. The selected function will depend upon the data type of the Java field accessed.

Table 41.6 Accessor Functions for Accessing Static Java Fields
GetStaticField Routine Name Native Type Java Type
GetStaticObjectField() jobject Object
GetStaticBooleanField() jboolean boolean
GetStaticByteField() jbyte byte
GetStaticCharField() jchar char
GetStaticShortField() jshort short
GetStaticIntField() jint int
GetStaticLongField() jlong long
GetStaticFloatField() jfloat float
GetStaticDoubleField() jdouble double

SetStaticField()--Table 41.7 shows the available accessor functions that can be used to modify the value of static Java fields. The chosen function will depend on the type of the target static field.


Table 41.7 Accessor Functions for Setting Static Field Values
SetStaticField Routine Name Native Type Java Type
SetStaticObjectField() jobject Object
SetStaticBooleanField() jboolean boolean
SetStaticByteField() jbyte byte
SetStaticCharField() jchar char
SetStaticShortField() jshort short
SetStaticIntField() jint int
SetStaticLongField() jlong long
SetStaticFloatField() jfloat float
SetStaticDoubleField() jdouble double

Accessing Static Methods

Static Java methods can be invoked just like instance methods, but by making use of the functions GetStaticMethodID() and CallStaticMethod().

GetStaticMethodID()--The usage of this function is identical to that of GetMethodID(). The developer will have to determine the method signature using Table 41.1, before invoking the function.

CallStaticMethod
--Table 41.8 shows a listing of the available CallStaticMethod() accessor functions. The developer will have to choose the appropriate one based on the Java-type of the data returned from the function call.

Table 41.8 Accessor Functions for Invoking Static Java Methods
CallStaticMethod Routine Name Native Type Java Type
CallStaticVoidMethod() void void
CallStaticObjectMethod() jobject Object
CallStaticBooleanMethod() jboolean boolean
CalStaticByteMethod() jbyte byte
CallStaticCharMethod() jchar char
CallStaticShortMethod() jshort short
CalStaticIntMethod() jint int
CallStaticLongMethod() jlong long
CallStaticFloatMethod() jfloat float

Exception Handling Within Native Methods

JNI methods can throw exceptions that can then be handled within the invoking Java object. Following are some of the important functions that are available within the JNI interface pointer for exception handling:

Throw

jint Throw(JNIEnv *env, jthrowable obj);

causes a java.lang.Throwable object to be thrown.

Parameters:

env--the JNI interface pointer obj--a java.lang.Throwable object

Returns:

0--on success
negative number--on failure

ThrowNew

jint ThrowNew(JNIEnv *env,  jclass classz, const char *msg);

initializes and constructs a new exception object instance of type classz with the diagnostic msg and throws it.

Where:

env--the JNI interface pointer classz--a subclass of java.lang.Throwable
msg--a diagnostic message for the class constructor

Returns:

0--on success
negative number--on failure

FatalError

void FatalError(JNIEnv *env, char *msg);

raises an unrecoverable fatal error

Where:

env--the JNI interface pointer
msg--an error message