serialization:串行化(Serialization)介绍



串行化是微软提供用于对对象进行文件I/O种机制该机制在框架(Frame)/文档(Document)/视图(View) 模式中得到了很好应用很多人对什么是串行化、如何使对象具有串行化能力和如何使用串行化功能等问题都不甚明了本文试图对串行化做个简单解释由于本人对串行化功能使用也不多不足的处敬请谅解 MFC 框架/文档/视图结构中文件读写

CFile是MFC类库中所有文件类基类所有MFC提供文件I/O功能都和这个类有关很多情况下大家都喜欢直接CFile::Write/WriteHuge来写文件CFile::Read/ReadHuge来读文件这样文件I/O其实和不使用MFC文件 I/O没有什么区别甚至和以前ANSI C文件I/O也没有多少差别所差别不外乎是API区别而已
在开始学习C时候大家定对cin/cout非常熟悉这两个对象使用非常明了<<和>>运算符进行 I/O其使用格式为:

//举例代码1 i; cin >> i; //here do something to object i cout << i;

使用这种方式进行I/O好处时利用运算符重载功能可以用个语句完成对系列对象读写而不需要区分对象具体类型MFC提供了类CArchive实现了运算符<<和>>重载希望按照前面cin和cout 方式进行文件I/O通过和CFile类配合不仅仅实现了对简单类型如/float等文件读写而且实现了对可序列化对象(Serializable Objects,这个概念后面描述)文件读写
般情况下使用CArchive对对象进行读操作过程如下:

//举例代码2 //定义文件对象和文件异常对象 CFile file; CFileException fe; //以读方式打开文件 (!file.Open(filename,CFile::modeRead,&fe)) { fe.ReportError; ; } //构建CArchive 对象 CArchive ar(&file,CArchive::load); ar >> obj1>>obj2>>obj3...>>objn; ar.Flush; //读完毕关闭文件流 ar.Close; file.Close; [Page]

使用CArchive对对象进行写操作过程如下:

//举例代码3 //定义文件对象和文件异常对象 CFile file; CFileException fe; //以读方式打开文件 (!file.Open(filename,CFile::modeWrite|CFile::modeCreate,&fe)) { fe.ReportError; ; } //构建CArchive 对象 CArchive ar(&file,CArchive::load); ar << obj1<<obj2<<obj3...<<objn; ar.Flush; //写完毕关闭文件流 ar.Close; file.Close;

可见对于个文件而言如果文件内对象排列顺序是固定那么对于文件读和写从形式上只有使用运算符区别在MFC框架/文档/视图结构中个文档内部对象构成往往是固定这种情况下写到文件中时对象在文件中布局也是固定因此CDocument利用其基类CObject提供Serilize虚实现自动文档读写
当用户在界面上选择文件菜单/打开文件(ID_FILE_OPEN)时CWinApp派生类OnFileOpen被自动它通过文档模板创建(MDI)/重用(SDI)框架、文档和视图对象并最终CDocument::OnOpenDocument来读文件CDocument::OnOpenDocument 处理流程如下:

//举例代码4 BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName) { (IsModied) TRACE0(\"Warning: _disibledevent=>



同样当用户选择菜单文件/文件保存(ID_FILE_SAVE)或者文件/另存为...(ID_FILE_SAVEAS)时通过CWinApp::OnFileSave和CWinApp::OnFileSaveAs 最终CDocument::OnSaveDocument这个处理如下:

//举例代码5 BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName) { CFileException fe; CFile* pFile = NULL; pFile = GetFile(lpszPathName, CFile::modeCreate | CFile::modeReadWrite | CFile::shareExclusive, &fe); (pFile NULL) { ReportSaveLoadException(lpszPathName, &fe, TRUE, AFX_IDP_INVALID_FILENAME); FALSE; } CArchive saveArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete); saveArchive.m_pDocument = this; saveArchive.m_bForceFlat = FALSE; TRY { CWaitCursor wait; Serialize(saveArchive); // save me 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); FALSE; } END_CATCH_ALL SetModiedFlag(FALSE); // back to unmodied TRUE; // success } [Page]

从前面两段代码可以看出文件读和文件写结构基本相同并且最终都了CObject::Serialize完成对文档自己读和写(参见注释中save me和load me)对于用AppWizard自动生成MDI和SDI系统自动生成了这个重载实现缺省实现为:

//举例代码6 void CMyDoc::Serialize(CArchive& ar) { (ar.IsStoring) { // TODO: add storing code here } { // TODO: add loading code here } }

如果个对VC非常熟悉喜欢手工生成所有代码(当然这是非常浪费时间也是没有必要)那么他提供CDocument派生类也应该实现这个缺省Serialize否则系统在文件读写时只能CObject::Serialize这个什么都不做当然也无法完成对特定对象文件保存/载入工作当然用户也可以截获ID_FILE_OPEN等菜单实现自己文件读写功能但是这样代码将变得非常烦琐也不容易阅读
回到CMyDoc::Serialize这个通过对ar对象判断决定当前是在读还是在写文件由于AppWizard不知道你文档是干什么所以它不会给你添加实际文件读写代码假设你文档中有 3个对象m_Obj_a,m_Obj_b,m_Obj_c那么实际代码应该为:

//举例代码7 void CMyDoc::Serialize(CArchive& ar) { (ar.IsStoring) { ar << m_Obj_a << m_Obj_b << m_Obj_c; } { ar >> m_Obj_a >> m_Obj_b >> m_Obj_c; } } [Page]



可串行化对象(Serializable Object)
要利用举例代码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 # !d(__PICTURE_H__) # __PICTURE_H__ # _MSC_VER > 1000 #pragma _disibledevent=>


注意:由于CRuntimeClass要求这个对象必须能够被例子化因此虽然Draw没有任何绘图操作这个类还是没有把它定义成纯虚
对象在CDocument派生类中保存和文件I/O过程
为了简化设计在CDocument类派生类中采用MFC提供模板类CPtrList来保存对象该对象定义为:
protected: CTypedPtrList m_listPictures;

由于CTypedPtrList和CPtrList都没有实现Serialize因此不能够通过ar << m_listPictures和ar >> m_listPictures 来序列化对象因此CPictureDocSerialize需要如下实现:
void CTsDoc::Serialize(CArchive& ar) { POSITION pos; (ar.IsStoring) { // TODO: add storing code here pos = m_listPictures.GetHeadPosition; while(pos != NULL) { ar << m_listPictures.GetNext (pos); } } { // TODO: add loading code here RemoveAll; CPicture * pPicture; do{ try { ar >> pPicture; TRACE(\"Read Object %d\\n\",pPicture->GetType ); m_listPictures.AddTail(pPicture); } catch(CException * e) { e->Delete ; ; } }while(pPicture != NULL); } m_pCurrent = NULL; SetModiedFlag(FALSE); } [Page]

实现派生类串行化功能
几何图形支持直线、矩形、 3角形、椭圆等图形分别以类CLine、CRectangle、CTriangle和CEllipse实现以类CLine为例实现串行化功能:
从CPicture派生CLine在CLine类定义中增加如下成员变量:
CPo m_ptStart,m_ptEnd;
在该行下行增加如下宏:
DECLARE_SERIAL(CLine)
实现Serialize
void CLine::Serialize(CArchive & ar) { CPicture::Serialize(ar); (ar.IsLoading) { ar>>m_ptStart.x>>m_ptStart.y>>m_ptEnd.x>>m_ptEnd.y; }{ ar<<m_ptStart.x<<m_ptStart.y<<m_ptEnd.x<<m_ptEnd.y; } }


在CPP文件中增加
IMPLEMENT_SERIAL(CLine,CPicture,TYPE_LINE);
这样定义CLine就具有串行化功能其他图形类可以类似定义



Tags:  初始化串行口失败 什么是串行化 串行化 serialization

延伸阅读

最新评论

发表评论