2
This chapter focuses on major design issues in the JNI. Most design issues in this section are related to native methods. The design of the Invocation API is covered in Chapter 5, "The Invocation API."The JNI interface pointer is only valid in the current thread. A native method, therefore, should not pass the interface pointer to another thread. A VM implementing the JNI may allocate and store thread-specific data in the area pointed to by the JNI interface pointer.
Native methods receive the JNI interface pointer as an argument. The VM is guaranteed to pass the same interface pointer to a native method when it makes multiple calls to the native method from the same Java thread. However, a native method can be called from different Java threads, and therefore may receive different JNI interface pointers.
System.loadLibrary method. In the following example, the class initialization method loads a platform-specific native library in which the native method f is defined:
package pkg; class Cls { native double f(int i, String s); static { System.loadLibrary("pkg_Cls"); } } |
The argument to System.loadLibrary is a library name chosen arbitrarily by the programmer. The system follows a standard, but platform-specific, approach to convert the library name to a native library name. For example, a Solaris system converts the name pkg_Cls to libpkg_Cls.so, while a Win32 system converts the same pkg_Cls name to pkg_Cls.dll.
The programmer may use a single library to store all the native methods needed by any number of classes.
If the underlying operating system does not support dynamic linking, all native methods must be pre-linked with the VM. In this case, the VM completes the System.loadLibrary call without actually loading the library.
The programmer can also call the JNI function RegisterNatives() to register the native methods associated with a class. The RegisterNatives() function is particularly useful with statically linked functions.
Java_
In the following example, the native method g does not have to be linked using the long name because the other method g is not a native method, and thus is not in the native library.
class Cls1 { int g (int i); native int g (double d); } |
We adopted a simple name mangling scheme to ensure that all Unicode characters translate into valid C function names. We use the underscore ("_") character as the substitute for the backslash ("/") in fully qualified class names. Since a name or type descriptor never begins with a number, we can use _0, ... , _9 for escape sequences, as Table 2-1 illustrates:
Table 2-1 Unicode Character Translation
Escape Sequence
Denotes
_0XXXX
a Unicode character XXXX.
_1
the character "_"
_2
the character ";" in signatures
_3
the character "[" in signatures
Both the native methods and the interface APIs follow the standard library calling convention on a given platform. For example, UNIX systems use the C calling convention, while Win32 systems use __stdcall.
The remaining arguments correspond to regular Java method arguments. The native method call passes its result back to the calling routine via the return value. Chapter 3, "JNI Types and Data Structures," describes the mapping between Java and C types.
Code Example 2-1 illustrates using a C function to implement the native method f. The native method f is declared as follows:
package pkg; class Cls { native double f(int i, String s); ... } |
The C function with the long mangled name Java_pkg_Cls_f_ILjava_lang_String_2 implements native method f:
Note that we always manipulate Java objects using the interface pointer env . Using C++, you can write a slightly cleaner version of the code, as shown in Code Example 2-2:
With C++, the extra level of indirection and the interface pointer argument disappear from the source code. However, the underlying mechanism is exactly the same as with C. In C++, JNI functions are defined as inline member functions that expand to their C counterparts.
Objects are passed to native methods as local references. All Java objects returned by JNI functions are local references. The JNI allows the programmer to create global references from local references. JNI functions expecting Java objects accept both global and local references. Similarly, a native method may return a local or global reference to the VM as its result.
In most cases, the programmer should rely on the VM to free all local references after the native method returns. However, there are times when the programmer should explicitly free a local reference. Consider, for example, the following situation. A native method accesses a large Java object, thereby creating a local reference to the Java object. The native method then performs additional computation before returning to the caller. The local reference to the large Java object will prevent the object from being garbage collected, even if the object is no longer used in the remainder of the computation.
To overcome this problem, the JNI allows the programmer to manually delete local references at any point within a native method. To ensure that programmers can always manually free local references, JNI functions are not allowed to create extra local references, except for references they return as the result.
There are different ways to implement a registry, such as using a table, a linked list, or a hash table. Reference counting may be used to avoid duplicated entries in the registry.
Note that local references cannot be faithfully implemented by conservatively scanning the native stack. The native code may store local references into global or heap data structures.
The overhead of using accessor functions through opaque references is higher than that of direct access to C data structures. We believe that, in most cases, Java programmers call native methods to perform non-trivial tasks that overshadow the overhead of this interface.
One solution introduces a notion of "pinning" so that the native method can ask the VM to pin down the contents of an array. The native method then receives a direct pointer to the elements. This approach, however, has two implications:
First, we provide a set of functions to copy scalar array elements between a region of a Java array and a native memory buffer. Use these functions if a native method needs access to only a small number of elements in a large array.
Second, programmers use another set of functions to retrieve a pinned-down version of array elements. Keep in mind that these functions may require the Java VM to perform storage allocation and copying. Whether these functions in fact copy the array depends on the VM implementation, as follows:
f in class cls, the native code first obtains a method ID, as follows:
jmethodID mid = |
The native code can then use the method ID repeatedly without the cost of method lookup, as follows:
jdouble result = env->CallDoubleMethod(obj, mid, 10, str); |
A field or method ID does not prevent the VM from unloading the class from which the ID has been derived. After the class is unloaded, the method or field ID becomes invalid. The native code, therefore, must make sure to:
if it intends to use a method or field ID for an extended period of time.The JNI does not impose any restrictions on how field and method IDs are implemented internally.
Certain JNI functions use the Java exception mechanism to report error conditions. In general, however, we believe that the Java exception mechanism is ill-suited for reporting the errors caused by the incorrect use of JNI functions (such as passing illegal arguments).
printf() function, for example, more likely causes a runtime error, rather than returning an error code, when it receives an invalid address. Forcing C library functions to check for all possible error conditions would likely result in such checks to be duplicated-once in the user code, and then again in the library.We therefore specify only a minimum set of exceptions that a conforming JNI implementation must raise when it encounters programming errors. JNI functions use a combination of error codes and Java exceptions to report error conditions. In addition, a JNI implementation may choose to detect more programming errors than required and return an error code, or it may detect more programming errors than required and raise Java exceptions. Note that the extra error codes or Java exceptions a JNI implementation might support do not affect the execution of correct programs.
Java Native Method Interface Specification (HTML generated by wegis on December 06, 1996)
Copyright © 1996 Sun Microsystems, Inc.
All rights reserved
Please send any comments or corrections to sl@eng.sun.com