首页
社区
课程
招聘
[study-note]Windows环境下32位汇编语言程序设计--第4章 第一个窗口程序4.1 开始了解窗口(3)
发表于: 2008-4-15 23:43 8399

[study-note]Windows环境下32位汇编语言程序设计--第4章 第一个窗口程序4.1 开始了解窗口(3)

2008-4-15 23:43
8399
第4章 第一个窗口程序4.1 开始了解窗口(3)  

      让我们打开一个DOS窗口,切换到FistWindow所在的目录,运行环境设置的批处理文件var.bat,再键入nmake编译出FirstWindow.exe,这个程序只有2 560字节,运行后窗口出来了,如图4.3所示。对于这个窗口,用户可以拖动边框去改变大小、按标题栏上的按钮来最大化和最小化,当光标移到边框的时候,会自动变成双箭头……总之,这个窗口包括了一个典型窗口的所有特征。(当用户去改变这个窗口的时候,系统自动调用缺省消息处理函数来处理相应的事件。在这个程序中我们只对WM_PAINT、WM_CLOSE这两个消息感兴趣而已,故我们为处理它们而添加了相应的处理消息的代码进去。)


图4.3 FirstWindow的运行结果

    接下来开始分析源代码,看了这三页多的源代码,第一个感觉是什么?是不是想撤退了?笔者刚开始编Win32程序的时候就是这种感觉,可能90%的人有同样的感觉,别急,过了这一关,Win32汇编的入门就成功了一半,所以千万要挺住!有个振奋人心的消息是,这个程序是大部分窗口程序的模板,以后要写一个新的程序,把它拷贝过来再往中间添砖加瓦就是了,功夫一点都不白费。(三页代码确实挺多的,容易让人产生恐惧感。如老罗所说,再难再苦也要咬着牙挺过来,很快就会好的。不过我对这个有疑问,如果实在是看不下去的话,我宁可把它放一边先,去学点自己更容易掌握和吸收的技术,等时机成熟了再来看。因为这本书如果让大多数人实在无法看下去的话,那还不如去找另一本更适合自己看的书去。)

      先静下心来分析一下程序的结构,还看得懂,很好!其实源程序的结构在第3章里已经了解过了,首先是注释……模式定义……include…… .data数据段,都没有问题,这些已经占去了近40行了,好了,终于是关键的代码段了,统计一下,只剩80行代码了。(消息处理机制下的程序确实是有个结构的,但并不是说它就是很死板的,我们大可不必把所有的代码放在窗口消息处理函数里,如果代码堆放得太多的话,程序会显得极度不平衡的。)

      分析一下程序的结构,发现入口是start,然后执行了一个_WinMain子程序,完成后就是程序退出的函数ExitProcess,再看_WinMain的结构,前面是顺序下来的几个API:

GetModuleHandle → RtlZeroMemory → LoadCursor → RegisterClassEx

→ CreateWindowEx → ShowWindow → UpdateWindow

从名称上就能看出它们的用途,很明显,窗口是在CreateWindowEx处建立的,ShowWindow则是把窗口显示在屏幕上,这些代码是窗口的建立过程。(确实,如众所周知的一样,API的名字已经很好的描述了它的功能了。因为我们很有必要仔细研究一下API的名字和它的功能的关系,这样就容易理解一点,而不会对API产生恐惧感了。即使不能完全理解,但看到名字至少也知道点意思。)

     接下来,就是一个由3个API组成的循环了:

GetMessage → TranslateMessage → DispatchMessage

       很明显,这是和消息有关的循环,因为API名称中都带有Message字样,如果退出这个循环,程序也就结束了,这个循环叫做消息循环。设置_WinMain子程序并不是必须的,可以把_WinMain的所有代码放到主程序中,没有任何影响,之所以这样只是为了将这里使用的变量定义成局部变量,这样可以方便移植。(这里表达了好几个重要的信息:一是处理消息的三个极其重要的API,它们名字里都带有Message字样;二是由这几个API构成的循环为消息循环,几乎没有哪个WINDOWS程序里少了这个循环的,这非常好的表明了这个循环的重要性;三是消息处理函数里的所有代码可以放到主函数内部,但把它做成一个函数的好处是方便移植。如果真将消息处理函数里的所有代码放到主程序内部,那我们在注册窗口类时就没必要将消息处理函数的地址传递到窗口类成员中去了,我这样做了,结果运行程序没有显示窗口,另外,我想将消息处理函数里的代码移到主程序中去,发现这样做很不妥。还是保持原状的好。)

       看了程序的流程,似乎没有什么地方涉及窗口的行为,如改变大小和移动位置的处理等。再看源程序,除了_WinMain,还有一个子程序_ProcWinMain,但除了在WNDCLASSEX结构的赋值中提到过它,好像就没有什么地方要用到这个子程序,起码在自己编写的源代码中没有任何一个地方调用过它。(表面上我们一次也没有调用_ProcWinMain这个函数,实际上这个函数一直在被调用,只要程序接受到了消息,它就会被调用,只是这个函数不是由我们调用而是由程序自动调用而已。这里面涉及了一个思想,那就是什么函数来消息用户触发的事件所发出的消息的思想。)

      再看_ProcWinMain,它是一个分支结构处理的子程序,功能是把参数uMsg取出来,根据不同的uMsg执行不同的代码,完了以后就退出了,中间也没有任何东西和主程序有关联。(这段话已经很清楚的描述了_ProcWinMain这个函数的功能了,它和主程序的一个关联就是它处理的消息由主程序传送过来的。)

       第一个窗口程序就是由这么两个似乎是风马牛不相及的部分组成的,但它确实能工作,对于写惯了DOS汇编的程序员来说,这似乎不可理解。下面来看看这么一个陌生而奇怪的程序是如何工作的。(当我第一次看到windows程序的这些函数的时候,给我的第一感觉就是有点多余,有点bt,因为当时实在不明白什么是消息处理函数。还好后来自学了点MFC,总算对WINDOWS下的消息处理机制有点理解了。因而现在学WIN32高级汇编编程也就没那么吃力了。)

3. 窗口程序的运行过程

     在屏幕上显示一个窗口的过程一般有以下步骤,这就是主程序的结构流程:

(1)得到应用程序的句柄(GetModuleHandle)。

(2)注册窗口类(RegisterClassEx)。在注册之前,要先填写RegisterClassEx的参数WNDCLASSEX结构。

(3)建立窗口(CreateWindowEx)。

(4)显示窗口(ShowWindows)。

(5)刷新窗口客户区(UpdateWindow)。

(6)进入无限的消息获取和处理的循环。首先获取消息(GetMessage),如果有消息到达,则将消息分派到回调函数处理(DispatchMessage),如果消息是WM_QUIT,则退出循环。(不管对这个主程序的执行流程感觉有多么的陌生,我们都应该要相信,这个流程是通用且非常重要的,因此对这个流程了如指掌非常必要。)

      程序的另一半_ProcWinMain子程序是用来处理消息的,它就是窗口的回调函数(Callback),也叫做窗口过程,之所以是回调函数是因为它是由Windows而不是我们自己调用的,我们调用DispatchMessage,而DispatchMessage再回过来调用窗口过程。(这里什么回调函数、窗口过程,又是一些非常专业的术语,这些术语常常成为我们学习的一个阻碍,其实这里它们只不过就是一个函数,只是这个函数被我们编写后,我们要将它的地址传送给一些指定的成员,让WINDOWS去根据具体情况调用它们,而不是由我们自己决定在哪里、什么时候调用它们。)

      所有的用户操作都是通过消息来传给应用程序的,如用户按键,鼠标移动,选择了菜单和拖动了窗口等,应用程序中由窗口过程接收消息并处理,在例子程序中就是_ProcWinMain。窗口过程构造了一个分支结构,对应不同的消息执行不同的代码,所以一个应用程序中几乎所有的功能代码都集中在窗口过程里。(用户的一举一动都会触发一个事件,然后由系统根据这些事件自动发出相应的消息到所要操作的程序中去,并让程序作出响应。后面一句话,即据有的功能代码都集中在窗口过程里,这句话让我看了有点汗颜啊,难以想像一个程序里的所有功能代码都集中在一个过程里,会是什么样的一种情况啊。)

     窗口程序运行中消息传输的流程可以由图4.4来表示。(很形象生动的表示了消息的发出和消息的处理整个过程。)

     先来看看Windows对消息的处理。Windows在系统内部有一个系统消息队列,当输入设备有所动作的时候,如用户按动了键盘、移动了鼠标,按下或放开了鼠标等,Windows都会产生相应的记录放在系统消息队列里,如图4.4中的箭头a和b所示,每个记录中包含消息的类型、发生的位置(如鼠标在什么坐标移动)和发生的时间等信息。


图4.4 窗口程序的运行过程

      同时,Windows为每个程序(严格地说是每个线程)维护一个消息队列,Windows检查系统消息队列里消息的发生位置,当位置位于某个应用程序的窗口范围内的时候,就把这个消息派送到应用程序的消息队列里,如图4.4中的箭头c所示。(每个线程应有一个消息队列,虽然还不太清楚啥是线程,但知道了线程有一个消息队列确实是件不错的事,而且随着学习的深入我将很快就会知道什么是线程了。这里说明了用户操作了硬件,即触发了事件,接着系统将事件对应的消息发送到系统消息队列里,再分析这获取的消息是属于哪个程序的,将它发送到对应程序的消息队列里去。消息经历了:系统消息队列=>就用程序消息队列.)

       当应用程序还没有来取消息的时候,消息就暂时保留在消息队列里,当程序中的消息循环执行到GetMessage的时候,控制权转移到GetMessage所在的USER32.DLL中(箭头1),USER32.DLL从程序消息队列中取出一条消息(箭头2),然后把这条消息返回应用程序(箭头3)。(消息经历了 应用程序的消息队列=>应用程序里。由GetMessage完成这伟大而又神圣的任务。)

     应用程序可以对这条消息进行预处理,如可以用TranslateMessage把基于键盘扫描码的按键消息转换成基于ASCII码的键盘消息,以后也会用到TranslateAccelerator把键盘快捷键转换成命令消息,但这个步骤不是必需的。(这里有三个概念需要理解:按键消息、键盘消息、命令消息。这三个概念都相当重要,需要加以注意啊。有空再回来瞄瞄。消息被处理的过程为:按键消息=>键盘消息=>命令消息。)

      然后应用程序将处理这条消息,但方法不是自己直接调用窗口过程来完成,而是通过DispatchMessage间接调用窗口过程,Dispatch的英文含义是“分派”,之所以是“分派”,是因为一个程序可能建有不止一个窗口,不同的窗口消息必须分派给相应的窗口过程。当控制权转移到USER32.DLL中的DispatchMessage时,DispatchMessage找出消息对应窗口的窗口过程,然后把消息的具体信息当做参数来调用它(箭头5),窗口过程根据消息找到对应的分支去处理,然后返回(箭头6),这时控制权回到DispatchMessage,最后DispatchMessage函数返回应用程序(箭头7)。这样,一个循环就结束了,程序又开始新一轮的GetMessage。(DispatchMessage和GetMessage都是USER32.DLL里的函数,算不算是应用程序的一部分呢?因为它们是USER32.DLL函数,就可以说最后DispatchMessage函数返回应用程序了吗?它本来不是应用程序的一部分吗?就好像一个应用程序不是操作系统的一部分,它在运行后要返回操作系统一样?不会是因为DispatchMessage是系统API的原因吧?)

      有个很常见的问题:为什么要由Windows来调用窗口过程,程序取了消息以后自己处理不是更简便吗?事实上并非如此,如果程序自己处理消息的“分派”,就必须自己维护本程序所属窗口的列表,当程序建立的窗口不止一个的时候,这个工作就变得复杂起来;另一个原因是:别的程序也可能用SendMessage通过Windows直接调用你的窗口过程;第三个原因:Windows并不是把所有的消息都放进消息队列,有的消息是直接调用窗口过程处理的,如WM_SETCURSOR等实时性很强的消息,所以窗口过程必须开放给Windows。(我对这里的通过用SendMessage来调用别的程序的窗口过程非常感兴趣啊,感觉非常神秘,很有趣.对第三个必须由WINDOWS来调用窗口过程的原因感觉太牵强了,因为如果由应用程序本身来处理不是一样可以直接处理WM_SETCUROSR等实时性消息吗?)

       应用程序之间也可以互发消息,PostMessage是把一个消息放到其他程序的消息队列中,如图4.4中箭头d所示,目标程序收到了这条消息就把它放入该程序的消息队列去处理;而SendMessage则越过消息队列直接调用目标程序的窗口过程(如图4.4中箭头I所示),窗口过程返回以后才从SendMessage返回(如图4.4中箭头II所示)。(实话说,看到这里感觉是最精彩的,对PostMessage和SendMessage的本质有了更加深入的认识了。一个是发送消息到另一个程序的队列中去,一个是直接调用另一个程序的窗口过程。)

      窗口过程是由Windows回调的,Windows又是怎么知道往哪里回调呢?答案是我们在调用RegisterClassEx函数的时候告诉了Windows。(在注册窗口类时将回调函数的偏移地址传递到窗口类成员中去。)

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
本资料来自罗云彬的WIN32汇编教程,本人只是添加一点点注解而已(见括号内容),而这仅是个人见解,如有雷同,纯属巧合。如有不同见解,也可发表出来,大家一起讨论,本文章最终解释权归罗去彬所有。谢谢。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

[课程]Android-CTF解题方法汇总!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 331
活跃值: (57)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
2
最近又翻出罗云彬的书看看,原来认为窗口回调函数中有MessageBox,那么在点确定前窗口会被冻结住,毕竟窗EIP阻塞在MessageBox里了嘛。但是!仔细调试了一下发现并不是这样,非常非常奇怪?
具体现象是这样的:在MessageBox前(窗口过程开始处)下断点,当别的程序(如sendmessage)向此窗口过程发送消息时,OD还是会断下来,这是为什么?
2008-4-16 10:05
0
雪    币: 331
活跃值: (57)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
3
感觉系统调用窗口过程像是多线程一样
2008-4-16 10:09
0
雪    币: 486
活跃值: (13)
能力值: ( LV9,RANK:430 )
在线值:
发帖
回帖
粉丝
4
你确定发送消息后,程序调用了相应的回调函数了吗?
如果没有的话,那它怎么断下来呢?
2008-4-17 00:57
0
雪    币: 331
活跃值: (57)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
5
我把messagebox换成了死循环,也确定调用了回调函数(CPU变成100%了),但窗口还是会移动!让我无法解释
上传的附件:
2008-4-17 09:17
0
雪    币: 331
活跃值: (57)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
6
明白了,那个可以移动的窗口,其实是explorer.exe的一个线程创建的假(Ghost)窗口,所以假窗口可以移动,且不会被覆盖!可以用spy++,进(线)程工具查看!
2008-4-18 15:46
0
雪    币: 107
活跃值: (326)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
这可是好东西了。。支持你。版主
2008-4-19 07:47
0
雪    币: 486
活跃值: (13)
能力值: ( LV9,RANK:430 )
在线值:
发帖
回帖
粉丝
8
更正一个问题:是楼主,不是版主。
2008-4-19 14:27
0
游客
登录 | 注册 方可回帖
返回
//