首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

MFC 窗口類註冊機制深度解析 (下):從 AfxDeferRegisterClass 到自定義窗口類

     編輯:VC 教學

前言

承接上篇,本文深入 MFC 源碼核心,揭示 AfxDeferRegisterClass 延遲註冊機制、PreCreateWindow 虛函數鏈,並指導如何在派生類中安全註冊自定義窗口類。

一、AfxDeferRegisterClass:延遲註冊的智慧

MFC 採用「按需註冊」策略,避免啟動時註冊所有預設類造成資源浪費。

核心宏與變量

#define AfxDeferRegisterClass(fClass) \
    ((afxRegisteredClasses & fClass) ? TRUE : AfxEndDeferRegisterClass(fClass))

#define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses
  • afxRegisteredClasses:位標記錄已註冊類別,定義於 AFX_MODULE_STATE。
  • 若目標類別位已置 1,直接返回 TRUE;否則調用 AfxEndDeferRegisterClass 實際註冊。

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 兼容。

二、窗口創建鏈:CreateEx → PreCreateWindow → 註冊

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);
    // ... 後續附加處理 ...
}

PreCreateWindow:派生類注入屬性的關鍵點

基類 CWnd::PreCreateWindow 提供默認註冊邏輯:

BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs) {
    if (cs.lpszClass == NULL) {
        AfxDeferRegisterClass(AFX_WND_REG);
        cs.lpszClass = _afxWnd;
    }
    return TRUE;
}

各派生類的差異化實現

類別註冊類別標誌類別名稱適用場景
CFrameWndAFX_WNDFRAMEORVIEW_REG_afxWndFrameOrViewSDI 主框架
CMDIFrameWndAFX_WNDMDIFRAME_REG_afxWndMDIFrameMDI 主框架 (帶圖標)
CMDIChildWnd繼承 CFrameWnd_afxWndFrameOrViewMDI 子視窗
CViewAFX_WNDFRAMEORVIEW_REG_afxWndFrameOrView視圖類
CControlBarAFX_WNDCONTROLBAR_REG_afxWndControlBar工具欄/狀態欄

三、實戰:在 MFC 中註冊你的專屬窗口類

若需自定義類風格 (CS_DBLCLKS)、類額外內存、專屬圖標/游標、背景刷等,必須在派生類的 PreCreateWindow 中完成。

標準範例:CMyWnd 派生自 CWnd

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;
}

進階:利用 cbWndExtra 綁定 this 指針 (無 Thunk 方案)

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 技術 (見專題文章)。

四、常見錯誤與調試技巧

  • 重複註冊:同一類名在同一模組多次註冊會失敗 (ERROR_CLASS_ALREADY_EXISTS)。使用靜態變量或 AfxDeferRegisterClass 模式保證單次註冊。
  • 類名衝突:避免使用系統保留前綴 (如 "MFC", "Windows"),建議加項目前綴 (如 "MyApp_CustomView")。
  • Unicode/ANSI 不匹配:統一使用 WNDCLASS (配合 RegisterClass) 或 WNDCLASSEX (配合 RegisterClassEx),MFC AfxRegisterClass 內部已處理 TCHAR 映射。
  • 調試斷點:在 AfxEndDeferRegisterClass 設置條件斷點,觀察 fClass 位標變化,理清註冊順序。

結語

MFC 窗口類註冊體系以 AfxDeferRegisterClass 為核心,配合 PreCreateWindow 虛函數鏈,實現了標準類的延遲註冊與派生類的靈活定制。掌握此機制,不僅能解決自定義類風格、圖標、背景等需求,更能深入理解 MFC 如何封裝 Windows 核心 API,為進階 UI 開發 (如 Thunk、Subclassing、Layered Window) 奠定堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved