装载:http://blog.csdn.net/silvergingko/article/details/6087722
在写Win32应用程序时,消息循环是最基本的框架组成部分之一。而消息循环使用最多的两种形式就是:
-
-
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0))
- {
-
- if (msg.message == WM_XXX)
- {
- Filter(&msg);
-
- }
-
-
- if (TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) continue;
-
-
- TranslateMessage(&msg);
-
- DispatchMessage(&msg);
- }
-
-
- while (TRUE)
- {
- if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
-
- if (msg.message == WM_QUIT) break;
-
-
- if (msg.message == WM_XXX)
- {
- Filter(&msg);
-
- }
-
-
- if (TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) continue;
-
-
- TranslateMessage(&msg);
-
- DispatchMessage(&msg);
- }
- else
- {
-
- OnIdle();
- }
- }
无论是使用哪一种,运行程序体验一下后,感觉都是一样的,看不出这两种循环方式对程序性能的影响。曾在<琢石成器—Windows环境下32位汇编语言程序设计>一书中,作者罗云杉指出, 当进入 GetMessage 后,Windows 在发现线程消息队列中无消息时,会将该线程置于睡眠状态,而不会通过 GetMessage 将执行权继续交给用户执行,直到线程的消息队列有消息后将线程再次唤醒,GetMessage 返回,执行该线程的代码。其中,作者一针见血的指出,既使通过 Windows 的线程调度机制,界面线程刚刚开始执行它的代码,只要调用了 GetMessage ,发现无消息, Windows 就会剥夺该线程剩余的时间片,置线程于睡眠状态,唤醒其它线程争夺 CPU 。而与 GetMessage 截然相反,PeekMessage 执行时,如果线程的消息队列中无消息,PeekMessage 就会立刻返回,执行后面的代码,直到该线程用完它本次的时间片,不会因为无消息而被剥夺剩余的时间片。
按照这种理论,显然通过 PeekMessage 的消息循环将使用更多的时间片。
按照程序设计理论,好的程序应尽可能多使用 CPU ,使 CPU 尽可能保持一直运作的状态, CPU 空闲是对 CPU 资源的浪费。按照这个理论,应该是尽可能使用 GetMessage 进行消息循环, 而不是利用 PeekMessage 进行消息循环。但事实上,这两种循环方式各有优缺点。
当线程的消息队列中无消息时:
对于 GetMessage ,由于它出让了自己的 CPU 时间片,界面线程使用 CPU 的时间可能会大大减少,而其他线程有更多的机会获得对 CPU 的使用,通常一个应用程序中会有一个界面线程伴随着 N 条工作线程,这样工作线程将能处理更多的工作。
对于 PeekMessage ,由于它不会出让自己的时间片,可以处理一些与消息无关但又与界面相关的工作。如上例第 48 行的 OnIdle,在其中可以进行处理,当然 OnIdle 函数理应尽快返回,否则有消息到达队列后,由于 OnIdle 没有返回,无法执行 DispatchMessage ,使得消息无法被窗口处理函数处理,造成界面呆滞,停止响应用户操作的糟糕状况。事实上,在 MFC 中, OnIdle 函数中,就是处理一些根据菜单状态(禁用/启用)、状态栏状态(大写键、插入键等toggle键的状态),进行相应显示的工作。
查看 MSDN 中关于 GetMessage 和 PeekMessage 的文档说明,并无明确指出以上提到的这种情况。为了进行验证,写了3个程序,两个 Win32 窗口程序,一个控制台程序。GetMsgApp 使用的是 GetMessage 进行消息循环, PeekMsgApp 使用的是 PeekMessage 进行消息循环。 MsgAppCaller 用来创建窗口程序,睡眠一段时间后,向窗口程序发送一个消息,窗口程序在收到消息后,显示自己已经使用的 CPU 时间(分成内核模式和用户模式时间)。
GetMsgApp:
-
-
-
- #include <Windows.h>
- #include <tchar.h>
- #include <strsafe.h>
- #include "ZString.h"
-
-
- ATOM MyRegisterClass(HINSTANCE hInstance);
- BOOL InitInstance(HINSTANCE, int);
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- BOOL SetMsg();
- VOID GetEllapsedTime();
- VOID ShowEllapsedTime(HDC hdc);
-
-
- #define FILETIME2INT64(ft) ((ft).dwLowDateTime | /
- (Int64ShllMod32((ft).dwHighDateTime, 32)))
-
-
- LPCTSTR g_pszClass = _T("GetMsgWndCls");
- LPCTSTR g_pszWindow = _T("GetMsgWnd");
- UINT g_uMsg = 0;
- HWND g_hWnd;
- DOUBLE g_dKernel;
- DOUBLE g_dUser;
- BOOL g_bErr = FALSE;
-
- int APIENTRY _tWinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPTSTR lpCmdLine,
- int nCmdShow)
- {
- UNREFERENCED_PARAMETER(hPrevInstance);
- UNREFERENCED_PARAMETER(lpCmdLine);
-
- if (SetMsg() == FALSE)
- {
- TCHAR szMessage[64] = { 0 };
- DWORD dwErr = GetLastError();
-
- StringCchPrintf(szMessage, _countof(szMessage),
- _T("ERROR: failed to SetMsg(#%u)"), dwErr);
- MessageBox(NULL, szMessage, _T("GetMsg App"), MB_OK);
- return 1;
- }
-
- MyRegisterClass(hInstance);
-
- if (!InitInstance (hInstance, nCmdShow))
- {
- return FALSE;
- }
-
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0))
- {
- if (msg.message == g_uMsg)
- {
- GetEllapsedTime();
- ShowWindow(g_hWnd, SW_SHOWNORMAL);
- continue;
- }
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
-
- return (int) msg.wParam;
- }
-
- ATOM MyRegisterClass(HINSTANCE hInstance)
- {
- WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
-
- wcex.style = CS_HREDRAW | CS_VREDRAW;
- wcex.lpfnWndProc = WndProc;
- wcex.hInstance = hInstance;
- wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
- wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
- wcex.lpszClassName = g_pszClass;
- return RegisterClassEx(&wcex);
- }
-
- BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
- {
- g_hWnd = CreateWindow(g_pszClass, g_pszWindow, WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, 0, 500, 200, NULL, NULL, hInstance, NULL);
-
- if (!g_hWnd)
- {
- return FALSE;
- }
-
- ShowWindow(g_hWnd, SW_HIDE);
- UpdateWindow(g_hWnd);
-
- return TRUE;
- }
-
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- int wmId, wmEvent;
- PAINTSTRUCT ps;
- HDC hdc;
-
- switch (message)
- {
- case WM_PAINT:
- hdc = BeginPaint(hWnd, &ps);
-
- if (g_dKernel > 0 || g_dUser > 0)
- {
- ShowEllapsedTime(hdc);
- }
-
- EndPaint(hWnd, &ps);
- return TRUE;
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- return 0;
- }
-
-
- BOOL SetMsg()
- {
- LPTSTR pszCmdLine = GetCommandLine();
- LPCTSTR pPos = NULL;
-
- pPos = _tcschr(pszCmdLine, _T('"'));
- if (pPos != NULL)
- {
- pPos = _tcschr(pPos, _T('"'));
- }
- pPos = _tcschr(pszCmdLine, _T(' '));
- if (pPos != NULL)
- {
- pPos++;
- }
- else
- {
- pPos = pszCmdLine;
- }
- if (*pPos == _T('/'))
- {
- pPos++;
- }
-
-
- return Str2UInt(pPos, &g_uMsg, FALSE);
- }
-
- VOID GetEllapsedTime()
- {
- FILETIME ftCreate, ftExit, ftKernel, ftUser;
-
- if (GetProcessTimes(GetCurrentProcess(), &ftCreate, &ftExit, &ftKernel, &ftUser)
- != NULL)
- {
- INT64 i64Kernel, i64User;
- i64Kernel = FILETIME2INT64(ftKernel);
- g_dKernel = (DOUBLE)(i64Kernel / 10000.0);
- i64User = FILETIME2INT64(ftUser);
- g_dUser = (DOUBLE)(i64User / 10000.0);
- }
- else
- {
- g_bErr = TRUE;
- }
- }
-
- VOID ShowEllapsedTime(HDC hdc)
- {
- TCHAR sz[64] = { 0 };
-
- if (g_bErr == FALSE)
- {
- StringCchPrintf(sz, _countof(sz),
- _T("kernel time-->%lfms && user time-->%lfms"),
- g_dKernel, g_dUser);
- }
- else
- {
- StringCchPrintf(sz, _countof(sz),
- _T("ERROR: failed to GetProcessTimes(#%u)"), GetLastError());
- }
-
- TextOut(hdc, 10, 10, sz, _tcslen(sz));
- }
-
PeekMsgApp:
-
-
-
- #include <Windows.h>
- #include <tchar.h>
- #include <strsafe.h>
- #include "ZString.h"
-
-
- ATOM MyRegisterClass(HINSTANCE hInstance);
- BOOL InitInstance(HINSTANCE, int);
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- BOOL SetMsg();
- VOID GetEllapsedTime();
- VOID ShowEllapsedTime(HDC hdc);
-
-
- #define FILETIME2INT64(ft) ((ft).dwLowDateTime | /
- (Int64ShllMod32((ft).dwHighDateTime, 32)))
-
-
- LPCTSTR g_pszClass = _T("PeekMsgWndCls");
- LPCTSTR g_pszWindow = _T("PeekMsgWnd");
- UINT g_uMsg = 0;
- HWND g_hWnd;
- DOUBLE g_dKernel;
- DOUBLE g_dUser;
- BOOL g_bErr = FALSE;
-
- int APIENTRY _tWinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPTSTR lpCmdLine,
- int nCmdShow)
- {
- UNREFERENCED_PARAMETER(hPrevInstance);
- UNREFERENCED_PARAMETER(lpCmdLine);
-
- if (SetMsg() == FALSE)
- {
- TCHAR szMessage[64] = { 0 };
- DWORD dwErr = GetLastError();
-
- StringCchPrintf(szMessage, _countof(szMessage),
- _T("ERROR: failed to SetMsg(#%u)"), dwErr);
- MessageBox(NULL, szMessage, _T("PeekMsg App"), MB_OK);
- return 1;
- }
-
- MyRegisterClass(hInstance);
-
- if (!InitInstance (hInstance, nCmdShow))
- {
- return FALSE;
- }
-
- MSG msg;
- while (TRUE)
- {
- if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
- if (msg.message == WM_QUIT) break;
- if (msg.message == g_uMsg)
- {
- GetEllapsedTime();
- ShowWindow(g_hWnd, SW_SHOWNORMAL);
- continue;
- }
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
-
- return (int) msg.wParam;
- }
-
- ATOM MyRegisterClass(HINSTANCE hInstance)
- {
- WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
-
- wcex.style = CS_HREDRAW | CS_VREDRAW;
- wcex.lpfnWndProc = WndProc;
- wcex.hInstance = hInstance;
- wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
- wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
- wcex.lpszClassName = g_pszClass;
- return RegisterClassEx(&wcex);
- }
-
- BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
- {
- g_hWnd = CreateWindow(g_pszClass, g_pszWindow, WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, 0, 500, 200, NULL, NULL, hInstance, NULL);
-
- if (!g_hWnd)
- {
- return FALSE;
- }
-
- ShowWindow(g_hWnd, SW_HIDE);
- UpdateWindow(g_hWnd);
-
- return TRUE;
- }
-
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- PAINTSTRUCT ps;
- HDC hdc;
-
- switch (message)
- {
- case WM_PAINT:
- hdc = BeginPaint(hWnd, &ps);
-
- if (g_dKernel > 0 || g_dUser > 0)
- {
- ShowEllapsedTime(hdc);
- }
-
- EndPaint(hWnd, &ps);
- return TRUE;
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- return 0;
- }
-
-
- BOOL SetMsg()
- {
- LPTSTR pszCmdLine = GetCommandLine();
- LPCTSTR pPos = NULL;
-
- pPos = _tcschr(pszCmdLine, _T('"'));
- if (pPos != NULL)
- {
- pPos = _tcschr(pPos, _T('"'));
- }
- pPos = _tcschr(pszCmdLine, _T(' '));
- if (pPos != NULL)
- {
- pPos++;
- }
- else
- {
- pPos = pszCmdLine;
- }
- if (*pPos == _T('/'))
- {
- pPos++;
- }
-
-
- return Str2UInt(pPos, &g_uMsg, FALSE);
- }
-
- VOID GetEllapsedTime()
- {
- FILETIME ftCreate, ftExit, ftKernel, ftUser;
-
- if (GetProcessTimes(GetCurrentProcess(), &ftCreate, &ftExit, &ftKernel, &ftUser)
- != NULL)
- {
- INT64 i64Kernel, i64User;
- i64Kernel = FILETIME2INT64(ftKernel);
- g_dKernel = (DOUBLE)(i64Kernel / 10000.0);
- i64User = FILETIME2INT64(ftUser);
- g_dUser = (DOUBLE)(i64User / 10000.0);
- }
- else
- {
- g_bErr = TRUE;
- }
- }
-
- VOID ShowEllapsedTime(HDC hdc)
- {
- TCHAR sz[64] = { 0 };
-
- if (g_bErr == FALSE)
- {
- StringCchPrintf(sz, _countof(sz),
- _T("kernel time-->%lfms && user time-->%lfms"),
- g_dKernel, g_dUser);
- }
- else
- {
- StringCchPrintf(sz, _countof(sz),
- _T("ERROR: failed to GetProcessTimes(#%u)"), GetLastError());
- }
-
- TextOut(hdc, 10, 10, sz, _tcslen(sz));
- }
-
MsgAppCaller:
-
-
- #include <stdio.h>
- #include <tchar.h>
- #include <Windows.h>
- #include <strsafe.h>
-
-
- VOID TestApp(LPTSTR, DWORD);
-
-
- UINT g_uMsg;
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- int nRet = 0;
- DWORD dwTime = 0;
- STARTUPINFO si = { sizeof(si) };
- PROCESS_INFORMATION pi;
- TCHAR szGetMsgApp[64] = { 0 };
- TCHAR szPeekMsgApp[64] = { 0 };
-
- g_uMsg = RegisterWindowMessage(_T("WM_MsgAppCaller"));
-
- StringCchPrintf(szGetMsgApp, _countof(szGetMsgApp), _T("GetMsgApp /%u"),
- g_uMsg);
- StringCchPrintf(szPeekMsgApp, _countof(szPeekMsgApp), _T("PeekMsgApp /%u"),
- g_uMsg);
-
- dwTime = 1000;
-
- TestApp(szGetMsgApp, dwTime);
- TestApp(szPeekMsgApp, dwTime);
-
- dwTime += 5000;
-
- TestApp(szGetMsgApp, dwTime);
- TestApp(szPeekMsgApp, dwTime);
-
- return 0;
- }
-
- VOID TestApp(LPTSTR pszCmdLine, DWORD dwTime)
- {
- STARTUPINFO si = { sizeof(si) };
- PROCESS_INFORMATION pi;
-
- if (CreateProcess(NULL, pszCmdLine, NULL, NULL, FALSE, 0, FALSE, FALSE,
- &si, &pi))
- {
- Sleep(dwTime);
- BOOL fRet = PostThreadMessage(pi.dwThreadId, g_uMsg, 0, 0);
- if (fRet == FALSE)
- {
- _tprintf(_T("ERROR: failed to call PostThreadMessage(#%u)/r/n"),
- GetLastError());
- }
- CloseHandle(pi.hThread);
- CloseHandle(pi.hProcess);
- }
- else
- {
- _tprintf(_T("ERROR: failed to call CreateProcess(#%u)/r/n"), GetLastError());
- }
- }
-
MsgAppCaller 运行后的结果:
从上图结果中观察 user time:
第一次 MsgAppCaller睡眠了一秒钟,然后 GetMsgWnd 显示一共使用的 CPU 时间大约为 15 毫秒,而第二次 MsgAppCaller 睡眠了六秒钟, GetMsgWnd 显示的 CPU 使用时间还是为 15 毫秒!由此可知,当界面线程没有消息时,由于 Windows 将它置于睡眠状态,因此它的 CPU 时间与该界面线程生存时间不成正比。
第一次 MsgAppCaller睡眠了一秒钟,然后 PeekMsgWnd 显示一共使用的 CPU 时间大约为 921 毫秒,将近1秒!而第二次 MsgAppCaller 睡眠了六秒钟, PeekMsgWnd 显示的 CPU 使用时间增长到约5秒!因此,使用PeekMessage ,界面线程即使在没有消息的情况下仍然占用着 CPU 在运作。
关于程序设计,有几点说明下:
1、获取线程使用 CPU 的 API 是 GetThreadTimes 。由于 GetMsgApp 和 PeekMsgApp 进程中只有一个线程且就是界面线程,因此调用 GetProcessTimes 来进行性能计算,并无多大偏差。
2、两个窗口程序的窗口初始状态都是 WM_HIDE ,这是为了避免测试时,用户的干扰,而将窗口故意隐藏了,只有当窗口收到了 MsgAppCaller 发来的消息时,窗口才显示出来。
3、由于 MsgAppCaller 使用 PostThreadMessage 函数,因此窗口程序如果在窗口处理函数 WndProc 中是无法获得该消息的,对该消息的捕获需要放在消息循环中进行。
4、窗口程序中使用了一个自定义函数 Str2UInt ,该函数负责将命令行传过来的字符串形式的窗口消息解析成UINT类型。Str2UInt 及其他一些函数在文件 ZString.h 和 ZString.cpp 中进行声明和定义。
ZString.h:
-
-
-
- #pragma once
- #include <Windows.h>
- #include <tchar.h>
-
-
- BOOL WINAPI Str2IntW(LPCWSTR pszNum, PINT pNum, BOOL bSpaceLead);
- BOOL WINAPI Str2IntA(LPCSTR pszNum, PINT pNum, BOOL bSpaceLead);
- BOOL WINAPI Str2UIntW(LPCWSTR pszNum, PUINT pNum, BOOL bSpaceLead);
- BOOL WINAPI Str2IntA(LPCSTR pszNum, PUINT pNum, BOOL bSpaceLead);
-
-
- #if defined(UNICODE) || defined(_UNICODE)
- #define Str2Int Str2IntW
- #else
- #define Str2Int Str2IntA
- #endif
-
- #if defined(UNICODE) || defined(_UNICODE)
- #define Str2UInt Str2UIntW
- #else
- #define Str2UInt Str2UIntA
- #endif
-
ZString.cpp:
-
-
-
- #include "ZString.h"
-
-
-
-
-
-
-
-
-
-
- BOOL WINAPI Str2IntW(LPCWSTR pszNum, PINT pNum, BOOL bSpaceLead)
- {
- __int64 num = 0;
- INT sign = 1;
- SIZE_T len = 0;
- BOOL bRet = FALSE;
- WCHAR ch = 0;
-
- if (pszNum == NULL || pNum == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return bRet;
- }
-
-
- if (bSpaceLead == TRUE)
- {
- while (*pszNum == _T(' ') || *pszNum == _T('/t'))
- pszNum++;
- }
-
-
- ch = *pszNum;
- if (ch == _T('-'))
- {
- sign = -1;
- pszNum++;
- } else if (ch == _T('+'))
- {
- pszNum++;
- }
-
-
- if (*pszNum < _T('0') || *pszNum > _T('9'))
- {
- SetLastError(ERROR_BAD_FORMAT);
- return bRet;
- }
-
-
- if (*pszNum == _T('/0'))
- {
- if (pszNum[1] == _T('/0'))
- {
- *pNum = 0;
- return TRUE;
- }
- else
- {
- SetLastError(ERROR_BAD_FORMAT);
- return bRet;
- }
- }
-
- while (TRUE)
- {
- ch = *pszNum++;
-
-
- if (ch >= _T('0') && ch <= _T('9'))
- {
- len++;
-
-
- if (len > 10)
- {
- SetLastError(ERROR_INVALID_DATA);
- break;
- }
-
- num = num * 10 + (ch - _T('0'));
-
- continue;
- }
-
- else if (ch == _T('/0'))
- {
- num = sign * num;
- if (num <= INT_MAX && num >= INT_MIN)
- {
- *pNum = (INT)num;
- bRet = TRUE;
- break;
- }
-
- else
- {
- SetLastError(ERROR_INVALID_DATA);
- break;
- }
- }
-
- else
- {
- SetLastError(ERROR_BAD_FORMAT);
- break;
- }
- }
-
- return bRet;
- }
-
-
-
-
-
- BOOL WINAPI Str2IntA(LPCSTR pszNum, PINT pNum, BOOL bSpaceLead)
- {
-
- WCHAR szNum[16] = { 0 };
- INT nRet = 0;
- SIZE_T cbLen = 0;
-
- if (pszNum == NULL || pNum == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return FALSE;
- }
-
-
- if (bSpaceLead == TRUE)
- {
- while (*pszNum == _T(' ') || *pszNum == _T('/t'))
- pszNum++;
- }
-
- cbLen = strnlen_s(pszNum, 16);
-
-
- if (cbLen == 16)
- {
- SetLastError(ERROR_BAD_FORMAT);
- return FALSE;
- }
-
- nRet = MultiByteToWideChar(CP_ACP, 0, pszNum, -1, szNum, 16);
- if (nRet == 0)
- {
- return FALSE;
- }
-
- return Str2IntW(szNum, pNum, bSpaceLead);
- }
-
-
-
-
-
-
-
-
-