在上一篇《明明白白看 MFC 之程式框架(一)》中,我們已分析過經典 SDK 應用程式的結構。本文將正式進入 MFC 應用程式架構的核心——以最經典的 SDI(單文檔介面)Hello World 為例,剖析 MFC 如何封裝 Windows 程式的啟動與運行流程。
使用 AppWizard 生成 SDI 專案後,會產生多個檔案。為了理清主流程,我們暫且忽略檔案劃分,直接提取關鍵原始碼進行分析(原始 MFC 產生的程式碼閱讀性較差,此處已重新整理排版)。
CHelloWorldApp theApp;
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX 內部初始化
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// 應用程式全域初始化(少用)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// 執行具體初始化
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
// ...
AfxWinTerm();
return nReturnCode;
}
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;
return TRUE;
}
BOOL CHelloWorldApp::InitInstance()
{
AfxEnableControlContainer();
// ...
// 修改註冊表鍵值以儲存設定
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(); // 載入標準 INI 選項(含 MRU)
// 註冊文檔模板:連接文檔、框架視窗與視圖
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloWorldDoc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架視窗
RUNTIME_CLASS(CHelloWorldView));
AddDocTemplate(pDocTemplate);
// 解析命令列參數
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 分派命令列指令
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 唯一視窗已初始化,顯示並更新
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
BOOL CWinApp::InitInstance()
{
return TRUE;
}
乍看之下,這段程式碼似乎無從下手,甚至找不到傳統意義上的 main() 入口點。實則 AfxWinMain() 正是 MFC 程式的入口點。其執行路徑如下:
AfxWinMain()AfxGetThread() 取得執行緒指標AfxGetApp() 取得應用程式物件指標AfxWinInit() 進行 MFC 內部初始化pApp->InitApplication() 應用程式層級初始化pThread->InitInstance() 執行個體初始化(關鍵步驟)pThread->Run() 進入訊息迴圈(對應 SDK 的 GetMessage/DispatchMessage 迴圈)程式啟動時,首先定義全域變數 theApp 代表整個應用程式的存在。AfxWinMain() 透過 AfxGetApp() 取得 CWinApp 衍生類別指標,進而呼叫初始化與啟動相關成員函式,最後進入 Run() 訊息迴圈。
你可能會疑惑:「Hello World」字串何時輸出? 這並非疏忽,而是 MFC 採用全新的訊息處理機制(相對於 SDK),其繪圖與輸出邏輯封裝在文檔/視圖架構中。這部分涉及 MFC 文件組織與類別間的呼叫關係,將在下一篇詳細剖析。
學習 MFC 是漫長過程,建立宏觀架構認識至關重要。下一篇將深入分析 MFC 產生的主要檔案與類別關係,敬請期待。