一、CObList::Serialize函数
最近CObList类串行化时出了一点问题,但是从网上找不到CObList::Serialize的源代码,现在将《输入浅出MFC》中的函数列于下方,方便查看。
void CObList::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
CObject::Serialize(ar);//目前我还没能找到这条函数的具体内容,只知道是CObject类的虚函数
if (ar.IsStoring())
{
ar.WriteCount(m_nCount);
for (CNode* pNode = m_pNodeHead; pNode != NULL; pNode = pNode->pNext)
{
ASSERT(AfxIsValidAddress(pNode, sizeof(CNode)));
ar << pNode->data;
}
}
else
{
DWORD nNewCount = ar.ReadCount();
CObject* newData;
while (nNewCount--)
{
ar >> newData;
AddTail(newData);
}
}
}
此外将某位大神对串行化的理解放于下方,帮助大家认识一下串行化
二、MFC串行化理解
MFC的连续存储(serialize)机制俗称串行化。在你的程序中尽管有着各种各样的数据,serialize机制会象流水一样按顺序存储到单一的文件中,而又能按顺序地取出,变成各种不同的对象数据。
1、串行化原理
在我们的程序里有各式各样的对象数据。如画图程序中,里面设计了点类,矩形类,圆形类等等,它们的绘图方式及对数据的处理各不相同。我们要在程序中设计函数store(),在我们单击“文件/保存”时能把各对象往里存储。那么这个store()函数要神通广大,它能清楚地知道我们设计的是什么样的类,产生什么样的对象。大家可能并不觉得这是一件很困难的事情,程序有能力知道我们的类的样子,对象也不过是一块初始化了存储区域罢了。就把一大堆对象“转换”成磁盘文件就行了。
即使这样的存储能成立,但当我们单击“文件/打开”时,程序当然不能预测用户想打开哪个文件,并且当打开文件的时候,要根据你那一大堆垃圾数据new出数百个对象,还原为你原来存储时的样子,又该怎么做呢?
试想,要是我们有一个能容纳各种不同对象的容器,这样,用户用我们的应用程序打开一个磁盘文件时,就可以把文件的内容读进我们程序的容器中。把磁盘文件读进内存,然后识别它“是什么对象”是一件很难的事情。比如保存一个矩形,程序并不是把矩形本身按点阵存储到磁盘中,它保存只是坐标值、线宽和某些标记等。程序面对“00 FF”这样的东西,当然不知道它是一个圆或是一个字符!
我们在写程序的时候,会不断创造新的类,构造新的对象。这些对象是旧的类对象(如MyDocument)从未见过的。那么,我们如何才能使文档对象可以保存自己新对象呢,又能动态创建自己新的类对象呢?
许多朋友在这个时候想起了CObject这个类,也想到了虚函数的概念,他们设想:“我们设计的MyClass(我们想用于串行化的对象)全部从CObject类派生,CObject类对象当然是MyDocument能认识的。由于MyClass从CObject类派生,构造的新类对象“是一个CObject”,所以MyDocument能把我们的新对象当作CObiect对象读出。或者根据书本上所说的:打开或保存文件的时候,MyDocument会调用Serialize(),MyDocument的Serialize()函会呼叫我们创建类的Serialize函数(即在MyDocument Serialize()中调用:m_pObject->Serialize())。最终结果是MyDocument的读出和保存变成了我们创建的类对象的读出和保存,
这种认识是不明朗的。我们创建的类MyClass并不是由MyDocument类派生,Serialize()函数为虚在MyDocument和MyClass之间没有任何意义。MyClass产生的MyObject对象仅仅是MyDocument的一个成员变量罢了。话说回来,由于MyClass从CObject派生,所以CObject类型指针能指向MyClass对象,并且能够让MyClass对象执行某些函数(特指重载的CObject虚函数),但前提必须在MyClass对象实例化了,即在内存中占领了一块存储区域之后。不过,我们的问题恰恰就是在应用程序随便打开一个文件,面对的是它不认识的MyClass类,当然实例化不了对象。
幸好我们知道一个概念——动态创建。即想要从CObject派生的MyClass成为可以动态创建的对象只要用到DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏就可以了(注意:最终可以Serialize的对象仅仅用到了DECLARE_SERIAL/IMPLEMENT_SERIAL宏,这是因为DECLARE_SERIAL/IMPLEMENT_SERIAL包含了DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏)。
2、 深入理解串行化
从解决上面的问题中,我们可以分步理解了:
1)Serialize的目的:让MyDocument对象在执行打开/保存操作时,能读出(构造)和保存它不认的MyClass类对象。
2) MyDocument对象在执行打开/保存操作时会调用它本身的Serialize()函数。但不要指望它会自动保存和读出我们的MyClass类对象。这个问题很容易解决,如下即可:
MyDocument:: Serialize(){
// 在此函数调用MyClass类的Serialize()就行了!即
MyObject. Serialize();
}
3)在MyClass类中加上DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏,将MyClass动态创建的对象。
目前的Serialize机制还是很抽象。下面作一个简单深刻的详解。先看一下我们文档类的Serialize():
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
目前这个子数什么也没做(没有数据的读出和写入),CMyDoc类正等待着我们去改写这个函数。现在假设CMyDoc有一个MFC可识别的成员变量m_MyVar,那么函数就可改写成如下形式:
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring()) //读写判断
{
ar<>m_MyVar; //读
}
}
许多网友问:自己写的类(即MFC未包含的类)为什么不行?我们在CMyDoc里包含自写类的头文件MyClass.h,这样CMyDoc就认识MyDoc类对象了。这是一般常识性的错误,MyDoc类认识MyClass类对象与否并没有用,关键是CArchive类,即对象ar不认识MyClass(当然你梦想重写CArchive类当别论)。“>>”、“<<”都是CArchive重载的操作符。上面ar>>m_MyVar说白即是在执行一个以ar和m_MyVar 为参数的函数,类似于function(ar,m_MyVar)罢了。我们当然不能传递一个它不认识的参数类型,也因此不会执行function(ar,m_MyObject)了。
这里我们可以用指针。让MyClass从Cobject派生,一切又起了质的变化,假设我们定义了:MyClass *pMyClass = new MyClass;因为MyClass从CObject派生,根据虚函数原理,pMyClass也是一个CObject*,即pMyClass指针是CArchive类可认识的。所以执行上述function(ar, pMyClass),即ar << pMyClass是没有太多的问题(在保证了MyClass对象可以动态创建的前提下)。
回过头来,如果想让MyClass类对象能Serialize,就得让MyClass从CObject派生,Serialize()函数在CObject里为虚,MyClass从CObject派生之后就可以根据自己的要求去改写它,像上面改写CMyDoc::Serialize()方法一样。这样MyClass就得到了属于MyClass自己特有的Serialize()函数。
现在,程序就可以这样写:
……
#include “MyClass.h”
……
void CMyDoc::Serialize(CArchive& ar)
{
//在此调用MyClass重写过的Serialize()
m_MyObject.Serialize(ar); // m_MyObject为MyClass实例
}
至此,串行化工作就算完成了,简单直观的讲:从CObject派生自己的类,重写Serialize()。在此过程中,我刻意安排:在没有用到DECLARE_SERIAL/IMPLEMENT_SERIAL宏,也没有用到CArray等模板类的前提下就完成了串行化的工作。我看过某些书,总是一开始就讲DECLARE_SERIAL/IMPLEMENT_SERIAL宏或马上用CArray模板,让读者觉得串行化就是这两个东西,导致许多朋友因此找不着北。
大家看到了,没有DECLARE_SERIAL/IMPLEMENT_SERIAL宏和CArray等数据结构模板也依然可以完成串行化工作。
3、 CArchive类
最后再补充讲解一下有些抽象的CArchive。我们先看以下程序(注:以下程序包含动态创建等,请包含DECLARE_SERIAL/IMPLEMENT_SERIAL宏)
void MyClass::Serialize(CArchive& ar)
{
if (ar.IsStoring()) //读写判断
{
ar<< m_pMyVar; //问题:ar 如何把m_pMyVar所指的对象变量保存到磁盘?
}
else
{
pMyClass = new MyClass; //准备存储空间
ar>> m_pMyVar;
}
}
为回答上面的问题,即“ar< “ar< 从上面可以看出,因为Serialize()为虚函数,即“ar<CArchive& operator<<( CArchive& ar, const CObject* pOb)
{
…………
//以下为CRuntimeClass链表中找到、识别pOb资料。
CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
//保存pClassRef即类信息(略)
((CObject*)pOb)->Serialize();//保存MyClass数据
…………
}