Because native language methods are so closely tied to a particular platform, the difference in procedures is more pronounced than with the JDK itself. For example, the compilers for different platforms have wildly different command-line arguments. Because of the possible variations and permutations, this section cannot present an exhaustive reference on the native compilers. Therefore, you should always consider the appropriate compiler reference as the ultimate authority. The procedures in these sections worked at the time this was written, using JDK 1.0.2 on the following platforms: SPARC Solaris 2.5, x86 Solaris 2.5, Microsoft Windows 95, Microsoft Windows NT 3.51, and Microsoft Windows NT 4.0 Beta. The Solaris platforms use the usual "cc" compiler. The Windows platforms have been tested using Visual C++ 2.0 and 4.0.
Configuring Your Environment for Solaris
If you have not already done so, you should modify your PATHvariable to include the /java/bindirectory. In addition, setting your JAVAHOMEand CLASSPATH variables properly will help javah run smoothly.
When your class's static block calls System.loadLibrary, the Java runtime will search for the library in the current directory. If you intend to install your dynamic libraries in any other directory, you will need to set your library search path to include that directory. For example, if your libraries are stored in java_libin your home directory, use the following command in Bourne and Korn shells:
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/java_lib$ export LD_LIBRARY_PATH
In C shell, use this:
% setenv LD_LIBRARY_PATH "$LD_LIBRARY_PATH:$HOME/java_lib"
Building Native Methods for Solaris
Use the following command to compile the code and link the dynamic library:
$ cc -G UserInformation.c UserInformationImp.c \> -o libUserInformation.so
You will probably need to use the -Iflag to tell the compiler where to find the Java header files:
$ cc -G -I${JAVA_HOME}/include -I${JAVA_HOME}/include/solaris \> UserInformation.c UserInformationImp.c \> -o libUserInformation.so
The linker will create libUserInformation.soin your current directory. For details about the linker options, refer to the man pages for ccand ld.
To execute System.loadLibrary("libname"), the Solaris Java Virtual Machine will search for a library named liblibname.so.
Configuring Your Environment for Windows 95/NT
You should either make these changes to your C:\AUTOEXEC.BATfile or to a batch file you will run every session.
Setting your JAVA_HOME and CLASSPATH variables properly will help javah run smoothly. In addition, if you have not already done so, you should modify your PATHvariable to include the %JAVA_HOME%\bindirectory.
When your class's static block calls System.loadLibrary, the Java runtime will search for the library in the PATHvariable, as well as the directory in which the executable lives. If you intend to install your dynamic libraries in any directory other than these, you will need to set your path to include that directory.
To make the compile process a little easier, you might want to set the IncLUDE and LIBvariables to point to the Java files:
set IncLUDE=%JAVA_HOME%\include;%IncLUDE%set LIB=%JAVA_HOME%\lib;%LIB%
Building Native Methods for Windows 95/NT
Use the following command to compile and link the dynamic library:
C:\> cl UserInformation.c UserInformationImp.c-FeUserInformation.dll -MD -LD [other_libs] javai.lib
Note |
You must use Visual C++ 2.0 or higher to compile the dynamically linked libraries. In particular, Visual C++ 1.52 or below produce 16-bit code, which will not work with Java. |
The linker will create UserInformation.dllin the current directory. For details on the linker options, refer to the Visual C++ online manual.
To execute System.loadLibrary("libname"), the Windows 95/NT Java Virtual Machine will search for a library named libname.dll.
Troubleshooting Native Methods
Here are some common exceptions you might see when you run your program.
java.lang.UnsatisfiedLinkError no hello in LD_LIBRARY_PATHat java.lang.Throwable.(Throwable.java)at java.lang.Error.(Error.java)at java.lang.LinkageError.(LinkageError.java)at java.lang.UnsatisfiedLinkError.(UnsatisfiedLinkError.java)at java.lang.Runtime.loadLibrary(Runtime.java)at java.lang.System.loadLibrary(System.java)at UserInformation.(UserInformation.java:5)atjava.lang.UnsatisfiedLinkError: Usernameat main.main(main.java:6)
This exception appears on Solaris systems. It means that you have a library path set, but the particular library is not in it. You need to modify your library path to include the directory where your library lives.
java.lang.NullPointerExceptionat java.lang.Runtime.loadLibrary(Runtime.java)at java.lang.System.loadLibrary(System.java)at UserInformation.(UserInformation.java:5)atjava.lang.UnsatisfiedLinkError: Usernameat main.main(main.java:6)
This exception also appears on Solaris systems. It indicates that you do not have a library path set at all, and that the runtime cannot find your library without it. You should either set your library path or move your library to the current directory.
Unable to load dll 'UserInformation.dll' (errcode = 485)Exception in thread "main" java.lang.UnsatisfiedLinkError:no UserInformation in shared library pathat java.lang.Runtime.loadLibrary(Runtime.java:268)at java.lang.System.loadLibrary(System.java:266)at UserInformation.<clinit>(UserInformation.java:3)atjava.lang.UnsatisfiedLinkError: Usernameat main.main(main.java:6)
This is essentially just the Windows version of the same problem. Again, the solution is to copy the library into a directory that is in the PATH, or to modify the PATH to include the library's directory.
java.lang.UnsatisfiedLinkError: Usernameat main.main(main.java:6)
If you get this exception by itself, without a larger walkback above it, then your library is missing the function being called by this native method.
As you can see, when you are using native methods, most of the problems show up when the runtime attempts to load the library. Unfortunately, there is no simple solution. This is basically an installation and configuration management problem. Worse yet, there is no way for one class to catch exceptions that occur when the JVM is loading another class.
The Method and the Function
There are two parts to every native method: the Java declaration and the native language definition. The Java Virtual Machine provides enough capabilities that the native language component can do virtually everything that a typical Java method can. This section will examine the two sides of a native method: the Java declaration and the native language definition. The next few sections will examine how the native language component works with the JVM to make all of this work.
Starting with the Java declaration, consider the method signature from the UserInformationexample earlier in the chapter.
public native String Username();
It looks more or less the same as any other method. The nativekeyword means that the method body is not needed. (In fact, it is not permitted.) The nativekeyword tells the compiler that the definition (the method body) is provided in a different language.
In this example, no arguments were needed. However, you can pass arguments to native methods and get return values from them. These arguments and return values can include objects. See "Arguments and Return Values" for specifics.
We used javah to create the header file for the native side. Take a look at the function signature created by javah.
struct Hjava_lang_String *UserInformation_Username(struct HUserInformation *);
By examining this piece by piece, you can see how the Java to C linkage works. First, the return type is declared as struct Hjava_lang_String *. In the native language, all objects are accessed through a handle-a special structures that allows access to an object's instance variables and the class's methods (the object's vtable.) You can translate Hjava_lang_Stringdirectly to "java.lang.String". Handles are always passed as pointers for efficiency.
As you can see, the function name itself is comprised of the class name, an underscore, and the method name itself. If you wanted to add a new native method to find out the user's disk quota, it would be named something like UserInformation_DiskQuota. Because javah can generate all of the function declarations, there is rarely a need to create these names, but because the names are not "mangled" (the way early C++ to C translators did), the resulting code is very readable.
The final item in this signature is the argument struct HUserInformation *. It is an automatic parameter, which will be explained in the next section.
Arguments and Return Values
Pure Java methods can pass arguments to native methods just as they would to any other method call. Native methods can return any value that a Java method would. How does this work when two such vastly different languages are involved? It works because the Java Virtual Machine exposes some of its internals to the native language. At the same time, the header files that support native methods use features of the C preprocessor and compiler to shield you, the developer, from the guts of the Java implementation. Essentially, when you are developing a native method, you are hooking right into the most fundamental aspects of Java's implementation. As a result, you must be careful to follow the rules, or you risk writing fragile code and upsetting the JVM itself.
Simple data types map directly from Java to C. Some difficulties arise because arrays are first class objects in Java, whereas they are aggregate data types in C. Arrays appear in C code as Java objects. Table 19.1 shows the data type mapping from Java to C.
Java type | C Argument | C Structure |
boolean | long | long |
char | long | long |
short | long | long |
ushort | long | long |
int | long | long |
float | float | float |
double | float | float |
Object | struct Hjava_lang_Object | struct Hjava_lang_Object |
boolean[] | long | long |
char[] | unicode | struct HArrayOfChar |
short[] | long | struct HArrayOfLong |
ushort[] | unsigned short | unsigned short |
int[] | long | struct HArrayOfLong |
float[] | float | struct HArrayOfFloat |
double[] | float | struct HArrayOfFloat |
Object[] | HArrayOfClass | struct Hjava_lang_Object |
The most important argument for any native method is the first parameter passed to all native methods. It is referred to as an automatic parameter. Look back at the example earlier in this chapter. The function UserInformation_Usernamewas passed a struct HUserInformation * as the first (and only) argument. This pointer serves the same function as the implicit thispointer in C++. That is, the first parameter passed to a native method points to the object instance for which that method is being called. You can use this pointer to access the instance variables of the class. With it, you can even call other methods of the object. This automatic parameter takes the form of a handle. (See the sidebar "What are Handles, Anyway?".)
The confluence of handles, multithreading, and garbage collection leads to some interesting consequences for your C code. First of all, your C code must be fully re-entrant. Because a Java application can have an arbitrary number of threads, any one of those threads can call your native method-even if another thread is already executing in that method, unless you synchronize the method. See "Multithreading and Native Methods." Also, remember that the garbage collection runs in an idle thread, but another thread may request that the garbage collector run immediately.
How does this affect your native methods? The garbage collector may relocate objects in memory! As long as you maintain a reference to your arguments, particularly your thispointer, it will not be relocated. However, if you try to store the obj pointer from the object handle (for example, hUserInformation->obj) in a global variable, two very bad things will happen. First, the garbage collector may relocate or even destroy the object. This is because each object has a reference count-a count of the outstanding handles to that object. When the object's reference count hits zero, it is put into a pool that can be garbage collected at any time. When your method returned, the handle passed to it went out of scope and the object's reference count was decremented. Although you still have a copy of the handle, the object may get destroyed without warning. For instance, another thread may cause the reference count to hit zero, ven though your variable still has a pointer. Then your variable points to deallocated memory. The second problem with this scenario comes from the multithreading itself. Any function that uses global data is suspect in a multithreaded program. It is very difficult to prove that a function using global data will behave correctly when faced with re-entrant calls. Here again, the most likely consequence is an invalid pointer. The JVM is well protected, but it can still be crashed. Never believe anyone who tells you that it is impossible to get a core dump or GPF from Java! Anytime native methods are involved, the JVM is only as robust as those native methods.
In Table 19.1, all of the Java array types appear as handles in the object instances. Java treats arrays as first-class types and implements them as objects. When passing an array to a native method, the JVM translates it into a C-like array. However, the array instance variables are still (Java) arrays when accessed from C. Therefore, the C code must treat it as an object.
Returning primitive and complex data types works exactly the same way as primitive and complex arguments. Sometimes, as in the UserInformationexample, this may involve constructing new Java objects in C code. The next section explains how to accomplish this feat.
No comments:
Post a Comment