窗口程序的运行过程
在屏幕上显示一个窗口的过程一般有以下步骤,这就是主程序
WinMain
的结构流程:
(
1
)得到应用程序的句柄(
GetModuleHandle
)。
(
2
)注册窗口类(
RegisterClassEx
)。在注册之前,要先填写
RegisterClassEx
的参数
WNDCLASSEX
结构。
(
3
)建立窗口(
CreateWindowEx
)。
(
4
)显示窗口(
ShowWindow
)。
(
5
)刷新窗口客户区(
UpdateWindow
)。
(
6
)进入无限的消息获取和处理的循环。首先获取消息(
GetMessage
),如果有消息到达,
则将消息分派到回调函数处理(
DispatchMessage
),如果消息是
WM_QUIT
,则退出循环。
程序的另一半
_ProcWinMain
子程序是用来处理消息的,
它就是窗口的回调函数
(
Callback
)
,
也叫做窗口过程,
之所以是回调函数是因为它是由
Windows
而不是我们自己调用的,
我们调
用
DispatchMessage
,而
DispatchMessage
在自己的内部回过来调用窗口过程。
所有的用户操作都是通过消息来传给应用程序的,
如用户按键、
鼠标移动、
选择了菜单和拖
动了窗口等,
应用程序中由窗口过程接收消息并处理,
在例子程序中就是
_ProcWinMain
。
由
于窗口过程构造了一个分支结构,
对应不同的消息执行不同的代码,
所以一个应用程序中几
乎所有的功能代码都集中在窗口过程里。
窗口程序运行中消息传输的流程可以由图
4.4
来表示。
先来看看
Windows
对消息的处理。
Windows
在系统内部有一个系统消息队列,当输入设备有
所动作的时候,如用户按动了键盘、移动了鼠标、按下或放开了鼠标等,
Windows
都会产生
相应的记录放在系统消息队列里,
如图
4.4
中的箭头
a
和
b
所示,
每个记录中包含消息的类
型、发生的位置(如鼠标在什么坐标移动)和发生的时间等信息。
同时,
Windows
为每个程序(严格地说是每个线程)维护一个消息队列,
Windows
检查系统
消息队列里消息的发生位置,
当位置位于某个应用程序的窗口范围内的时候,
就把这个消息
派送到应用程序的消息队列里,如图
4.4
中的箭头
c
所示。
当应用程序还没有来取消息的时候,
消息就暂时保留在消息队列里,
当程序中的消息循环执
行到
GetMessage
的时候,控制权转移到
GetMessage
所在的
USER32.DLL
中(箭头
1
),
USER32.DLL
从程序消息队列中取出一条消息(箭头
2
),然后把这条消息返回应用程序(箭
头
3
)。
应用程序可以对这条消息进行预处理,如可以用
TranslateMessage
把基于键盘扫描码的按
键消息转换成基于
ASCII
码的键盘消息,以后也会用到
TranslateAccelerator
把键盘快捷
键转换成命令消息,但这个步骤不是必需的。
然后应用程序将处理这条消息,但方法不是自己直接调用窗口过程来完成,而是通过
DispatchMessage
间接调用窗口过程,
Dispatch
的英文含义是
"
分派
"
,
之所以是
"
分派
"
,
是
因为一个程序可能建有不止一个窗口,
不同的窗口消息必须分派给相应的窗口过程。
当控制
权转移到
USER32.DLL
中的
DispatchMessage
时,
DispatchMessage
找出消息对应窗口的窗
口过程,然后把消息的具体信息当做参数来调用它(箭头
5
),窗口过程根据消息找到对应
的分支去处理,
然后返回
(箭头
6
)
,
这时控制权回到
DispatchMessage
,
最后
DispatchMessage
函数返回应用程序
(箭头
7
)
。
这样,
一个循环就结束了,
程序又开始新一轮的
GetMessage
。
有个很常见的问题:
为什么要由
Windows
来调用窗口过程,
程序取了消息以后自己处理不是
更简便吗?事实上并非如此,
如果程序自己处理消息的
"
分派
"
,
就必须自己维护本程序所属
窗口的列表,
当程序建立的窗口不止一个的时候,
这个工作就变得复杂起来;
另一个原因是:
别的程序也可能用
SendMessage
通过
Windows
直接调用你的窗口过程;
第三个原因:
Windows
并不是把所有的消息都放进消息队列,有的消息是直接调用窗口过程处理的,如
WM_SETCURSOR
等实时性很强的消息,所以窗口过程必须开放给
Windows
。
应用程序之间也可以互发消息,
PostMessage
是把一个消息放到其他程序的消息队列中,如
图
4.4
中箭头
d
所示,目标程序收到了这条消息就把它放入该程序的消息队列去处理;而
SendMessage
则越过消息队列直接调用目标程序的窗口过程(如图
4.4
中箭头
I
所示),窗
口过程返回以后才从
SendMessage
返回(如图
4.4
中箭头
II
所示)。
窗口过程是由
Windows
回调的,
Windows
又是怎么知道往哪里回调呢?答案是我们在调用
RegisterClassEx
函数的时候已经把窗口过程的地址告诉了
Windows
。