开始阅读MFC源码,从MFC角度学习win32窗口与消息机制,同时学习MFC一些成熟的架构和设计模式。
源码阅读基于vs2012,位于目录 "安装目录\Microsoft Visual Studio 11.0\VC\atlmfc\src\mfc",可以看到 afx*.cpp, app*.cpp, ctl*.cpp, db*.cpp, dlg*.cpp, doc*.cpp, file*.cpp, ole*.cpp, view*.cpp, win*.cpp,代码十分庞大,这里只简单介绍核心代码。
winmain.cpp:入口函数 AfxWinMain 的实现;
cmdtarg.cpp:类CCmdTarget的实现;
wincore.cpp:类CWnd;doccore.cpp:类CDocument;thrdcore.cpp:类CWinThread;appcore.cpp:类CWinApp; ===>继承自 CCmdTarget
winfrm.cpp:类CFrameWnd;viewcore.cpp:类CView;dlgcore.cpp:类CDialog;===>继承自 CWnd
MFC类组织结构图可参考链接:https://blog.csdn.net/bflong/article/details/47316241
Win32应用程序的函数入口一般是WinMain/wWinMain,分别对应ANSI/UNICODE字符集版本。(控制台程序的函数入口main/wmain)
贴一下核心代码(去掉部分Debug代码):
CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp(); // AFX internal initialization if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) goto InitFailure; // App global initializations (rare) if (pApp != NULL && !pApp->InitApplication()) goto InitFailure; // Perform specific initializations if (!pThread->InitInstance()) { if (pThread->m_pMainWnd != NULL) { pThread->m_pMainWnd->DestroyWindow(); } nReturnCode = pThread->ExitInstance(); goto InitFailure; } nReturnCode = pThread->Run();
依次执行 AfxWinInit , CWinApp::InitApplication , CWinThread::InitInstance 初始化函数后,进入主循环 CWinThread::Run 。
(1)AfxWinInit (appinit.cpp)
BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)
{
......
// 保存进程实例与资源句柄
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_hCurrentInstanceHandle = hInstance;
pModuleState->m_hCurrentResourceHandle = hInstance;
// 保存自WinMain的初始化参数
CWinApp* pApp = AfxGetApp();
if (pApp != NULL)
{
// Windows specific initialization (not done if no CWinApp)
pApp->m_hInstance = hInstance;
hPrevInstance; // Obsolete.
pApp->m_lpCmdLine = lpCmdLine;
pApp->m_nCmdShow = nCmdShow;
pApp->SetCurrentHandles();
}
if (!afxContextIsDLL)
AfxInitThread();
......
}
void AFXAPI AfxInitThread()
{
if (!afxContextIsDLL)
{
//设置本线程的消息钩子
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
ASSERT(pThreadState->m_hHookOldMsgFilter == NULL);
pThreadState->m_hHookOldMsgFilter = ::SetWindowsHookEx(WH_MSGFILTER, _AfxMsgFilterHook, NULL, ::GetCurrentThreadId());
}
}
总结下AfxWinInit主要初始化CWinApp的一些参数:hInstance, lpCmdLine, nCmdShow, 随后设置了当前线程的消息钩子(回调函数_AfxMsgFilterHook)。
(2)CWinApp::InitApplication
BOOL CWinApp::InitApplication() { if (CDocManager::pStaticDocManager != NULL) { if (m_pDocManager == NULL) m_pDocManager = CDocManager::pStaticDocManager; CDocManager::pStaticDocManager = NULL; } if (m_pDocManager != NULL) m_pDocManager->AddDocTemplate(NULL); else CDocManager::bStaticInit = FALSE; LoadSysPolicies(); return TRUE; }
CWinApp::InitApplication只是简单初始化了CDocManager的全局变量。
(3)CWinThread::InitInstance
BOOL CWinThread::InitInstance() { ASSERT_VALID(this); return FALSE; // by default don't enter run loop }
CWinThread::InitInstance几乎什么也没做。
(4)CWinThread::Run
int CWinThread::Run() { ASSERT_VALID(this); _AFX_THREAD_STATE* pState = AfxGetThreadState(); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; //获取和派发窗口消息直到接收WM_QUIT消息 for (;;) { // 阶段一:检测是否可以执行一些空闲操作 while (bIdle && !::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)) { if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // 阶段二:执行消息循环 do { // 处理消息转发和响应 if (!PumpMessage()) return ExitInstance(); //重置 bIdle 状态 if (IsIdleMessage(&(pState->m_msgCur))) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)); } }
对比一下win32窗口消息的循环:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
for( ;; )
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
return TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
BackgroundProcessing();
}
PeekMesssage 与 GetMessage
GetMessage一直等待到有窗口消息才会返回,会阻塞线程;
PeekMessage等待一段时间,没有窗口消息则返回FALSE。
PeekMessage可以用于一些后台进程,或没有消息需要处理时执行其他操作(如MFC的CWinThread::OnIdle中执行内存堆块检测,更新控件状态)。
PeekMessage 的 PM_REMOVE 和 PM_NOREMOVE 参数
PM_NOREMOVE:PeekMessage处理后,消息不从队列里除掉;
PM_REMOVE:PeekMessage处理后,消息从队列里除掉。