友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第112部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
■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 里的
位置导出的。这个例子假设有一个名为MYMATH 的DLL,这个 DLL 在位置编号 3 处包含了一个函数。那个函数
获取两个整数作为自变量,并返回两个整数的和。
656
…………………………………………………………Page 658……………………………………………………………
public class ByOrdinal {
public static void main(String args'')
throws UnsatisfiedLinkError {
int j=3; k=9;
System。out。println(〃Result of DLL function:〃
+ Add(j;k));
}
/** @dll。import(〃MYMATH〃; entrypoint = 〃#3〃) */
private static native int Add(int op1;int op2);
}
可以看出,唯一的差异就是entrypoint 自变量的形式。
2。 将@dll。import 应用于整个类
@dll。import 引导命令可应用于整个类。也就是说,那个类的所有方法都是在相同的DLL 里实现的,并具有
相同的链接属性。引导命令不会由子类继承;考虑到这个原因,而且由于 DLL 里的函数是自然的 static 函
数,所以更佳的设计方案是将API 函数封装到一个独立的类里,如下所示:
/** @dll。import(〃USER32〃) */
class MyUser32Access {
public static native int
MessageBox(int hwndOwner; String text;
String title; int fuStyle);
public native static boolean
MessageBeep(int uType);
}
public class WholeClass {
public static void main(String args'')
throws UnsatisfiedLinkError {
MyUser32Access。MessageBeep(4);
MyUser32Access。MessageBox(0;
〃Created by the MessageBox() Win32 func〃;
〃Thinking in Java〃; 0);
}
}
由于MessageBeep()和 MessageBox()函数已在不同的类里被声明成 static 函数,所以必须在调用它们时规定
作用域。大家也许认为必须用上述的方法将所有Win32 API (函数、常数和数据类型)都映射成Java 类。但
幸运的是,根本不必这样做。
A。3。2 。ms。win32 包
Win32 API 的体积相当庞大——包含了数以千计的函数、常数以及数据类型。当然,我们并不想将每个Win32
API 函数都写成对应Java 形式。微软考虑到了这个问题,发行了一个Java 包,可通过 J/Direct 将 Win32
API 映射成 Java 类。这个包的名字叫作 。ms。win32。安装Java SDK 2。0 时,若在安装选项中进行了相应
的设置,这个包就会安装到我们的类路径中。这个包由大量Java 类构成,它们完整再现了 Win32 API 的常
数、数据类型以及函数。包容能力最大的三个类是 User32。class,Kernel。class 以及Gdi32。class。它们包
含的是Win32 API 的核心内容。为使用它们,只需在自己的 Java 代码里导入即可。前面的ShowMsgBox 示例
可用。ms。win32 改写成下面这个样子(这里也考虑到了用更恰当的方式使用UnsatisfiedLinkError):
import 。ms。win32。*;
657
…………………………………………………………Page 659……………………………………………………………
public class UseWin32Package {
public static void main(String args'') {
try {
User32。MessageBeep(
winm。MB_ICONEXCLAMATION);
User32。MessageBox(0;
〃Created by the MessageBox() Win32 func〃;
〃Thinking in Java〃;
winm。MB_OKCANCEL |
winm。MB_ICONEXCLAMATION);
} catch(UnsatisfiedLinkError e) {
System。out。println(〃Can’t link Win32 API〃);
System。out。println(e);
}
}
}
Java 包是在第一行导入的。现在,可在不进行其他声明的前提下调用MessageBeep()和 MessageBox()函数。
在MessageBeep()里,我们可看到包导入时也声明了 Win32 常数。这些常数是在大量Java 接口里定义的,全
部命名为winx (x 代表欲使用之常数的首字母)。
写作本书时,。ms。win32 包的开发仍未正式完成,但已可堪使用。
A。3。3 汇集
“汇集”(Marshaling)是指将一个函数自变量从它原始的二进制形式转换成与语言无关的某种形式,再将
这种通用形式转换成适合调用函数采用的二进制格式。在前面的例子中,我们调用了MessageBox()函数,并
向它传递了两个字串。MessageBox()是个C 函数,而且 Java 字串的二进制布局与C 字串并不相同。但尽管如
此,自变量仍获得了正确的传递。这是由于在调用 C 代码前,J/Direct 已帮我们考虑到了将Java 字串转换
成C 字串的问题。这种情况适合所有标准的Java 类型。下面这张表格总结了简单数据类型的默认对应关系:
Java C
byte BYTE 或CHAR
short SHORT或 WORD
int INT,UINT ,LONG,ULONG 或 DWORD
char TCHAR
long __int64
float Float
double Double
boolean BOOL
String LPCTSTR (只允许在OLE 模式中作为返回值)
byte'' BYTE *
short'' WORD *
char'' TCHAR *
int'' DWORD *
这个列表还可继续下去,但已很能说明问题了。大多数情况下,我们不必关心与简单数据类型之间的转换问
题。但一旦必须传递用户自定义类型的自变量,情况就立即变得不同了。例如,可能需要传递一个结构化
的、用户自定义的数据类型,或者需要把一个指针传给原始内存区域。在这些情况下,有一些特殊的编译引
导命令标记一个Java 类,使其能作为一个指针传给结构(@dll。struct 引导命令)。欲知使用这些关键字的
细节,请参考产品文档。
658
…………………………………………………………Page 660……………………………………………………………
A。3。4 编写回调函数
有些Win32 API 函数要求将一个函数指针作为自己的参数使用。Windows API 函数随后就可以调用自变量函
数(通常是在以后发生特定的事件时)。这一技术就叫作“回调函数”。回调函数的例子包括窗口进程以及
我们在打印过程中设置的回调(为后台打印程序提供回调函数的地址,使其能更新状态,并在必要的时候中
止打印)。
另一个例子是API 函数EnumWindows() ,它能枚举目前系统内所有顶级窗口。EnumWindows()要求获取一个函
数指针作为自己的参数,然后搜索由Windows 内部维护的一个列表。对于列表内的每个窗口,它都会调用回
调函数,将窗口句柄作为一个自变量传给回调。
为了在Java 里达到同样的目的,必须使用 。ms。dll 包里的 Callback 类。我们从 Callback 里继承,并取
消callback()。这个方法只能接近int 参数,并会返回 int或void。方法签名和具体的实施取决于使用这个
回调的Windows API 函数。
现在,我们要进行的全部工作就是创建这个Callback 衍生类的一个实例,并将其作为函数指针传递给API 函
数。随后,J/Direct 会帮助我们自动完成剩余的工作。
下面这个例子调用了Win32 API 函数EnumWindows() ;EnumWindowsProc 类里的 callback()方法会获取每个
顶级窗口的句柄,获取标题文字,并将其打印到控制台窗口。
import 。ms。dll。*;
import 。ms。win32。*;
class EnumWindowsProc extends Callback {
public boolean callback(int hwnd; int lparam) {
StringBuffer text = new StringBuffer(50);
User32。GetWindowText(
hwnd; text; text。capacity()+1);
if(text。length() != 0)
System。out。println(text);
return true; // to continue enumeration。
}
}
public class ShowCallback {
public static void main(String args'')
throws InterruptedException {
boolean ok = User32。EnumWindows(
new EnumWindowsProc(); 0);
if(!ok)
System。err。println(〃EnumWindows failed。〃);
Thread。currentThread()。sleep(3000);
}
}
对 sleep()的调用允许窗口进程在main()退出前完成。
A。3。5 其他 J/Direct 特性
通过@dll。import 引导命令内的修改符(标记),还可用到 J/Direct 的另两项特性。第一项是对OLE 函数的
简化访问,第二项是选择API 函数的ANSI 及 Unicode 版本。
根据约定,所有OLE 函数都会返回类型为HRESULT 的一个值,它是由 定义的一个结构化整数值。若在
那一级编写程序,并希望从一个OLE 函数里返回某些不同的东西,就必须将一个特殊的指针传递给它—
—该指针指向函数即将在其中填充数据的那个内存区域。但在 Java 中,我们没有指针可用;此外,这种方法
并不简练。利用J/Direct,我们可在@dll。import 引导命令里使用ole 修改符,从而方便地调用OLE 函数。
标记为 ole 函数的一个固有方法会从Java 形式的方法签名(通过它决定返回类型)自动转换成 形式的函
659
…………………………………………………………Page 661……………………………………………………………
数。
第二项特性是选择ANSI 或者 Unicode 字串控制方法。对字串进行控制的大多数 Win32 API 函数都提供了两个
版本。例如,假设我们观察由 USER32。DLL 导出的符号,那么不会找到一个MessageBox()函数,相反会看到
MessageBoxA()和MessageBoxW() 函数——分别是该函数的ANSI 和 Unicode 版本。如果在@dll。import 引导命
令里不规定想调用哪个版本,JVM 就会试着自行判断。但这�
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!