小鸡模拟器电脑驱动:CObject作用

来源:百度文库 编辑:偶看新闻 时间:2024/04/30 20:37:19

CObject类提供了一些公共服务:包括支持序列化,支持运行时提供类信息,支持动态创建以及支持对象诊断输出等,所以凡是从CObject类继承的类都有这些功能。

1  诊断功能

C     Object类其中的Dump()和AssertValid()成员函数提供了诊断功能。Dump()成员函数为类的内容提供调试输出; Dump()成员函数为const类型的成员函数,因此它不能改变对象的数据。

    AssertValid()成员函数提供了自定义测试以验证类对象的数据成员的一致性。也就是提供对对象内部状态的运行时检查。该成员函数只在调试的时候有用,也是const类型的成员函数,因此它不能改变对象的数据。

     CObject类中的AssertValid()成员函数仅能断言(assert)当前的对象不为NULL。尽管从CObject派生类的时候,不需要重写AssertValid(),但是可以通过重写使自己的类更安全可靠。AssertValid()应该在对象的所有成员变量上执行断言,以验证它们包含有效数值。触发了断言说明了指示的对象一定有错误,并且执行将暂停,然而,缺少断言指示未找到任何问题,但并不能保证对象是好的。

2 运行时类信息

     CObject类支持运行时提供其类的信息。通过其中的IsKindOf()成员函数,可以确定对象是属于指定类还是从指定类派生。

       CObject类支持运行时提供其类的信息,是通过一个很重要的结构完成CRuntimeClass(这是一个结构),Each class derived from CObject is associated with a CRuntimeClass structure that you can use to obtain information about an object or its base class at run time. The ability to determine the class of an object at run time is useful when extra type checking of function arguments is needed, or when you must write special-purpose code based on the class of an object.(Come from MSDN).

CRuntimeClass提供了C++对象的运行时候的类的信息,比如ASCII类名,基类的CRuntimeClass,对象的大小,构造函数等信息,此外还提供了一组成员函数用来动态创建对象,确定对象的类型等。因此有了对象的大小,构造函数等信息,就可以动态的创建对象了,这需要用结构 CRuntimeClass中的成员函数CreateObject()。

另外,结构体CRuntimeClass还维持了指向基类的CRuntimeClass指针,这样,按照类的派生关系就构成了CRuntimeClass 链表。从而通过查找该链表中是否有指定的CRuntimeClass,可以判断给定的类是否从某个类派生。这需要用CRuntimeClass的成员函数中的IsDerivedFrom()。通过结构体CRuntimeClass中的静态成员变量m_pNextClass,MFC为每个模块(EXE应用程序)维护了一个全局的CRuntimeClass链表,这个链表保存在模块的状态中。

      但是要注意,一般并不直接在类中使用CRuntimeClass,而是通过一组宏将CRuntimeClass引入到类中,并添加相应的成员。

3 添加运行时类的信息

       为了添加运行时类信息,需要为类添加CRuntimeClass类型的静态成员函数,保存本类的相关信息,比如对象的大小,基类等等,另外还要添加一些方法操作CRuntimeClass成员等。凡是从CObject类派生的类,都可以通过一组宏自动完成上述任务。

     比如在类的头文件中加入宏DECLARE_DYNAMIC(Class_name),这个宏声明了CRuntimeClass结构体中的静态成员.如果传入的Class_name为CPerson,则对应的变量名为classCPerson并且返回一个CRuntimeClass指针的虚函数。然后在实现文件中使用IMPLEMENT_DYNAMIC(派生类名,基类名)宏,这个宏会对DECLARE_DYNAMIC(Class_name)中声明的 CRuntimeClass静态成员惊醒初始化,比如设定类名,对象大小,基类的CRuntimeClass等等。实现文件中的宏 RUNTIME_CLASS(className)的作用是返回类className的CRuntimeClass类型的静态成员的指针。CObject 成员函数IsKindOf()利用CRuntimeClass静态成员确定具体某个对象是属于特定的类还是从特定的来派生的。这个函数内部调用了结构体 CRuntimeClass中的成员函数IsDerivedFrom()。所以如果这个类支持运行时提供类信息,那么使用带类名的 RUNTIME_CLASS(className)宏可以获取表示该类信息的CRuntimeClass对象。比如

CRuntimeClass * pClass= RUNTIME_CLASS(CPerson);或者通过对象调用GetRuntimeClass成员,也可以获得该对象所属于类的信息成员。一旦获得了运行时类信息,将其传递到IsKindOf(),就可以确定对象是否属于特定类。

4动态创建

     所谓的动态创建不是使用C++的运算符号new在堆中创建对象,而是要从文件中获得某个类的信息之后,利用这些信息恢复或创建该类的对象。因此在类中应该有一个专门存储这个类的相关信息的记录成员。为了在动态创建对象的时候能够获得待创建对象的所属的类以及其基类的相关信息,MFC要求,在所有可以动态创建对象的类中,要有一个专门存储这个类的相关信息的记录成员。这就是 CRuntimeClass结构体。

     这样,如果在某个类中有这样一个CRuntimeClass结构体信息表,则在创建这个类的对象的时候,系统就可以根据类名找到这个类的信息表,同时根据这个结构体中的指针m_pBaseClass依次找到其一系列基类的信息。这样系统就可以根据这些信息表提供的信息把对象创建出来。

     为了把各个类中的类信息表组织成一个大表,MFC再次使用了链表的结构。该链表把每个类的类信息表作为一个链表项,并用指针m_pNextClass把它们连接起来,从而组织了一个大的类信息表。于是,要想想使得某个类具有动态创建对象的能力,则该类中必须有类信息链表的声明,而在类的外边应该有类信息链表的实现。所以MFC把在类中声明类信息表的代码封装到宏DECLARE_DYNCREATE中,而把实现类信息表和链表的代码封装在了宏 IMPLEMENT_DYNCREATE中。

     在需要动态创建一个类的对象的时候,要根据类名到存放类信息的链表中去查找和收集该类的信息,为了达到这个目标,MFC又提供了一个宏 RUNTIME_CLASS(CPerson),这个宏可以按照参数提供的类名,在类信息链表中收集该类的相关信息并调用该类的构造函数来创建对象。 C++不直接支持,但是CObject提供了动态创建对象的能力。为了支持动态创建,需要加入运行时类信息。同时需要在初始化CRuntimeClass 静态成员的时候,把CRuntimeClass结构体中的一个变量m_pfnCreateObject设置为不为(NULL),以表示 CRuntimeClass可以支持动态创建。这样就可以调用CRuntimeClass的CreateObject成员函数创建了。

    要想让一个类有动态创建功能,需要遵循下面的步骤。

(1)从CObject派生这个类。然后在类的声明中使用宏DECLARE_DYNCREATE。该宏内部调用了前面我们用到的宏DECLARE_DYNAMIC(Class_name)。

(2)为这个类定义一个不带参数的构造函数(默认构造函数)。这是因为动态创建过程要使用这个构造函数进行初始化。

(3)在类的实现文件中使用IMPLEMENT_DYNCREATE宏。这个宏除了完成IMPLEMENT_DYNAMIC的功能外,还负责实现DECLARE_DYNCREATE中声明的静态成员CreateObject()

5 序列化

    序列化是比动态创建更高级的功能,它提供了将对象保存到文件或其他存储设备以及从存储中创建对象的能力。为了支持序列化,也要向类添加运行时信息,添加动态创建,此外要专门添加针对序列化的代码。CObject用另外一组宏支持序列化。为了简化对象的序列化操作,MFC类库提供了CArchive类,作为序列化对象和存储介质之间的中间物。

     因此为了支持序列化,必须把类中的结构体CRuntimeClass的静态成员m_wSchema成员设置为非OXFFFF的数值。

     这项设置工作是通过一组宏来实现的,首先在类声明中使用DECLARE_SERIAL宏。该宏除了完成DECLARE_DYNCREATE所做的工作外,还声明了友元函数operatro>>以支持从ar中创建对象。CArchive充当内存数据和存储之间的直接的中介,利用它可以把内存数据,如对象轻松的写入持久性的存储设备如文件中,也可以从持久性存储中轻松构造对象。

     CArchive对象提供了一个类型安全缓冲机制,用于将可序列化对象写入CFile对象或从中读取可序列化对象。通常,CFile对象表示磁盘文件;但是,它也可以表示“剪贴板”的内存文件(CSharedFile文件)。当存档将对象存储到文件(存储设备)的时候,存档先写入对象的 CRuntimeClass信息。然后再写入对象的数据。当另一个存档将对象从文件加载到内存的时候,将首先获得对象的CRuntimeClass,然后用它动态创建一个对象。将数据序列化到CArchive对象的时候,CArchive对象积累数据,直到它的缓冲区被添满为止。然后,CArchive对象将其缓冲区写入为其指定的CFile对象。读出的时候,反之。在文档/视图应用框架下,每当用户选择打开文件,新建文件或者保存文件的时候,框架会自动创建CArchive对象以将文档对象保存到文件或从文件中加栽数据。另外,在其他场合也可能需要CArchive对象,例如,可能要序列化到达或来自剪贴板的数据,由CSharedFile对象表示。或者可能要使用用户界面来保存与框架提供的文件不同的文件,在这种情况下,可以显式创建CArchive 对象,也就是需要先创建构造CFile对象,然后将CFile对象传递到CArchive的构造函数。

 

CObject

  CObject是大多数MFC类的根类或基类。CObject类有很多有用的特性:对运行时类信息的支持,对动态创建的支持,对串行化的支持,对象诊断输出,等等。MFC从CObject派生出许多类,具备其中的一个或者多个特性。程序员也可以从CObject类派生出自己的类,利用CObject类的这些特性。

[编辑本段]

编辑本段]

CObject类的特性

  下面,对三种特性分别描述,并说明程序员在派生类中支持这些特性的方法。

  对运行时类信息的支持

  该特性用于在运行时确定一个对象是否属于一特定类(是该类的实例),或者从一个特定类派生来的。CObject提供IsKindOf函数来实现这个功能。

  从CObject派生的类要具有这样的特性,需要:

  ● 定义该类时,在类说明中使用DECLARE_DYNAMIC(CLASSNMAE)宏;

  ● 在类的实现文件中使用IMPLEMENT_DYNAMIC(CLASSNAME,BASECLASS)宏。

  对动态创建的支持

  前面提到了动态创建的概念,就是运行时创建指定类的实例。在MFC中大量使用,如前所述框架窗口对象、视对象,还有文档对象都需要由文档模板类(CDocTemplate)对象来动态的创建。

  从CObject派生的类要具有动态创建的功能,需要:

  ● 定义该类时,在类说明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;

  ● 定义一个不带参数的构造函数(默认构造函数);

  ● 在类的实现文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏;

  ● 使用时先通过宏RUNTIME_CLASS得到类的RunTime信息,然后使用CRuntimeClass的成员函数CreateObject创建一个该类的实例。

  例如:

  CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CNname)

  //CName必须有一个缺省构造函数

  CObject* pObject = pRuntimeClass->CreateObject();

  //用IsKindOf检测是否是CName类的实例

  Assert( pObject->IsKindOf(RUNTIME_CLASS(CName));

  对序列化的支持

  “序列化”就是把对象内容存入一个文件或从一个文件中读取对象内容的过程。从CObject派生的类要具有序列化的功能,需要:

  ● 定义该类时,在类说明中使用DECLARE_SERIAL(CLASSNMAE)宏;

  ● 定义一个不带参数的构造函数(默认构造函数);

  ● 在类的实现文件中使用IMPLEMENT_SERIAL(CLASSNAME,BASECLASS)宏;

  ● 覆盖Serialize成员函数。(如果直接调用Serialize函数进行序列化读写,可以省略前面三步。)

  对运行时类信息的支持、动态创建的支持、串行化的支持层(不包括直接调用Serailize实现序列化),这三种功能的层次依次升高。如果对后面的功能支持,必定对前面的功能支持。支持动态创建的话,必定支持运行时类信息;支持序列化,必定支持前面的两个功能,因为它们的声明和实现都是后者包含前者。

  综合示例

  定义一个支持串行化的类CPerson:

  class CPerson : public CObject

  {

  public:

  DECLARE_SERIAL( CPerson )

  // 缺省构造函数

  CPerson(){}{};

  CString m_name;

  WORD m_number;

  void Serialize( CArchive& archive );

  // rest of class declaration

  };

  实现该类的成员函数Serialize,覆盖CObject的该函数:

  void CPerson::Serialize( CArchive& archive )

  {

  // 先调用基类函数的实现

  CObject::Serialize( archive );

  // now do the stuff for our specific class

  if( archive.IsStoring() )

  archive << m_name << m_number;

  else

  archive >> m_name >> m_number;

  }

  使用运行时类信息:

  CPerson a;

  ASSERT( a.IsKindOf( RUNTIME_CLASS( CPerson ) ) );

  ASSERT( a.IsKindOf( RUNTIME_CLASS( CObject ) ) );

  动态创建:

  CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CPerson)

  //Cperson有一个缺省构造函数

  CObject* pObject = pRuntimeClass->CreateObject();

  Assert( pObject->IsKindOf(RUNTIME_CLASS(CPerson));

  实现CObject特性的机制

  由上,清楚了CObject的结构,也清楚了从CObject派生新类时程序员使用CObject特性的方法。现在来考察这些方法如何利用CObjet的结构,CObject结构如何支持这些方法。

  首先,要揭示DECLARE_DYNAMIC等宏的内容,然后,分析这些宏的作用。

  DECLARE_DYNAMIC等宏的定义

  MFC提供了DECLARE_DYNAMIC、DECLARE_DYNCREATE、DECLARE_SERIAL声明宏的两种定义,分别用于静态链接到MFC DLL和动态链接到MFC DLL。对应的实现宏IMPLEMNET_XXXX也有两种定义,但是,这里实现宏就不列举了。

  MFC对这些宏的定义如下:

  #ifdef _AFXDLL //动态链接到MFC DLL

  #define DECLARE_DYNAMIC(class_name) \

  protected: \

  static CRuntimeClass* PASCAL _GetBaseClass(); \

  public: \

  static const AFX_DATA CRuntimeClass class##class_name; \

  virtual CRuntimeClass* GetRuntimeClass() const; \

  #define _DECLARE_DYNAMIC(class_name) \

  protected: \

  static CRuntimeClass* PASCAL _GetBaseClass(); \

  public: \

  static AFX_DATA CRuntimeClass class##class_name; \

  virtual CRuntimeClass* GetRuntimeClass() const; \

  #else

  #define DECLARE_DYNAMIC(class_name) \

  public: \

  static const AFX_DATA CRuntimeClass class##class_name; \

  virtual CRuntimeClass* GetRuntimeClass() const; \

  #define _DECLARE_DYNAMIC(class_name) \

  public: \

  static AFX_DATA CRuntimeClass class##class_name; \

  virtual CRuntimeClass* GetRuntimeClass() const; \

  #endif

  // not serializable, but dynamically constructable

  #define DECLARE_DYNCREATE(class_name) \

  DECLARE_DYNAMIC(class_name) \

  static CObject* PASCAL CreateObject();

  #define DECLARE_SERIAL(class_name) \

  _DECLARE_DYNCREATE(class_name) \

  friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);

  由于这些声明宏都是在CObect派生类的定义中被使用的,所以从这些宏的上述定义中可以看出,DECLARE_DYNAMIC宏给所在类添加了一个CRuntimeClass类型的静态数据成员class##class_name(类名加前缀class,例如,若类名是CPerson,则该变量名称是classCPerson),且指定为const;两个(使用MFC DLL时,否则,一个)成员函数:虚拟函数GetRuntimeClass和静态函数_GetBaseClass(使用MFC DLL时)。

  DECLARE_DYNCREATE宏包含了DECLARE_DYNAMIC,在此基础上,还定义了一个静态成员函数CreateObject。

  DECLARE_SERIAL宏则包含了_DECLARE_DYNCREATE,并重载了操作符“>>”(友员函数)。它和前两个宏有所不同的是CRuntimeClass数据成员class##class_name没有被指定为const。

  对应地,MFC使用三个宏初始化DECLARE宏所定义的静态变量并实现DECLARE宏所声明的函数:IMPLEMNET_DYNAMIC,IMPLEMNET_DYNCREATE,IMPLEMENT_SERIAL。

  首先,这三个宏初始化CRuntimeClass类型的静态成员变量class#class_name。IMPLEMENT_SERIAL不同于其他两个宏,没有指定该变量为const。初始化内容在下节讨论CRuntimeClass时给出。

  其次,它实现了DECLARE宏声明的成员函数:

  _GetBaseClass()

  返回基类的运行时类信息,即基类的CRuntimeClass类型的静态成员变量。这是静态成员函数。

  GetRuntimeClass()

  返回类自己的运行类信息,即其CRuntimeClass类型的静态成员变量。这是虚拟成员函数。

  对于动态创建宏,还有一个静态成员函数CreateObject,它使用C++操作符和类的缺省构造函数创建本类的一个动态对象。

  操作符的重载

  对于序列化的实现宏IMPLEMENT_SERIAL,还重载了操作符<<和定义了一个静态成员变量

  static const AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name));

  比如,对CPerson来说,该变量是_init_Cperson,其目的在于静态成员在应用程序启动之前被初始化,使得AFX_CLASSINIT类的构造函数被调用,从而通过AFX_CLASSINIT类的构造函数在模块状态的CRuntimeClass链表中插入构造函数参数表示的CRuntimeClass类信息。至于模块状态,在后文有详细的讨论。

  重载的操作符函数用来在序列化时从文档中读入该类对象的内容,是一个友员函数。定义如下:

  CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb)

  {

  pOb = (class_name*) ar.ReadObject(

  RUNTIME_CLASS(class_name));

  return ar;

  }

  回顾CObject的定义,它也有一个CRuntimeClass类型的静态成员变量classCObject,因为它本身也支持三个特性。

  以CObject及其派生类的静态成员变量classCObject为基础,IsKindOf和动态创建等函数才可以起到作用。

  这个变量为什么能有这样的用处,这就要分析CRuntimeClass类型变量的结构和内容了。下面,在讨论了CRuntimeClass的结构之后,考察该类型的静态变量被不同的宏初始化之后的内容。

  CruntimeClass类的结构与功能

  从上面的讨论可以看出,在对CObject特性的支持上,CRuntimeClass类起到了关键作用。下面,考查它的结构和功能。

  CRuntimeClass的结构

  CruntimeClass的结构如下:

  Struct CRuntimeClass

  {

  LPCSTR m_lpszClassName;//类的名字

  int m_nObjectSize;//类的大小

  UINT m_wSchema;

  CObject* (PASCAL* m_pfnCreateObject)();

  //pointer to function, equal to newclass.CreateObject()

  //after IMPLEMENT

  CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

  CRumtieClass* m_pBaseClass;

  //operator:

  CObject *CreateObject();

  BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

  ...

  }

  CRuntimeClass成员变量中有两个是函数指针,还有几个用来保存所在CruntimeClass对象所在类的名字、类的大小(字节数)等。

  这些成员变量被三个实现宏初始化,例如:

  m_pfnCreateObject,将被初始化指向所在类的静态成员函数CreateObject。CreateObject函数在初始化时由实现宏定义,见上文的说明。

  m_pfnGetBaseClass,如果定义了_AFXDLL,则该变量将被初始化指向所在类的成员函数_GetBaseClass。_GetBaseClass在声明宏中声明,在初始化时由实现宏定义,见上文的说明。

  下面,分析三个宏对CObject及其派生类的CRuntimeClass类型的成员变量class##class_name初始化的情况,然后讨论CRuntimeClass成员函数的实现。

  成员变量class##class_name的内容

  IMPLEMENT_DYNCREATE等宏将初始化类的CRuntimeClass类型静态成员变量的各个域,表3-1列出了在动态类信息、动态创建、序列化这三个不同层次下对该静态成员变量的初始化情况:

  表3-1 静态成员变量class##class_name的初始化

  http://hiphotos.baidu.com/luqingfei/pic/item/60caf9245af40b298644f933.jpg

  m_wSchema类型是UINT,定义了序列化中保存对象到文档的程序的版本。如果不要求支持序列化特性,该域为0XFFFF,否则,不能为0。

  Cobject类本身的静态成员变量classCObject被初始化为:

  { "CObject", sizeof(CObject), 0xffff, NULL, &CObject::_GetBaseClass, NULL };

  对初始化内容解释如下:

  类名字符串是“CObject”,类的大小是sizeof(CObject),不要求支持序列化,不支持动态创建。

  成员函数CreateObject

  回顾3.2节,动态创建对象是通过语句pRuntimeClass->CreateObject完成的,即调用了CRuntimeClass自己的成员函数,CreateObject函数又调用m_pfnCreateObject指向的函数来完成动态创建任务,如下所示:

  CObject* CRuntimeClass::CreateObject()

  {

  if (m_pfnCreateObject == NULL) //判断函数指针是否空

  {

  TRACE(_T("Error: Trying to create object which is not ")

  _T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"),

  m_lpszClassName);

  return NULL;

  }

  //函数指针非空,继续处理

  CObject* pObject = NULL;

  TRY

  {

  pObject = (*m_pfnCreateObject)(); //动态创建对象

  }

  END_TRY

  return pObject;

  }

  成员函数IsDerivedFrom

  该函数用来帮助运行时判定一个类是否派生于另一个类,被CObject的成员函数IsKindOf函数所调用。其实现描述如下:

  如果定义了_AFXDLL则,成员函数IsDerivedFrom调用成员函数m_pfnGetBaseClass指向的函数来向上逐层得到基类的CRuntimeClass类型的静态成员变量,直到某个基类的CRuntimeClass类型的静态成员变量和参数指定的CRuntimeClass变量一致或者追寻到最上层为止。

  如果没有定义_AFXDLL,则使用成员变量m_pBaseClass基类的CRuntimeClass类型的静态成员变量。

  程序如下所示:

  BOOL CRuntimeClass::IsDerivedFrom(

  const CRuntimeClass* pBaseClass) const

  {

  ASSERT(this != NULL);

  ASSERT(AfxIsValidAddress(this, sizeof(CRuntimeClass), FALSE));

  ASSERT(pBaseClass != NULL);

  ASSERT(AfxIsValidAddress(pBaseClass, sizeof(CRuntimeClass), FALSE));

  // simple SI case

  const CRuntimeClass* pClassThis = this;

  while (pClassThis != NULL)//从本类开始向上逐个基类搜索

  {

  if (pClassThis == pBaseClass)//若是参数指定的类信息

  return TRUE;

  //类信息不符合,继续向基类搜索

  #ifdef _AFXDLL

  pClassThis = (*pClassThis->m_pfnGetBaseClass)();

  #else

  pClassThis = pClassThis->m_pBaseClass;

  #endif

  }

  return FALSE; // 搜索完毕,没有匹配,返回FALSE。

  }

  由于CRuntimeClass类型的成员变量是静态成员变量,所以如果两个类的CruntimeClass成员变量相同,必定是同一个类。这就是IsDerivedFrom和IsKindOf的实现基础。

  RUNTIME_CLASS宏

  RUNTIME_CLASS宏定义如下:

  #define RUNTIME_CLASS(class_name) (&class_name::class##class_name)

  为了方便地得到每个类(Cobject或其派生类)的CRuntimeClass类型的静态成员变量,MFC定义了这个宏。它返回对类class_name的CRuntimeClass类型成员变量的引用,该成员变量的名称是“class”加上class_name(类的名字)。例如:

  RUNTIME_CLASS(CObject)得到对classCObject的引用;

  RUNTIME_CLASS(CPerson)得到对class CPerson的引用。

  动态类信息、动态创建的原理

  MFC对Cobject动态类信息、动态创建的实现原理:

  动态类信息、动态创建都建立在给类添加的CRuntimeClass类型的静态成员变量基础上,总结如下。

  C++不支持动态创建,但是支持动态对象的创建。动态创建归根到底是创建动态对象,因为从一个类名创建一个该类的实例最终是创建一个以该类为类型的动态对象。其中的关键是从一个类名可以得到创建其动态对象的代码。

  在一个类没有任何实例之前,怎么可以得到该类的创建动态对象的代码?借助于C++的静态成员数据技术可达到这个目的:

  静态成员数据在程序的入口(main或WinMain)之前初始化。因此,在一个静态成员数据里存放有关类型信息、动态创建函数等,需要的时候,得到这个成员数据就可以了。

  不论一个类创建多少实例,静态成员数据只有一份。所有的类的实例共享一个静态成员数据,要判断一个类是否是一个类的实例,只须确认它是否使用了该类的这个静态数据。

  从前两节的讨论知道,DECLARE_CREATE等宏定义了一个这样的静态成员变量:类型是CRuntimeClass,命名约定是“calss”加上类名;IMPLEMENT_CREATE等宏初始化该变量;RUNTIME_CLASS宏用来得到该成员变量。

  动态类信息的原理在分析CRuntimeClass的成员函数IsDerivedFrom时已经作了解释。

  动态创建的过程和原理了,用图表示其过程如下:

  http://hiphotos.baidu.com/luqingfei/pic/item/11e55e0fee6dd0306159f333.jpg

  注:下面一个方框内的逐级缩进表示逐层调用关系。

  序列化的机制

  由上所述可知,一个类要支持实现序列化,使得它的对象可以保存到文档中或者可以从文档中读入到内存中并生成对象,需要使用动态类信息,而且,需要覆盖基类的Serialize虚拟函数来完成其对象的序列化。

  仅仅有类的支持是不够的,MFC还提供了一个归档类CArchive来支持简单类型的数据和复杂对象的读写。

  CArchive 在文件和内存对象之间充当一个代理者的角色。它负责按一定的顺序和格式把内存对象写到文件中,或者读出来,可以被看作是一个二进制的流。它和文件类CFile的关系如图3-2所示:

  http://hiphotos.baidu.com/luqingfei/pic/item/d250d82a59cd2237d52af133.jpg

  一个CArchive对象在要序列化的对象和存储媒体(storage medium,可以是一个文件或者一个Socket)之间起了中介作用。它提供了系列方法来完成序列化,不仅能够把int、float等简单类型数据进行序列化,而且能够把复杂的数据如string等进行序列化,更重要的是它能把复杂的对象(包括复合对象)进行序列化。这些方法就是重载的操作符>>和<<。对于简单类型,它针对不同类型直接实现不同的读写操作;对于复杂的对象,其每一个支持序列化的类都重载了操作符>>,从前几节可以清楚地看到这点:IMPLEMENT_SERIAL给所在类重载了操作符>>。至于<<操作,就不必每个序列化类都重载了。

  复杂对象的“<<”操作,先搜索本模块状态的CRuntimeClass链表看是否有“<<”第二个参数指定的对象类的运行类信息(搜索过程涉及到模块状态,将在9.5.2节描述),如果有(无,则返回),则先使用这些信息动态的创建对象(这就是是序列化类必须提供动态类信息、支持动态创建的原因),然后对该对象调用Serilize函数从存储媒体读入对象内容。

  复杂对象的“>>”操作先把对象类的运行类信息写入存储媒体,然后对该对象调用Serilize函数把对象内容写入存储媒体。

  在创建CArchive对象时,必须有一个CFile对象,它代表了存储媒介。通常,程序员不必做这个工作,打开或保存文档时MFC将自动的创建CFile对象和CArchive对象并在适当的时候调用序列化类的Serialize函数。在后面讨论打开(5.3.3.2节)或者关闭(6.1节)文档时将会看到这样的流程。

  CArchive对象被创建时,需要指定它是用来读还是用来写,即指定序列化操作的方向。Serialize函数适用CArchive的函数IsStoring判定CArchive是用于读出数据还是写入数据。

  在解释实现序列化的方法时,曾经提到如果程序员直接调用Serilize函数完成序列化,而不借助CArchive的>>和<<操作,则可以不需要动态类信息和动态创建。从上文的论述可以看出,没有CArchive的>>和<<操作,的确不需要动态类信息和动态创建特性。