MFC---理解Windows消息循环机制



最近在学习MFC,对Windows的消息循环机制弄得晕头转向,感受这篇文章不错:html

      理解消息循环和整个消息传送机制对Windows编程来讲很是重要。若是对消息处理的整个过程不了解,在windows编程中会遇到不少使人困惑的地方。

什么是消息(Message)

每一个消息是一个整型数值,若是查看头文件(查看头文件了解API是一个很是好的习惯和广泛的作法)能够发现以下一些宏定义:
编程

#define WM_INITDIALOG                   0x0110
#define WM_COMMAND                      0x0111

#define WM_LBUTTONDOWN                  0x0201
// ...

在Windows通讯中,至少一些基本Windows通讯,几乎都要用到消息。若是你想让窗口或控件(实质上,控件是特殊的窗口)执行何种动做,你应该传送一个消息给它;若是另外一个窗口想让你执行何种操做,它能够传送一个消息给你。若是一个事件,如敲击键盘、移动鼠标、点击按钮等,系统将消息传送给窗口,若是你是这些窗口之一,你将接收到消息执行相应的操做。

每一个Windows消息共有两个参数,wParam和lParam。最初的wParam是16位(Win16时代)的,lParam是32位的。在Win32中,两个参数都是32位的。并非全部的消息都是用这两个参数,每一个消息使用它们的方式也不尽相同。如WM_CLOSE消息会忽略上述两个参数;再如WM_COMMAND消息使用上述两个参数,wParam包含”两个”值,HIWORD(wParam)是通知信息(若是可用),LOWORD(wParam)是发送消息的控件或菜单的ID,lParam是发送消息的控件的HWND(窗口句柄),若是这个值为NULL,表示这个消息不是由控件发送的。

HIWORD()和LOWORD()是Windows定义的宏,分别取出一个32位整型值的高字和低字。在Win32中,一个”字”是一个16位整型,DWORD(Double WORD)是32位整型。

能够用PostMessage()或SendMessage()发送消息。PostMessage()把一个消息放入消息队列(Message Queue)后当即返回,也就是当调用PostMessage(),函数执行完成返回时,极可能消息还没有处理。SendMessage()直接将消息发送到窗口,直到这个消息处理完成才返回。若是要关闭一个窗口,能够给它发送一个WM_CLOSE消息,像PostMessage(hwnd, WM_CLOSE, 0, 0); 效果跟点击窗口右上角的 (关闭)按钮是同样的。注意这里的wParam和lParam的值都是0,由于前面提到过,WM_CLOSE消息会忽略上述两个参数。

对话框(Dialogs)

若是使用对话框,为跟控件通讯,你须要向控件发送消息。你或者可使用GetDlgItem()函数根据控件的ID取得控件的句柄,而后调用SendMessage()函数发送消息;或者使用SendDlgItemMessage()组合了上面的步骤。传入一个窗口句柄和子控件的ID可以取得子控件的句柄,用这个句柄发送消息。跟SendDlgItemMessage()相似的API如GetDlgItemText()可以对全部的窗口进行操做,而不只仅是对话框。

什么是消息队列(Message Queue)

假设一个场景:系统正在处理WM_PAINT消息,就在这时用户在键盘上敲击了一些按键,这时会发生什么呢?系统应该中断绘图操做而后处理按键消息仍是应该丢弃按键的消息?很明显这些都是不合理的,所以咱们引入了消息队列,当消息发送过来,将消息加入消息队列,当一个消息被处理时,将其从消息队列移除。这样确保消息不会丢失,当你正在处理一个消息时,其它到来的消息能够加入到消息队列直到被处理。

什么是消息循环(Message Loop)

while (GetMessage( & Msg, NULL, 0 , 0 ) > 0 )
{
    TranslateMessage(
& Msg);
    DispatchMessage(
& Msg);
}

上面代码的执行过程为:
1. 消息循环调用GetMessage()从消息队列中查找消息进行处理,若是消息队列为空,程序将中止执行并等待(程序阻塞)。
2. 事件发生时致使一个消息加入到消息队列(例如系统注册了一个鼠标点击事件),GetMessage()将返回一个正值,这代表有消息须要被处理,而且消息已经填充到传入的MSG参数中;当传入WM_QUIT消息时返回0;若是返回值为负代表发生了错误。
3. 取出消息(在Msg变量中)并将其传递给TranslateMessage()函数,这个函数作一些额外的处理:将虚拟键值信息转换为字符信息。这一步其实是可选的,但有些地方须要用到这一步。
4. 上面的步骤执行完后,将消息传递给DispatchMessage()函数。DispatchMessage()函数将消息分发到消息的目标窗口,而且查找目标窗口过程函数,给窗口过程函数传递窗口句柄、消息、wParam、lParam等参数而后调用该函数。
5. 在窗口过程函数中,检查消息和其余参数,你能够用它来实现你想要的操做。若是不想处理某些特殊的消息,你应该老是调用DefWindowProc()函数,系统将按按默认的方式处理这些消息(一般认为是不作任何操做)。
6. 一旦一个消息处理完成,窗口过程函数返回,DispatchMessage()函数返回,继续循环处理下一个消息。

消息循环对Windows编程来讲是一个很是重要的概念。窗口过程函数并非系统自动调用的,而是由开发人员本身经过调用DispatchMessage()间接的调用的。若是你愿意,能够调用GetWindowLong()函数经过窗口句柄查找到窗口过程函数直接调用达到消息处理的目的。

while (GetMessage( & Msg, NULL, 0 , 0 ) > 0 )
{
    WNDPROC fWndProc
= (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
    fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
我尝试着写了上面的代码,它确实能工做,但这里存在各类问题,像Unicode/ANSI编码转换、定时器回调等等都这样的代码都不适合,而且极可能致使不少打断不少程序的正常运行。所以这样的代码在这里仅仅是试验,真实项目中必定不能编写这样的代码。

注意这里咱们用GetWindowLong()来得到相关窗口的窗口过程函数。为何咱们不直接调用WndProc()函数呢?消息循环会处理程序中全部窗口的消息,包括像按钮、列表框等有他们本身的窗口过程函数的控件,所以咱们要保证调用正确的窗口过程函数。尽管有时几个窗口调用同一个窗口过程函数,但函数的第一个参数 (窗口的句柄) 一般用于告知窗口过程函数是那个窗口发送的消息。

代码能够看出,程序的大部分时间都在处理消息循环。窗口会不断的处理发过来的消息,但若是要退出程序该怎么作呢?由于咱们用的是while()循环,若是GetMessage()返回的是FALSE(即0)会退出循环,程序可以执行到WinMain()结束处,即程序退出:这正是PostQuitMessage()函数完成的工做,该函数会将WM_QUIT消息添加到消息队列的队尾,GetMessage()从消息队列取出WM_QUIT消息,填充Msg结构,返回的不是正数,而是0。与此同时,结构Msg的成员wParam的值会被置为你传给PostQuitMessage()函数参数的值,你能够选择忽略它或作为WinMain()函数的返回值即进程的退出代码(Exit Code)。

注意:若是发生错误,GetMessage()函数将返回-1。你应该记住这点,说不定你的程序会所以出错。尽管GetMessage()返回值位BOOL型,但它能够返回TRUE或FALSE以外的值,由于BOOL被定义成UINT(unsigned int)。下面的程序貌似能正常工做,但有些时候不能正常工做。

while (GetMessage( & Msg, NULL, 0 , 0 ))

while (GetMessage( & Msg, NULL, 0 , 0 ) != 0 )

while (GetMessage( & Msg, NULL, 0 , 0 ) == TRUE)

上面的代码都是错误的!有些程序中你会看到会使用第一中方式,使用这种方式你必须保证GetMessage()老是执行成功,不然应该使用下面这段代码:
while (GetMessage( & Msg, NULL, 0 , 0 ) > 0 )


但愿你对Windows消息循环能有很好的理解,若是尚未,慢慢来,在使用过程当中会逐渐理解的。windows

中文原文:http://www.cnblogs.com/zxjay/archive/2009/06/27/1512372.html

英文原文:http://winprog.org/tutorial/message_loop.html
函数

相关文章
相关标签/搜索