按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
“垃圾”收集去。为确保这一点,事先创建了“局部引用”,并在固有方法调用之后立即清除。由于它们的
“生命期”与调用过程息息相关,所以能够保证对象在固有方法调用期间的有效性。
由于这些引用会在每次函数调用的时候创建和破坏,所以不可在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 是调用 Win32 DLL 函数最简单的方式。它的主要设计目标是与Win32API 打交道,但完全可用它调
用其他任何 API。但是,尽管这一特性非常方便,但它同时也造成了某些限制,且降低了性能(与RNI 相
比)。但J/Direct 也有一些明显的优点。首先,除希望调用的那个DLL 里的代码之外,没有必要再编写额外
的非Java 代码,换言之,我们不需要一个封装器或者代理/存根DLL。其次,函数自变量与标准数据类型之
间实现了自动转换。若必须传递用户自定义的数据类型,那么 J/Direct 可能不按我们的希望工作。第三,就
象下例展示的那样,它非常简单和直接。只需少数几行,这个例子便能调用Win32 API 函数MessageBox(),
它能弹出一个小的模态窗口,并带有一个标题、一条消息、一个可选的图标以及几个按钮。
public class ShowMsgBox {
public static void main(String args'')
throws UnsatisfiedLinkError {
MessageBox(0;
〃Created by the MessageBox() Win32 func〃;
〃Thinking in Java〃; 0);
}
655
…………………………………………………………Page 657……………………………………………………………
/** @dll。import(〃USER32〃) */
private static native int
MessageBox(int hwndOwner; String text;
String title; int fuStyle);
}
令人震惊的是,这里便是我们利用 J/Direct 调用Win32 DLL 函数所需的全部代码。其中的关键是位于示范代
码底部的MessageBox()声明之前的@dll 。import 引导命令。它表面上看是一条注释,但实际并非如此。它的
作用是告诉编译器:引导命令下面的函数是在 USER32 DLL 里实现的,而且应相应地调用。我们要做的全部事
情就是提供与DLL 内实现的函数相符的一个原型,并调用函数。但是毋需在Java 版本里手工键入需要的每一
个Win32 API 函数,一个Microsoft Java 包会帮我们做这件事情(很快就会详细解释)。为了让这个例子正
常工作,函数必须“按名称”由DLL 导出。但是,也可以用@dll。import 引导命令“按顺序”链接。举个例
子来说,我们可指定函数在DLL 里的入口位置。稍后还会具体讲述@dll。import 引导命令的特性。
用非Java 代码进行链接的一个重要问题就是函数参数的自动配置。正如大家看到的那样,MessageBox()的
Java 声明采用了两个字串自变量,但原来的C 方案则采用了两个 char 指针。编译器会帮助我们自动转换标
准数据类型,同时遵照本章后一节要讲述的规则。
最好,大家或许已注意到了main()声明中的 UnsatisfiedLinkError 异常。在运行期的时候,一旦链接程序
不能从非Java 函数里解析出符号,就会触发这一异常(事件)。这可能是由多方面的原因造成的:。dll 文
件未找到;不是一个有效的DLL;或者J/Direct 未获您所使用的虚拟机的支持。为了使DLL 能被找到,它必
须位于Windows 或WindowsSystem 目录下,位于由PATH 环境变量列出的一个目录中,或者位于和。class 文
件相同的目录。J/Direct 获得了 Microsoft Java 编译器 1。02。4213 版本及更高版本的支持,也获得了
Microsoft JVM 4。79。2164 及更高版本的支持。为了解自己编译器的版本号,请在命令行下运行 JVC,不要加
任何参数。为了解 JVM 的版本号,请找到msjava。dll 的图标,并利用右键弹出菜单观察它的属性。
A。3。1 @dll。import 引导命令
作为使用J/Direct 唯一的途径,@dll。import 引导命令相当灵活。它提供了为数众多的修改符,可用它们自
定义同非Java 代码建立链接关系的方式。它亦可应用于类内的一些方法,或应用于整个类。也就是说,我们
在那个类内声明的所有方法都是在相同的 DLL 里实现的。下面让我们具体研究一下这些特性。
1。 别名处理和按顺序链接
为了使@dll。import 引导命令能象上面显示的那样工作,DLL 内的函数必须按名字导出。然而,我们有时想使
用与DLL 里原始名字不同的一个名字(别名处理),否则函数就可能按编号(比如按顺序)导出,而不是按
名字导出。下面这个例子声明了FinestraDiMessaggio() (用意大利语说的“MessageBox”)。正如大家看
到的那样,使用的语法是非常简单的。
public class Aliasing {
public static void main(String args'')
throws UnsatisfiedLinkError {
FinestraDiMessaggio(0;
〃Created by the MessageBox() Win32 func〃;
〃Thinking in Java〃; 0);
}
/** @dll。import(〃USER32〃;
entrypoint=〃MessageBox〃) */
private static native int
FinestraDiMessaggio(int hwndOwner; String text;
String title; int fuStyle);
}
下面这个例子展示了如何同DLL 里并非按名字导出的一个函数建立链接,那个函数事实是按它们在DLL