MFC序列化機(jī)制中的一個(gè)潛在的錯(cuò)誤
MFC在IMPLEMENT_SERIAL使用VERSIONABLE_SCHEMA|schema number來(lái)提供文件格式的向后兼容.即新的程序既可以讀新的文件格式,也可以讀較老的文件格式.當(dāng)它發(fā)現(xiàn)要被反序列化的CObject派生類對(duì)象內(nèi)容的shema和程序該類的schema不一致時(shí),如果該類是VERSIONABLE_SCHEMA的(這個(gè)信息記錄在該類所對(duì)應(yīng)的CRuntimeClass中),那么它仍然調(diào)用該類的序列化函數(shù);否則終止反序列化過(guò)程,拋出CArchiveException異常,最終向用戶報(bào)告文件格式錯(cuò)誤.
如果一個(gè)類是VERSIONABLE_SCHEMA的,那么該類的反序列化必須根據(jù)不同的schma number來(lái)進(jìn)行.就在這里,微軟犯了一個(gè)潛在的錯(cuò)誤,微軟對(duì)于一個(gè)對(duì)象只保存一個(gè)schema number[對(duì)象所屬類的],這是不夠的.因?yàn)橐粋€(gè)對(duì)象從CObject到對(duì)象的實(shí)際類型可能不止一級(jí),而每一級(jí)的schema number都有變化的可能.當(dāng)中間層次的類發(fā)生變化時(shí),VERSIONABLE_SCHEMA機(jī)制就失效了.
解決這個(gè)問(wèn)題的辦法就是自己在每個(gè)類層次里序列化類的版本號(hào),根據(jù)該版本號(hào)進(jìn)行反序列化,完全忽略MFC的VERSIONABLE_SCHEMA機(jī)制.
當(dāng)然,這個(gè)問(wèn)題不可能在MFC框架內(nèi)得到解決,我覺(jué)得MFC開(kāi)發(fā)小組可以有兩個(gè)辦法:一是完全讓用戶負(fù)責(zé)文件的向后兼容;一是在MSDN里說(shuō)明這個(gè)潛在的問(wèn)題,引起程序員的注意.
------------------------------------------------------------------------------------------------------
MFC源碼:
#define DECLARE_DYNAMIC(class_name) \
public: \
static const CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \#define _DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
// not serializable, but dynamically constructable
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
#define _DECLARE_DYNCREATE(class_name) \
_DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); }
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
AFX_COMDAT CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); }
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)
#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, NULL)
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
extern AFX_CLASSINIT _init_##class_name; \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject, &_init_##class_name) \
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; }
以下是我(fyl)對(duì)這些宏的理解:
IMPLEMENT_RUNTIMECLASS中定義了靜態(tài)CRuntimeClass對(duì)象,并賦予當(dāng)前類與基類的信息 ,當(dāng)前類類名及版本號(hào)wSchema,動(dòng)態(tài)創(chuàng)建當(dāng)前類對(duì)象的函數(shù)地址CreateObject等。
它實(shí)現(xiàn)了基類與派生類之間的連接,在MFC類層次之間形成了樹(shù)形層次的結(jié)構(gòu),所以可以在此基礎(chǔ)上實(shí)現(xiàn)動(dòng)態(tài)類 型的識(shí)別IsKindOf:
while (pClassThis != NULL)
{
if (pClassThis == pBaseClass)
return TRUE;
if (pClassThis->m_pfnGetBaseClass == NULL)
return FALSE;
pClassThis = (*pClassThis->m_pfnGetBaseClass)();
}
DECLARE_DYNAMIC僅提供動(dòng)態(tài)類型的識(shí)別,只需要基類與派生類之間的連接,不需要其它信息,所以IMPLEMENT_DYNAMIC調(diào)用的IMPLEMENT_RUNTIMECLASS版本號(hào):0xFFFF(表示無(wú)效), 動(dòng)態(tài)創(chuàng)建當(dāng)前類對(duì)象的函數(shù)地址:NULL。
DECLARE_DYNCREATE增加了動(dòng)態(tài)創(chuàng)建,所以IMPLEMENT_DYNCREATE增加了動(dòng)態(tài)創(chuàng)建當(dāng)前類對(duì)象的函數(shù)CreateObject,并傳遞給IMPLEMENT_RUNTIMECLASS。
DECLARE_SERIAL實(shí)現(xiàn)串行化(保存與讀?。?不同版本的保存與讀取是可能存在不同的,所以需要版本號(hào),所以IMPLEMENT_SERIAL增加了版本號(hào)傳遞;
另外,在讀取時(shí),讀到class信息時(shí)需要判斷是那個(gè)類后才可以動(dòng)態(tài)生成,所以IMPLEMENT_SERIAL中增加了AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name))。
AFX_CLASSINIT是結(jié)構(gòu)的定義:
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass); };
僅有一個(gè)構(gòu)造函數(shù),定義如下:
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
此構(gòu)造函數(shù)負(fù)責(zé) linked list 的串接工作。在MFC類中依靠AFX_CLASSINIT在原有樹(shù)形的結(jié)構(gòu)上增加了鏈表,鏈表將所有包含*_SERIAL對(duì)的MFC類串接后形成一個(gè)表。所以讀到class信息時(shí)在此鏈表查詢即可!
問(wèn)題1(主要問(wèn)題):
*_SERIAL中增加了friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
但我沒(méi)有使用它時(shí)也可以串行化!
CTrip是可串行化的類.操作如下:
頭文件中用_DECLARE_DYNCREATE(CTrip)替換DECLARE_SERIAL
源文件中用CObject* PASCAL CTrip::CreateObject() \
{ return new CTrip; } \
_IMPLEMENT_RUNTIMECLASS(CTrip, CObject, 2, \
CTrip::CreateObject, NULL)\
AFX_CLASSINIT _init_CTrip(RUNTIME_CLASS(CTrip));
替換IMPLEMENT_SERIAL!
替換后CTrip同樣可以完成串行化的工作?
在源文件中增加CArchive& AFXAPI operator>>(CArchive& ar, CTrip* &pOb) \
{ pOb = (CTrip*) ar.ReadObject(RUNTIME_CLASS(CTrip)); \
return ar; },并在此處設(shè)置斷點(diǎn),但是始終不進(jìn)入,說(shuō)明沒(méi)有用到?
問(wèn)題2:
struct CRuntimeClass
{