Win32Asm中使用ReadConsoleInput时遇到结构内存对齐

来源:互联网 发布:软件积分破解器 编辑:程序博客网 时间:2024/06/03 18:03

在控制台中使用ReadConsoleInput函数读取键盘事件时,发现访问KeyEvent.uChar.AsciiChar得到的字符跟输入的总是不一致。比如从小键盘输入1,得到的是OO,输入2,得到的是PP。可以根据这个结果推断,程序能识别出键盘事件,但是在判断按键状态和取输入字符的时候出了问题。

下面的是为了说明这个问题而编写的MASM代码。

;MasmDemo.asm, 编译环境, MasmPlus.386.modelflat,stdcalloptioncasemap:noneincludewindows.incincludekernel32.incincludelibkernel32.libincludelibc.incincludelibmsvcrt.libputcharprotoC :DWORDEVENTunionKeyEventKEY_EVENT_RECORD<>MouseEventMOUSE_EVENT_RECORD<>WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD <>MenuEventMENU_EVENT_RECORD<>FocusEventFOCUS_EVENT_RECORD<>EVENTendsINPUT_RECORDstructEventTypeWORD  ?EventEVENT<>INPUT_RECORDends.codemainproclocal@stKeyRec:INPUT_RECORDlocal@hStdIn,@dwResinvokeGetStdHandle,STD_INPUT_HANDLEmov@hStdIn,eax@StartLoop:invokeReadConsoleInput,@hStdIn,addr @stKeyRec,1,addr @dwRes.if@stKeyRec.EventType == KEY_EVENT.if@stKeyRec.Event.KeyEvent.bKeyDownmoval,@stKeyRec.Event.KeyEvent.uChar.AsciiChar.ifal > 20h && al < 7ehinvokeputchar,eax.endif.endif.endifjmp@StartLoopxoreax,eaxretmainendpendmain

一开始以为是自己代码编写错误,后来仔细检查了许久,觉得不会是代码的问题。想着会不会是编译环境的问题就去VC6用C编写了功能一样的程序。
这下居然没有错误。访问KeyEvent.uChar.AsciiChar字段得到的就是键盘输入的字符。

下面的是不存在此问题的C代码。

//VCDemo.cpp,  编译环境 VC++ 6.0#include <windows.h>#include <stdio.h>intmain(void){INPUT_RECORDstKeyRec;HANDLEhStdIn;DWORDdwRes;charch;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.EventType00401069   and         ecx,0FFFFh0040106F   cmp         ecx,100401072   jne         main+0D5h (004010e5)00401074   cmp         dword ptr [ebp-10h],0   //ebp-10h 指向 stKeyRec.Event.KeyEvent.bKeyDown00401078   je          main+0D5h (004010e5)
MASM的结果

00401022   cmp word ptr ss:[ebp-0x12],0x1     ;  ebp-12h 指向 stKeyRec.EventType00401027   jnz short 0040104300401029   cmp dword ptr ss:[ebp-0x10],0x0    ;  ebp-10h 指向 stKeyRec.Event.KeyEvent.bKeyDown0040102D   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上的东西不是能拿来就用的,还是得多想想)。
因此可以对改结构稍作修改。

其中的联合类型不用修改。

EVENTunionKeyEventKEY_EVENT_RECORD<>MouseEventMOUSE_EVENT_RECORD<>WindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD <>MenuEventMENU_EVENT_RECORD<>FocusEventFOCUS_EVENT_RECORD<>EVENTends
可以为INPUT_RECORD添加一个WORD类型的Reverse或者把EventType定义为DWORD。
如果选后者,在读取EventType的时候需要注意只能读低字部分。

INPUT_RECORDstructEventTypeWORD  ?ReverseWORD?EventEVENT<>INPUT_RECORDends
或者

INPUT_RECORDstructEventTypeDWORD  ?EventEVENT<>INPUT_RECORDends
如此,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

第一次写博文,写得没什么技术含量,也不通顺,有错也难免。但总算还是有些收获。