Windows 是一个消息驱动的OS, 为了存放消息系统提供了一个系统消息队列, OS在监控到事件的发生时就会产生相应的消息并存放到系统消息队列中。 而每个Windows 应用程序自己也有一个消息队列,OS在处理系统消息队列时会将属于本应用程序的消息投递到此消息队列中. 而消息循环体就不断的从这个消息队列中提取消息,分发给对应的窗体过程函数去处理。这里我引用于了网上的图来说明:
如图所示应用程序通过消息循环不断的从应用程序队列中获取消息并分发给对应的窗体函数, Windows 实现消息循环有两种方式:
a. 等待模式 - 队列中无消息时程序挂起
{
MSG Msg ;
while ( GetMessage (& Msg , NULL , 0, 0))
{
TranslateMessage (& Msg );
DispatchMessage (& Msg );
}
}
b. 非等待模式 – 队列中无消息时程序不挂起
{
MSG Msg ;
while ( true )
{
if ( PeekMessage (& Msg , NULL , 0, 0, PM_REMOVE ))
{
if ( WM_QUIT == Msg . message )
{
break ;
}
TranslateMessage (& Msg );
DispatchMessage (& Msg );
} else {
OnIdle ( ghWnd );
}
}
}
等待模式和非等待模式差别在于当消息队列里没有消息时, 是继续等待消息才工作还是继续做别的事. 我们看一下以上述两份代码,都比较简单,我们联合解说一下:
1. MSG Msg; 消息结构
typedef struct tagMSG
{
HWND hwnd ; // 接受消息的窗体句柄
UINT message ; // 消息ID
WPARAM wParam ; // 消息参数
LPARAM lParam ; // 消息参数
DWORD time ;
POINT pt ;
} MSG, * PMSG ;
2. GetMessage(LPMSG, HWND, UINT, UINT)
从线程消息队列中获取消息,若消息队列中存在消息时函数立即返回,否则此函数会被挂起直到等待新消息的到来。如果获到的消息为WM_QUIT时, GetMessage返回0;
当用户在收到WM_QUIT后也就退出消息循环,结束了应用程序线程. 我们可以编译一下Demo源码运行看看Demo对CPU的消耗, 几乎为0, 因为在Demo在没有消息时一起被挂起。
LPMSG – 从线程消息队列中读取的消息,
HWND - 读取此消息对应的窗体,NULL 表示消息所有队列中的消息
3. PeekMessage(LPMSG, HWND, UINT, UINT, UINT)
窥视线程消息队列消息,也可以设置参数在窥视时将消息从队列中删除。 此函数只是查看队列消息,若队列中不存在消息时此函数会立即还回, 这是它与GetMessage最大的不同.
大家将Demo中#define WAITMODE 宏注释,编译运行下可看到窗体里不断的画不同颜色的矩形,此进程CPU一起有消耗。
4. TranslateMessage(const LPMSG)
此函数主要功能实现一些键盘消息转换,比如我们在按下键人列时会将收到WM_KEYDOWN消息后,此函数会将此消息发给windows,windows 会转发出WM_CHAR消息
5. DispatchMessage(const LPMSG)
此函数执行时会通知windows分发这个消息,Windows收到处消息后会窗体过程序函数去处理对应的消息.
参考文档:
http://www.cppblog.com/mzty/archive/2006/11/24/15619.html
http://www.cppblog.com/tx7do/archive/2007/04/11/21665.aspx
在消息机制1部分中,我们主要讲解了消息循环部分,实际更突出说明了GetMessage 和 PeekMessage之间的差别,本章我们将讲解入队消息和非入队消息,入队消息就是指消息在产生后直接放入到应用程序消息队列中,由消息循环获取并通过DispatchMessage分发给窗体过程函数。 非入队消息那自然就是消息产生后不被放入到应用程序消息队列,而是由windows直接调用窗体过程函数处理。 我们先看一段代码:
{
static HWND hWnd ;
MSG Msg ;
while ( true )
{
if ( PeekMessage (& Msg , NULL , 0, 0, PM_REMOVE ))
{
if ( WM_CREATE == Msg . message )
{
hWnd = Msg . hwnd ;
}
if ( WM_QUIT == Msg . message )
{
break ;
}
TranslateMessage (& Msg );
DispatchMessage (& Msg );
} else {
if ( NULL != hWnd )
{
OnIdle ( hWnd );
}
}
}
return ;
}
这个代码有什么问题呢? 这是我在写Demo0003时写的一段代码,我调试了一下才发现OnIdle参数hWnd一直为空, 我在WM_CREATE时不是给hWnd赋值了吗? 分析了一下才知道,WM_CREATE 根本没有进入消息队列, Windows在创建窗体时而去直接调用窗体过程函数的。 由此看来,消息循环并不能获取所有的消息,但消息一定会被窗体过程函数捕获。 那除了
WM_CREATE消息不入队列还有什么消息不入队呢, 有什么规律的吗? 个人认为没有严格的规定一般输入式的消息被放入消息队列如: WM_KEYDOWN, WM_KEYUP, WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_LBUTTONUP等, 我也发现还会包括WM_TIMER, WM_PAINT, WM_QUIT也会被队列化。但被SendMessage的消息一定不进入消息列队,而PostMessage消息会进入消息队列。 我们重点看看这两个函数:
1. LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
MSDN Description: Sends a message to the window and does not return until the window procedure has processed the message.
=> 发送消息给窗体并由窗体过程处理完后才返回.
2. BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
MSDN Description: Places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for
the thread to process the message.
=> 将一个消息存放创建窗体线程的消息队列中并不等线程处理消息就返回.
比较一个两个函数:
a. 参数部分,它们有着相同的参数
hWnd - 接受消息的窗体,当此参数为HWND_BROADCAST时,发送的消息将会被所有的(其他应用)顶层窗体所接受.
Msg - 消息ID, 当hWnd参数为HWND_BROADCAST时,此消息必须由RegisterWindowsMessage函数生成,才可被接受.
wParam – 消息所属加的参数
lParam – 消息所属加的参数
b. 队列化方面,SendMessage发出的消息是不进消息队列的, PostMessage发出的消息都会进入消息队列。
c. 编程应注意,SendMessage调用时相当于把对应的窗体过徎函数中此消息段的代码搬到过来运行即与调用函数是同步的。而PostMessage调用时仅仅是放到消息队列中,不等其消息处
理 代码结果的即与调用函数是异步的.
接下来我们来验证一下我们以上的理论,SendMessage 发送的消息不入队列, PostMessage 发送的消息会入队列的
验证的方法:
1. 使用timer调用SendMessage和PostMessage发送消息
{
switch ( wParam )
{
case IDT_SENDMESSAGE :
{
SendMessage ( hWnd , 0XFFFC, 0, 0);
break ;
}
case IDT_POSTMESSAGE :
{
PostMessage ( hWnd , 0XFFFE, 0, 0);
break ;
}
}
return 0;
}
2. 在消息循环中将获取的消息记录下来;
{
MSG Msg ;
while ( true )
{
if ( PeekMessage (& Msg , ( HWND ) NULL , 0, 0, PM_REMOVE ))
{
_PrintMessage ( _T ( "PeekMessage" ), Msg . hwnd , Msg . message , Msg . wParam , Msg . lParam );
TranslateMessage (& Msg );
_PrintMessage ( _T ( "Translate" ), Msg . hwnd , Msg . message , Msg . wParam , Msg . lParam );
DispatchMessage (& Msg );
_PrintMessage ( _T ( "Dispatch" ), Msg . hwnd , Msg . message , Msg . wParam , Msg . lParam );
if ( Msg . message == WM_QUIT )
{
break ;
}
}
}
return ;
}
3. 将进入窗体过程函数的消息记录下来;
{
for ( int ii = 0; ii < sizeof ( MsgMapTable ) / sizeof (* MsgMapTable ); ii ++)
{
if ( nMessage == MsgMapTable [ ii ]. nMessage )
{
MsgMapTable [ ii ]. MsgProc ( hWnd , wParam , lParam );
break ;
}
}
_PrintMessage ( _T ( "WndProcess" ), hWnd , nMessage , wParam , lParam );
return DefWindowProc ( hWnd , nMessage , wParam , lParam );
}
4. 对比一下两边的消息是否一样;
结果: 进入消息循环的会打印: PeekMessage, Translate, DispatchMessage, WndProcess
不进入消息循环的只打印: WndProcess
MsgID = FFFF 是由PostMessage发出来打印PeekMessage, Translate, DispatchMessage, WndProcess即进入了消息队列。
MsgID = FFFC 是由SendMessage发出去了我们看到进入这个函数WndProcess。
注: 大家看到结果文件中WndProcess 显示在DispatchMessage之后, 这个因为我调用DispatchMessage之后才打印,而DispatchMessage函数被调用时会系统会调用WndProcess,所以
显我们看到WndProcess显示在DispatchMessage 之前.