Contents | Prev | Next | Java Native Interface Specification |
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."
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, as long as these classes are to be loaded with the same class loader. The VM internally maintains a list of loaded native libraries for each class loader. Vendors should choose native library names that minimize the chance of name clashes.
If the underlying operating system does not support dynamic linking, all
native methods must be prelinked 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 slash ("/") 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:
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
:
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
JNIEnv *env, /* interface pointer */
jobject obj, /* "this" pointer */
jint i, /* argument #1 */
jstring s) /* argument #2 */
{
/* Obtain a C-copy of the Java string */
const char *str = (*env)->GetStringUTFChars(env, s, 0);
/* process the string */
...
/* Now we are done with str */
(*env)->ReleaseStringUTFChars(env, s, str);
return ...
}
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:
extern "C" /* specify the C calling convention */
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
JNIEnv *env, /* interface pointer */
jobject obj, /* "this" pointer */
jint i, /* argument #1 */
jstring s) /* argument #2 */
{
const char *str = env->GetStringUTFChars(s, 0);
...
env->ReleaseStringUTFChars(s, str);
return ...
}
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 that expect Java objects accept both global and local references. 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 situations:
Local references are only valid in the thread in which they are created. The native code must not pass local references from one thread to another.
There are different ways to implement a registry, such as using a table, a linked list, or a hash table. Although reference counting may be used to avoid duplicated entries in the registry, a JNI implementation is not obliged to detect and collapse duplicate entries.
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 use native methods to perform nontrivial 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 primitive array elements between a segment 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 can 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:
Our approach provides flexibility. A garbage collector algorithm can make separate decisions about copying or pinning for each given array. For example, the garbage collector may copy small objects, but pin the larger objects.
A JNI implementation must ensure that native methods running in multiple threads can simultaneously access the same array. For example, the JNI may keep an internal counter for each pinned array so that one thread does not unpin an array that is also pinned by another thread. Note that the JNI does not need to lock primitive arrays for exclusive access by a native method. Simultaneously updating a Java array from different threads leads to nondeterministic results.
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:
env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");
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:
The JNI does not impose any restrictions on how field and method IDs are implemented internally.
printf()
function, for example, usually 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.
The programmer must not pass illegal pointers or arguments of the wrong type to JNI functions. Doing so could result in arbitrary consequences, including a corrupted system state or VM crash.
ExceptionOccurred()
, to obtain the exception object that
contains a more detailed description of the error condition.
ExceptionOccurred()
to check for
possible exceptions that occurred during the execution of the Java method.
ArrayIndexOutOfBoundsException
or
ArrayStoreException
.
ExceptionOccurred()
to explicitly check for
synchronous and asynchronous exceptions.
Native methods should insert ExceptionOccurred()
checks in necessary
places (such as in a tight loop without other exception checks) to ensure that
the current thread responds to asynchronous exceptions in a reasonable
amount of time.
ExceptionClear()
, and
then execute its own exception-handling code.
ExceptionOccurred(),
ExceptionDescribe()
, and ExceptionClear()
. The
ExceptionDescribe()
function prints a debugging message about the
pending exception.
Java Native Interface Specification (HTML generated by dkramer on March 15, 1997)
Copyright © 1996, 1997 Sun Microsystems, Inc.
All rights reserved
Please send any comments or corrections to jni@java.sun.com