ar.Flush();
//写完毕,关闭文件流
ar.Close();
file.Close();
可见,对于一个文件而言,如果文件内对象的排列顺序是固定的,那么对于文件读和写从形式上只有使用的运算符的不同。
在MFC的框架/文档/视图结构中,一个文档的内部对象的构成往往是固定的,这种情况下,写到文件中时对象在文件中的布局也是固定的。
因此CDocument利用其基类CObject提供的Serilize虚函数,实现自动文档的读写。
当用户在界面上选择文件菜单/打开文件(ID_FILE_OPEN)时,CWinApp派生类的OnFileOpen函数被自动调用,它通过文档模板创建(MDI)/重用(SDI)框架、文档和视图对象,并最终调用CDocument:
:
OnOpenDocument来读文件,CDocument:
:
OnOpenDocument的处理流程如下:
//示例代码4
BOOLCDocument:
:
OnOpenDocument(LPCTSTRlpszPathName)
{
if(IsModified())
TRACE0("Warning:
OnOpenDocumentreplacesanunsaveddocument.\n");
CFileExceptionfe;
CFile*pFile=GetFile(lpszPathName,
CFile:
:
modeRead|CFile:
:
shareDenyWrite,&fe);
if(pFile==NULL)
{
ReportSaveLoadException(lpszPathName,&fe,
FALSE,AFX_IDP_FAILED_TO_OPEN_DOC);
returnFALSE;
}
DeleteContents();
SetModifiedFlag();//dirtyduringde-serialize
CArchiveloadArchive(pFile,CArchive:
:
load|CArchive:
:
bNoFlushOnDelete);
loadArchive.m_pDocument=this;
loadArchive.m_bForceFlat=FALSE;
TRY
{
CWaitCursorwait;
if(pFile->GetLength()!
=0)
Serialize(loadArchive);//loadme
loadArchive.Close();
ReleaseFile(pFile,FALSE);
}
CATCH_ALL(e)
{
ReleaseFile(pFile,TRUE);
DeleteContents();//removefailedcontents
TRY
{
ReportSaveLoadException(lpszPathName,e,
FALSE,AFX_IDP_FAILED_TO_OPEN_DOC);
}
END_TRY
DELETE_EXCEPTION(e);
returnFALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE);//startoffwithunmodified
returnTRUE;
}
同样,当用户选择菜单文件/文件保存(ID_FILE_SAVE)或者文件/另存为...(ID_FILE_SAVEAS)时,通过CWinApp:
:
OnFileSave和CWinApp:
:
OnFileSaveAs最终调用CDocument:
:
OnSaveDocument,这个函数处理如下:
//示例代码5
BOOLCDocument:
:
OnSaveDocument(LPCTSTRlpszPathName)
{
CFileExceptionfe;
CFile*pFile=NULL;
pFile=GetFile(lpszPathName,CFile:
:
modeCreate|
CFile:
:
modeReadWrite|CFile:
:
shareExclusive,&fe);
if(pFile==NULL)
{
ReportSaveLoadException(lpszPathName,&fe,
TRUE,AFX_IDP_INVALID_FILENAME);
returnFALSE;
}
CArchivesaveArchive(pFile,CArchive:
:
store|CArchive:
:
bNoFlushOnDelete);
saveArchive.m_pDocument=this;
saveArchive.m_bForceFlat=FALSE;
TRY
{
CWaitCursorwait;
Serialize(saveArchive);//saveme
saveArchive.Close();
ReleaseFile(pFile,FALSE);
}
CATCH_ALL(e)
{
ReleaseFile(pFile,TRUE);
TRY
{
ReportSaveLoadException(lpszPathName,e,
TRUE,AFX_IDP_FAILED_TO_SAVE_DOC);
}
END_TRY
DELETE_EXCEPTION(e);
returnFALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE);//backtounmodified
returnTRUE;//success
}
从前面两段代码可以看出,文件读和文件写的结构基本相同,并且最终都调用了CObject:
:
Serialize函数完成对文档自己的读和写(参见注释中的saveme和loadme)。
对于用AppWizard自动生成的MDI和SDI,系统自动生成了这个函数的重载实现,缺省的实现为:
//示例代码6
voidCMyDoc:
:
Serialize(CArchive&ar)
{
if(ar.IsStoring())
{
//TODO:
addstoringcodehere
}
else
{
//TODO:
addloadingcodehere
}
}
如果一个对VC非常熟悉的人,喜欢手工生成所有的代码(当然这是非常浪费时间也是没有必要的),那么他提供的CDocument派生类也应该实现这个缺省的Serialize函数,否则,系统在文件读写时只能调用CObject:
:
Serialize,这个函数什么都不做,当然也无法完成对特定对象的文件保存/载入工作。
当然,用户也可以截获ID_FILE_OPEN等菜单,实现自己的文件读写功能,但是这样的代码将变得非常烦琐,也不容易阅读。
回到CMyDoc:
:
Serialize函数。
这个函数通过对ar对象的判断,决定当前是在读还是在写文件。
由于AppWizard不知道你的文档是干什么的,所以它不会给你添加实际的文件读写代码。
假设你的文档中有三个对象m_Obj_a,m_Obj_b,m_Obj_c,那么实际的代码应该为:
//示例代码7
voidCMyDoc:
:
Serialize(CArchive&ar)
{
if(ar.IsStoring())
{
ar<}
else
{
ar>>m_Obj_a>>m_Obj_b>>m_Obj_c;
}
}
可串行化对象(SerializableObject)
要利用示例代码7中的方式进行文件I/O的一个基本条件是:
m_Obj_a等对象必须是可串行化的对象。
一个可串行化对象的条件为:
∙这个类从CObject派生)
∙该类实现了Serialize函数
∙该类在定义时使用了DECLARE_SERIAL宏
∙在类的实现文件中使用了IMPLEMENT_SERIAL宏
∙这个类有一个不带参数的构造函数,或者某一个带参数的构造函数所有的参数都提供了缺省参数
这里,可串行化对象条件中没有包括简单类型,对于简单类型,CArchive基本都实现了运算符<<和>>的重载,所以可以直接使用串行化方式进行读写。
从CObject类派生
串行化要求对象从CObject派生,或者从一个CObject的派生类派生。
这个要求比较简单,因为几乎所有的类(不包括CString)都是从CObject派生的,因此对于从MFC类继承的类都满足这个要求。
对于自己的数据类,可以指定它的基类为CObject来满足这个要求。
实现Serialize函数
Serialize函数是对象真正保存数据的函数,是整个串行化的核心。
其实现方法和CMyDoc:
:
Serialize一样,利用CArchive:
:
IsStoring和CArchive:
:
IsLoading判断当前的操作,并选择<<和>>来保存和读取对象。
使用DECLARE_SERIAL宏
DECLARE_SERIAL宏包括了DECLARE_DYNAMIC和DECLARE_DYNCREATE功能,它定义了一个类的CRuntimeClass相关信息,并实现了缺省的operator>>重载。
实现了该宏以后,CArchive就可以利用ReadObject和WriteObject来进行对象I/O,并能够在事先不知道类型的情况下从文件中读对象。
使用IMPLEMENT_SERIAL
DECLARE_SERIAL宏和IMPLEMENT_SERIAL宏必须成对出现,否则DECLARE_SERIAL宏定义的实体将无法实现,最终导致连接错误。
缺省构造函数
这是CRuntimeClass:
:
CreateObject对对象的要求。
特殊情况
∙只通过Serialize函数对对象读写,而不使用ReadObject/WriteObject和运算符重载时,前面的可串行化条件不需要,只要实现Serialize函数即可。
∙对于现存的类,如果它没有提供串行化功能,可以通过使用重载友元operator<<和operator>>来实现。
例子
假设需要实现一个几何图形显示、编辑程序,支持可扩展的图形功能。
这里不想讨论具体图形系统的实现,只讨论图像对象的保存和载入。
基类CPicture
每个图形对象都从CPicture派生,这个类实现了串行化功能,其实现代码为:
//头文件picture.h
#if!
defined(__PICTURE_H__)
#define__PICTURE_H__
#if_MSC_VER>1000
#pragmaonce
#endif//_MSC_VER>1000
constintTYPE_UNKNOWN=-1;
classCPicture:
publicCObject
{
intm_nType;//图形类别
DECLARE_SERIAL(CPicture)
public:
CPicture(intm_nType=TYPE_UNKNOWN):
m_nType(m_nType){};
intGetType()const{returnm_nType;};
virtualvoidDraw(CDC*pDC);
voidSerialize(CArchive&ar);
};
#endif
//cpp文件picture.cpp
#include"stdafx.h"
#include"picture.h"
#ifdef_DEBUG
#definenewDEBUG_NEW
#undefTHIS_FILE
staticcharTHIS_FILE[]=__FILE__;
#endif
voidCPicture:
:
Draw(CDC*pDC)
{
//基类不实现绘图功能,由派生类实现
}
voidCPicture:
:
Serialize(CArchive&ar)
{
if(ar.IsLoading())
{
ar<}else{
ar>>m_nType;
}
}
注意:
由于CRuntimeClass要求这个对象必须能够被实例化,因此虽然Draw函数没有任何绘图操作,这个类还是没有把它定义成纯虚函数。
对象在CDocument派生类中的保存和文件I/O过程
为了简化设计,在CDocument类派生类中,采用MFC提供的模板类CPtrList来保存对象。
该对象定义为:
protected:
CTypedPtrListm_listPictures;
由于CTypedPtrList和CPtrList都没有实现Serialize函数,因此不能够通过ar<>m_listPictures来序列化对象,因此CPictureDoc的Serialize函数需要如下实现:
voidCTsDoc:
:
Serialize(CArchive&ar)
{
POSITIONpos;
if(ar.IsStoring())
{
//TODO:
addstoringcodehere
pos=m_listPictures.GetHeadPosition();
while(pos!
=NULL)
{
ar<}
}
else
{
//TODO:
addloadingcodehere
RemoveAll();
CPicture*pPicture;
do{
try
{
ar>>pPicture;
TRACE("ReadObject%d\n",pPicture->GetType());
m_listPictures.AddTail(pPicture);
}
catch(CException*e)
{
e->Delete();
break;
}
}while(pPicture!
=NULL);
}
m_pCurrent=NULL;
SetModifiedFlag(FALSE);
}
实现派生类的串行化功能
几何图形程序支持直线、矩形、三角形、椭圆等图形,分别以类CLine、CRectangle、CTriangle和CEllipse实现。
以类CLine为例,实现串行化功能:
1.从CPicture派生CLine,在CLine类定义中增加如下成员变量:
2.CPointm_ptStart,m_ptEnd;
3.在该行下一行增加如下宏:
4.DECLARE_SERIAL(CLine)
5.实现Serialize函数
6.voidCLine:
:
Serialize(CArchive&ar)
7.{
8.CPicture:
:
Serialize(ar);
9.if(ar.IsLoading())
10.{
11.ar>>m_ptStart.x>>m_ptStart.y>>m_ptEnd.x>>m_ptEnd.y;
12.}else{
13.ar<14.}
15.}
16.在CPP文件中增加
17.IMPLEMENT_SERIAL(CLine,CPicture,TYPE_LINE);
这样定义的CLine就具有串行化功能,其他图形类可以类似定义。
附注
本文仓促草就,不足之处在所难免。
请发现谬误者给我来信说明,谢谢。
最新评论[发表评论][文章投稿]
查看所有评论
推荐给好友
打印
罗恩大哥,你好,我在实际变成中想自动载入上次的文件有什么好的方法吗?
?
(yangqifengfan发表于2005-3-258:
24:
00)
写的不错。
。
。
。
(徐明刚发表于2004-10-1212:
27:
00)
罗恩大哥,你好,我是vc++初学者,(以前看过windows程序设计(第5版),
机工社的《c++精髓 软件工程方法》),
最近在看《深入浅出MFC》,第8章时,里边也说到了Serialize,我不是很懂,
我看你的个人专栏里边看了串行化(Serialization)这篇文章也提到了,
你能顺便给我解释一下吗Serialize
在你的示例代码7中有 ar >> m_Obj_a
这里operator >>是不是调用
_AFX_INLINE CArchive& AFXAPI operator>>(CArchive& ar,CObject*& pOb) ?
如果是,那么IMPLEMENT_SERIAL宏 展开后的
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb)
在哪里用到?
还有为什么IMPLEMENT_SERIAL宏 展开后的
有CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb)
没有CArchive& AFXAPI operator<<(CArchive& ar, class_name* &pOb)
在示例7中出现ar << m_Obj_a(yckyck2001发表于2004-7-1114:
03:
00)
我就把你当成我现在的目标了(弃卒发表于2004-6-1415:
25:
00)
将内存指针系列化可不是个好主意(m_listPictures这一段)(jiangsanhuo发表于2004-2-49:
04:
00)
最明显的错误是:
ar >> pPicture;之前没有为pPicture分配内存:
pPicture = new CPicture;(eeixy2000发表于2003-12-2719:
12:
00)
唉,希望这个文档不要被大家看成是教人家怎么绘图的:
((阿荣发表于2003-11-2013:
51:
00)
建议看看vckbase上面得EastDraw程序,该文章中说得所有内容都可以从那个例子中找到,并且那个例子中还有undo功能得实现。
(coyer发表于2003-11-617:
14:
00)
不错,建议以后多发表类似的文章!
(xiaojin发表于2003-11-423:
06:
00)
如果发生版本升级,那么新版本的程序能不能兼容旧版本本来就需要看代码怎么设计,这种情况下,不使用串行化一样不能达到目的。
有两个方法可以解决这个问题:
1)根据扩展名不同实现不同的Serialize功能,可以在S