MFC 應用程式啟動後,首要任務是註冊視窗類別。MFC 內建多種預定義視窗類別 (子視窗、OLE 控件、MDI/SDI 框架、分割視窗等),由 AfxEndDeferRegisterClass 統一註冊。本文詳細剖析其原始碼邏輯、AfxRegisterClass 封裝、以及 CMainFrame::PreCreateWindow 如何觸發註冊。
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS));
wndcls.lpfnWndProc = DefWindowProc; // 預設視窗程序
wndcls.hInstance = AfxGetInstanceHandle(); // 當前模組實例
wndcls.hCursor = afxData.hcurArrow; // 預設箭頭游標
// ... hbrBackground, lpszMenuName 等稍後依類別設定
// 位元旗標決定註冊哪些類別
if (fToRegister & AFX_WND_REG) {
// 一般子視窗:無背景筆刷、無圖示、雙擊/重繪樣式
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWnd; // "Afx:xxxxxx:xxxxx"
if (AfxRegisterClass(&wndcls)) fRegisteredClasses |= AFX_WND_REG;
}
if (fToRegister & AFX_WNDOLECONTROL_REG) {
// OLE 控件:使用父 DC 加速
wndcls.style |= CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWndOleControl;
if (AfxRegisterClass(&wndcls)) fRegisteredClasses |= AFX_WNDOLECONTROL_REG;
}
if (fToRegister & AFX_WNDMDIFRAME_REG) {
// MDI 框架 / 分割視窗:無背景筆刷,僅雙擊
wndcls.style = CS_DBLCLKS;
wndcls.hbrBackground = NULL;
if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
}
if (fToRegister & AFX_WNDFRAMEORVIEW_REG) {
// SDI 框架 / MDI 子視窗 / View:系統視窗背景色
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
// ... 其他類別 (OleDocIPFrame 等)
return TRUE;
}
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
WNDCLASS wndcls;
// 1. 若已註冊同名類別,直接成功
if (GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName, &wndcls))
return TRUE;
// 2. 呼叫 Win32 API 註冊
if (!::RegisterClass(lpWndClass)) {
TRACE1("Can't register window class named %s\n", lpWndClass->lpszClassName);
return FALSE;
}
// 3. DLL 專案需記錄以便卸載時反註冊
if (afxContextIsDLL) {
AfxLockGlobals(CRIT_REGCLASSLIST);
// 將類別名追加至模組狀態的 m_szUnregisterList
// ... 字串操作 ...
AfxUnlockGlobals(CRIT_REGCLASSLIST);
}
return TRUE;
}
// AFXIMPL.H
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
CFrameWnd::PreCreateWindow 中呼叫:
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL) {
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
cs.lpszClass = _afxWndFrameOrView; // 使用系統視窗背景色的類別
}
// ... 樣式微調 ...
return TRUE;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CFrameWnd::PreCreateWindow(cs)) return FALSE;
// TODO: 修改 cs.style, cs.lpszClass 等
return TRUE;
}
執行順序:WinMain → AfxWinMain → CWinApp::InitInstance → CFrameWnd::Create → CWnd::CreateEx → PreCreateWindow (CMainFrame → CFrameWnd) → AfxDeferRegisterClass → 註冊類別。
| 旗標常數 | 類別名稱變數 | 用途 | 關鍵樣式/背景 |
|---|---|---|---|
| AFX_WND_REG | _afxWnd | 一般子視窗 | CS_DBLCLKS\|CS_HREDRAW\|CS_VREDRAW, 無背景 |
| AFX_WNDOLECONTROL_REG | _afxWndOleControl | OLE 控件 | + CS_PARENTDC |
| AFX_WNDMDIFRAME_REG | _afxWndMDIFrame | MDI 框架/分割視窗 | CS_DBLCLKS, 無背景, 標準圖示 |
| AFX_WNDFRAMEORVIEW_REG | _afxWndFrameOrView | SDI 框架/MDI 子視窗/View | CS_DBLCLKS\|CS_HREDRAW\|CS_VREDRAW, COLOR_WINDOW+1 |
CMainFrame::PreCreateWindow 設中斷點,F5 執行,呼叫堆疊將顯示完整註冊路徑。Afx:xxxxxx:xxxxxx:xxxxxx 格式 (即 _afxWndFrameOrView 實際名稱)。CMainFrame::PreCreateWindow 中自行 AfxRegisterClass 並指派 cs.lpszClass。MFC 透過 AfxEndDeferRegisterClass 延遲註冊,按需載入預定義視窗類別。CFrameWnd::PreCreateWindow 為 SDI/MDI 框架選擇 _afxWndFrameOrView,開發者可在衍生類 PreCreateWindow 中攔截修改。理解此機制,有助於解決視窗類別衝突、自訂背景繪製、DLL 視窗類別隔離等進階問題。