深入浅出MFC 第8章 关于序列化(Serialize)的一些问题
来源:互联网 发布:qq人工智能聊天机器人 编辑:程序博客网 时间:2024/05/21 02:49
很好的一篇文章,转载自:http://blog.csdn.net/jwj070524/article/details/10263191
不知不觉跟随者侯捷的脚步对着电子书看了将近600页,就一个感觉:云里雾里。函数调用一层嵌一层,还有那该死的virtual关键字,每次调用时都得长个心眼,先要看看派生类有没有重写它。吐槽归吐槽,MFC设计之精巧不容质疑,其中的RTTI(Running Time Type Information),动态生成(Dynamic Create),序列化(Serialization),消息映射(Message Map),命令路由(Command Route)(最后这两个还没深入研究),我已经无法用更好的赞美之词去评价它了,至少,在了解它之前我从没见过类似的设计。
好了,下面是我发现的问题以及解释。
电子书第602页(也应该是书本的第537页,我只有电子书,所以这是电子书上显示的页码)上标题为“在CObList中加入CStroke以外的类别”这一小节,侯捷用三个类做写文件的测试,分别是CStroke,CRectangle,CCircule。生成的文件内容如下:
000000: 06 00 FF FF 01 00 07 00 43 53 74 72 6F 6B 65 02 ........CStroke.
000010: 00 02 00 6E 00 00 00 24 00 00 00 6E 00 00 00 24 ...n...$...n...$
000020: 00 00 00 FF FF 01 00 0A 00 43 52 65 63 74 61 6E .........CRectan
000030: 67 6C 65 11 00 00 00 22 00 00 00 33 00 00 00 44 gle...."...3...D
000040: 00 00 00 FF FF 01 00 07 00 43 43 69 72 63 6C 65 .........CCircle
000050: 55 00 00 00 66 00 00 00 77 00 00 00 01 80 02 00 U...f...w.......
000060: 02 00 6E 00 00 00 55 00 00 00 6E 00 00 00 55 00 ..n...U...n...U.
000070: 00 00 03 80 11 00 00 00 22 00 00 00 33 00 00 00 ........"...3...
000080: 44 00 00 00 05 80 55 00 00 00 66 00 00 00 77 00 D.....U...f...w.
000090: 00 00 ..
图8-10a TEST.SCB 文件内容,文件全长146 个字节。
先回顾一下,当CStroke,CRectangle,CCircule的对象第一次存入文件时CArchive的实例会自动将类的信息一并写入文件,包括FFFF标识(新的类的标识),nSchema(版本标识),类名的长度,类名,存储的内容。具体一点比如说文件中的第一个对象:FF FF 01 00 07 00 43 53 74 72 6F 6B 65 02 00 02 00 6E 00 00 00 24 00 00 00 6E 00 00 00 24 00 00 00。第一个字(WORD)FFFF表示是一个新的类,第二个字0001(注意Little Endian)表示类的索引值,第三个字0007表示类名的长度(长度是7),接着7个字节43 53 74 72 6F 6B 65就是类名了("CStroke"),然后剩下的就是实例中存储的内容,由CStroke类的Serialize函数控制,不再解释。读完之后接着又是FFFF标识,表明又是一个新的类,,,,,,(注意:文件的第一个字节是0006表明有六个实例存在文件上这是由CTypedPtrList类的Sreialize函数控制的,感觉奇怪吗?如果你仔细跟踪源代码就会明白)
好了,前面三个类得的对象已经存储了,再次,我们把全新的三个类的实例按原先顺序再存储一次。由于这三个类的信息已经在先前的一次存数过程中配置好了,所以就不必再存那些多余的东西,MFC所做的就是将原先的索引值放到文件中去,代表一个已经在前面出现过的类。我现在的疑惑是:“为什么这三个索引值是8001,8003,8005而不是期望中的8001,8002,8003?”
羊毛出在羊身上,百思不得其解之下我翻开MFC之中这部分的源代码,在ARCOBJ.CPP这个文件之中(我用的是VC6),具体关注以下三个函数:
void CArchive::MapObject(const CObject* pOb)
void CArchive::WriteObject(const CObject* pOb)
void CArchive::WriteClass(const CRuntimeClass* pClassRef)
我只考虑存储的情景,读文件也是类似的操作。
先分析MapObject函数,这个函数主要完成两个任务,来维护存放在CArchive中的一个映射(Map)。1.若Map为空,对它分配内存并初始化,把m_nMapCount置1,这点需注意。2.若pObj不空(表示它指向的实例即将要写到文件中去),则把pObj放到映射中去,key是pObj,value是(void*)m_nMapCount++。总结:一个key对应一个独一无二的value,n_MapCount在赋值之后会自动加1。
- void CArchive::MapObject(const CObject* pOb)
- {
- if (IsStoring())
- {
- if (m_pStoreMap == NULL)
- {
- // initialize the storage map
- // (use CMapPtrToPtr because it is used for HANDLE maps too)
- m_pStoreMap = new CMapPtrToPtr(m_nGrowSize);
- m_pStoreMap->InitHashTable(m_nHashSize);
- m_pStoreMap->SetAt(NULL, (void*)(DWORD)wNullTag);
- m_nMapCount = 1;
- }
- if (pOb != NULL)
- {
- CheckCount();
- (*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
- }
- }
- else
- {
- if (m_pLoadArray == NULL)
- {
- // initialize the loaded object pointer array and set special values
- m_pLoadArray = new CPtrArray;
- m_pLoadArray->SetSize(1, m_nGrowSize);
- ASSERT(wNullTag == 0);
- m_pLoadArray->SetAt(wNullTag, NULL);
- m_nMapCount = 1;
- }
- if (pOb != NULL)
- {
- CheckCount();
- m_pLoadArray->InsertAt(m_nMapCount++, (void*)pOb);
- }
- }
- }
再看WriteObject,假设pOb不为空。若pOb所指向的对象已经存到文件中去了(也就是进入第二个if子句),那么只要往文件中存储先前那个对象的索引值就可以了,这就避免了同一份数据重复存储,并且还原时能够做到彻底还原(存储时两个指针指向同一个对象,读取后两个指针依旧指向同一个对像)。若pOb指向的对象是第一次存储(进入最后的那个else),先调用WriteClass,接着在Map中给pOb加一个索引值。
至此似乎没有什么异常,但是为什么文件中的索引值不是连续的呢?谜底就要揭开,蹊跷就在前面最后两小句中“先调用WriteClass,接着在Map中给pOb加一个索引值”。
- void CArchive::WriteObject(const CObject* pOb)
- {
- // object can be NULL
- ASSERT(IsStoring()); // proper direction
- DWORD nObIndex;
- ASSERT(sizeof(nObIndex) == 4);
- ASSERT(sizeof(wNullTag) == 2);
- ASSERT(sizeof(wBigObjectTag) == 2);
- ASSERT(sizeof(wNewClassTag) == 2);
- // make sure m_pStoreMap is initialized
- MapObject(NULL);
- if (pOb == NULL)
- {
- // save out null tag to represent NULL pointer
- *this << wNullTag;
- }
- else if ((nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb]) != 0)
- // assumes initialized to 0 map
- {
- // save out index of already stored object
- if (nObIndex < wBigObjectTag)
- *this << (WORD)nObIndex;
- else
- {
- *this << wBigObjectTag;
- *this << nObIndex;
- }
- }
- else
- {
- // write class of object first
- CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
- WriteClass(pClassRef);
- // enter in stored object table, checking for overflow
- CheckCount();
- (*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
- // cause the object to serialize itself
- ((CObject*)pOb)->Serialize(*this);
- }
- }
我给你举个例子,你就明白了:CStroke类是CObject的派生类,我现在有一个CStroke类的实例化要写到文件中去,假设文件中什么都没有。经过重重函数嵌套调用,终于来到了CArchive中的WriteObject方法。在WriteObject中,首先初始化Map成员,并令m_nMapCount=1,然后由于Map为空,进入最后一个else中。生成pClassRef后调用WriteClass,在WriteClass中判断pClassRef是否在Map中(肯定不在,Map是空的),所以果断地把pClassRef加到Map中,并令m_nMapCount自增,此时m_nMapCount为2,pClassRef的索引为1。跳出WriteClass回到WriteObject,执行下面这句:
(*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
这时候m_nMapCount为3,pObj的索引为2。
明白了吗,存储一个全新的类的实例,索引会自增两次!一个是CRuntimeClass指针,一个是CStroke指针。
如果以后再次存储一个新的CStroke类的实例,索引只会自增一次,因为CStroke类中的CRuntimeClass成员是静态(static)的,在程序执行过程中它的地址是不会变的。
好了,根据文章开始所介绍的那个测试的例子,当所有的成员都序列化之后,Map应该就是这个样子:
指针(Map中的Key)Map中的索引(Value) 文件中的标志(Tag)&CStroke::classCStroke1CStroke*2FFFF0001&CRectangle::classCRectangle3
CRectangle*4FFFF0001&CCircle::classCCircle5
CCircle*6FFFF0001CStroke*78001CRectangle*88003CCircle*98005
再解释下,当存储一个新类的实例时,文件中的Tag应该是它的m_nSchema(版本号,在CRuntime::Store函数内),加上前面的FFFF表示一个新类;当存储一个旧类的实例时,文件中的Tag应该是那个类中的静态RuntimeClass成员的索引与0x8000 位运算(或)操作的值也就是0x8000 | 0001 =》 0x8001这样子(WriteClass函数中处理)。
最后把WriteClass的码贴上来,看看是不是这样?写文件是这样,读文件也类似。
- void CArchive::WriteClass(const CRuntimeClass* pClassRef)
- {
- ASSERT(pClassRef != NULL);
- ASSERT(IsStoring()); // proper direction
- if (pClassRef->m_wSchema == 0xFFFF)
- {
- TRACE1("Warning: Cannot call WriteClass/WriteObject for %hs.\n",
- pClassRef->m_lpszClassName);
- AfxThrowNotSupportedException();
- }
- // make sure m_pStoreMap is initialized
- MapObject(NULL);
- // write out class id of pOb, with high bit set to indicate
- // new object follows
- // ASSUME: initialized to 0 map
- DWORD nClassIndex;
- if ((nClassIndex = (DWORD)(*m_pStoreMap)[(void*)pClassRef]) != 0)
- {
- // previously seen class, write out the index tagged by high bit
- if (nClassIndex < wBigObjectTag)
- *this << (WORD)(wClassTag | nClassIndex);
- else
- {
- *this << wBigObjectTag;
- *this << (dwBigClassTag | nClassIndex);
- }
- }
- else
- {
- // store new class
- *this << wNewClassTag;
- pClassRef->Store(*this);
- // store new class reference in map, checking for overflow
- CheckCount();
- (*m_pStoreMap)[(void*)pClassRef] = (void*)m_nMapCount++;
- }
- }
如有错误,欢迎指正。
- 深入浅出MFC 第8章 关于序列化(Serialize)的一些问题
- 深入浅出MFC 第8章 关于序列化(Serialize)的一些问题
- 深入浅出MFC 第8章 关于序列化(Serialize)的一些问题
- MFC序列化Serialize
- MFC的Serialize序列化函数
- Serialize序列化函数(MFC)
- 关于序列化(serialize)
- MFC 创建可序列化的对象 Serialize用法
- VC序列化问题(Serialize)
- VC序列化问题(Serialize)
- VC序列化问题(Serialize)
- 浅谈对象的序列化(Serialize)
- 浅谈对象的序列化(Serialize)
- 有关ajax表单序列化serialize()的问题
- 《深入浅出MFC》第109页,关于Iskindof的介绍
- 关于虚拟函数的一些总结 (参考:深入浅出MFC 第二版 候俊杰)
- 关于虚拟函数的一些总结 (参考:深入浅出MFC 第二版 候俊杰)
- 字符窜序列化serialize问题
- [Java] Spring 3.0 /id.vs.name/简单属性的注入/bean中的scope属性/集合注入/自动装配/生命周期/
- Android通讯录开发之解决快速搜索联系人线程同步问题
- iOS网络通信http之NSURLConnection
- CentOS 6.4 安装源(163的源)
- 到Autodesk 应用程序商店里一键下载大量插件;让你的插件传遍Autodesk产品的用户
- 深入浅出MFC 第8章 关于序列化(Serialize)的一些问题
- 转载和积累系列 - Lua 字符串库
- 利用Cobbler批量布署CentOS
- 黑马程序员_javaIO流_1
- 汇编语言中PTR的含义及作用以及Mov和lea的区别
- 链表:顺序链表和单链表
- rman对特定表空间迁移技术
- Java垃圾回收(garbage collection)介绍
- PL/SQL中复制中文再粘贴出现乱码问题的解决