Windows消息机制要点

1. 窗口过程
   每一个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window Handle),消息ID(Message ID),和两个消息参数(wParam, lParam), 当窗口收到消息时系统就会调用此窗口过程来处理消息。(因此叫回调函数) html

2 消息类型
1) 系统定义消息(System-Defined Messages)

在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 能够分为如下三类:    
1> 窗口消息(Windows Message)
与窗口的内部运做有关,如建立窗口,绘制窗口,销毁窗口等。能够是通常的窗口,也能够是Dialog,控件等。    
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL...    
2> 命令消息(Command Message)
与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。    
WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。若是是控件, HIWORD(wParam)表示控件消息类型    
3> 控件通知(Notify Message)
控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 能够任意扩展。    
2) 程序定义消息(Application-Defined Messages)
用户自定义的消息, 对于其范围有以下规定:    
WM_USER: 0x0400-0x7FFF      (ex. WM_USER+10)    
WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4)    
RegisterWindowMessage: 0xC000-0xFFFF 程序员

3 消息队列(Message Queues)
Windows中有两种类型的消息队列    
1) 系统消息队列(System Message Queue)
这是一个系统惟一的Queue,设备驱动(mouse, keyboard)会把操做输入转化成消息存在系统队列中,而后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific message queue)中等待处理    
2) 线程消息队列(Thread-specific Message Queue)
每个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用GDI函数时才会建立,默认不建立)。而后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理.    
注意: 线程消息队列中WM_PAINT,WM_TIMER只有在Queue中没有其余消息的时候才会被处理,WM_PAINT消息还会被合并以提升效率。其余全部消息以先进先出(FIFO)的方式被处理。 编程

4 队列消息(Queued Messages)和非队列消息(Non-Queued Messages)
1)队列消息(Queued Messages)
消息会先保存在消息队列中,消息循环会今后队列中取消息并分发到各窗口处理    
如鼠标,键盘消息。    
2) 非队列消息(NonQueued Messages)
消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理    
如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED    
注意: postMessage发送的消息是队列消息,它会把消息Post到消息队列中; SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理 网络

5 PostMessage(PostThreadMessage), SendMessage
PostMessage:把消息放到指定窗口所在的线程消息队列中后当即返回。 PostThreadMessage:把消息放到指定线程的消息队列中后当即返回。    
SendMessage:直接把消息送到窗口过程处理, 处理完了才返回。 app

6 GetMessage, PeekMessage
PeekMessage会当即返回    能够保留消息    
GetMessage在有消息时返回  会删除消息 ide

7 TranslateMessage, TranslateAccelerator
TranslateMessage: 把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。    
TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或 WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND 或WM_SYSCOMMAND消息, 而后把转化后的 WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理, 处理完后才会返回。 函数

8(消息死锁( Message Deadlocks)
假设有线程A和B, 如今有如下下步骤    
1) 线程A SendMessage给线程B, A等待消息在线程B中处理后返回    
2) 线程B收到了线程A发来的消息,并进行处理, 在处理过程当中,B也向线程A SendMessgae,而后等待从A返回。    
由于此时, 线程A正等待从线程B返回, 没法处理B发来的消息, 从而致使了线程A,B相互等待, 造成死锁。多个线程也能够造成环形死锁。    
可使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。 工具

9 BroadcastSystemMessage
咱们通常所接触到的消息都是发送给窗口的, 其实, 消息的接收者能够是多种多样的,它能够是应用程序(applications), 可安装驱动(installable drivers), 网络设备(network drivers), 系统级设备驱动(system-level device drivers)等,    
BroadcastSystemMessage这个API能够对以上系统组件发送消息。 post

  1、引言
随着Windows操做系统的不断推广,众多软件开发包都提供有开发基于Windows平台应用软件的功能。虽然这些开发包不尽相同,流行的有Visual C++、Visual Basic、Delphi、C++ Builder 等多种,但由这些不一样语言开发的软件有一点倒是相同的--都是运行于Windows 操做平台,都必须接受Windows 的运行机制。做为Windows 操做系统灵魂的消息机制也就必然为众多用不一样语言开发的Windows操做系统下运行的应用程序所接受。所以,要编写深刻的Windows程序,就必须对 Windows的运行机制有很好的认识和理解。本文下面将对Windows操做系统下的消息运行机制作较为深刻的剖析。    
2、Windows事件驱动机制      
咱们当中很多使用VC、Delphi等做为开发语言的程序员是一步步从DOS下的Basic、C++中走过来的,并且大多在刚开始学习编程时也是先从 DOS下的编程环境入手的,所以在习惯了DOS下的过程驱动形式的顺序程序设计方法后,每每在向Windows下的开发环境转型的过程当中会对 Windows所采起的事件驱动方式感到没法适应。由于DOS和Windows这两种操做系统的运行机制是大相径庭的,DOS下的任何程序都是使用顺序的、过程驱动的程序设计方法。这种程序都有一个明显的开始、明显的过程以及一个明显的结束,所以经过程序就能直接控制程序事件或过程的所有顺序。即便是在处理异常时,处理过程也仍然是顺序的、过程驱动的结构。而Windows的驱动方式则是事件驱动的,即程序的流程不是由事件的顺序来控制,而是由事件的发生来控制,全部的事件是无序的,所为一个程序员,在编写程序时,并不知道用户会先按下哪一个按纽,也就不知道程序先触发哪一个消息。所以咱们的主要任务就是对正在开发的应用程序要发出的或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。    
3、Windows的消息循环      
Windows操做系统为每个正在运行的应用程序保持有一个消息队列。当有事件发生后,Windows并非将这个激发事件直接送给应用程序,而是先将其翻译成一个Windows消息,而后再把这个消息加入到这个应用程序的消息队列中去。应用程序须要经过消息循环来接收这些消息。在MFC中使用了对 WinAPI进行了很好封装的类库,虽然能够为编程提供一个面向对象的界面,使Windows程序员可以以面象对象的方式进行编程,把那些进行SDK编程时最繁琐的部分提供给程序员,使之专一于功能的实现,可是因为引入了很好的封装特性,使咱们不能直接操纵部分核心代码。对于消息的循环和接收也只是经过相似于下面的消息映射予以很简单的表示:    
BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)    
//{ { AFX_MSG_MAP(CTEMMSView)    
ON_WM_LBUTTONDOWN()    
ON_COMMAND(ID_OPENDATA, OnOpenData)    
ON_WM_TIMER()    
ON_WM_PAINT()    
//} } AFX_MSG_MAP    
END_MESSAGE_MAP()    
虽然上述消息映射在编程过程当中处理消息很是简练方便,但显然是难于理解消息是如何参与循环和分发的。所以有必要经过SDK(Software Developers Kit,软件开发工具箱)代码深刻到被MFC封装的Windows编程的核心中来研究其具体是如何工做的。在SDK编程中,通常是在Windows应用程序的入口点WinMain函数中添加处理消息循环的代码以检索Windows送来的消息,而后WinMain再把这些消息分配给相应的窗口函数并处理它们:    
……    
MSG msg; //定义消息名    
while (GetMessage (& msg, NULL, 0, 0))    
{    
TranslateMessage (& msg) ; //翻译消息    
DispatchMessage (& msg) ; //撤去消息    
}    
return msg.wParam ;    
上述几句虽然简单但倒是全部Windows程序的关键代码,担负着获取、解释和分发消息的任务,下面就重点对其功能和做用进行分析:    
MSG结构在头文件中定义以下:    
typedef struct tagMSG    
{    
HWND hwnd;    
UINT message;    
WPARAM wParam;    
LPARAM lParam;    
DWORD time;    
POINT pt;    
} MSG, *PMSG;    
其数据成员的具体意义以下:    
hwnd:消息将要发送到的那个窗口的句柄,用这个参数能够决定让哪一个窗口接收消息。    
message:消息号,它惟一标识了一种消息类型。每种消息类型都在Windows文件进行了预约义。    
wParam:一个32位的消息参数,这个值的确切意义取决于消息自己。    
lParam:同上。    
time:消息放入消息队列中的时间,在这个域中写入的并不是当时日期,而是从Windows启动后所测量的时间值。Windows用    
这个域来使用消息保持正确的顺序。    
pt:消息放入消息队列时的鼠标坐标。    
消息循环以GetMessage调用开始,它从消息队列中取出一个消息。该函数的四个参数能够有控制地获取消息,第一个参数指定要接收消息的MSG结构的地址,第二个参数表示窗口句柄,通常将其设置为空,表示要获取该应用程序建立的全部窗口的消息;第3、四参数用于指定消息范围。后面三个参数被设置为默认值,用于接收发送到属于这个应用程序的任何一个窗口的全部消息。在接收到除WM_QUIT以外的任何一个消息后,GetMessage()返回 TRUE;若是GetMessage收到一个WM_QUIT消息,则返回FALSE以退出消息循环,终止程序运行。所以,在接收到WM_QUIT以前,带有GetMessage()的消息循环能够一直循环下去。当除WM_QUIT的消息用GetMessage读入后,首先要通过函数 TranslateMessage()对其进行解释,但对大多数消息来讲并不起什么做用。这里起关键做用的是DispatchMessage()函数,把由GetMessage获取的Windows消息传送给在MSG结构中为窗口所指定的窗口过程。在消息处理函数处理完消息以后,代码又循环到开始去接收另外一个消息,这样就完成了一个完整的消息循环。    
因为Windows操做系统是一种非剥夺式多任务操做系统。只有在应用程序主动交出CPU控制权后,Windows才能把控制权交给其余应用程序。在消息循环中,必定要有能交出控制的系统函数才能实现协同式多任务操做。能完成该功能的只有GetMessage、PeekMessage和 WaitMessage这三个函数,若是在应用程序中长期不去调用这三个函数之一其余任务则没法执行。GetMessage函数在找不到等待应用程序处理的消息时,会自动交出控制权,由Windows把CPU的控制权交给其余等待获取控制权的应用程序。因为任何Windows应用程序都含有一个消息循环,这种隐式交出控制权的方式能够保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。使用GetMessage函数的消息循环在消息队列中没有消息时将等待,若是须要,能够利用这段时间进行I/O端口操做等耗时操做,不过须要在消息循环中使用PeekMessage函数来代替GetMessage。使用PeekMessage的方法同GetMessage相似,下面是一段使用 PeekMessage函数的消息循环的典型例子:    
MSG msg;    
BOOL bDone=FALSE;    
do{    
if(PeekMessage(& msg,NULL,0,0,PM_REMOVE)){    
if(msg.message==WM_QUIT)    
bDone=TRUE;    
else{    
TranslateMessage(& msg);    
DispatchMessage(& msg);    
}    
}    
//无消息处理,进行长时间操做    
else{    
……//长时间操做    
}    
} while(!bDone)    
……    
不管应用程序消息队列中是否有消息,PeekMessage函数都当即返回,若是但愿等待新消息入队,能够利用无返回值的函数WaitMessage配合PeekMessage进行消息循环。    
4、对Windowds消息的处理    
窗口过程处理消息一般以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句,这在功能上同MFC的消息映射有些相似:    
switch(uMsgId)    
{    
case WM_TIMER:    
//对WM_TIMER定时器消息的处理过程    
return 0;    
case WM_LBUTTONDOWN:    
//对WM_ LBUTTONDOWN鼠标左键单击消息的处理过程    
ruturn 0;    
……    
default:    
//其余消息由这个默认处理函数来处理    
return DefWindowProc(hwnd,uMsgId,wParam,lParam);    
}    
在处理完消息后必须返回0,这很重要,不然Windows将要不停地重试下去。对于那些在程序中不许备处理的消息,窗口过程会把它们都扔给 DefWindowProc进行缺省处理,并且还要返回那个函数的返回值。在消息传递层次中,能够认为DefWindowProc函数是最顶层的函数。该函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各类通用操做,如更新窗口的正文标题等等。在MFC下能够用下述部分代码实现与上述SDK代码相同的功能:    
BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)    
//{ { AFX_MSG_MAP(CTEMMSView)    
ON_WM_LBUTTONDOWN()    
ON_WM_TIMER()    
//} } AFX_MSG_MAP    
END_MESSAGE_MAP()    
小结:Windows环境提供有很是丰富的系统资源,在这个基础上能够编制出能知足各类各样目标功能的应用系统。要深刻Windows编程就必须首先对Windows系统的运行机理有很好的认识,本文仅针对Windows的一种重要运行机制--消息机制做了较深刻的剖析和阐述。对培养在Windows 下的编程思想有必定的帮助。对某些相关问题的详细论述能够参考MSDN在线帮助的" SDK Reference" 部分。 学习


转自:http://www.cnblogs.com/railgunman/archive/2010/12/10/1902446.html