ZeePedia

Resources:Software, Books, My own list of books

<< Java Programming Guidelines:Design, Implementation
Index >>
img
B: The Java Native
Interface (JNI)
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:
Create, inspect, and update Java objects (including arrays and Strings)
Call Java methods
1065
img
Catch and throw exceptions
Load classes and obtain class information
Perform run-time type checking
Thus, virtually everything you can do with classes and objects in ordinary
Java you can also do in native methods.
Calling a native method
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.
1066
Thinking in Java
img
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.
The header file generator: javah
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
}
Appendix B: The Java Native Interface (JNI)
1067
img
#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.
Name mangling and function
signatures
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.
Implementing your DLL
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 ):
//: appendixb:MsgImpl.cpp
//# Tested with VC++ & BC++. Include path must
//# be adjusted to find the JNI headers. See
1068
Thinking in Java
img
//# 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.
Accessing JNI functions:
the JNIEnv argument
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
Appendix B: The Java Native Interface (JNI)
1069
img
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:
Obtaining version information
Performing class and object operations
Handling global and local references to Java objects
Accessing instance fields and static fields
Calling instance methods and static methods
Performing string and array operations
Generating and handling Java exceptions
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);
1070
Thinking in Java
img
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.
Accessing Java Strings
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.
Passing and using Java
objects
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
Appendix B: The Java Native Interface (JNI)
1071
Table of Contents:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index