串行化(Serialization)
副标题#e#
串行化是微软提供的用于对工具举办文件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
int i;
cin >> i;
//here do something to object i
cout << i;
利用这种方法举办I/O的长处时,操作运算符重载成果,可以用一个语句完成对一系列的工具的读写,而不需要区分工具详细的范例。MFC提供了类CArchive,实现了运算符<<和>>的重载,但愿凭据前面cin和cout 的方法举办文件I/O。通过和CFile类的共同,不只仅实现了对简朴范譬喻int/float等的文件读写,并且实现了对可序列化工具(Serializable Objects,这个观念后头描写)的文件读写。
一般环境下,利用CArchive对工具举办读操纵的进程如下:
//示例代码2
//界说文件工具和文件异常工具
CFile file;
CFileException fe;
//以读方法打开文件
if(!file.Open(filename,CFile::modeRead,&fe))
{
fe.ReportError();
return;
}
//构建CArchive 工具
CArchive ar(&file,CArchive::load);
ar >> obj1>>obj2>>obj3...>>objn;
ar.Flush();
//读完毕,封锁文件流
ar.Close();
file.Close();
利用CArchive对工具举办写操纵的进程如下://示例代码3
//界说文件工具和文件异常工具
CFile file;
CFileException fe;
//以读方法打开文件
if(!file.Open(filename,CFile::modeWrite|CFile::modeCreate,&fe))
{
fe.ReportError();
return;
}
//构建CArchive 工具
CArchive ar(&file,CArchive::load);
ar << obj1<<obj2<<obj3...<<objn;
ar.Flush();
//写完毕,封锁文件流
ar.Close();
file.Close();
#p#副标题#e#
可见,对付一个文件而言,假如文件内工具的分列顺序是牢靠的,那么对付文件读和写从形式上只有利用的运算符的差异。在MFC的框架/文档/视图布局中,一个文档的内部工具的组成往往是牢靠的,这种环境下,写到文件中时工具在文件中的机关也是牢靠的。因此CDocument操作其基类CObject提供的Serilize虚函数,实现自动文档的读写。
当用户在界面上选择文件菜单/打开文件(ID_FILE_OPEN)时,CWinApp派生类的OnFileOpen函数被自动挪用,它通过文档模板建设(MDI)/重用(SDI)框架、文档和视图工具,并最终挪用CDocument::OnOpenDocument来读文件,CDocument::OnOpenDocument 的处理惩罚流程如下:
//示例代码4
BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)
{
if (IsModified())
TRACE0("Warning: OnOpenDocument replaces an unsaved document.\n");
CFileException fe;
CFile* pFile = GetFile(lpszPathName,
CFile::modeRead|CFile::shareDenyWrite, &fe);
if (pFile == NULL)
{
ReportSaveLoadException(lpszPathName, &fe,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
return FALSE;
}
DeleteContents();
SetModifiedFlag(); // dirty during de-serialize
CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
loadArchive.m_pDocument = this;
loadArchive.m_bForceFlat = FALSE;
TRY
{
CWaitCursor wait;
if (pFile->GetLength() != 0)
Serialize(loadArchive); // load me
loadArchive.Close();
ReleaseFile(pFile, FALSE);
}
CATCH_ALL(e)
{
ReleaseFile(pFile, TRUE);
DeleteContents(); // remove failed contents
TRY
{
ReportSaveLoadException(lpszPathName, e,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
}
END_TRY
DELETE_EXCEPTION(e);
return FALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE); // start off with unmodified
return TRUE;
}
#p#分页标题#e#
同样,当用户选择菜单文件/文件生存(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);
if (pFile == NULL)
{
ReportSaveLoadException(lpszPathName, &fe,
TRUE, AFX_IDP_INVALID_FILENAME);
return 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);
return FALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE); // back to unmodified
return TRUE; // success
}
从前面两段代码可以看出,文件读和文件写的布局基内情同,而且最终都挪用了CObject::Serialize函数完成对文档本身的读和写(拜见注释中的save me和load me)。对付用AppWizard自动生成的MDI和SDI,系统自动生成了这个函数的重载实现,缺省的实现为://示例代码6
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
假如一个对VC很是熟悉的人,喜欢手工生成所有的代码(虽然这长短常挥霍时间也是没有须要的),那么他提供的CDocument派生类也应该实现这个缺省的Serialize函数,不然,系统在文件读写时只能挪用CObject::Serialize,这个函数什么都不做,虽然也无法完成对特定工具的文件生存/载入事情。虽然,用户也可以截获ID_FILE_OPEN等菜单,实现本身的文件读写成果,可是这样的代码将变得很是啰嗦,也不容易阅读。
回到CMyDoc::Serialize函数。这个函数通过对ar工具的判定,抉择当前是在读照旧在写文件。由于AppWizard不知道你的文档是干什么的,所以它不会给你添加实际的文件读写代码。假设你的文档中有三个工具m_Obj_a,m_Obj_b,m_Obj_c,那么实际的代码应该为://示例代码7
可串行化工具(Serializable Object)
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_Obj_a << m_Obj_b << m_Obj_c;
}
else
{
ar >> m_Obj_a >> m_Obj_b >> m_Obj_c;
}
}
要操作示例代码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派生,这个类实现了串行化成果,其实现代码为:
#p#分页标题#e#
//头文件picture.h
#if !defined(__PICTURE_H__)
#define __PICTURE_H__
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
const int TYPE_UNKNOWN = -1;
class CPicture:public CObject
{
int m_nType;//图形种别
DECLARE_SERIAL(CPicture)
public:
CPicture(int m_nType=TYPE_UNKNOWN):m_nType(m_nType){};
int GetType()const {return m_nType;};
virtual void Draw(CDC * pDC);
void Serialize(CArchive & ar);
};
#endif
//cpp文件picture.cpp
#include "stdafx.h"
#include "picture.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
void CPicture::Draw(CDC * pDC)
{
//基类不实现画图成果,由派生类实现
}
void CPicture::Serialize(CArchive & ar)
{
if(ar.IsLoading())
{
ar << m_nType;
}else{
ar >> m_nType;
}
}
留意:由于CRuntimeClass要求这个工具必需可以或许被实例化,因此固然Draw函数没有任何画图操纵,这个类照旧没有把它界说成纯虚函数。
工具在CDocument派生类中的生存和文件I/O进程
为了简化设计,在CDocument类派生类中,回收MFC提供的模板类CPtrList来生存工具。该工具界说为:
protected:
CTypedPtrList m_listPictures;
由于CTypedPtrList和CPtrList都没有实现Serialize函数,因此不可以或许通过ar << m_listPictures和ar >> m_listPictures 来序列化工具,因此CPictureDoc的Serialize函数需要如下实现:
void CTsDoc::Serialize(CArchive& ar)
实现派生类的串行化成果
{
POSITION pos;
if (ar.IsStoring())
{
// TODO: add storing code here
pos = m_listPictures.GetHeadPosition();
while(pos != NULL)
{
ar << m_listPictures.GetNext (pos);
}
}
else
{
// 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 ();
break;
}
}while(pPicture != NULL);
}
m_pCurrent = NULL;
SetModifiedFlag(FALSE);
}
几许图形措施支持直线、矩形、三角形、椭圆等图形,别离以类CLine、CRectangle、CTriangle和CEllipse实现。以类CLine为例,实现串行化成果:
从CPicture派生CLine,在CLine类界说中增加如下成员变量:CPoint m_ptStart,m_ptEnd;
在该行下一行增加如下宏:DECLARE_SERIAL(CLine)
实现Serialize函数
void CLine::Serialize(CArchive & ar)
{
CPicture::Serialize(ar);
if(ar.IsLoading())
{
ar>>m_ptStart.x>>m_ptStart.y>>m_ptEnd.x>>m_ptEnd.y;
}else{
ar<<m_ptStart.x<<m_ptStart.y<<m_ptEnd.x<<m_ptEnd.y;
}
}
在CPP文件中增加IMPLEMENT_SERIAL(CLine,CPicture,TYPE_LINE);
这样界说的CLine就具有串行化成果,其他图形类可以雷同界说。
附注
本文急遽草就,不敷之处在所不免。请发明谬误者给我来信说明,感谢。
本文配套源码