在MFC当中有一个不为人知的类CArchive,这个类主要提供文件的操作,即对>>和<<进行重载。不过CArchive除了对所有的简单类型可以进行文件串行化操作之外,还可以对CObject以及CObject的子类进行串行化,即保存他们的信息到文件当中,以及根据文件当中读取的信息重新生成一个和之前保存到文件当中相对应的类。
整个类的信息保存可以,由CArchive当中的三个重载符号实现:
friend CArchive&operator<<(CArchive& ar, const CObject* pOb);
而这个函数的实现很简单,仅仅调用CArchive的WriteObject函数就可以了。当然用户不能直接调用WriteObject,即使WriteObject函数式公有的,但是在写操作的过程当中可能会处理被写类的私有数据,而友元函数可以访问类的私有成员变量。
WriteObject函数的实现包含三个关键部分,第一个部分是MapObject,第二个部分和第三部分根据传入的pOb的差异而有所差别。假设pOb为NULL,那么很简单向文件当中写入一个标记wNullTag就可以了。还有一种就是当pOb存在于CArchive的map当中的时候就只需要写一个标记,而当pOb没有存在于map当中的时候就需要将pOb添加到map当中,并且利用WriteClass写入与pOb相关的CRuntimeClass,最后调用pOb的Serialize函数保存pOb特有的信息。对WriteClass的处理类似于WriteObject。不过从WriteClass可以看出,当一个继承自CObject的类需要有类信息串行化功能的时候,需要有自己实现的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; } \ 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; }
最后一点就是CArchive最好只作为一个局部变量存在,而不要作为成员变量或者全局变量存在。因为从MapObject函数的实现可以看出来,整个CArchive只能在Store或者Load一种形式下存在,否则可能引起程序的崩溃。
而ReadObject函数就是WriteObject的逆操作,只不过整个程序的CRunTimeClass都保存在一个全局的链表当中,然后通过从文件当中得到文件名称和链表当中的CRunTimeClass进行比对,如果比对成功就返回CRunTimeClass的指针,然后通过这个指针的CreateObject创建一个相应的对象。