Win32Asm中使用ReadConsoleInput时遇到结构内存对齐
2013-10-27 22:04
627 查看
在控制台中使用ReadConsoleInput函数读取键盘事件时,发现访问KeyEvent.uChar.AsciiChar得到的字符跟输入的总是不一致。比如从小键盘输入1,得到的是OO,输入2,得到的是PP。可以根据这个结果推断,程序能识别出键盘事件,但是在判断按键状态和取输入字符的时候出了问题。
下面的是为了说明这个问题而编写的MASM代码。
一开始以为是自己代码编写错误,后来仔细检查了许久,觉得不会是代码的问题。想着会不会是编译环境的问题就去VC6用C编写了功能一样的程序。
这下居然没有错误。访问KeyEvent.uChar.AsciiChar字段得到的就是键盘输入的字符。
下面的是不存在此问题的C代码。
直觉很可能是自己定义的INPUT_RECORD有问题。因为MasmDemo中的是自己定义的。而VC中的是早已定义的。但是出了什么问题就不清楚了。
没办法。我的能力决定我已没办法干想就找出问题了。只好分别阅读两个程序的汇编代码。看看编译之后,程序哪里不一样了。
VC6的结果
MASM的结果
通过两处关键代码的对比可以知道。 VCDemo中EventType之后显然有2个字节的无用空间。这是怎么产生的呢?显然跟编译器密切有关。
根据VC6的对齐规则,可以推断Event联合体将会被放置在相对stKeyRect首址偏移为4的地方。而EventType只占用2个字节,剩余的2个字节便被废弃不用了。正是这种对齐机制影响了之后所有字段的偏移。
而Masm编译器的默认对齐是1字节。这就导致了MasmDemo通过访问KeyEvent.uChar.AsciiChar得不到正确的输入。如果MasmDemo想得到正确的输入字符,需要修正对应字段的偏移,以模拟出VC6内存对齐的效果。
MSDN中对INPUT_RECORD的定义如下:
这个定义不适合MASM程序。从MasmDemo的执行结构就可以知道(看来MSDN上的东西不是能拿来就用的,还是得多想想)。
因此可以对改结构稍作修改。
其中的联合类型不用修改。
可以为INPUT_RECORD添加一个WORD类型的Reverse或者把EventType定义为DWORD。
如果选后者,在读取EventType的时候需要注意只能读低字部分。
或者
如此,MasmDemo.ASM的的代码执行可以得到正确的结果了。
快写完这篇文章的时候突然想起机子上有RadAsm。于是把MasmDemo.ASM复制到RadAsm中运行。惊讶地发现INPUT_RECORD已经有定义了。
而这个定义跟我探索的也差不多,但是它的更合理,明确指出了2个字节用于内存对齐。RadAsm对INPUT_RECORD的定义如下。
第一次写博文,写得没什么技术含量,也不通顺,有错也难免。但总算还是有些收获。
下面的是为了说明这个问题而编写的MASM代码。
;MasmDemo.asm, 编译环境, MasmPlus .386 .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib include libc.inc includelib msvcrt.lib putchar proto C :DWORD EVENT union KeyEvent KEY_EVENT_RECORD <> MouseEvent MOUSE_EVENT_RECORD <> WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD <> MenuEvent MENU_EVENT_RECORD <> FocusEvent FOCUS_EVENT_RECORD <> EVENT ends INPUT_RECORD struct EventType WORD ? Event EVENT <> INPUT_RECORD ends .code main proc local @stKeyRec:INPUT_RECORD local @hStdIn,@dwRes invoke GetStdHandle,STD_INPUT_HANDLE mov @hStdIn,eax @StartLoop: invoke ReadConsoleInput,@hStdIn,addr @stKeyRec,1,addr @dwRes .if @stKeyRec.EventType == KEY_EVENT .if @stKeyRec.Event.KeyEvent.bKeyDown mov al,@stKeyRec.Event.KeyEvent.uChar.AsciiChar .if al > 20h && al < 7eh invoke putchar,eax .endif .endif .endif jmp @StartLoop xor eax,eax ret main endp end main
一开始以为是自己代码编写错误,后来仔细检查了许久,觉得不会是代码的问题。想着会不会是编译环境的问题就去VC6用C编写了功能一样的程序。
这下居然没有错误。访问KeyEvent.uChar.AsciiChar字段得到的就是键盘输入的字符。
下面的是不存在此问题的C代码。
//VCDemo.cpp, 编译环境 VC++ 6.0 #include <windows.h> #include <stdio.h> int main(void) { INPUT_RECORD stKeyRec; HANDLE hStdIn; DWORD dwRes; char ch; hStdIn = GetStdHandle(STD_INPUT_HANDLE); while (true) { ReadConsoleInput(hStdIn, &stKeyRec, 1, &dwRes); if (stKeyRec.EventType == KEY_EVENT) { if (stKeyRec.Event.KeyEvent.bKeyDown) { ch = stKeyRec.Event.KeyEvent.uChar.AsciiChar; if (ch>0x20 && ch<0x7e) { putchar(ch); } } } } return 0; }
直觉很可能是自己定义的INPUT_RECORD有问题。因为MasmDemo中的是自己定义的。而VC中的是早已定义的。但是出了什么问题就不清楚了。
没办法。我的能力决定我已没办法干想就找出问题了。只好分别阅读两个程序的汇编代码。看看编译之后,程序哪里不一样了。
VC6的结果
00401066 mov ecx,dword ptr [ebp-14h] //ebp-14h 指向 stKeyRec.EventType 00401069 and ecx,0FFFFh 0040106F cmp ecx,1 00401072 jne main+0D5h (004010e5) 00401074 cmp dword ptr [ebp-10h],0 //ebp-10h 指向 stKeyRec.Event.KeyEvent.bKeyDown 00401078 je main+0D5h (004010e5)
MASM的结果
00401022 cmp word ptr ss:[ebp-0x12],0x1 ; ebp-12h 指向 stKeyRec.EventType 00401027 jnz short 00401043 00401029 cmp dword ptr ss:[ebp-0x10],0x0 ; ebp-10h 指向 stKeyRec.Event.KeyEvent.bKeyDown 0040102D je short 00401043
通过两处关键代码的对比可以知道。 VCDemo中EventType之后显然有2个字节的无用空间。这是怎么产生的呢?显然跟编译器密切有关。
根据VC6的对齐规则,可以推断Event联合体将会被放置在相对stKeyRect首址偏移为4的地方。而EventType只占用2个字节,剩余的2个字节便被废弃不用了。正是这种对齐机制影响了之后所有字段的偏移。
而Masm编译器的默认对齐是1字节。这就导致了MasmDemo通过访问KeyEvent.uChar.AsciiChar得不到正确的输入。如果MasmDemo想得到正确的输入字符,需要修正对应字段的偏移,以模拟出VC6内存对齐的效果。
MSDN中对INPUT_RECORD的定义如下:
typedef struct _INPUT_RECORD { WORD EventType; union { KEY_EVENT_RECORD KeyEvent; MOUSE_EVENT_RECORD MouseEvent; WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; MENU_EVENT_RECORD MenuEvent; FOCUS_EVENT_RECORD FocusEvent; } Event; } INPUT_RECORD;
这个定义不适合MASM程序。从MasmDemo的执行结构就可以知道(看来MSDN上的东西不是能拿来就用的,还是得多想想)。
因此可以对改结构稍作修改。
其中的联合类型不用修改。
EVENT union KeyEvent KEY_EVENT_RECORD <> MouseEvent MOUSE_EVENT_RECORD <> WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD <> MenuEvent MENU_EVENT_RECORD <> FocusEvent FOCUS_EVENT_RECORD <> EVENT ends
可以为INPUT_RECORD添加一个WORD类型的Reverse或者把EventType定义为DWORD。
如果选后者,在读取EventType的时候需要注意只能读低字部分。
INPUT_RECORD struct EventType WORD ? Reverse WORD ? Event EVENT <> INPUT_RECORD ends
或者
INPUT_RECORD struct EventType DWORD ? Event EVENT <> INPUT_RECORD ends
如此,MasmDemo.ASM的的代码执行可以得到正确的结果了。
快写完这篇文章的时候突然想起机子上有RadAsm。于是把MasmDemo.ASM复制到RadAsm中运行。惊讶地发现INPUT_RECORD已经有定义了。
而这个定义跟我探索的也差不多,但是它的更合理,明确指出了2个字节用于内存对齐。RadAsm对INPUT_RECORD的定义如下。
INPUT_RECORD STRUCT EventType WORD ? two_byte_alignment WORD ? UNION KeyEvent KEY_EVENT_RECORD <> MouseEvent MOUSE_EVENT_RECORD <> WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD <> MenuEvent MENU_EVENT_RECORD <> FocusEvent FOCUS_EVENT_RECORD <> ENDS INPUT_RECORD ENDS
第一次写博文,写得没什么技术含量,也不通顺,有错也难免。但总算还是有些收获。
相关文章推荐
- (原创)VB调用DLL(VC)使用结构体参数时的内存对齐及分配的问题.
- Delphi结构中使用String时遇到的内存泄露问题
- Delphi结构中使用String时遇到的内存泄露问题(没有利用String的引用计数自动销毁字符串的功能)
- 结构体存储内存对齐
- NumPy-快速处理数据--ndarray对象--多维数组的存取、结构体数组存取、内存对齐、Numpy内存结构
- 使用RabbitMQ过程中遇到的一个问题(队列为空,但内存暴涨)以及与开发者的邮件沟通
- 结构体成员的内存分布与对齐
- 结构变量作为方法的参数调用,在方法内部使用的“坑”你遇到过吗?
- 使用控制台程序寄宿wcf服务时,不正确使用Console.Read()方法导致服务寄宿失败
- 结构体位制与内存对齐
- gpu显存(全局内存)在使用时数据对齐的问题
- 在使用renderTo中遇到的Uncaught TypeError: Cannot read property 'insertAdjacentHTML' of null
- 结构体(struct)的使用、内存分布以及字节对齐测试总结
- 结构变量作为方法的参数调用,在方法内部使用的“坑”你遇到过吗?
- 06-数据结构_预备知识-跨函数使用内存
- C# Console.Read()的使用
- 关于 内存对齐 && sizeof 的介绍 2 —— 结构体
- CUDA的内存结构,通过实例展示寄存器和共享内存的使用
- Delphi中复制带有String的记录结构时不能使用Move之类的内存操作函数