HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

The evolution of Java has been truly mind-boggling, and quite unlike anything we have ever seen before in the computing domain. Seldom does a day go by without yet another software vendor pledging allegiance to the Java paradigm. Today, few among us will question the power and flexibility of Java. Still, one may ask, "Can Java, by itself, meet all our needs?" The answer to that question was given indirectly by the designers of the language itself when they made allowances for extending the reach of Java through a standard interface utilizing native methods.

So, what exactly are native methods, you ask? Simply put, they are methods which are implemented not in Java itself, but in a different language like C or C++. The methods are compiled into either a DLL or a shared library, depending on the target platform.

Prior to JDK 1.1, the native method interface itself varied quite a bit, depending on the vendor implementing it. Here, the standard native method interface offered by Sun's JDK was replaced by proprietary interfaces like Microsoft's Raw Native Interface (RNI) and Netscape's Java Runtime Interface (JRI) within their respective JVM implementations. For a while, it almost seemed as if Sun's "Write Once, Run Anywhere" clarion call did not apply to Java applications using native methods!

But as with everything else related to Java, the native method interface itself has come a long way since those rough and tumble days. The Java development community (at least those visionary enough to recognize the futility of having to "customize" Java programs for every vendor's JVM) heaved a collective sigh of relief when Sun announced a unifying native method interface called the Java Native Interface (JNI). The JNI, available as an integral part of JDK 1.1, was developed following extensive consultations between JavaSoft and other Java licensees. Although it borrows heavily from Netscape's JRI, JNI holds the promise of making the native method implementation truly independent of the underlying JVM. It should be noted, however, that so far Microsoft has been a holdout on adopting the JNI standard, and has announced that it would support native methods via the RNI and Java/COM interfaces instead.

Why Use Native Methods?
The ability to invoke functionality implemented in C or C++ may be the only way to solve certain types of problems, while still working within the Java paradigm. What if you wanted to write a Java application that needed to directly access a modem or a sound card? Or perhaps your Java servers needed to source data from a data repository that came with its own non-Java API? In short, if your Java application ever needed to access functionality that was not made available via the JDK or third party Java class libraries, or it needed to take advantage of easily available C'-based APIs, taking the native method route is quite possibly the only way out.

Even if you do not have any need for accessing peripheral devices, native methods can facilitate the integration of tried and tested legacy code without causing extensive rewrite.

Some developers have even rationalized that the lack of speed in Java applications is yet another reason to implement core functionality within native methods. While it is true that a C or C++ native method would be much faster than its pure Java counterpart, implementing something as native solely for the sake of speed is a short-sighted move at best! This is especially true considering that Java execution speeds have shown significant improvements over the past few months with the emergence of highly efficient Java Virtual Machines and Just-In-Time (JIT) compilers.

The Flip Side
Before you bring out your trusty old C or C++ compiler, be sure to understand some of the shortcomings in using native methods. It certainly raises the bar a couple of notches in that now the developer not only has to be a competent Java programmer, but also must be proficient in whichever language they may choose for the native method implementation. Furthermore, the portability of your Java application is also substantially curbed since now you need to re-implement your shared library or DLL for each and every platform your application needs to run under. This may not be a particularly easy task, especially considering that a platform's proprietary features may be utilized in implementing the native method functionality.

It is important to note that native methods can be used only as part of Java applications, and not within applets. The security manager implementations in both Netscape's Navigator and Microsoft's Internet Explorer browsers currently disallow the loading of applets that link to shared libraries over the network. However, there is a possibility that future implementations may allow the user to modify the default browser settings. Then, applets containing native methods can at least be run in an Intranet mode, behind a corporate firewall if need be.

Six Steps to Native Method Success!
All native methods written to the JNI standard have the following six steps in common:

  1. Develop the Java code
  2. Compile the Java code to a class file
  3. Generate the header file
  4. Implement the native method
  5. Create the shared library or DLL
  6. Run the Java program
Now, let us delve into the details with a simple example. Our example program, NativeQuote.java, invokes the native method quote which in turn displays a popular quote as output. Please note that our example programs show a strong bias towards Sun's Solaris operating environment and uses C' to implement the native method functionality.

Develop the Java Code
Consider

public native void quote();

The presence of the keyword native within the declaration of the method quote indicates that the method is implemented outside of Java, in a different programming language. Also note that the native methods are always just merely declared within the class and are never implemented, unlike regular Java methods.

The static block

static {
System.loadLibrary("nativequote");
}

tells Java to load the shared library within which it can find the native method implementation. Since we are assuming a Solaris platform for this example, the shared library libnativequote.so is dynamically loaded at run time. If we were to implement the shared library under Windows 95 or NT, then Java would try to load nativequote.dll instead. It is important that the path of the shared library is present within the Java environment variable LD_LIBRARY_PATH.

The main method creates an instance of the class NativeQuote and invokes the native method quote.

Compile the Java Code to a Class File
Nothing new here. Compile the Java source file as usual to obtain the class file.

javac NativeQuote.java

Generate the Header File
The javah utility given to you as part of the JDK is applied on the Java class file to generate the JNI header file. JNI, unlike the native method specification under JDK 1.02, does not make use of stub files.

javah -jni NativeQuote.java

The generated header file is always named after the Java class file, which contains the native method declaration. In this case, the header file NativeQuote.h is generated by javah.

Implement the Native Method
We can obtain the native function prototype from the generated header file, NativeQuote.h, shown in Listing 2.

The stdio.h header file is needed in our implementation, marvinQuote.c (Listing 3), since we make use of the printf function.

Create the Shared Library or DLL.
This step is platform-dependent and your library can be implemented either as a shared object under Solaris, or as a DLL under Windows 95 or NT. Under Solaris, the shared object can be created by using the following compiler options:

cc -G marvinQuote.c -I$JAVAHOME/include
-I$JAVAHOME/include/solaris -o libnativequote.so

For Windows NT/Windows 95 use:

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

Run the Java Program
Assuming that the environment variable, LD_LIBRARY_PATH, contains the path of the shared library, when the application is run as:

java NativeQuote

we should see Marvin the Martian's famous quote as the output:

"Where's the kaboom? There was supposed to be an earth-shattering kaboom!" -- Marvin the Martian

Invoking Java Methods From Native Methods
JNI supports the seamless invocation of Java methods from within native methods. We will see how the recursive Java method factorial, which is a part of the Factorial.java shown in Listing 4, can be invoked from within the native method computeFact shown in Listing 5. The example also shows us how we can pass data to and from native methods.

On running the Java program as

java Factorial

we get the output:

Factorial(5) = 120

We create the shared library fact containing the native implementation, as explained earlier. Also, the function prototype for computeFact is obtained from the header file, Factorial.h, that is generated by applying javah on the compiled class file.

Java methods can be invoked from within native methods by following the following five-step approach:

Step 1: Obtain the class type to which the Java object belongs using the GetObjectClass accessor function

Step 2: Get the Java method ID, by passing the method name and its signature to the function GetMethodID.

The general form of a method signature is

"(argument-types)return-type"

In our example, the Java method factorial takes in an int and returns a double. From Table 1, we can deduce its method signature as (I)D.

Table 1

Step 3: Initialize the parameters to the Java method, if any. In the above example, we initialize the integer parameter x to 5.

Step 4: Use the appropriate CallMethod accessor function to invoke the Java method. For our example, since the Java method factorial returns a double we make use of the CallDoubleMethod accessor function.

Table 2 shows the various CallMethod functions available, based on the return type from the Java method invocation.

Table 2

Step 5: Convert any returned Java object to its native method equivalent. For our example, since we have an equivalent native method type (jdouble) for the Java primitive type double, no conversion is necessary.

A highlight of JNI methods is that they are source-portable across platforms as long as the target platform is JNI-compatible and the native method does not make use of any system-dependent functionality. Table 3 shows the equivalent native types for Java data types.

Table 3

Accessing Java Fields From Native Methods
JNI provides you with numerous accessor functions to seamlessly access and set Java fields from within native methods. Listing 6 is an example Java program whose data is accessed and changed by the native method implementation shown in Listing 7.

We see from Listing 7 how easy it is to access any Java field from within a native method. After first obtaining the class to which the parent object belongs, we obtain the field id of the target field. It is important to provide the correct field name and signature at this stage. After this, accessing or changing the value of the field is just a matter of calling the appropriate GetField() or SetField() accessor function. Tables 4 and 5 show the available functions for accessing or modifying Java fields.

Table 4 Table 5

Throwing Exceptions
Any number of things could potentially go wrong within a native method. In such an eventuality, it is imperative for us to pass along the exception so that it can be properly handled within regular Java methods. The JNI provides functionality for exactly such an eventuality via the Throw, ThrowNew and FatalError accessor functions.

Throw and ThrowNew are used to trigger exceptions from within the native method implementation. For example, consider the following code segments:

(*env)->Throw(env, new java/lang/IllegalArgumentException);

and

(*env)->ThrowNew(env, java/lang/IllegalArgumentException, "Some diagnostic message");

Both of the above enable IllegalArgumentException to be passed over to the parent Java object. JNI also provides for more catastrophic situations by providing the FatalError accessor function.

These types of errors are unrecoverable and typically result in the JVM exiting. The following code snippet is a good example of the usage of FatalError:

(*env)->FatalError(env, "Out of memory! Exiting!");

Using Native Methods in a Multithreaded Environment
Native methods can certainly be accessed by more than one thread concurrently within Java. For example:

public synchronized native void someNativeMethod();

indicates that someNativeMethod can be used in a multithreaded environment.

JNI also provides for data-locking, permitting thread-safe operations within native methods via a monitor-based mechanism. The MonitorEnter() and MonitorExit() accessor functions are instrumental in setting up a critical region for shared data, so that they can be accessed by concurrent threads of execution.

Conclusion
The native methods interface has been totally revamped under JDK 1.1. The new standard - JNI - offers greater power and flexibility to advanced Java developers who need to extend Java's reach into legacy systems, peripheral devices, etc. JNI also promises to make your native method implementations portable across all JVM's adopting this new standard.

Native methods come at a price, however. Currently, they cannot be used within downloadable applets and, consequently, are practical only within stand-alone applications. Native method development itself is not a trivial task, and needs developer expertise not only in Java but also the native implementation language.

About the Author
Govind Seshadri is a Lead Systems Analyst for a leading financial services company and heads a major Java client/server initiative. He can be reached at [email protected]

	

Listing 1: NativeQuote.java

public class NativeQuote {
	public native void quote();

	static {
		System.loadLibrary("nativequote");
	}

	public static void main(String[] args) 
	{
		new NativeQuote().quote();
	}
	
}

Listing 2: NativeQuote.h

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

#ifndef _Included_NativeQuote
#define _Included_NativeQuote
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeQuote
 * Method:    quote
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_NativeQuote_quote
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Listing 3: marvinQuote.c

#include "NativeQuote.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_NativeQuote_quote(JNIEnv *env, 
jobject this) {
	printf("Where's the kaboom? There was supposed to be \
        an earth-shattering kaboom! -- Marvin the Martian\n);
	return;
}

Listing 4: Factorial.java

public class Factorial {
	// method to recursively compute factorial
	//e.g. factorial(3) = 3 * 2 * 1 = 6
	public double factorial (int x) {
		if (x > 1) 
			return ((double) factorial(x-1) * x);
		else
			return ((double) 1);
	}
	
  	//native method which invokes above method
	public native void computeFact();

	static {
		System.loadLibrary("fact");
	}

	public static void main(String[] args) 
	{
		new Factorial().computeFact();
	}
}

Listing 5: callFactorial.c

#include "Factorial.h"
#include <stdio.h>

JNIEXPORT void  JNICALL Java_Factorial_computeFact(JNIENV *env, jobject this)  
{
	jdouble res;
	jint x ;
	jclass cls = (*env)->GetObjectClass(env,this);
	jmethodID mid = 
(*env)->GetMethodID(env,cls,"factorial","(I)D");
	if (mid ==0) return;
	x =5;
	res = (*env)->CallDoubleMethod(env,this,mid,x);
	printf("Factorial(%d) = %.0f\n",x,res);
}

Listing 6: FaveDrink.java

public class FaveDrink {
	public String beverage = "Café Au Lait";

	public native void changeDrink();

	static {
		System.loadLibrary("favedrink");
	}	
	
	public static void main(String[] args) {
		FaveDrink aDrink = new FaveDrink();

		System.out.println("Calling native method...\n");		 
		aDrink.changeDrink();
System.out.println("Back from native method 
    call...\n");
System.out.println("My favorite drink is now "+ 
    beverage);	
	}
}
   
Listing 7 alterDrink.c

#include "FaveDrink.h" 
#include <stdio.h>

JNIEXPORT void JNICALL Java_FaveDrink_changeDrink (JNIEnv *env, jobject this){

	jfieldID fid;
	jstring jstr;
	const char *drink;

	//obtain the class to which the current object belongs to	
	jclass cls = (*env)->GetObjectClass(env,this);

	//retrieve the fieldID for the Java field which 
	//you want to access
	fid =(*env)->GetFieldID(env,cls,"beverage",
                             "Ljava/lang/String;");
	//retrieve the value of the field using the appropriate 
	//accessor function
	
jstr = (*env)->GetObjectField(env,this,fid);
	drink = (*env)->GetStringUTFChars(env,jstr,0);
	printf("My favorite drink currently is %s \n",drink);

	printf("Hmm...not strong enough...");

	jstr = (*env)->NewStringUTF(env,"Turkish Coffee");
	//reset the value of the Java field using the appropriate
	//accessor function
	(*env)->SetObjectField(env,this,fid,jstr);
}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.