分享

MFC序列化-IMPLEMENT_SERIAL(...)

 夜下月光 2014-04-26

MFC序列化机制中的一个潜在的错误

MFC在IMPLEMENT_SERIAL使用VERSIONABLE_SCHEMA|schema number来提供文件格式的向后兼容.即新的程序既可以读新的文件格式,也可以读较老的文件格式.当它发现要被反序列化的CObject派生类对象内容的shema和程序该类的schema不一致时,如果该类是VERSIONABLE_SCHEMA的(这个信息记录在该类所对应的CRuntimeClass中),那么它仍然调用该类的序列化函数;否则终止反序列化过程,抛出CArchiveException异常,最终向用户报告文件格式错误.
如果一个类是VERSIONABLE_SCHEMA的,那么该类的反序列化必须根据不同的schma number来进行.就在这里,微软犯了一个潜在的错误,微软对于一个对象只保存一个schema number[对象所属类的],这是不够的.因为一个对象从CObject到对象的实际类型可能不止一级,而每一级的schema number都有变化的可能.当中间层次的类发生变化时,VERSIONABLE_SCHEMA机制就失效了.
解决这个问题的办法就是自己在每个类层次里序列化类的版本号,根据该版本号进行反序列化,完全忽略MFC的VERSIONABLE_SCHEMA机制.

当然,这个问题不可能在MFC框架内得到解决,我觉得MFC开发小组可以有两个办法:一是完全让用户负责文件的向后兼容;一是在MSDN里说明这个潜在的问题,引起程序员的注意.
------------------------------------------------------------------------------------------------------
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)对这些宏的理解:
IMPLEMENT_RUNTIMECLASS中定义了静态CRuntimeClass对象,并赋予当前类与基类的信息 ,当前类类名及版本号wSchema,动态创建当前类对象的函数地址CreateObject等。
它实现了基类与派生类之间的连接,在MFC类层次之间形成了树形层次的结构,所以可以在此基础上实现动态类 型的识别IsKindOf:
 while (pClassThis != NULL)
 {
  if (pClassThis == pBaseClass)
   return TRUE;

  if (pClassThis->m_pfnGetBaseClass == NULL)
   return FALSE;

  pClassThis = (*pClassThis->m_pfnGetBaseClass)();
 }
DECLARE_DYNAMIC仅提供动态类型的识别,只需要基类与派生类之间的连接,不需要其它信息,所以IMPLEMENT_DYNAMIC调用的IMPLEMENT_RUNTIMECLASS版本号:0xFFFF(表示无效), 动态创建当前类对象的函数地址:NULL。

DECLARE_DYNCREATE增加了动态创建,所以IMPLEMENT_DYNCREATE增加了动态创建当前类对象的函数CreateObject,并传递给IMPLEMENT_RUNTIMECLASS。

DECLARE_SERIAL实现串行化(保存与读取),不同版本的保存与读取是可能存在不同的,所以需要版本号,所以IMPLEMENT_SERIAL增加了版本号传递;
另外,在读取时,读到class信息时需要判断是那个类后才可以动态生成,所以IMPLEMENT_SERIAL中增加了AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name))。
AFX_CLASSINIT是结构的定义:
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass); };
仅有一个构造函数,定义如下:
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
此构造函数负责 linked list 的串接工作。在MFC类中依靠AFX_CLASSINIT在原有树形的结构上增加了链表,链表将所有包含*_SERIAL对的MFC类串接后形成一个表。所以读到class信息时在此链表查询即可!

问题1(主要问题):
*_SERIAL中增加了friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
但我没有使用它时也可以串行化!

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; },并在此处设置断点,但是始终不进入,说明没有用到?

问题2:
struct CRuntimeClass
{



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多