在 MFC 開發中,必須清晰區分兩個核心概念:消息循環(Message Loop)與消息路由(Message Routing)。前者負責從線程消息隊列中獲取並分發消息(::GetMessage、::PeekMessage、::DispatchMessage);後者則是系統(USER32.DLL)將消息投遞至特定窗口,以及消息在窗口層級間傳遞的機制。MFC 應用程式(以 CWinThread 為基類)必須擁有一個消息循環,持續讀取隊列並派送消息。
WM_MOUSEMOVE、WM_CHAR)、WM_PAINT、WM_TIMER、WM_QUIT。驅動程式將硬體事件轉為消息,經系統隊列後分發至線程隊列。WM_ACTIVATE、WM_SETFOCUS、WM_SETCURSOR,以及建立視窗時的 WM_CREATE。這種設計讓關鍵 UI 事件能即時處理,避免隊列延遲。MFC 非對話框程式的啟動流程如下:
_tWinMain → AfxWinMain → AfxWinInit → CWinThread::InitApplication → CWinThread::InitInstance → CWinThread::Run
所有消息循環邏輯始於 CWinThread::Run()。
CWinThread::Run)原始碼位於 thrdcore.cpp,核心為雙階段無限循環:
while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) {
if (!OnIdle(lIdleCount++))
bIdle = FALSE;
}
bIdle=TRUE)且隊列無消息(PeekMessage 配合 PM_NOREMOVE 僅檢查不移除)時,呼叫虛函數 OnIdle。OnIdle 負責更新 UI 狀態(工具列按鈕啟用/禁用)、刪除臨時物件(如 FromHandle 取得的指標)。可重寫並回傳 TRUE 維持空閒狀態。Attach/Detach 管理生命週期。do {
if (!PumpMessage()) return ExitInstance();
if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0; }
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
PumpMessage() 取得並派送消息。若收到 WM_QUIT 則回傳 FALSE,觸發 ExitInstance() 結束程式。CWinThread::PumpMessageBOOL CWinThread::PumpMessage() {
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) return FALSE; // WM_QUIT
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) {
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
關鍵點:
GetMessage 隊列為空時會讓線程睡眠(釋放 CPU),而 PeekMessage 立即返回。Run() 同時使用兩者:Phase 1 用 PeekMessage 非阻塞檢查以執行 OnIdle;Phase 2 用 GetMessage 高效等待並處理實際消息。PreTranslateMessage 預處理機制:在 DispatchMessage 前攔截消息。流程如下:
hwnd == NULL(線程消息),呼叫 DispatchThreadMessageEx 進入訊息映射表處理。PreTranslateMessage,直到主視窗。若某層回傳 TRUE(已處理),傳遞停止。PreTranslateMessage 機會,確保加速鍵(通常在 CFrameWnd 處理)仍能生效。CWnd::RunModalLoop對話框專案的 InitInstance 回傳 FALSE,故 CWinThread::Run 不會執行。模態對話框擁有獨立消息循環,位於 CDialog::DoModal() → RunModalLoop()。
WM_QUIT,而是 ContinueModal() 檢查 WF_CONTINUEMODAL 標誌。EndModalLoop 清除標誌並 PostMessage(WM_NULL) 喚醒循環。WM_KICKIDLE 給父視窗,觸發 UpdateDialogControls 更新對話框內工具列/狀態列 UI。處理方式:
LRESULT CMyDlg::OnKickIdle(WPARAM, LPARAM) {
UpdateDialogControls(this, TRUE);
return 0;
}
WM_SYSTIMER (0x118) 或 WM_SYSKEYDOWN 時顯示視窗(MLF_SHOWONIDLE 標誌)。DoModal() 內部建立對話框(最終呼叫 CreateDialogIndirect),停用父視窗,進入專屬消息循環阻塞調用者。Create() 立即返回,共用主程式消息循環,行為同一般視窗無異。GetParent()->EnableWindow(TRUE) 可重新啟用主視窗選單/工具列(MFC 允許,純 Win32 SDK 則無效)。DispatchMessage 阻塞直到視窗程序返回。若視窗程序進入死循環,消息循環卡死,PostQuitMessage 也無效。GetMessage/PeekMessage/DispatchMessage 保持響應。SendMessage 至自身視窗,USER32 直接回呼視窗程序(重入),避免死鎖。故 Win32 SDK 應少用全域/靜態變量,MFC 以類封裝自然規避此問題。PeekAndPump),或改用多線程(但需處理同步通訊,複雜度較高)。MFC 消息循環核心思想與 Win32 SDK 一致,但深度整合於框架:雙階段循環支援 OnIdle UI 更新、PreTranslateMessage 多層預處理、模態/非模態循環分離、WM_KICKIDLE 跨層 UI 同步。理解這些機制,能從根本上掌控 MFC 程式行為,避免常見卡頓、快捷鍵失效、模態阻塞等問題。