您的位置:首页 > 编程语言

[译文] 使用JNA来简化对本地代码的访问[三]

2012-07-09 23:24 106 查看


原文:Simplify Native Code Access with JNA

作者:Sanjay Dasgupta

出处:http://today.java.net/article/2009/11/11/simplify-native-code-access-jna

[译文] 使用JNA来简化对本地代码的访问[一]

[译文] 使用JNA来简化对本地代码的访问[二]

检测结构的内存对齐问题

这里的情况是问题存在于细节中,故不能简单地、非侵入性地做出结论说,某个特别的错误是因为内存对齐不匹配造成的,而是要通过下面这些必定乏味的讨论来找出一种方法。

相关的代码(如下所示)在JTwain.java中,可以通过搜索“Memory Alignment Problem Demo”来找到位置。该代码创建了一个TW_IDENTITY(一个Java的结构代替)实例,并把它传递给TWAIN运行时,TWAIN接着与用户交互以选择一个源设备,TW_IDENTITY实例在被从Java传递给TWAIN时是未初始化的,但在返回时填充了关于选中设备的信息,代码最后的print()和dump()显示结构的各个部分以便在检测问题时有所帮助。

//起始: 内存对齐问题演示

TW_IDENTITY srcID = new TW_IDENTITY(Structure.ALIGN_DEFAULT);

stat = SelectSource(g_AppID, srcID);

if (stat != TWRC_SUCCESS) {

//... (基于清晰的目的而删除的一些行) ...

}

System.out.printf("ProtocolMajor: %02x%n", srcID.ProtocolMajor);

System.out.printf("ProtocolMinor: %02x%n", srcID.ProtocolMinor);

System.out.printf("SupportedGroups: %04x%n", srcID.SupportedGroups);

System.out.printf("Manufacturer: %s%n", new String(srcID.Manufacturer, 0, 34));

dump(srcID);

// 终止:内存对齐问题演示

下面显示的printf()语句的输出明显示意了有内存对齐问题存在:

ProtocolMajor: 01

ProtocolMinor: 09

SupportedGroups: 694d0000

Manufacturer: crosoft Tw

头两个值:ProtocolMajor和ProtocolMinor是正确的,但是接下来的两个则明显是有问题的,在之前的TWAIN调用中,Java代码议定SupportedGroups的值为0x0003,因此应该返回同一个值才对,还有Manufacturer的值看上去明显像似“Microsoft”的头两个字母被砍掉了。

现在让我们来看看下面展示的dump()的输出,“dump”显示结构在从本地代码接收到时的内容,还没有被JNA分离成单个的成员值。

000: 11 04 00 00 01 00 00 00 0d 00 01 00 32 36 20 4a

000: . . . . . . . . . . . . 2 6 J

016: 75 6e 65 20 32 30 30 30 00 00 00 00 00 00 00 00

016: u n e 2 0 0 0 . . . . . . . .

032: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00

032: . . . . . . . . . . . . . . . .

048: 09 00 03 00 00 00 4d 69 63 72 6f 73 6f 66 74 00

048: . . . . . . M i c r o s o f t .

064: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

064: . . . . . . . . . . . . . . . .

080: 00 00 00 00 00 00 00 00 54 77 61 69 6e 20 44 61

080: . . . . . . . . T w a i n D a

... (基于清晰的目的而删除的其他行) ...

dump的每一行显示16个字节的“原始”内存的值,每行开始时的数值(在冒号之前)是该行从结构开始的位置算起的偏移值,每组16个字节打印两次——首先是16进制的整数,然后是ASCII字符。颜色(手工配上的)用以分隔相邻的结构成员,有下划线的部分则是内嵌在结构TW_IDENTITY(参见Win32Twain.java)中的结构TW_VERSION,每个成员在结构的内存空间中的位置和所占有的空间取决于每个成员被声明的顺序和大小。

从上面的转储内容看来,很明显本地代码返回的信息是正确的(应该可以想到PC的Inter处理器是小端字节序的(little-endian)),具体来说,那就是在转储内存中的SupportedGroups和Manufacturer的值也都是正确的。

Ÿ ProtocolMajor(从偏移量46开始的涂成紫色的2个字节)=0x01

Ÿ ProtocolMinor(从偏移量48开始的涂成青色的两个字节)=0x09

Ÿ SupportedGroups(从偏移量50开始的涂成紫色的4个字节)=0x0003

Ÿ Manufacturer(从偏移量54开始的涂成青色的34个字节)=“Microsoft”(使用值为0的字节填充至34个字节)

比较printf()语句打印的值和转储内存中的值就可以看出来,JNA所理解的结构成员的位置从SupportedGroups开始不可思议地后移了两个字节,这是内存对齐问题的典型症状。

对齐错误的出现是因为本地代码把结构成员无任何插入间隔地串在一起,然而JNA代码却期待在倍数于成员长度的内存偏移量位置上找到它们。因此,本地代码把SupportedGroups放在偏移量为50的地方,但是JNA却在偏移量为52(SupportedGroupds的长度4的倍数)的地方开始查找。紧随在SupportedGroups之后的结构成员也被往后推了两个字节,导致了前面显示的Manufacturer值的变化,你现在应该也能够解释“Tw”如何会悄然出现在Manufacturer值的尾部。

最后简短地说一点关于指针的另一个方面的题外话:dump()的代码展示了使用Structure.getPointer()来获取指向结构开始之处的指针的做法,可以把结构当成是字节数组(C程序员的void*),使用由getPointer()返回的com.sun.jna.Pointer对象来访问它。

重现结构对齐错误

文件JTwain.java实际上包含了有内存对齐错误的代码,因此如果希望的话,读者可以就此问题做进一步的研究,不过TWAIN演示程序仍能够正常工作,因为它没有用到结构中的值。

若要重现内存对齐错误的话,可按照后面“运行示例代码”一节中的描述来编译程序,然后执行JTwainDemo.bat。你应该会看到下面图3中的标题为“JTwain Demo”的窗口,在菜单栏中选择“File”->“Select Source…”,如图中所示,一个列出了已安装的TWAIN设备的题为“Select Source”的窗口会弹出来,选择任一个TWAIN设备,然后点击“Select”按钮,这会执行有对齐错误的代码,并会在命令窗口中显示结构TW-VERSION的内容。



图3:运行JTwain演示程序

需要注意的是,你看到的TW_VERSION结构的内容可能会与前面例子展示的值有所不同(除非你已经安装了同样的TWAIN设备),不过你应该能够看到是同样类型的内存对齐问题的迹象。

如果标题为“Select Source”的弹出窗口中没有显示任何的TWAIN设备的话,那么你应该下载并安装TWAIN developer tookit,该工具包模拟一个图像源(前面图3中的“Select Source”窗口中的第一项),返回一个TWAIN标志的图像。

防止结构对齐错误

一些本地库有着不同的内存对齐风格(因为编译器和编译器选项存在差异的缘故),因此,既然JNA通常在不选择重新编译本地代码的情况下使用,故它可以方便地设置使用的对齐策略。

可以通过调用Structure.setAlignType(int alignType)来设置扩展了Structure的Java类的成员的对齐策略,对齐方式有四种选项,如下表中的描述。

对齐规范

JNA描述

ALIGN_DEFAULT

使用平台缺省的对齐

ALIGN_GNUC

对32位的x86 linux/gcc有效,对齐字段大小,最大为4个字节

ALIGN_MSVC

对w32/msvc有效,根据字段大小来对齐

ALIGN_NONE

不对齐,所有字段都用1个字节的最小分界线

前面展示的dump()输出表明,TWAIN的本地代码使用了没有特别对齐的策略(前面表中的ALIGN_NONE),不过既然这也不是JNA的缺省设定,那么所有代替C结构的Java类都有一个缺省的构造方法,该方法把对齐方式设置为ALIGN_NONE(参见Win32Twain.java)。下面的代码是结构TW_INDENTITY的带有缺省构造方法的Java类的简略形式。

public class TW_IDENTITY extends Structure {

public TW_IDENTITY() {

setAlignType(Structure.ALIGN_NONE);

}

public int Id;

public TW_VERSION Version = new TW_VERSION();

public short ProtocolMajor;

public short ProtocolMinor;

. . .

}

一般来说是无法知道任何具体的本地库所使用的对齐策略的,因此,如果DLL的文档没有说明这部分信息的话,则就需要做一些实验来确定要使用的正确的对齐设置。

运行示例代码

按照如下步骤来运行本文中描述的示例代码:

Ÿ 下载包含了示例代码的压缩文件,并把它解压到某个目录(例如samples)中。

Ÿ 打开一个命令窗口,然后使用“CD”命令进入samples\code目录。

Ÿ 执行批处理文件build.bat,编译所有的代码(并且只要运行一次就可以了),类文件被放置在名为samples\bin的目录下。

Ÿ 若要运行某个程序的话,就执行有着相同名字(例如LockWorkStation.bat,、BeepMorse.bat、GetLogicalDrives.bat、GetSystemTime.bat、GetVolumeInformation.bat或者JTwainDemo.bat)的批处理文件。

示例的压缩文件中包含了jna.jar,所以不需要再下载其他任何东西,前面列出的批处理文件也已经指定了类路径,所以不需要做任何修改就可以编译和运行示例代码。

资源

Ÿ 本文的示例代码

Ÿ JNA项目网站

Ÿ Windows API参考

Ÿ Windows数据类型

Ÿ 不再需要指针

Ÿ 维基百科指针页面

Ÿ 维基百科“Windows编程/句柄和数据类型”

Ÿ C字符串

Ÿ Java技术:使用TWAIN和SANE来获取图像,第1部分

Ÿ TWAIN开发者工具包

Ÿ TWAIN头文件

Sanjay Dasgupta自1996年以来一直在使用Java(在多年使用了多种其他的语言之后),他是一个独立顾问,现在住在印度的加尔各答。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: