CObject 是 MFC 類別階層的根基,超過 90% 的 MFC 類別直接或間接繼承自它。它不僅提供虛擬解構子、序列化介面,更奠定了 MFC 獨特的執行期類型識別(RTTI)、動態建立與序列化三大核心機制。本文將由原始碼角度,剖析 CRuntimeClass 結構與 DECLARE_/IMPLEMENT_ 宏族如何協作實現這些功能。
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 實現動態建立。CRuntimeClass 實例,構成單向鏈表(m_pNextClass)。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; // 全域註冊鏈表
};
#define RUNTIME_CLASS(class_name) \
((CRuntimeClass*)(&class_name::class##class_name))
利用 Token Pasting(##)構造靜態成員變數名 class<ClassName>,返回其位址。編譯期解析,零執行期成本。
宣告端(置於類別定義內):
#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 能力。
在 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」流程。
#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 語法直接還原指標。ar << pObj → WriteObject → WriteClass(存 CRuntimeClass) → pObj->Serialize(ar)。ar >> pObj → ReadObject → ReadClass(載入 CRuntimeClass) → pClass->CreateObject() → pObj->Serialize(ar)。pfnCreateObject = NULL,CreateObject 返回 NULL,導致 ReadObject 拋出 CMemoryException。VERSIONABLE_SCHEMA,拋出 CArchiveException::badSchema。IsKindOf 只能檢查線性基類。MFC 以 CRuntimeClass 為核心,透過宏展開在編譯期為每個類別生成「類別描述元」,配合虛擬函式表與靜態鏈表,在無標準 RTTI 支援的早期編譯器環境下,構建了一套完整的動態型別系統。理解其運作細節,對於除錯序列化錯誤、擴充框架功能、整合第三方元件都極具價值。