In this chapter you'll learn how to access native C code from Java. You will see how to use the supplied tools to create a dynamic link library (DLL), which the Java runtime interpreter can call to perform native functions. When you finish this chapter, you will be able to link your Java code with native C methods.
The Java programming language has gone to great lengths to hide machine-specific facilities from the programmer.
However, regardless of the reason, there might always be a need to access the raw speed or other platform-specific facilities. To accommodate this need, Java provides access to native code stored in dynamic link libraries.
When Java code is executed on a machine other than a native Java microprocessor, a runtime interpreter is needed. The interpreter is actually the program that executes the Java code. So to allow the Java code access to native methods, the interpreter must act as the go-between for the two platforms. This is achieved by standard entry points defined in native DLLs.
In the big picture of accessing native methods, calling native methods from Java is one of the most straightforward processes. The task of calling the native method can be broken down into three steps, all of which need to be implemented in order for the native call to work:
The first step is to load the library. This is achieved through the java.lang.Runtime class, which handles all the overhead involved in dynamically linking to a DLL. The following example shows how this is done:
import java.lang.Runtime;
public class calcTC //Calculates the two's
{ //complement of a passed number.
Static
{
try
{
loadLibrary("nativeDLL");
}
catch(Exception e)
{
//Do Something here
}
}
public native int twos(int number);
}
It is usually a good idea to call the loadLibrary method during the static initialization. This way the library is loaded before there's any chance of the method being called. If the loadLibrary method is unsuccessful, it will throw the UnsatisfiedLinkError exception. This provides a mechanism to replace the native method call.
The next step in calling a native method is to declare the method:
public native int twos(int number);
The native directive tells the compiler that this method is natively implemented. Besides the native directive, the method resembles a normal method declaration, except for the fact that code does not follow the declaration.
After the method has been declared, it can be called using Java's normal calling conventions. That's it from Java's standpoint. The rest of the work involves the creation of the DLL.
As mentioned before, the Java runtime interpreter acts as the go-between for the Java code and the native DLL. In order to facilitate this communication, a common interface is needed.
Shipped with the Java Developer's Kit is a utility called javah, which is designed to create the interface with which the Java runtime interpreter can communicate. It accomplishes this by taking as a parameter the class file containing the native call. javah then produces a header file, and an associated C file is needed. The process to create a header and stub C file for the calcTC class are as follows.
Execute javah to create the header file:
javah calcTC
This command creates a file under the cclassHeaders directory named calcTC.h. This file contains the following header information:
/* Header for class calcTC */
#ifndef _Include_calcTC
#define _Include_calcTC
typedef struct ClasscalcTC{
int number;
} ClasscalcTC;
HandleTo(calcTc);
#ifdef __cplusplus
extern "C" __declspec(dllexport)
#endif
int clacTC_twos(struct HcalcTC *,int);
#endif
This file declares the interface with which the Java runtime interpreter can communicate.
The final step in creating the native DLL interface is to create a stub file containing some of the needed interface overhead. To create this stub file for the calcTC class, execute the following command:
javah -stubs calcTC
This creates the file calcTC.stubs in the subs subdirectory, which contains the following information:
/* Stubs for class calcTC */
/* DO NOT EDIT THIS FILE - it is machine generated*/
/* SYMBOL: "calcTC\twos(I)I", calcTC_twos_stub,*/
_declspec(dllexport) stack_item
*calcTC_twos_stub(stack_item *_P_,struct execenv *_EE_)
{
extern int clacTC_twos(void *,int);
_P_[0].i = calcTC_twos(_P_[0].p,((_P_[1].i)));
return _P_+1;
}
calcTC.stubs, along with stubsPreamble.h (found in the /hotjava/include subdirectory), must preface the actual implementation of the native method. That is, you must include stubsPreamble.h and the header file created by the javah command. After the include statements, insert the contents of the stub file and the native method definition. For example, the code for the calcTC native DLL would be the following:
#include calcTC.h
#include stubsPreamble.h
//cut and paste the stub file
/* Stubs for class calcTC */
/* DO NOT EDIT THIS FILE - it is machine generated*/
/* SYMBOL: "calcTC\twos(I)I", calcTC_twos_stub,*/
_declspec(dllexport) stack_item
*calcTC_twos_stub(stack_item *_P_,struct execenv *_EE_)
{
extern int clacTC_twos(void *,int);
P_[0].i = calcTC_twos(_P_[0].p,((_P_[1].i)));
return _P_+1;
}
//implement the native method prot-typed in calcTC.h
int clacTC_twos(struct HcalcTC *this,int number)
{
return ~number;
}
Compile this file into a DLL with the name specified in the loadLibrary() Java call, and you're ready to go. Got all that? If not, take a look at the steps involved again, as well as at what the generated code is doing. This process will become easier after you have successfully completed it at least once.
In this chapter you have learned how to call and create native methods. This is probably the hardest task in Java, because it requires tools outside the scope of Java. Use the technology at your discretion. Just keep in mind that native code will run only on the platform on which it was compiled. The fact that Java can call a function stored in a native DLL does not make the DLL more platform independent.