承接上篇,本文深入 MFC 源碼核心,揭示 AfxDeferRegisterClass 延遲註冊機制、PreCreateWindow 虛函數鏈,並指導如何在派生類中安全註冊自定義窗口類。
MFC 採用「按需註冊」策略,避免啟動時註冊所有預設類造成資源浪費。
#define AfxDeferRegisterClass(fClass) \
((afxRegisteredClasses & fClass) ? TRUE : AfxEndDeferRegisterClass(fClass))
#define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses
afxRegisteredClasses:位標記錄已註冊類別,定義於 AFX_MODULE_STATE。TRUE;否則調用 AfxEndDeferRegisterClass 實際註冊。BOOL AFXAPI AfxEndDeferRegisterClass(short fClass) {
WNDCLASS wndCls = {0};
wndCls.lpfnWndProc = DefWindowProc; // 默認消息處理
if (fClass & AFX_WND_REG) {
wndCls.lpszClassName = _afxWnd; // "MFCWindow"
AfxRegisterClass(&wndCls);
} else if (fClass & AFX_WNDOLECONTROL_REG) {
wndCls.lpszClassName = _afxWndOleControl;
AfxRegisterClass(&wndCls);
} else if (fClass & AFX_WNDCONTROLBAR_REG) {
wndCls.lpszClassName = _afxWndControlBar;
AfxRegisterClass(&wndCls);
} else if (fClass & AFX_WNDMDIFRAME_REG) {
RegisterWithIcon(&wndCls, _afxWndMDIFrame, AFX_IDI_MDIFRAME);
} else if (fClass & AFX_WNDFRAMEORVIEW_REG) {
RegisterWithIcon(&wndCls, _afxWndFrameOrView, AFX_IDI_STD_FRAME);
} else if (fClass & AFX_WNDCOMMCTLS_REG) {
InitCommonControls(); // 系統公共控制項類
}
return TRUE;
}
註:RegisterWithIcon 內部最終調用 AfxRegisterClass,後者封裝了 RegisterClassEx 並處理 Unicode/ANSI 兼容。
CWnd::CreateEx 是所有窗口創建的總入口:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, ...)
{
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
// ... 填充 cs 其他成員 ...
PreCreateWindow(cs); // 關鍵虛函數調用
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, ..., cs.lpCreateParams);
// ... 後續附加處理 ...
}
基類 CWnd::PreCreateWindow 提供默認註冊邏輯:
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs) {
if (cs.lpszClass == NULL) {
AfxDeferRegisterClass(AFX_WND_REG);
cs.lpszClass = _afxWnd;
}
return TRUE;
}
| 類別 | 註冊類別標誌 | 類別名稱 | 適用場景 |
|---|---|---|---|
| CFrameWnd | AFX_WNDFRAMEORVIEW_REG | _afxWndFrameOrView | SDI 主框架 |
| CMDIFrameWnd | AFX_WNDMDIFRAME_REG | _afxWndMDIFrame | MDI 主框架 (帶圖標) |
| CMDIChildWnd | 繼承 CFrameWnd | _afxWndFrameOrView | MDI 子視窗 |
| CView | AFX_WNDFRAMEORVIEW_REG | _afxWndFrameOrView | 視圖類 |
| CControlBar | AFX_WNDCONTROLBAR_REG | _afxWndControlBar | 工具欄/狀態欄 |
若需自定義類風格 (CS_DBLCLKS)、類額外內存、專屬圖標/游標、背景刷等,必須在派生類的 PreCreateWindow 中完成。
class CMyWnd : public CWnd {
DECLARE_DYNAMIC(CMyWnd)
public:
CMyWnd();
virtual ~CMyWnd();
protected:
// 重寫 PreCreateWindow
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
DECLARE_MESSAGE_MAP()
};
BOOL CMyWnd::PreCreateWindow(CREATESTRUCT& cs) {
// 1. 必須先調用基類版本 (可選,視需求)
// if (!CWnd::PreCreateWindow(cs)) return FALSE;
// 2. 註冊自定義窗口類
static const TCHAR* pszMyClass = _T("MyCustomWndClass");
WNDCLASS wc = {0};
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; // 自定義類風格
wc.lpfnWndProc = ::DefWindowProc; // 或 AfxWndProc (MFC 內部)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0; // 若需綁定 this 指針,見下文 thunk 技術
wc.hInstance = AfxGetInstanceHandle();
wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_MYICON));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = pszMyClass;
if (!AfxRegisterClass(&wc)) {
TRACE0("自定義窗口類註冊失敗\n");
return FALSE;
}
// 3. 將類名寫入 CREATESTRUCT
cs.lpszClass = pszMyClass;
// 4. 可選:修改其他創建參數
cs.style |= WS_CLIPCHILDREN;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
wc.cbWndExtra = sizeof(CMyWnd*); // 預留指針大小空間
// 創建窗口後,在 WM_NCCREATE 或 OnCreate 中:
// SetWindowLongPtr(hWnd, 0, (LONG_PTR)this);
// 靜態消息處理函數中:
// CMyWnd* pThis = (CMyWnd*)GetWindowLongPtr(hWnd, 0);
缺點:佔用 GWL_USERDATA (索引 0),與 MFC 內部 AfxHookWindowCreate 衝突,可能導致消息映射失效。推薦使用 Thunk 技術 (見專題文章)。
AfxDeferRegisterClass 模式保證單次註冊。WNDCLASS (配合 RegisterClass) 或 WNDCLASSEX (配合 RegisterClassEx),MFC AfxRegisterClass 內部已處理 TCHAR 映射。AfxEndDeferRegisterClass 設置條件斷點,觀察 fClass 位標變化,理清註冊順序。MFC 窗口類註冊體系以 AfxDeferRegisterClass 為核心,配合 PreCreateWindow 虛函數鏈,實現了標準類的延遲註冊與派生類的靈活定制。掌握此機制,不僅能解決自定義類風格、圖標、背景等需求,更能深入理解 MFC 如何封裝 Windows 核心 API,為進階 UI 開發 (如 Thunk、Subclassing、Layered Window) 奠定堅實基礎。