WIN32汇编语言教程:第04章 第一个窗口程序 · 4.4 实验(1)
在这一节中,将通过不同的实验,进一步介绍窗口的运行。首先构造一个程序,在程序中将收到的消息查表翻译成文本以“WM_XXX”格式显示出来,并且将调用各个API函数的过程也显示出来,这样可以分析窗口的各种行为和消息之间的关系。
4.4.1 MsgWindow程序
为了把需要的内容显示出来,可以选择在客户区显示文本,但这样会引入新的消息,干扰实验,所以,这里选择了一种新的方法,就是先打开Windows附件中自带的Notepad记事本程序,然后在程序中将要显示的内容通过SendMessage发送到记事本中,可以通过查看记事本中的内容来了解MsgWindow的运行情况。
同样,先拷贝一份FirstWindow程序进行修改,共增加3个部分。第一部分是将消息查表转换为字符串,首先在 .const段中增加两个表:16进制的消息编号列表dwMsgTable和字符串列表szStringTable,两表中的项目一一对应,代码如下:
.const dwMsgTable dd WM_NULL dd WM_CREATE dd WM_DESTROY dd WM_MOVE ... dd WM_EXITSIZEMOVE MSG_TABLE_LEN equ ($ - dwMsgTable)/sizeof dword MSG_STRING_LEN equ sizeof szStringTable szStringTable db 'WM_NULL ',0 db'WM_CREATE ',0 db 'WM_DESTROY ',0 db 'WM_MOVE ',0 ... db 'WM_EXITSIZEMOVE ',0 szFormat db 'WndProc: [%04x]%s %08x %08x',0dh,0
MSG_TABLE_LEN定义了表的项数,MSG_STRING_LEN定义了字符串表中每一项的长度。sizeof操作符取的是szStringTable这一行中的数据长度,而非包括下面全部的字符串行。为了简化处理,全部字符串的长度保持相等。由于篇幅所限,这里没有列出全部的消息列表,完整的源代码可以在本书附带光盘的Chapter04\MsgWindow01目录中找到。程序在窗口过程的入口处调用_ShowMessage子程序来翻译消息并传给记事本:
_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam invoke _ShowMessage,uMsg,wParam,lParam mov eax,uMsg .if eax == WM_XXX ... _ShowMessage子程序用来将消息查表翻译成字符串,源程序如下: _ShowMessage proc _uMsg,_wParam,_lParam local @szBuffer[128]:byte pushad ;******************************************************************** ; 查找消息的说明字符串 ;******************************************************************** mov eax,_uMsg mov edi,offset dwMsgTable mov ecx,MSG_TABLE_LEN cld repnz scasd .if ZERO? sub edi,offset dwMsgTable + sizeof dword shr edi,2 mov eax,edi mov ecx,MSG_STRING_LEN mul ecx add eax,offset szStringTable ;******************************************************************** ; 翻译格式并发送到 Notepad 窗口 ;******************************************************************** invoke wsprintf,addr @szBuffer,addr szFormat,\ _uMsg,eax,_wParam,_lParam invoke _SendtoNotepad,addr @szBuffer .endif popad ret _ShowMessage endp
在这里要用到repnz scasd指令,scasd指令是把eax中的值从[edi]开始的内存中按双字比较,同时将edi加4,如果相等,则ZR标志置位,否则为NZ,repnz表示如果标志为NZ,则以ecx为重复次数重复搜索,直到相等或ecx为零为止。
将ecx赋值为消息表的项数MSG_TABLE_LEN,将edi赋值为消息表的开始地址offset dwMsgTable,然后开始查找,停止后可以查看标志Zero位,如果是非ZERO,表示查完全部都没有找到,如果是ZERO,则表示找到表项。
当标志为ZERO时,edi指向找到项目的后一项,将edi减去一项的长度(sizeof dword)以及表的基址,再除以表项的长度(sizeof dword等于4,除以4等于右移两位,所以程序中用shr edi,2),就是消息在表中的索引了,接下来算出消息字符串的位置,位置等于:索引×字符串长+字符串表基址,代码如下:
mov ecx,MSG_STRING_LEN mul ecx add eax,offset szStringTable
这样,eax中就是字符串的地址了。最后将消息编号、名称和参数用wsprintf函数格式化成可以发送的字符串存放到@szBuffer中,并用_SendtoNotepad子程序将@szBuffer中的内容发送到记事本去。
程序增加的第二部分就是下面这个_SendtoNotepad子程序:
szDestClass db 'Notepad',0 _SendtoNotepad proc _lpsz local @hWinNotepad pushad invoke FindWindow,addr szDestClass,NULL .if eax mov ecx,eax invoke ChildWindowFromPoint,ecx,20,20 .endif .if eax mov @hWinNotepad,eax mov esi,_lpsz @@: lodsb or al,al jz @F movzx eax,al invoke PostMessage,@hWinNotepad,WM_CHAR,eax,1 jmp @B @@: .endif popad ret _SendtoNotepad endp