序列化是以字节流的形式将数据保存到磁盘和从磁盘上将数据读到内存中的过程。
一、标准MFC框架程序的文件保存和读取函数:
1. 打开文件,参见\Microsoft Visual Studio\VC98\MFC\SRC\DOCCORE.CPP:
BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)
2. 保存文件,参见\Microsoft Visual Studio\VC98\MFC\SRC\DOCCORE.CPP:
BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)
这两个函数实现过程类似,都是先得到一个文件指针,然后声明一个CArchive对象,之后调用Serialize函数实现序列化数据输入或输出,由于Serialize是一个虚函数,因此实际上调用的是用户程序中文档类的序列化函数。在CDocument类中调用Serialize函数时使用try和catch对异常情况进行处理,比如在读取文件时越界将给出提示。
二、在自己的应用程序的序列化中使用异常处理
1. 序列化异常类:
CArchiveException,派生于CException类。CArchiveException包含一个int类型的m_cause成员,它为以下枚举值之一:
a) CArchiveException::none 无错
b) CArchiveException::generic 一个无法识别的错误
c) CArchiveException::readOnly 试图写以读入方式打开的文件
d) CArchiveException::endOfFile 已到文件尾
e) CArchiveException::writeOnly 试图读以存储方式打开的文件
f) CArchiveException::badIndex 无效文件格式
g) CArchiveException::badClass 把一个对象读到错误对象类型中去
h) CArchiveException::badSchema 版本号错误
2. 抛出序列化异常函数:
void AfxThrowArchiveException( int cause, LPCTSTR lpszArchiveName = NULL);
在有序列化异常发生时可使用AfxThrowArchiveException抛出异常,参数cause为异常的原因,为上面的枚举值之一,参数lpszArchiveName为档案文件名。
3. 在序列化函数中加入异常处理,例如以下代码:
void CModernFASTDoc::Serialize(CArchive& ar)
{
CString strMark(_T("ModernFAST")); // 文件类型标志
int nVer = 2; // 版本号:初始为1,升级后为2
if (ar.IsStoring())
{
ar << strMark;
ar << nVer;
m_Group.Serialize(ar);
ar << m_MemberInVer2; // 在版本2时加入的新变量
}
else
{
CString str;
ar >> str;
if (0 != str.Compare(strMark))
{
// 文件格式不对
AfxThrowArchiveException(CArchiveException::badIndex);
// 抛出异常后函数返回,不再执行后面的代码
}
ar >> nVer;
m_Group.Serialize(ar);
switch(nVer)
{
case 1:
m_MemberInVer2 = 4; // 在加入版本2时加入
break;
case 2:
ar >> m_MemberInVer2;
break;
default:
// 版本号错误
AfxThrowArchiveException(CArchiveException::badSchema);
break;
}
}
}
三、需要注意的问题:
1. 在Serialize函数中只识别和抛出异常,但并不应进行处理,对异常发生后的处理应由Serialize函数的调用者根据情况进行处理。
2. 在Serialize函数中AfxThrowArchiveException抛出异常后,程序将进入到catch模块,因此在AfxThrowArchiveException之后的语句将不再被执行。
3. 在抛出异常后程序进入到catch模块,而且不再回到try模块,因此在抛出异常前应注意释放申请的动态内存。
4. 使用标准MFC框架应用程序打开和保存文件时,在CDocument::OnOpenDocument函数中捕获异常并给出消息框提示,因此除非改变派生类的打开和保存文件函数,一般不需要自己写异常处理代码。通过CDocument::OnOpenDocument给出的消息框的语言与操作系统的语言一致,而且应用程序只在OnOpenDocument函数返回TRUE的时候才新建一个窗口和视,在有异常时返回FALSE不新建窗口和视。
5. 在不使用CDocument::OnOpenDocument和CDocument::OnSaveDocument读取和保存数据时应捕获异常并处理。如:
... ...
CFile file(... ...);
CArchive ar(&file, ...);
try
{
m_Group.Serilize(ar);
}
catch (CArchiveException* e)
{
// ReportError函数可在消息框中显示错误信息:
e->ReportError();
e->Delete();
/* // 或自己根据情况对异常进行处理:
if (e->m_cause == CArchiveException::endOfFile) // 已读到文件尾
{ …… }
else if (e->m_cause == CArchiveException::badIndex) // 文件格式错
{ …… }
……
e->Delete();
*/
}
6. 在Serialize函数中应避免使用AfxMessageBox对错误进行提示,原因:
a) AfxMessageBox越过了函数的调用者直接提示用户并等待用户响应,不是一种面向对象的程序设计方式,尤其是在一个底层类的序列化函数中;函数出现错误后应使调用者获得错误信息,但Serialize函数并不需要关心错误是如何被处理的;
b) 连续多个同样内容的消息框提示对用户来说是难以接受的;
c) 如果是一个包含在dll中的通用类时,使用AfxMessageBox提示不方便实现多语言版本的应用程序。
d) 由于Serialize函数没有返回值,在多文档应用中打开一个错误类型的文件时,使用AfxMessageBox提示错误后仍然会新建一个窗口,窗口标题为文件名。