发送的消息和程序队列
当一个应用程序开始时,一个队列将会因此而被创建。程序队列(有时会称为任务队列)常常用于储存“正在被发往应用程序的一个窗口” 的消息。唯一常驻程序队列的消息是那些由PostMessage或PostAppMessage明确发送的消息。(SendMessage从不使用系统队列)PostQuitMessage函数不会发送一个消息到程序队列。(WM_QUIT消息将在下文中论讨)
默认的,每个程序队列可以保持八个消息。一般情况下这是相当足够的,因为PostMessage极少被使用。但是如果一个应用程序试图强制调用很多的PostMessage到某个应用程序时,那么这类应用程序将会用使用SetMessageQueue函数来增加消息队列的长度。你必须小心的使用SetMessageQueue函数,因为它无论何时都会先删掉当前的程序队列,并创建一个预期大小的新队列,此时任何在旧队列中的消息都会被销毁。因此,它必须在你的WinMain例程中在所有其它的应用程序编程接口(API)之前调用或在应用程序用PeekMessage明确的清除队列之后调用。
GetMessage和PeekMessage是怎样工作的
在Windows的内部,GetMessage和PeekMessage执行着相同的代码。而两者最大的不同之处则体现在没有任何消息返回到应用程序的情况下。在此种情况下,PeekMessage会返回一个空值到应用程序,GetMessage会在此时让应用程序休眠。在它们之间还有一些其它的不同,我们将会在下面讨论,但它们相当次要。
GetMessage和PeekMessage逻辑
下面一步步的讲述了在Windows3.1版的GetMessage和PeekMessage公用代码。
提示:下面所示步骤按照消息类型的优先权进行排序。举个例子,发送的消息总在键盘和鼠标消息之前被返回,而键盘和鼠标的消息又会在绘图(paint)消息之前反回,以此类推。
1. 检视在为“活动中任务”服务的程序队列中是否有消息的存在。如果是,首先在队首删除此消息并将其返回到应用程序。然后,应用程序中的GetMessage和PeekMessage会调用一些代码,用以从程序队列中接收此消息,这些代码是由该应用程序调用的动态链接库(DLL)生成的。记住,只有由PostMessage发送的消息会常驻于此队列中。
2. 与所有消息和窗体句柄过滤器进行对照,核查此消息。如果此消息不匹配指定的过滤器,就会把此消息留在程序队列中。如果队列中在此消息的后面还有其它消息,则会转向对下一个消息的处理。
3. 如果在程序队列中没有消息了,就扫描系统队列中的事件。这个过程相当复杂,并且我们将在下面的“扫描系统队列”小节中XX。一般来讲,在系统队列首部的事件是供这个应用程序所使用的,系统会将其转化为消息,并将消息返回到这个应用程序中(它不会首先被置于应用队列中)。注意,这个扫描系统队列的过程可能导致当前活动的应用程序将控制权让给其它的应用程序。
4. 如果在系统队列中没有等待处理的事件,则核查所有与当前应用程序(任务)相关的窗体以确定更新区域。当一个窗体的一部分需要被重绘时,一个更新区域就被创建在那个窗体部分之上。这个区域将与此窗体中现存的所有更新区域相结合,并储存在内部窗体结构体中。如果GetMessage或PeekMessage在这个任务中发现某些窗体有一些未处理的更新区域,将产生一个WM_PAINT消息,并为那个窗体返回到应用程序中。WM_PAINT从不驻留在任何队列中。此时,一个应用程序将为某个窗体不断的接收WM_PATIN消息,直到更新区域由BeginPaint/EndPaint,ValidateRect,或ValidateRgn所清除。
5. 如果这个任务中没有任何窗体需要被更新,GetMessage和PeekMessage就会在这一点让出控制权,除非PeekMessage调用被设置为PM_NOYIELD属性。
6. 当让步返回时,检视在当前任务中是否有计时器到期。如果是,创建一个WM_TIMER消息并返回。它不但发生在“返回一个WM_TIMER消息到窗体”的计时器上,同样也发生在“调用一个计时器处理过程”的计时器上。如要了解更多信息,请看在微软开发者网络(MSDN)光盘(包括技术文章、Windows文章、核心和驱动程序文章)中的文章“Timers and Timing in Microsoft Windows”(译者注:如果读者能够认可我的工作,我会不遗余力地翻译这篇关于计时器的文章)。
7. 如果这个应用程序没有计时器事件服务,并且一个应用程序正在被终止,代码将尝试去缩小图形设备界面(GDI)的本地内存堆。一些应用程序,比如绘图应用程序(像Paintbrush?),为GDI分配了大量的堆内存。当应用程序终止时释放这些对象时,会使GDI本地内存堆被空闲空间填满而膨胀。为了恢复这些空闲的空间, 在GetMessage/PeekMessage处理中,LocalShrink将在这一点被调用于GDI的内存堆。这个被完成一次,(每次)一个应用程序将终止。
8. 在这一时刻,代码将分叉为两条路,一是代码任意的返回一个有效的消息,另一个是完全没有这个应用程序去处理的消息、事件,而代码最终会走哪条路决定于PeekMessage和GetMessage中的哪一个被调用。
·PeekMessage. 如果PeekMessage被调用,并设置了PM_NOYIELD标记,PeekMessage在此刻返回一个空值,这个空返回值指出已经没有要处理的消息了。如果没有设置PM_NOYIELD标记,PeekMessage就在此刻让出控制权。它不会休眠,但会单一的交给其它已准备好的应用程序一个执行的机会。(请参阅下面的“让步与休眠的不同)当让步返回,PeekMessage直接将控制权返回到应用程序,并返回一个空值,它指出这个应用程序没有要处理的消息了。
GetMessage. 在此刻,GetMessage会让应用程序休眠、等待,直到一些事件发生需要唤醒应用程序。控制权不会返回到调用GetMessage的应用程序,直到有应用程序必须去处理的消息出现。一旦这个应用程序从被置入休眠状态中醍来,GetMessage内部的循环将回到最开始(步骤1)。
PeekMessage与GetMessage的对比
相同点:
PeekMessage函数与GetMessage函数都用于查看应用程序消息队列,有消息时将队列中
的消息派发出去。
不同点:
无论应用程序消息队列是否有消息,PeekMessage函数都立即返回,程序得以继续执行
后面的语句(无消息则执行其它指令,有消息时一般要将消息派发出去,再执行其它
指令)。
GetMessage函数只有在消息对立中有消息时返回,队列中无消息就会一直等,直至下
一个消息出现时才返回。在等的这段时间,应用程序不能执行任何指令。
这是两个原理图:
对比的一个例子:
#include <windows.h>
#include <stdlib.h> // for the rand function
#include "process.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawRectangle(HWND);
int cxClient, cyClient;
HWND g_hWnd;
HANDLE first_handle;
BOOL g_continue;
#define WM_USER_TEST 0x0400 + 1
void TestSendMessage(void* pData)
{
int count = 0;
g_continue = TRUE;
while (g_continue)
{
Sleep(1000);
if (count == 5)
{
break;
}
SendMessage(g_hWnd, WM_USER_TEST, 0, 0);
count++;
}
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("RandRect");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, TEXT ("Random Rectangles"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
g_hWnd = hwnd;
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
first_handle = (HANDLE)_beginthread(TestSendMessage, 0, NULL);
//用于替换的部分
// while (TRUE)
// {
// if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
// {
// if (msg.message == WM_QUIT)
// break ;
// TranslateMessage (&msg);
// DispatchMessage (&msg);
// }
// else
// {
// DrawRectangle (hwnd);
// }
// }
// //用于替换的部分
while (TRUE)
{
if (GetMessage(&msg, NULL, 0, 0))
{
if (msg.message == WM_QUIT)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
DrawRectangle(hwnd);
}
else
{
break;
}
}
g_continue = FALSE;
WaitForSingleObject(first_handle, 100);
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch (iMsg)
{
case WM_SIZE:
cxClient = LOWORD (lParam);
cyClient = HIWORD (lParam);
return 0;
case WM_KEYUP:
if (wParam == VK_ESCAPE)
{
PostQuitMessage(0);
}
break;
case WM_USER_TEST:
MessageBox(0, "WM_USER_TEST", "", MB_OK);
break;
case WM_DESTROY:
g_continue = FALSE;
WaitForSingleObject(first_handle, 100);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
void DrawRectangle(HWND hwnd)
{
HBRUSH hBrush;
HDC hdc;
RECT rect;
if (cxClient == 0 || cyClient == 0)
return;
SetRect(&rect, rand () % cxClient, rand() % cyClient,
rand() % cxClient, rand() % cyClient);
hBrush = CreateSolidBrush(
RGB(rand() % 256, rand() % 256, rand() % 256));
hdc = GetDC(hwnd);
FillRect(hdc, &rect, hBrush);
ReleaseDC(hwnd, hdc);
DeleteObject(hBrush);
}
参考链接:
http://www.cppblog.com/tx7do/archive/2007/04/11/21665.html
http://www.cnblogs.com/faceang/archive/2010/05/25/1743757.html