Abstract: Interfacing to hardware or existing code in Java requires the use of Native Methods. Unfortunately, there are currently three standards used for the interface to these native methods. This means that Java code which uses native methods is currently not portable. This has been a major point of controversy between Sun and Microsoft in recent months. Fortunately, there is a simple solution. A technique for producing native methods which operate with Sun and Microsoft Java interpreters, versions 1.0 and 1.1 is described.
Introduction
Development of the Java programming language began at Sun Microsystems
in 1990. The goal was to provide a portable development environment
for embedded systems. The plan was to provide a "Write Once, Run
Anywhere" programming language. This would mean that developers
of embedded systems would not be tied to any particular CPU
architecture.
The Java concept was developed and marketed to the embedded systems market, mostly unsuccessfully. But in 1995, a change of direction lead to Java as we know it today. At his time, the internet and the World Wide Web were undergoing a period of explosive growth. For the first time, people with various incompatible systems could access information remotely in a standard format. Whether you were on a workstation, PC or a Macintosh, Web pages all looked the same.
The next obvious step was to provide more active content for Web pages. Some sort of programming language support would be necessary. But the language would have to be portable. It would have to execute on a variety of diverse hardware and operating system platforms.
Of course, Java was the solution waiting in the wings for active
content for the Web. It was quickly embraced and introduced to
literally millions. While much of the use of Java was for
applets to enhance Web pages, Java was still a portable,
general-purpose object-oriented programming language. Many
programmers began to trade in their C and C++
compilers for Java.
Compatibility Issues
Java was defined to be a portable language. As such, all
implementations of Java must be essentially identical. To enforce
this compatibility, Sun Microsystems, the originators of Java, have
defined a test suite that all Java implementations must pass before
they receive the Java seal of approval. Since the name
Java is a trademark of Sun Microsystems, Sun will only
permit Java compilers and interpreters which conform to their standard
to use the name Java.
Unfortunately, this approach to standardizations has led to an acrimonious battle between Java's two biggest supporters: Sun Microsystems and Microsoft [1] [2]. Suits and countersuits for millions of dollars have been filed, and the ensuing media battle has made it difficult to understand the technical issues involved. The battle has quickly achieved the status of the sort of religious war usually associated with Macintosh versus PC users, or perhaps even Emacs versus Vi.
This article does not seek to wade into the legal issues, nor does it
take sides. But it does offer a solution for the first, and perhaps
largest claim of incompatibility between the Sun and Microsoft
versions of Java: Native methods.
Native Methods
Java, as the language is defined, is hardware independent. While this
is a great benefit to most Java users, it provides no mechanism for
interfacing to either hardware or non-Java code. Unfortunately, in
many instances, particularly in embedded design, it is necessary to
communicate with device drivers, C or C++ libraries,
or even the hardware itself.
The current technique for communicating with non-Java software is the native method. Here, a class function is defined with the prefix "native". No implementation is provided, but a technique for interfacing this function definition to other code is supplied.
In the current situation, Sun has has initially supplied a version of the native interface in its version 1.0 Java Development Kit (JDK). This version made use of a technique using something called "stubs" (more on stubs later). In their version 1.1, Sun defined another native interface called the Java Native Interface or JNI. Finally, Microsoft has jumped into the fray and defined its own interface, known as Raw Native Interface or RNI for short.
Of course, these three interfaces to the hardware are incompatible. This means that when writing Java code using native methods, a choice must be made. Will the code support the Sun 1.0 interpreter, the Sun 1.1 interpreter or the Microsoft Java interpreter? With this choice made, users of the code must also have the correct Java interpreter to execute the application. Clearly this has impacted the portability of Java.
This paper proposes a simple solution to this problem: develop code that supports all three native method interface simultaneously. The method uses a single piece of interface code, in this case in C, and builds three different interface libraries which each interface to the selected native method. While in any situation, two of the three interfaces will be unused, the overhead of carrying these extra libraries is minimal. Figure 1 diagrams this approach to supporting different interfaces in seperate DLL libraries.
The examples below were developed on an IBM-PC compatible machine running Microsoft Windows NT/4.0. The C compiler used was Microsoft Visual C++ 4.0, standard edition. The Java Development environments used were the Sun JDK 1.0 and 1.1 as well as Microsoft's J++. These are all freely available for download from Sun and Microsoft's WWW sites at http://java.sun.com/ and http://www.microsoft.com/java/, respectively.
Java 1.0 "Stubs"
Of course, the first thing to do is to write your Java code, with the
necessary native methods defined. The example below is somewhat
contrived. It is a simple Java class that has a native method called
PrintNum(). This function just prints out an integer. The
main routine prints out the first ten integers, and should produce the
output "0123456789". The Native method PrintNum()
takes in an integer returns an integer. This is done primarily to
demonstrate the passing of parameters in Java native methods. The
Java code for the PrintNum example is shown in Figure 2.
public class PrintNum { public native int printNum(int num); static { try { System.loadLibrary("PrintNumStubs"); System.loadLibrary("PrintNumJNI"); System.loadLibrary("PrintNumRNI"); } catch (UnsatisfiedLinkError ule) { System.err.println("Unsatisfied Link Exception: Could not load library."); System.exit(-1); } /* end try{}*/ } /* end static() */ public static void main(String argv[]) { int i; int result; PrintNum pn = new PrintNum(); for (i=0; i<10; i++) result = pn.printNum(i); } /* end main() */ }; /* end class PrintNum */
Figure 2: The Java code for the PrintNum example.
Note that all that is in this Java class is a native method, a main routine, and static section which is used to load the external libraries. In this case, we have three libraries, one for each of the native method interface standards. We call these PrintNumStubs, PrintNumJNI and PrintNumRNI. These correspond to the Sun Java 1.0 "Stubs" format, the Sun 1.1 JNI format and the Microsoft RNI interface format, respectively. Since we are running on a Windows machine, these libraries will be Dynamic Link Libraries or DLLs.
Once this code is written and compiled, we can begin to write and interface to the libraries. For the Sun Java 1.0 format, we must use the javah utility. This utility produces a C header file (or ".h" file). This defines the C interface to the DLL that the Java interpreter is expecting. Additionally, Javah must be run with the "-stubs" flag to produce a stubs file. This file contains an interface layer used by the Sun Java 1.0 interpreter. Note that the Sun JDK 1.1 version of javah may be used to produce the files required by the 1.0 interface. Figure 3 gives the commands used to produce the stubs files.
C:\> javah -o PrintNum_stubs.h PrintNum C:\> javah -stubs -o PrintNum_stubs.c PrintNum
Figure 3: The commands used to produce the stubs interface.
Now the C code has to be written. In the standard implementation of the native interface, the function prototypes in the PrintNum_stubs.h file should be implemented. But to make the porting job easier, a non-native C interface based on the function prototype is defined. In this case we will implement a C version of the native method. We will attempt to use appropriate data types. In Java, and int is 32 bits, so we will us a long in C. The implementation of the C code is in Figure 4.
long PrintNum(long num) { printf("%d", num); return (num); } /* end PrintNum() */
Figure 4: The C interface code.
Note that this code is independent of Java, and can compile on its own and may also be used to provide a library interface to other C code. Now that we have this implementation of the function, we have only to attach it to the Java interface generated by Javah. This code is fairly simple and mostly involves casting parameters to their proper data types. The code for the Java interface is shown in Figure 5.
long PrintNum_printNum(struct HPrintNum *h, long num) { return (PrintNum(num)); } /* end PrintNum_printNum() */
Figure 5: The Java stubs interface code.
Now all that is required is to compile this code into a DLL. Note that the PrintNum_stubs.c file must also be included in this compilation. Additionally, some include files from the Sun JDK 1.0 are also required. These must be in the include path for the compilation to succeed. Finally, some compilers will have problems automatically generating dependency information if the Sun JDK 1.0 include files are used. This is because many include files are enclosed in #ifdefs. The best solution is to (carefully) comment out the #includes which are interfering with compilation.
Compiling these files successfully should produce the PrintNumStubs.dll file loaded by the Java code. You can test this using the sun Java interpreter by commenting out the loadLibrary() commands for the other two DLLs in the Java code and re-compiling. You should be able to execute the command and receive the output in Figure 6.
C:\> java PrintNum 0123456789 C:\>
Figure 6: Execution of the Sun Java 1.0 native method.
Now that the first DLL is implemented, it is a fairly simple process to support the other interface formats. Figure 7 below shows the general design flow used to build the DLL libraries. Note that since the design flow is essentially the same for each DLL, producing subsequent DLLs is realatively easy once the first has been implemented.
Sun Java 1.1 JNI
With the existing C code in PrintNum.c we can move
on to producing a Sun Java 1.1 JNI interface. The steps are
almost identical to producing the 1.0 interface, except that stubs are
no longer necessary. Figure 8 shows the javah
commands used to produce the JNI interface.
C:\> javah -jni -o PrintNumJNI.h PrintNum
Figure 8: The command to produce the JNI interface.
This produces the C header file PrintNumJNI.h. This file contains the functions prototypes for the library interface expected by the Sun Java 1.1 interpreter. Again, these functions must be implemented to produce the DLL. As with the 1.0 implementation, the functions are fairly trivial. Figure 9 shows the code used to implement the interface. Note that it is similar to the 1.0 interface in Figure 5.
JNIEXPORT jint JNICALL Java_PrintNum_printNum(JNIEnv *env, jobject obj, jint num) { return ((jint) PrintNum(num)); } /* end Java_PrintNum_printNum() */
Figure 9: The Java JNI interface code.
Compiling this code results in the PrintNumJNI.dll library. Note that the include files necessary to compile the JNI interface must come from the Sun JDK 1.1. The Sun 1.0 JDK include files will not work correctly.
Once this code is compiled, the DLL can be loaded and executed by the Java code. The execution of this code should look identical to the run in Figure 5. Note that if the loadLibrary() call for the Stubs DLL is commented out of the Java code, and only the JNI interface library is loaded, the code will execute correctly on a Java 1.1 interpreter, but will fail with a run time unsatisfied link exception on a Java 1.0 interpreter.
Microsoft Java 1.1 RNI
Finally, we can compile a library for Microsoft's RNI interface.
While Microsoft has provided a different interface, it still uses the
same Java source code. Only the way the native method interface is
specified is different. The reason for not adopting the Sun approach
seems to be for compatibility with existing Microsoft libraries.
Whatever the reason (or the legality) it is a simple matter to produce
a DLL which the Microsoft interpreters recognize. The process should
be familiar by now.
First, the Microsoft SDK has a header generation program called msjavah, which is the Microsoft version of Sun's javah. Msjavah is used to produce the PrintNumRNI.h header file, which contains the function prototypes for the DLL interface. Filling in these functions in the same manner as the Sun stubs and JNI interfaces is left as an exercise to the reader. From here a DLL is compiled. Again, the correct include files from the Microsoft SDK must be used. The Sun JDK files will not work. Finally, the compiled DLL PrintNumRNI.dll may be loaded and executed by the Java code. Now the Microsoft Java interpreter, jview is supported.
Conclusions
A technique for simultaneously supporting all three standards for Java
native method interfaces is described. The Sun 1.0 "Stubs",
the Sun 1.1 JNI and the Microsoft RNI interfaces are
provided using a minimal amount of code and effort. This approach is
only incrementally more difficult than supporting any one of the
standards.
The techniques described in this paper permit Java developers to produce and ship portable code using native methods. Users will not be required to switch from one Java interpreter to another. We have successfully used this approach within Xilinx to produce our latest Java-based portable design and debug tools for Field Programmable Gate Arrays (FPGAs).
While this approach to native methods does provide a simple solution to the compatibility issues with respect to native methods in Java, it seems likely that the Java standards controversies will continue. But for now, it is possible to produce simple, portable native code. In spite of what you may read in press releases.
Steven A. Guccione is a Senior Staff Engineer at Xilinx in San Jose, CA and can be reached at Steven.Guccione@xilinx.com.
References
[1] Sun press Release, Microsoft fails Java compatibility tests for Internet Explorer 4.0, http://java.sun.com/pr/1997/oct/pr971007.html Palo Alto, CA. October 7, 1997.
[2] Microsoft Press Release, Microsoft Countersues Sun Microsystems, http://www.microsoft.com/corpinfo/press/1997/oct97/msuncspr.htm San Jose, CA. October 27, 1997.
[3] Mary Campione and Kathy Walrath, "The Java(tm) Tutorial", Addison-Wesley, 1996.
[4] Jerry R. Jackson and Alan L. McClellan, "Java by Example (Second Edition)", Prentice-Hall, 1997.
Steven A. Guccione