The material in this appendix was contributed by and
used with the permission of Andrea Provaglio
(www.AndreaProvaglio.com).
The Java language and its standard API are rich
enough to write full-fledged applications. But in some cases you must call non-Java code; for example, if you want
to access operating-system-specific features, interface
with special hardware devices, reuse a preexisting,
non-Java code base, or implement time-critical sections of
code.
Interfacing with non-Java code requires dedicated support
in the compiler and in the Virtual Machine, and additional tools to map the Java
code to the non-Java code. The standard solution for calling non-Java code that
is provided by JavaSoft is called the Java Native Interface, which will
be introduced in this appendix. This is not an in-depth treatment, and in some
cases you’re assumed to have partial knowledge of the related concepts and
techniques.
JNI is a fairly rich programming interface that allows you
to call native methods from a Java application. It was added in Java 1.1,
maintaining a certain degree of compatibility with its Java 1.0 equivalent: the
native method interface (NMI). NMI
has design characteristics that make it unsuitable for adoption across all
virtual machines. For this reason, future versions of the language might no
longer support NMI, and it will not be covered here.
Currently, JNI is designed to interface with native
methods written only in C or C++. Using JNI, your native
methods can:
Thus, virtually everything you can do with classes and
objects in ordinary Java you can also do in native methods.
We’ll start with a simple example: a Java program that
calls a native method, which in turn calls the standard C library function
printf( ).
The first step is to write the Java code declaring a
native method and its arguments:
//: appendixb:ShowMessage.java
public class ShowMessage {
private native void ShowMessage(String msg);
static {
System.loadLibrary("MsgImpl");
// Linux hack, if you can't get your library
// path set in your environment:
// System.load(
// "/home/bruce/tij2/appendixb/MsgImpl.so");
}
public static void main(String[] args) {
ShowMessage app = new ShowMessage();
app.ShowMessage("Generated with JNI");
}
} ///:~
The native method declaration is followed by a
static block that calls System.loadLibrary( ) (which you
could call at any time, but this style is more appropriate).
System.loadLibrary( ) loads a DLL in memory and links to it. The DLL
must be in your system library path. The file name extension is automatically
added by the JVM depending on the platform.
In the above code you can also see a call to the
System.load( ) method, which is commented out. The path specified
here is an absolute path, rather than relying on an environment variable. Using
an environment variable is naturally the better and more portable solution, but
if you can’t figure that out you can comment out the loadLibrary( )
call and uncomment this one, adjusting the path to your own directory.
Now compile your Java source file and run javah on the resulting .class file, specifying
the —jni switch (this is done automatically for you by the makefile in
the source code distribution for this book):
javah —jni ShowMessage
javah reads the Java class file and for each native
method declaration it generates a function prototype in a C or C++ header file.
Here’s the output: the ShowMessage.h source file (edited slightly to fit
into this book):
/* DO NOT EDIT THIS FILE
- it is machine generated */
#include <jni.h>
/* Header for class ShowMessage */
#ifndef _Included_ShowMessage
#define _Included_ShowMessage
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ShowMessage
* Method: ShowMessage
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL
Java_ShowMessage_ShowMessage
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
As you can see by the #ifdef __cplusplus
preprocessor directive, this file can be compiled either by a C or a C++
compiler. The first #include directive includes jni.h, a header
file that, among other things, defines the types that you can see used in the
rest of the file. JNIEXPORT and JNICALL are macros that expand to match
platform-specific directives. JNIEnv, jobject and jstring
are JNI data type definitions, which will be explained shortly.
JNI imposes a naming convention (called name
mangling) on native methods. This is important, since it’s part of the
mechanism by which the virtual machine links Java calls to native methods.
Basically, all native methods start with the word “Java,” followed by the name
of the class in which the Java native declaration appears, followed by the name
of the Java method. The underscore character is used as a separator. If the Java
native method is overloaded, then the function signature is appended to the name
as well; you can see the native signature in the comments preceding the
prototype. For more information about name mangling and native method
signatures, please refer to the JNI documentation.
At this point, all you have to do is write a C or C++
source code file that includes the javah-generated header file and
implements the native method, then compile it and generate a dynamic link
library. This part is platform-dependent. The code below is compiled and linked
into a file called MsgImpl.dll for Windows or MsgImpl.so for
Unix/Linux (the makefile packaged with the code listings contains the commands
to do this—it is available on the CD ROM bound into this book, or as a free
download from www.BruceEckel.com):
//: appendixb:MsgImpl.cpp
//# Tested with VC++ & BC++. Include path must
//# be adjusted to find the JNI headers. See
//# the makefile for this chapter (in the
//# downloadable source code) for an example.
#include <jni.h>
#include <stdio.h>
#include "ShowMessage.h"
extern "C" JNIEXPORT void JNICALL
Java_ShowMessage_ShowMessage(JNIEnv* env,
jobject, jstring jMsg) {
const char* msg=env->GetStringUTFChars(jMsg,0);
printf("Thinking in Java, JNI: %s\n", msg);
env->ReleaseStringUTFChars(jMsg, msg);
} ///:~
The arguments that are passed into the native method are
the gateway back into Java. The first, of type JNIEnv, contains
all the hooks that allow you to call back into the JVM. (We’ll look at this in
the next section.) The second argument has a different meaning depending on the
type of method. For non-static methods like the example above, the second
argument is the equivalent of the “this” pointer in C++ and similar to
this in Java: it’s a reference to the object that called the native
method. For static methods, it’s a reference to the Class object
where the method is implemented.
The remaining arguments represent the Java objects passed
into the native method call. Primitives are also passed in this way, but they
come in by value.
In the following sections we’ll explain this code by
looking at the ways that you access and control the JVM from inside a native
method.
JNI functions are those that the programmer uses to
interact with the JVM from inside a native method. As you can see in the example
above, every JNI native method receives a special argument as its first
parameter: the JNIEnv argument, which is a pointer to a special JNI data
structure of type JNIEnv_. One element of the JNI data structure is a
pointer to an array generated by the JVM. Each element of this array is a
pointer to a JNI function. The JNI functions can be called from the native
method by dereferencing these pointers (it’s simpler than it sounds). Every JVM
provides its own implementation of the JNI functions, but their addresses will
always be at predefined offsets.
Through the JNIEnv argument, the programmer has
access to a large set of functions. These functions can be grouped into the
following categories:
The number of JNI functions is quite large and won’t be
covered here. Instead, I’ll show the rationale behind the use of these
functions. For more detailed information, consult your compiler’s JNI
documentation.
If you take a look at the jni.h header file, you’ll
see that inside the #ifdef __cplusplus preprocessor conditional, the
JNIEnv_ structure is defined as a class when compiled by a C++ compiler.
This class contains a number of inline functions that let you access the JNI
functions with an easy and familiar syntax. For example, the line of C++ code in
the preceding example:
env->ReleaseStringUTFChars(jMsg, msg);
could also be called from C like
this:
(*env)->ReleaseStringUTFChars(env, jMsg, msg);
You’ll notice that the C style is (naturally) more
complicated—you need a double dereferencing of the env pointer, and you
must also pass the same pointer as the first parameter to the JNI function call.
The examples in this appendix use the C++ style.
As an example of accessing a JNI function, consider the
code in MsgImpl.cpp. Here, the JNIEnv argument env is used
to access a Java String. Java Strings are in Unicode format, so if
you receive one and want to pass it to a non-Unicode function
(printf( ), for example), you must first convert it into ASCII
characters with the JNI function GetStringUTFChars( ). This function
takes a Java String and converts it to UTF-8 characters. (These are 8
bits wide to hold ASCII values or 16 bits wide to hold Unicode. If the content
of the original string was composed only of ASCII, the resulting string will be
ASCII as well.)
GetStringUTFChars( ) is one of the member
functions in JNIEnv. To access the JNI function, we use the typical C++
syntax for calling a member function though a pointer. You use the form above to
access all of the JNI functions.
In the previous example we passed a String to the
native method. You can also pass Java objects of your own creation to a native
method. Inside your native method, you can access the fields and methods of the
object that was received.
To pass objects, use the ordinary Java syntax when
declaring the native method. In the example below, MyJavaClass has one
public field and one public method. The class UseObjects
declares a native method that takes an object of class MyJavaClass.
To see if the native method manipulates its argument, the public field of
the argument is set, the native method is called, and then the value of the
public field is printed.
//: appendixb:UseObjects.java
class MyJavaClass {
public int aValue;
public void divByTwo() { aValue /= 2; }
}
public class UseObjects {
private native void
changeObject(MyJavaClass obj);
static {
System.loadLibrary("UseObjImpl");
// Linux hack, if you can't get your library
// path set in your environment:
// System.load(
//"/home/bruce/tij2/appendixb/UseObjImpl.so");
}
public static void main(String[] args) {
UseObjects app = new UseObjects();
MyJavaClass anObj = new MyJavaClass();
anObj.aValue = 2;
app.changeObject(anObj);
System.out.println("Java: " + anObj.aValue);
}
} ///:~
After compiling the code and running javah, you can
implement the native method. In the example below, once the field and method ID
are obtained, they are accessed through JNI functions.
//: appendixb:UseObjImpl.cpp
//# Tested with VC++ & BC++. Include path must
//# be adjusted to find the JNI headers. See
//# the makefile for this chapter (in the
//# downloadable source code) for an example.
#include <jni.h>
extern "C" JNIEXPORT void JNICALL
Java_UseObjects_changeObject(
JNIEnv* env, jobject, jobject obj) {
jclass cls = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(
cls, "aValue", "I");
jmethodID mid = env->GetMethodID(
cls, "divByTwo", "()V");
int value = env->GetIntField(obj, fid);
printf("Native: %d\n", value);
env->SetIntField(obj, fid, 6);
env->CallVoidMethod(obj, mid);
value = env->GetIntField(obj, fid);
printf("Native: %d\n", value);
} ///:~
Ignoring the “this” equivalent, the C++ function receives
a jobject, which is the native side of the Java object reference we pass
from the Java code. We simply read aValue, print it out, change the
value, call the object’s divByTwo( ) method, and print the value out
again.
To access a Java field or method, you must first obtain
its identifier using GetFieldID( ) for fields and
GetMethodID( ) for methods. These functions take the class object, a
string containing the element name, and a string that gives type information:
the data type of the field, or signature information for a method (details can
be found in the JNI documentation). These functions return an identifier that
you use to access the element. This approach might seem convoluted, but your
native method has no knowledge of the internal layout of the Java object.
Instead, it must access fields and methods through indexes returned by the JVM.
This allows different JVMs to implement different internal object layouts with
no impact on your native methods.
If you run the Java program, you’ll see that the object
that’s passed from the Java side is manipulated by your native method. But what
exactly is passed? A pointer or a Java reference? And what is the garbage
collector doing during native method calls?
The garbage collector continues to
operate during native method execution, but it’s guaranteed that your objects
will not be garbage-collected during a native method call. To ensure this,
local references are created before, and destroyed right after, the
native method call. Since their lifetime wraps the call, you know that the
objects will be valid throughout the native method call.
Since these references are created and subsequently
destroyed every time the function is called, you cannot make local copies in
your native methods, in static variables. If you want a reference that
lasts across function invocations, you need a global reference. Global
references are not created by the JVM, but the programmer can make a global
reference out of a local one by calling specific JNI functions. When you create
a global reference, you become responsible for the lifetime of the referenced
object. The global reference (and the object it refers to) will be in memory
until the programmer explicitly frees the reference with the appropriate JNI
function. It’s similar to malloc( ) and free( ) in
C.
With JNI, Java exceptions can be
thrown, caught, printed, and rethrown just as they are inside a Java program.
But it’s up to the programmer to call dedicated JNI functions to deal with
exceptions. Here are the JNI functions for exception
handling:
Among these, you can’t ignore
ExceptionOccurred( ) and ExceptionClear( ). Most JNI
functions can generate exceptions, and there is no language feature that you can
use in place of a Java try block, so you must call ExceptionOccurred( )
after each JNI function call to see if an exception was thrown. If you
detect an exception, you may choose to handle it (and possibly rethrow it). You
must make certain, however, that the exception is eventually cleared. This can
be done in your function using ExceptionClear( ) or in some other
function if the exception is rethrown, but it must be done.
You must ensure that the exception is cleared, because
otherwise the results will be unpredictable if you call a JNI function while an
exception is pending. There are few JNI functions that are safe to call during
an exception; among these, of course, are all the exception handling
functions.
Since Java is a multithreaded language, several threads
can call a native method concurrently. (The native method might be suspended in
the middle of its operation when a second thread calls it.) It’s entirely up to
the programmer to guarantee that the native call is thread-safe; i.e., it does
not modify shared data in an unmonitored way. Basically, you have two options:
declare the native method as synchronized, or implement some other
strategy within the native method to ensure correct, concurrent data
manipulation.
Also, you should never pass the JNIEnv pointer
across threads, since the internal structure it points to is allocated on a
per-thread basis and contains information that makes sense only in that
particular thread.
The easiest way to implement JNI native methods is to
start writing native method prototypes in a Java class, compile that class, and
run the .class file through javah. But what if you have a large,
preexisting code base that you want to call from Java? Renaming all the
functions in your DLLs to match the JNI name mangling convention is not a viable
solution. The best approach is to write a wrapper DLL “outside” your original
code base. The Java code calls functions in this new DLL, which in turn calls
your original DLL functions. This solution is not just a work-around; in most
cases you must do this anyway because you must call JNI functions on the object
references before you can use them.
You can find further introductory material, including a C
(rather than C++) example and discussion of Microsoft issues, in Appendix A of
the first edition of this book, which can be found on the CD ROM bound in with
this book, or in a free download from www.BruceEckel.com. More extensive
information is available at java.sun.com (in the search engine, select
“training & tutorials” for keywords “native methods”). Chapter 11 of Core
Java 2, Volume II, by Horstmann & Cornell (Prentice-Hall, 2000)
gives excellent coverage of native methods.