The power of Java is its portability. Once you write a pure Java applet or program, it can run on any hardware and operating system that supports the Java platform. That's fine for applications that are written entirely in Java, but what happens if you need to access some non-Java code from your Java program? Java will let you do this, but it comes at the expense of portability. Once you use non-Java code in an application or applet, it can only run on those platforms to which the non-Java code is also ported.
The Java Native Interface (JNI) is the link between your Java application and native code. It allows you to invoke native methods from an application or applet, pass arguments to these methods, and use the results returned by the methods. In this chapter you'll learn how to write a Java application that invokes a native method to perform a specialized calculation. You'll use the javah header generation tool to generate a C header file for the native method and implement the native method in the C programming language. You'll also learn important information about the conversion of Java types to and from C types. When you finish this chapter, you'll be able to use native methods in your Java applications.
Java's motto is "Write Once, Run Anywhere." In order to accomplish this, Java is designed to provide all that you would need to write a platform-independent program. However, there may be a time when you need to access a platform-dependent function or method. These platform-dependent functions or methods are written in a non-Java language (typically C or C++) and are referred to as native methods. Reasons for using native methods include interfacing with a legacy application and accessing capabilities that aren't present (yet) in the Java API.
The Java Native Interface (JNI) provides the capability to access native methods through shared dynamic link libraries, referred to as DLLs on Windows systems. The JNI allows Java programs to invoke the native method, pass arguments to it, and receive the results returned by it. The JNI also provides programmers with the capability to include the JVM in non-Java applications.
In order to access a native method from a Java program, you create a class for the native method and invoke the native method using the normal Java method invocation syntax. Native methods are created using the following steps:
We'll cover each of these steps in an extended example that uses a native method to calculate Fibonacci numbers.
Listing 53.1 contains the NativeApp program that is used to calculate and display Fibonacci numbers. The Fibonacci numbers are an important sequence of numbers in mathematics. They were developed by Leonardo of Pisa, who was also called Fibonacci, meaning "son of good fortune" in Italian. The first Fibonacci number is 0 and the second Fibonacci number is 1. Subsequent Fibonacci numbers are the sum of the previous two Fibonacci numbers. For example, the first 10 Fibonacci numbers are 0, 1, 1, 2, 3, 5, 8, 13, 21, and 34.
NOTE: For more information on Fibonacci numbers, check out the Web page at http://www.ee.surrey.ac.uk/Personal/R.Knott/Fibonacci/fib.html.
Anyway, back to the NativeApp program. It takes an integer command-line parameter and assigns it to the variable n. It then creates a new instance of the Native class and invokes the instance's fibonacci() method, passing it the value of n. The result returned by the method is assigned to the answer variable and then displayed to the console window.
NOTE: The names NativeApp and Native are my own. These names are not required to use native methods.
The NativeApp program looks pretty simple (see Listing 53.1). You are probably wondering where the native methods are declared and used. The fibonacci() method is a native method that is declared in the Native class. To find out how this is done, you must move along to the next section.
class NativeApp { public static void main(String[] args) { if(args.length!=1) { System.out.println("Usage: java NativeApp n"); System.exit(0); } int n=new Integer(args[0]).intValue(); int answer = new Native().fibonacci(n); System.out.println(answer); } }
In order to simplify the use of native methods, it is always a good idea to put them in their own class. The Native class is the class used to declare the fibonacci() method, as shown in Listing 53.2. Note that the fibonacci() method is declared just like Java methods, except that it is preceded by the native keyword and its body is replaced by a semicolon (;). In addition, the loadLibrary() method of the System class is invoked to load the shared library specified by native. In Windows 95 and Windows NT, this causes the native.dll dynamic link library to be loaded. The loadLibary() method is invoked as part of a static initializer of the Native class. A static initializer is used so that the library is loaded only once, when the Native class itself is loaded.
Go ahead and compile Native.java and NativeApp.java before proceeding to the next section.
class Native { public native int fibonacci(int n); static { System.loadLibrary("native"); } }
The javah tool is part of the JDK. It is used to create C language header files that specify the interface between C language native methods and Java. It provides a number of options, which you can read about by invoking the -help option:
javah -help Usage: JAVAH.EXE [-v] [-options] classes... where options include: -help print out this message -o specify the output file name -d specify the output directory -jni create a JNI-style header file -td specify the temporary directory -stubs create a stubs file -trace adding tracing information to stubs file -v verbose operation -classpath <directories separated by colons> -version print out the build version
In practice, the only option that you'll ever need is -jni, which creates a JNI-style header file instead of the old-style header files used with the JDK 1.02.
Create a header for the Native class as follows:
javah -jni Native
This creates the Native.h file, shown in Listing 53.3. It is a C language header file. Heed the warning of the first line of the file.
/* DO NOT EDIT THIS FILE - it is machine generated */
The second line of the file includes the jni.h file that defines the types used in Native.h. The jni.h file contains a number of type and function definitions. If you are a C programmer, you may want to scan this file to see what's in it. The jni.h file is located in the \jdk1.2\include directory. It is accessed using the following:
#include <jni.h>
After that, several conditional compilation directives and a comment are included:
#ifndef _Included_Native #define _Included_Native #ifdef __cplusplus extern "C" { #endif /* * Class: Native * Method: fibonacci * Signature: (I)I */
The lines beginning with # are of no consequence and are included in all header files generated by javah. They are used to determine whether Native.h was previously included in the current compilation.
The comment identifies the Native class, the fibonacci() method, and the method's signature.
The heart of the Native.h file is the declaration of the Java_Native_fibonacci() method. This is the method that you'll need to implement. It specifies that it has three parameters of the types JNIEnv *, jobject and jint. The JNIEnv * and jobject parameters are passed to all native methods and usually can be ignored. The JNIEnv * parameter is a pointer to the environment in which the method is invoked. The jobject parameter is a reference to the object or class in which the method is defined (for example, Native). The jint parameter corresponds to the int parameter of the fibonacci() method, shown in Listing 53.2. You'll use this parameter to calculate a Fibonacci number.
The return value of the Java_Native_fibonacci() method looks pretty complicated, but it's not:
JNIEXPORT jint JNICALL Java_Native_fibonacci (JNIEnv *, jobject, jint);
JNIEXPORT and JNICALL surround the actual jint return value. This jint return value is the type of value that you'll actually return from the native method. JNIEXPORT and JNICALL are used to define the function-calling sequence and are defined in the jni_md.h file, which is located in the \jdk1.2\include\win32 directory on Windows 95 and NT implementations.
The remainder of the file ends the conditional compilation directives (see Listing 53.3).
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Native */ #ifndef _Included_Native #define _Included_Native #ifdef __cplusplus extern "C" { #endif /* * Class: Native * Method: fibonacci * Signature: (I)I */ JNIEXPORT jint JNICALL Java_Native_fibonacci (JNIEnv *, jobject, jint); #ifdef __cplusplus } #endif #endif
The Native.h header file tells you what the C implementation of the fibonacci() method must look like. In order to implement this method, you must create a C language file with the implementation of Java_Native_fibonacci(), as shown in Listing 53.4.
The file begins with the following two include statements:
#include <jni.h> #include "Native.h"
The first line includes the jni.h header file that was mentioned earlier. The second line includes the Native.h header file that you generated in the previous section.
Following the include statements is the C language fibo() function, which calculates the nth Fibonacci number:
int fibo(int n){ if(n<=1) return 0; if(n==2) return 1; return fibo(n-1)+fibo(n-2); }
And finally, we include an implementation for Java_Native_fibonacci():
JNIEXPORT jint JNICALL Java_Native_fibonacci(JNIEnv *env, jobject obj, jint n) { return fibo(n); }
This method is defined in the same manner as in the Native.h header file. All it does is retrieve the n parameter, pass it to fibo(), and return the result. The complete code of the NativeImp.c file is shown in Listing 53.4.
#include <jni.h> #include "Native.h" int fibo(int n){ if(n<=1) return 0; if(n==2) return 1; return fibo(n-1)+fibo(n-2); } JNIEXPORT jint JNICALL Java_Native_fibonacci(JNIEnv *env, jobject obj, jint n) { return fibo(n); }
At this point, all of our coding is done. All we need to do is compile NativeImp.c in such a way that it produces a shared library (that is, a DLL on Windows systems). I use an old version 2.0 of the Microsoft C++ compiler. After Java came out as an alpha version on Windows NT in 1995, I stopped using C++. This is how I compile NativeImp.c using the Microsoft C++ compiler:
cl -Ic:\jdk1.2\include -Ic:\jdk1.2\include\win32 -Ic:\msvc20\include -LD ÂNativeImp.c c:\msvc20\lib\*.lib -Fenative.dll
The same command line works with subsequent versions of Microsoft C++. If you use a different compiler, you'll have to check with its documentation to see how to build a DLL. Note that I included the c:\jdk1.2\include and c:\jdk1.2\include\win32 directories. These directories are where the jni.h and jni_md.h files are located.
Your compiler should produce the native.dll file, which is the shared library loaded to implement the native method. This file is contained in the ch53 directory.
Now that you've gone through the trouble of implementing the fibonacci() native method, you can enjoy your reward. Use the program to generate as many Fibonacci numbers as you desire. For example, you can generate the 13th Fibonacci number as follows:
java NativeApp 13 144
You could have easily implemented fibonacci() in Java, but the point of the example was to show you how to create and invoke a native method, pass an argument to the method, and receive the value returned by the method.
The one thing that we haven't talked about yet is the use of the jint type. The JNI defines a list of C language types that correspond to Java types. These types are defined in jni.h. The primitive Java boolean, byte, char, short, int, long, float, and double types are represented by the C jboolean, jbyte, jchar, jshort, jint, jlong, jfloat, and jdouble types. See a pattern here?
Java objects are represented by the jobject type, arrays by the jarray type, and String objects by the jstring type. In addition, the jni.h file defines other C types for other kinds of Java objects.
The Java void type is implemented by the C void type. (They must have run out of Js.)
The Java primitive types are converted to C types in a natural fashion and can be used without problems. However, nonprimitive types, including the String type, are not converted to C types in a natural, easy-to-use manner. For example, Java String objects are 16-bit Unicode strings, whereas C strings are 8-bit ASCII strings. The jni.h file defines a number of conversion functions that can be used to access the converted types. These conversion functions are provided as a convenience. You can use DLLs that are compiled without jni.h.
In this chapter you learned how to write a Java application that invokes a native method to calculate a Fibonacci number. You used the javah header generation tool to generate a C header file for the native method, and implemented the native method in the C programming language. You also learned important information about the conversion of Java types to and from C types. In the next chapter you'll learn how to write Java programs that take advantage of Microsoft's Java extensions.
© Copyright, Macmillan Computer Publishing. All rights reserved.