这一部分:侯捷大大说要模拟的东西太多了,所以没有了样例工程,说实话还有点小失落呢~~~~~~
原本不准备写这次的笔记了,不过想想还是记录下来,至少以后留个念想~~,
(还有一件事:侯捷大大说在第8章会详细地剖析这部分内容,所以后面我应该还是会有机会来详细的看看这个模块的内容~~~~小惊喜。。。暂时就让我先体会体会精神吧~~)
废话不多说了,直接进入正题:
MFC有自己实现了一套Serialize的机制,目的即是封装关于序列化过程中的文件名的选择,文件的开关,缓冲区的建立,数据的读写,提取运算符(>>)和插入运算符的重载(<<),对象的动态创建,当然动态创建在上一章已经全部完成了~~~
在MFC中每次序列化记录对象内容的时候都是会先写入一个代码:表示次类型的对象是否已经被记录过了,如果是新的类型就乖乖的记录其类名称及基本信息,如果是已经记录过了的类型的对象,则直接以代码表示,这样可以节省文件大小和程序解析的时间。(可惜这里没有代码)
在MFC中,每一个可以写入到文件或者从文件读取的类都应该有它自己的Serailize函数,负责他自己的数据读写文件的操作,且此类应当改写<<和>>运算符,以负责将数据流与文件缓冲区(archive)的交互。
对于>>和<<的俩个运算符的重载,根据MFC的尿性,还是喜欢通过使用宏定义来实现这些(大大减少代码的冗余,不过貌似可读性也降低了一点),首先说一点,类之所以可以进行读写操作,前提是拥有动态操作的能力,所以关于序列化的宏也不由自主的变成了之前动态创建宏的再封装~~~~(基于之前动态创建基础的扩展)
样例代码如下:
#define DECLARE_SERIAL(class_name) \
DECLARE_DYNCREATE(class_name) \
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 ; } \
_IMPLEMENT_RUNTIMECLASS(class_name , base_class_name ,wSchema , \
class_name::CreateObject) \
CArchive& AFXAPI operator>>(CArchive& ar ,class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; }
为了每一个被处理的对象被处理之前可以检查是否已经储存过该类对象,记录版本号码,记录文件名等等,我们的类型存储单元CRuntimeClass也需要做出一些对应的改变:
样例代码如下:
struct CRuntimeClass
{
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema;
CObject* (PASCAL* m_pfnCreateObject)();
CRuntimeClass* m_pBaseClass;
CObject* CreateObject();
void Store(CArcive& ar) const;
statc CRuntimeClass* PASCAL load(CArchive& ar, UINT* pwSchemaNum);
static CRuntimeClass* pFirstClass;
CRuntimeClass* m_pNextClass;
};
其实也就是加了一个Store和Load函数(这里的Load函数才是原貌,之前的版本由于无需考虑哪些问题,所以直接过滤了参数)
最后别忘了在每个需要序列化类的实现中加上一个声明:
void Serialize(CArchive&)
之前说过的,每一个可以序列化的类都要有自己的序列化函数,,,
其实我想这个声明可以加在宏定义里面的(反正每个序列化的类都要有这个东西),实现再单独写不就好了嘛,不过我猜大概是由于他们不喜欢产生这种会报错的代码,所以~~~~这个函数我们只得自己来声明~~~~