📄 appendixa.html
字号:
is write a C or C++ source 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, and I’ll assume that you know
how to create a DLL. The code below implements the native method by calling a
Win32 API. It is then compiled and linked into a file called <B>MsgImpl.dll</B>
(for “Message Implementation”).</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>#include <windows.h>
#include <font color=#004488>"ShowMsgBox.h"</font>
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD dwReason, <font color=#0000ff>void</font>** lpReserved) {
<font color=#0000ff>return</font> TRUE;
}
JNIEXPORT <font color=#0000ff>void</font> JNICALL
Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv,
jobject <font color=#0000ff>this</font>, jstring jMsg) {
<font color=#0000ff>const</font> <font color=#0000ff>char</font> * msg;
msg = (*jEnv)->GetStringUTFChars(jEnv, jMsg,0);
MessageBox(HWND_DESKTOP, msg,
<font color=#004488>"Thinking in Java: JNI"</font>,
MB_OK | MB_ICONEXCLAMATION);
(*jEnv)->ReleaseStringUTFChars(jEnv, jMsg,msg);
}</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you have no interest in Win32,
just skip the <B>MessageBox( )</B> call; the interesting part is the
surrounding code. The arguments that are passed into the native method are the
gateway back into Java. The first, of type <B>JNIEnv</B>,<B> </B>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-<B>static</B> methods like the example above (also
called <I>instance methods</I>), the second argument is the equivalent of the
“this” pointer in C++ and similar to <B>this</B> in Java: it’s
a reference to the object that called the native method. For <B>static</B>
methods, it’s a reference to the <B>Class</B> object where the method is
implemented.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the following sections
we’ll explain this code by looking at how to access and control the JVM
from inside a native method.</FONT><A NAME="_Toc408018820"></A><BR></P></DIV>
<A NAME="Heading589"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Accessing JNI functions: <BR>The JNIEnv
argument<BR><A NAME="Index3083"></A><A NAME="Index3084"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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 <B>JNIEnv</B> argument, which is a pointer to a special
JNI data structure of type <B>JNIEnv_</B>. 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.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Through the <B>JNIEnv</B> argument,
the programmer has access to a large set of functions. These functions can be
grouped into the following categories:</FONT><BR></P></DIV>
<UL>
<LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Obtaining version
information</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Performing
class and object
operations</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Handling
global and local references to Java
objects</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Accessing
instance fields and static
fields</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Calling
instance methods and static
methods</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Performing
string and array
operations</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia">Generating
and handling Java
exceptions</FONT></UL><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you take a look at the
<B>jni.h</B> header file, you’ll see that inside the <B>#ifdef
__cplusplus</B> preprocessor conditional, the <B>JNIEnv_</B> 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 in the preceding example</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>(*jEnv)->ReleaseStringUTFChars(jEnv, jMsg,msg);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">can be rewritten as follows in
C++:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>jEnv->ReleaseStringUTFChars(jMsg,msg);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You’ll notice that you no
longer need the double dereferencing of the <B>jEnv</B> pointer, and that the
same pointer is no longer passed as the first parameter to the JNI function
call. In the rest of these examples, I’ll use the C++
style.</FONT><BR></P></DIV>
<A NAME="Heading590"></A><FONT FACE = "Verdana"><H4 ALIGN="LEFT">
Accessing Java Strings</H4></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As an example of accessing a JNI
function, consider the code shown above. Here, the <B>JNIEnv</B> argument
<B>jEnv</B> is used to access a Java <B>String</B>. Java <B>String</B>s are in
Unicode format, so if you receive one and want to pass it to a non-Unicode
function (<B>printf( )</B>, for example), you must first convert it into
ASCII characters with the JNI function <B>GetStringUTFChars( )</B>. This
function takes a Java <B>String</B> 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.)</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia"><B>GetStringUTFChars</B> is the
name of one of the fields in the structure that <B>JNIEnv</B> is indirectly
pointing to, and this field in turn is a pointer to a function. To access the
JNI function, we use the traditional C syntax for calling a function though a
pointer. You use the form above to access all of the JNI
functions.</FONT><A NAME="_Toc408018821"></A><BR></P></DIV>
<A NAME="Heading591"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Passing and using Java objects</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the previous example we passed a
<B>String</B> 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.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To pass objects, use the ordinary
Java syntax when declaring the native method. In the example below,
<B>MyJavaClass</B> has one <B>public</B> field and one <B>public</B> method. The
class <B>UseObjects </B>declares a native method that takes an object of class
<B>MyJavaClass</B>. To see if the native method manipulates its argument, the
<B>public</B> field of the argument is set, the native method is called, and
then the value of the <B>public</B> field is printed.</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>class</font> MyJavaClass {
<font color=#0000ff>public</font> <font color=#0000ff>void</font> divByTwo() { aValue /= 2; }
<font color=#0000ff>public</font> <font color=#0000ff>int</font> aValue;
}
<font color=#0000ff>public</font> <font color=#0000ff>class</font> UseObjects {
<font color=#0000ff>public</font> <font color=#0000ff>static</font> <font color=#0000ff>void</font> main(String [] args) {
UseObjects app = <font color=#0000ff>new</font> UseObjects();
MyJavaClass anObj = <font color=#0000ff>new</font> MyJavaClass();
anObj.aValue = 2;
app.changeObject(anObj);
System.out.println(<font color=#004488>"Java: "</font> + anObj.aValue);
}
<font color=#0000ff>private</font> <font color=#0000ff>native</font> <font color=#0000ff>void</font>
changeObject(MyJavaClass obj);
<font color=#0000ff>static</font> {
System.loadLibrary(<font color=#004488>"UseObjImpl"</font>);
}
}</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">After compiling the code and
handing the <B>.class </B>file to <B>javah</B>, you can implement the native
method. In the example below, once the field and method ID are obtained, they
are accessed through JNI functions.</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>JNIEXPORT <font color=#0000ff>void</font> JNICALL
Java_UseObjects_changeObject(
JNIEnv * env, jobject jThis, jobject obj) {
jclass cls;
jfieldID fid;
jmethodID mid;
<font color=#0000ff>int</font> value;
cls = env->GetObjectClass(obj);
fid = env->GetFieldID(cls,
<font color=#004488>"aValue"</font>, <font color=#004488>"I"</font>);
mid = env->GetMethodID(cls,
<font color=#004488>"divByTwo"</font>, <font color=#004488>"()V"</font>);
value = env->GetIntField(obj, fid);
printf(<font color=#004488>"Native: %d\n"</font>, value);
env->SetIntField(obj, fid, 6);
env->CallVoidMethod(obj, mid);
value = env->GetIntField(obj, fid);
printf(<font color=#004488>"Native: %d\n"</font>, value);
}</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The first argument aside, the C++
function receives a <B>jobject</B>, which is the native side of the Java object
reference we pass from the Java code. We simply read <B>aValue</B>, print it
out, change the value, call the object’s <B>divByTwo( )</B> method,
and print the value out again. </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To access a field or method, you
must first obtain its identifier. Appropriate JNI functions take the class
object, the element name, and the signature. 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.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">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?</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <A NAME="Index3085"></A>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, <I>local references</I> 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.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Since these references are created
and subsequently destroyed every time the function is called, you cannot make
local copies in your native methods, in <B>static</B> 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 <B>malloc( )</B> and
<B>free( )</B> in C.</FONT><A NAME="_Toc408018822"></A><BR></P></DIV>
<A NAME="Heading592"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
JNI and Java exceptions</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">With <A NAME="Index3086"></A>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:</FONT><BR></P></DIV>
<UL>
<LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia"><B>Throw( )</B></FONT><BR><FONT FACE="Georgia">Throws
an existing exception object. Used in native methods to rethrow an
exception.</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia"><B>ThrowNew( )</B></FONT><BR><FONT FACE="Georgia">Generates
a new exception object and throws
it.</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia"><B>ExceptionOccurred( )</B></FONT><BR><FONT FACE="Georgia">Determines
if an exception was thrown and not yet
cleared.</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia"><B>ExceptionDescribe( )</B></FONT><BR><FONT FACE="Georgia">Prints
an exception and the stack
trace.</FONT><LI><FONT FACE="Symbol"> </FONT><FONT FACE="Georgia"><B>ExceptionClear( )</B></FONT><BR><FONT FACE="Georgia">Clears
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -