MFC文檔/視圖架構中,框架視窗、視圖、文檔等核心物件皆由文檔模板動態建立。初學者常困惑:「明明沒寫CFrameWnd myFrame;,視窗怎麼彈出來的?」
關鍵在於:MFC不知道開發者會如何衍生這些類別,無法靜態實例化。必須在執行期根據衍生類別資訊動態創建對應物件。
struct CRuntimeClass {
LPCSTR m_lpszClassName; // 類別名稱
CObject* (PASCAL *m_pfnCreateObject)(); // 建立物件的函數指標
CRuntimeClass* m_pBaseClass; // 基類別RuntimeClass (支援RTTI)
CRuntimeClass* m_pNextClass; // 單向鏈表指標 (遍歷所有動態類別)
CObject* CreateObject(); // 呼叫m_pfnCreateObject建立物件
static CRuntimeClass* PASCAL Load(); // 依類別名稱查找鏈表
static CRuntimeClass* pFirstClass; // 鏈表頭指標
};
m_pfnCreateObject指向靜態函數CreateObject(),回傳new DerivedClass。每個衍生類別擁有獨立的CRuntimeClass實例,透過m_pNextClass串成單向鏈表。
DECLARE_DYNCREATE(ClassName):類別宣告中使用,宣告靜態CRuntimeClass成員與CreateObjectIMPLEMENT_DYNCREATE(ClassName, BaseClass):實作檔中使用,定義CRuntimeClass實例並插入鏈表頭
// 簡化版邏輯
CRuntimeClass* pNewClass = &ClassName::classClassName; // 靜態實例
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass; // 新節點指向舊頭
CRuntimeClass::pFirstClass = pNewClass; // 更新頭指標
註:DYNCREATE宏包含DYNAMIC (RTTI) 功能,為增強版。
pFirstClass遍歷鏈表,strcmp比對m_lpszClassNameCRuntimeClass*m_pfnCreateObject != NULL(*m_pfnCreateObject)()建立物件,回傳CObject*
// 查找類別
CRuntimeClass* PASCAL CRuntimeClass::Load() {
char szClass[64];
cin >> szClass; // 假設取得類別名稱
for (CRuntimeClass* p = pFirstClass; p; p = p->m_pNextClass) {
if (strcmp(szClass, p->m_lpszClassName) == 0) return p;
}
return NULL;
}
// 建立物件
CObject* CRuntimeClass::CreateObject() {
if (!m_pfnCreateObject) return NULL;
return (*m_pfnCreateObject)(); // 函數指標呼叫
}
CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc), // 取得CRuntimeClass*
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CMyView)
);
RUNTIME_CLASS(ClassName)宏展開為((CRuntimeClass*)(&ClassName::classClassName)),直接取得靜態實例位址。
CRuntimeClass* pClass = RUNTIME_CLASS(CMyView);
CObject* pObj = pClass->CreateObject(); // 動態建立CMyView實例
CMyView* pView = static_cast(pObj);
CreateObject內部new ClassName()不帶參數MFC類別階層是樹狀結構。m_pBaseClass僅向上追溯基類別,無法遍歷同層或其他分支。動態建立需掃描所有註冊類別,故必須有單向鏈表m_pNextClass連接所有CRuntimeClass節點,從pFirstClass遍歷至尾端 (NULL)。
MFC動態建立機制核心在於:CRuntimeClass單向鏈表 + 函數指標工廠模式 + 雙宏註冊。將類別中繼資料 (名稱、建立函數、繼承關係) 登記在全域鏈表,執行期透過名稱查找並呼叫對應工廠函數實例化。這種設計完美解決「框架不知衍生類別細節」的逆向控制問題,是MFC文檔/視圖架構運作的基石。