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

MFC 核心機制淺析:CObject 執行期類型資訊與動態建立

     編輯:關於VC++

前言

CObject 是 MFC 類別階層的根基,超過 90% 的 MFC 類別直接或間接繼承自它。它不僅提供虛擬解構子、序列化介面,更奠定了 MFC 獨特的執行期類型識別(RTTI)、動態建立與序列化三大核心機制。本文將由原始碼角度,剖析 CRuntimeClass 結構與 DECLARE_/IMPLEMENT_ 宏族如何協作實現這些功能。

一、CObject 關鍵宣告概覽

class CObject
{
public:
    virtual CRuntimeClass* GetRuntimeClass() const;
    virtual ~CObject();

    // 記憶體管理
    void* PASCAL operator new(size_t nSize);
    void* PASCAL operator new(size_t, void* p);  // placement new
    void PASCAL operator delete(void* p);
    void PASCAL operator delete(void* p, void* pPlace);

#if defined(_DEBUG)
    void* PASCAL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
    void PASCAL operator delete(void *p, LPCSTR lpszFileName, int nLine);
#endif

protected:
    CObject();                      // 僅允許衍生類建構
private:
    CObject(const CObject&);        // 禁止複製
    void operator=(const CObject&); // 禁止指派

// Attributes
public:
    BOOL IsSerializable() const;
    BOOL IsKindOf(const CRuntimeClass* pClass) const;

// Overridables
    virtual void Serialize(CArchive& ar);

#if defined(_DEBUG)
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

public:
    static const AFX_DATA CRuntimeClass classCObject;
    static CRuntimeClass* PASCAL _GetBaseClass();
};

關鍵設計點:

  • 受保護建構子:強制堆積配置,配合 DECLARE_DYNCREATE 實現動態建立。
  • 禁止複製/指派:避免指標語意混淆,衍生類若需複製需自行實作。
  • 靜態 classCObject:每個具體類別擁有唯一的 CRuntimeClass 實例,構成單向鏈表(m_pNextClass)。

二、CRuntimeClass:類別的「身分證」

struct CRuntimeClass
{
    LPCSTR m_lpszClassName;           // 類別名稱字串
    int    m_nObjectSize;             // 物件大小
    UINT   m_wSchema;                 // 序列化版本號
    CObject* (PASCAL* m_pfnCreateObject)(); // 動態建立函式指標,NULL=抽象類

    // 基類鏈結:DLL 與靜態連結不同策略
#ifdef _AFXDLL
    CRuntimeClass* (PASCAL* m_pfnGetBaseClass)(); // 延遲綁定
#else
    CRuntimeClass* m_pBaseClass;      // 直接指標
#endif

    // Operations
    CObject* CreateObject();          // 呼叫 m_pfnCreateObject
    BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; // 繼承關係檢查
    void Store(CArchive& ar) const;   // 序列化寫入類別資訊
    static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum); // 讀取

    CRuntimeClass* m_pNextClass;      // 全域註冊鏈表
};

三、RUNTIME_CLASS 宏:取得類別資訊指標

#define RUNTIME_CLASS(class_name) \
    ((CRuntimeClass*)(&class_name::class##class_name))

利用 Token Pasting(##)構造靜態成員變數名 class<ClassName>,返回其位址。編譯期解析,零執行期成本。

四、三層支援機制:DYNAMIC / DYNCREATE / SERIAL

1. DECLARE_DYNAMIC / IMPLEMENT_DYNAMIC —— 基礎 RTTI

宣告端(置於類別定義內):

#define DECLARE_DYNAMIC(class_name) \
protected: \
    static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
    static const AFX_DATA CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const;

實作端(置於 .cpp):

#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
    IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)

#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
    CRuntimeClass* PASCAL class_name::_GetBaseClass() \
    { return RUNTIME_CLASS(base_class_name); } \
    AFX_COMDAT const AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
        #class_name, sizeof(class class_name), wSchema, pfnNew, \
        &class_name::_GetBaseClass, NULL }; \
    CRuntimeClass* class_name::GetRuntimeClass() const \
    { return RUNTIME_CLASS(class_name); }

效果:擁有 GetRuntimeClass()、IsKindOf()、RUNTIME_CLASS() 三大 RTTI 能力。

2. DECLARE_DYNCREATE / IMPLEMENT_DYNCREATE —— 動態建立

在 DYNAMIC 基礎上新增 CreateObject() 靜態函式:

#define DECLARE_DYNCREATE(class_name) \
    DECLARE_DYNAMIC(class_name) \
    static CObject* PASCAL CreateObject();

#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
    CObject* PASCAL class_name::CreateObject() \
    { return new class_name; } \
    IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
                           class_name::CreateObject)

pfnCreateObject 指向 CreateObject,使 CRuntimeClass::CreateObject() 可實例化物件,配合 CArchive::ReadObject 實現「讀取類別資訊 → 動態建立 → 呼叫 Serialize」流程。

3. DECLARE_SERIAL / IMPLEMENT_SERIAL —— 完整序列化

#define DECLARE_SERIAL(class_name) \
    _DECLARE_DYNCREATE(class_name) \
    AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name*& pOb);

#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
    CObject* PASCAL class_name::CreateObject() \
    { return new class_name; } \
    _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
                            class_name::CreateObject) \
    AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
    CArchive& AFXAPI operator>>(CArchive& ar, class_name*& pOb) \
    { pOb = (class_name*)ar.ReadObject(RUNTIME_CLASS(class_name)); return ar; }

關鍵差異:

  • wSchema 由使用者指定(而非 0xFFFF),支援版本控制。
  • AFX_CLASSINIT 全域物件建構時自動呼叫 CRuntimeClass::Load 註冊,確保 ReadObject 能找到類別。
  • 提供 operator>> 重載,支援 ar >> pObj 語法直接還原指標。

五、運作流程圖解

  1. 寫入:ar << pObj → WriteObject → WriteClass(存 CRuntimeClass) → pObj->Serialize(ar)。
  2. 讀取:ar >> pObj → ReadObject → ReadClass(載入 CRuntimeClass) → pClass->CreateObject() → pObj->Serialize(ar)。

六、常見坑點與最佳實務

  • 忘寫 IMPLEMENT_*:連結錯誤 LNK2001,缺少靜態成員定義。
  • 抽象類別誤用 DYNCREATE:pfnCreateObject = NULL,CreateObject 返回 NULL,導致 ReadObject 拋出 CMemoryException。
  • Schema 衝突:版本號不符且未加 VERSIONABLE_SCHEMA,拋出 CArchiveException::badSchema。
  • 多重繼承:MFC RTTI 僅支援單一繼承鏈,IsKindOf 只能檢查線性基類。

總結

MFC 以 CRuntimeClass 為核心,透過宏展開在編譯期為每個類別生成「類別描述元」,配合虛擬函式表與靜態鏈表,在無標準 RTTI 支援的早期編譯器環境下,構建了一套完整的動態型別系統。理解其運作細節,對於除錯序列化錯誤、擴充框架功能、整合第三方元件都極具價值。

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