很神奇吧, ar是怎麽根據文件(強調壹下,是根據文件,而不是硬編碼)判斷需要創建什麽類的.
它大概有這麽幾個步驟:
1. 因為DECLARE_SERIAL重載了>>操作符,所以可以保證是調用CMessg類的>>函數.
2. >>函數實際上調用的是ar的ReadObject(CRuntimeClass*)函數
3. ReadObject首先從文件中讀取類判斷信息(可能是壹個字符串,可能是壹個類索引),得到Class對應的ClassName;
4. 程序的模塊狀態中有所有的RuntimeClass的列表,因此,查找對應的程序支持的RuntimeClass(對比ClassName),獲得對應的RuntimeClass;
5. RuntimeClass中含有創建對象的方法CreateObject,調用它,創建對應的對象.這裏,因為CreateObject實際就是 New 壹個對象,類似 new CMessg; 所以,為了支持序列化,必須有沒有參數的構造函數.
6. 創建對象之後,調用Seralize(ar),讀入真正的對象的信息.
7. 將對象的指針返回.
8. pMessg就指向壹個對應的對象了. MFC 六大關鍵技術之仿真
DECLARE_SERIAL / IMPLEMENT_SERIAL 宏 要將<< 和>> 兩個運算子多載化,還要讓Serialize 函數神不知鬼不覺地放入類別聲明
之中,最好的作法仍然是使用宏。
類別之能夠進行文件讀寫動作,前提是擁有動態生成的能力,所以,MFC 設計了兩個宏
DECLARE_SERIAL 和IMPLEMENT_SERIAL:
#define DECLARE_SERIAL(class_name) \
DECLARE_DYNCREATE(class_name) \
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) \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
為了在每壹個對象被處理(讀或寫)之前,能夠處理瑣屑的工作,諸如判斷是否第壹次
出現、記錄版本號碼、記錄文件名等工作,CRuntimeClass 需要兩個函數Load 和Store
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
CObject* CreateObject();
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes
};
妳已經在上壹節看過Load 函數,當時為了簡化,我把它的參數拿掉,改為由屏幕上獲
得類別名稱,事實上它應該是從文件中讀壹個類別名稱。至於Store 函數,是把類別名
稱寫入文件中:
// Runtime class serialization code
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
{
WORD nLen;
char szClassName[64];
CRuntimeClass* pClass;
ar >> (WORD&)(*pwSchemaNum) >> nLen;
if (nLen >= sizeof(szClassName) || ar.Read(szClassName, nLen) != nLen)
return NULL;
szClassName[nLen] = ~\0~;
for (pClass = pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (lstrcmp(szClassName, pClass->m_lpszClassName) == 0)
return pClass;
}
return NULL; // not found
}
void CRuntimeClass::Store(CArchive& ar) const
// stores a runtime class description
{
WORD nLen = (WORD)lstrlenA(m_lpszClassName);
ar << (WORD)m_wSchema << nLen;
ar.Write(m_lpszClassName, nLen*sizeof(char));
}
class CScribDoc : public CDocument
{
DECLARE_DYNCREATE(CScribDoc)
...
};
class CStroke : public CObject
{
DECLARE_SERIAL(CStroke)
public:
void Serialize(CArchive&);
...
};
class CRectangle : public CObject
{
DECLARE_SERIAL(CRectangle)
public:
void Serialize(CArchive&);
...
};
class CCircle : public CObject
{
DECLARE_SERIAL(CCircle)
public:
void Serialize(CArchive&);
...
};
以及在.CPP 檔中做這樣的動作:
IMPLEMENT_DYNCREATE(CScribDoc, CDocument)
IMPLEMENT_SERIAL(CStroke, CObject, 2)
IMPLEMENT_SERIAL(CRectangle, CObject, 1)
IMPLEMENT_SERIAL(CCircle, CObject, 1)
然後呢?分頭設計CStroke、CRectangle 和CCircle 的Serialize 函數吧。
當然,毫不令人意外地,MFC 源代碼中的CObList 和CDWordArray 有這樣的內容:
// in header files
class CDWordArray : public CObject
{
DECLARE_SERIAL(CDWordArray)
public:
void Serialize(CArchive&);
...
};
class CObList : public CObject
{
DECLARE_SERIAL(CObList)
public:
void Serialize(CArchive&);
...
};
// in implementation files
IMPLEMENT_SERIAL(CObList, CObject, 0)
IMPLEMENT_SERIAL(CDWordArray, CObject, 0)
而CObject 也多了壹個虛擬函數Serialize:
class CObject
{
public:
virtual void Serialize(CArchive& ar);
...
}