《Java编程思想第4版[中文版](PDF格式)》第278章


app。changeObject(anObj);
System。out。println(〃Java: 〃 + anObj。aValue);

private native void
changeObject(MyJavaClass obj);
static {
System。loadLibrary(〃UseObjImpl〃);


编译好代码,并将。class 文件传递给 javah 后,就可以实现固有方法。在下面这个例子中,一旦取得字段和 
方法 ID,就会通过 JNI 函数访问它们。
JNIEXPORT void JNICALL
653 
…………………………………………………………Page 655……………………………………………………………
Java_UseObjects_changeObject(
JNIEnv * env; jobject jThis; jobject obj) {
jclass cls;
jfieldID fid;
jmethodID mid;
int value;
cls = env…》GetObjectClass(obj);
fid = env…》GetFieldID(cls;
〃aValue〃; 〃I〃);
mid = env…》GetMethodID(cls;
〃divByTwo〃; 〃()V〃);
value = env…》GetIntField(obj; fid);
printf(〃Native: %dn〃; value);
env…》SetIntField(obj; fid; 6);
env…》CallVoidMethod(obj; mid);
value = env…》GetIntField(obj; fid);
printf(〃Native: %dn〃; value);

除第一个自变量外,C++函数会接收一个 jobject,它代表Java 对象引用“固有”的那一面——那个引用是 
我们从 Java 代码里传递的。我们简单地读取 aValue,把它打印出来,改变这个值,调用对象的divByTwo() 
方法,再将值重新打印一遍。
为访问一个字段或方法,首先必须获取它的标识符。利用适当的JNI 函数,可方便地取得类对象、元素名以 
及签名信息。这些函数会返回一个标识符,利用它可访问对应的元素。尽管这一方式显得有些曲折,但我们 
的固有方法确实对Java 对象的内部布局一无所知。因此,它必须通过由 JVM 返回的索引访问字段和方法。这 
样一来,不同的JVM 就可实现不同的内部对象布局,同时不会对固有方法造成影响。
若运行 Java 程序,就会发现从 Java 那一侧传来的对象是由我们的固有方法处理的。但传递的到底是什么 
呢?是指针,还是Java 引用?而且垃圾收集器在固有方法调用期间又在做什么呢?
垃圾收集器会在固有方法执行期间持续运行,但在一次固有方法调用期间,我们的对象可保证不会被当作 
“垃圾”收集去。为确保这一点,事先创建了“局部引用”,并在固有方法调用之后立即清除。由于它们的 
“生命期”与调用过程息息相关,所以能够保证对象在固有方法调用期间的有效性。
由于这些引用会在每次函数调用的时候创建和破坏,所以不可在static 变量中制作固有方法的局部副本(本 
地拷贝)。若希望一个引用在函数存在期间持续有效,就需要一个全局引用。全局引用不是由JVM 创建的, 
但通过调用特定的 JNI 函数,程序员可将局部引用扩展为全局引用。创建一个全局引用时,需对引用对象的 
“生存时间”负责。全局引用(以及它引用的对象)会一直留在内存里,直到用特定的JNI 函数明确释放了 
这个引用。它类似于C 的malloc()和 free()。
A。1。4 JNI 和 Java 异常
利用 JNI,可丢弃、捕捉、打印以及重新丢弃Java 异常,就象在一个 Java 程序里那样。但对程序员来说, 
需自行调用专用的JNI 函数,以便对异常进行处理。下面列出用于异常处理的一些JNI 函数:
■Throw():丢弃一个现有的异常对象;在固有方法中用于重新丢弃一个异常。
■ThrowNew():生成一个新的异常对象,并将其丢弃。
■ExceptionOccurred():判断一个异常是否已被丢弃,但尚未清除。
■ExceptionDescribe():打印一个异常和堆栈跟踪信息。
■ExceptionClear():清除一个待决的异常。
■FatalError():造成一个严重错误,不返回。
在所有这些函数中,最不能忽视的就是ExceptionOccurred()和ExceptionClear()。大多数JNI 函数都能产 
生异常,而且没有象在 Java 的try 块内的那种语言特性可供利用。所以在每一次 JNI 函数调用之后,都必须 
调用ExceptionOccurred(),了解异常是否已被丢弃。若侦测到一个异常,可选择对其加以控制(可能时还 
要重新丢弃它)。然而,必须确保异常最终被清除。这可以在自己的函数中用ExceptionClear()来实现;若 
异常被重新丢弃,也可能在其他某些函数中进行。但无论如何,这一工作是必不可少的。
654 
…………………………………………………………Page 656……………………………………………………………
我们必须保证异常被彻底清除。否则,假若在一个异常待决的情况下调用一个JNI 函数,获得的结果往往是 
无法预知的。也有少数几个JNI 函数可在异常时安全调用;当然,它们都是专门的异常控制函数。
A。1。5 JNI 和线程处理
由于Java 是一种多线程语言,几个线程可能同时发出对一个固有方法的调用(若另一个线程发出调用,固有 
方法可能在运行期间暂停)。此时,完全要由程序员来保证固有调用在多线程的环境中安全进行。例如,要 
防范用一种未进行监视的方法修改共享数据。此时,我们主要有两个选择:将固有方法声明为“同步”,或 
在固有方法内部采取其他某些策略,确保数据处理正确地并发进行。
此外,绝对不要通过线程传递 JNIEnv,因为它指向的内部结构是在“每线程”的基础上分配的,而且包含了 
只对那些特定的线程才有意义的信息。
A。1。6 使用现成代码
为实现JNI 固有方法,最简单的方法就是在一个Java 类里编写固有方法的原型,编译那个类,再通过 javah 
运行。class 文件。但假若我们已有一个大型的、早已存在的代码库,而且想从Java 里调用它们,此时又该 
如何是好呢?不可将DLL 中的所有函数更名,使其符合 JNI 命名规则,这种方案是不可行的。最好的方法是 
在原来的代码库“外面”写一个封装DLL。Java 代码会调用新 DLL 里的函数,后者再调用原始的DLL 函数。 
这个方法并非仅仅是一种解决方案;大多数情况下,我们甚至必须这样做,因为必须面向对象引用调用 JNI 
函数,否则无法使用它们。
A。2 微软的解决方案
到本书完稿时为止,微软仍未提供对JNI 的支持,只是用自己的专利方法提供了对非Java 代码调用的支持。 
这一支持内建到编译器 Microsoft JVM 以及外部工具中。只有程序用 Microsoft Java 编译器编译,而且只有 
在Microsoft Java 虚拟机(JVM)上运行的时候,本节讲述的特性才会有效。若计划在因特网上发行自己的 
应用,或者本单位的内联网建立在不同平台的基础上,就可能成为一个严重的问题。
微软与Win32 代码的接口为我们提供了连接 Win32 的三种途径:
(1) J/Direct:方便调用Win32 DLL 函数的一种途径,具有某些限制。
(2) 本原接口(RNI):可调用Win32 DLL 函数,但必须自行解决“垃圾收集”问题。
(3) Java/ 集成:可从 Java 里直接揭示或调用 服务。
后续的小节将分别探讨这三种技术。
写作本书的时候,这些特性均通过了Microsoft SDK for Java 2。0 beta 2 的支持。可从微软公司的Web 站 
点下载这个开发平台(要经历一个痛苦的选择过程,他们叫作“Active Setup”)。Java SDK 是一套命令行 
工具的集合,但编译引擎可轻易嵌入Developer Studio 环境,以便我们用 Visual J++ 1。1 来编译 Java 1。1 
代码。
A。3 J/Direct
J/Direct 是调?
小说推荐
返回首页返回目录