|
|||||
![]() 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
![]() ♦
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
![]() 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
![]() #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
![]() //#
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
![]() 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
![]() 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:
|
|||||