串行化

MFC的默认文件菜单,提供了“新建”、“打开”、“保存”“另存为”等功能。序列化(串行化)是微软提供的用于对对象进行文件I/O的一种机制,该机制在框架(Frame)/文档(Document)/视图(View) 模式中得到了很好的应用。“打开”、“保存”、“另存为”都涉及到对象的串行化。

一、       串行化条件

一个类若需要支持串行化,则需要:

Ø  这个类从CObject派生。串行化要求对象从CObject派生,或者从一个CObject的派生类派生。因为这是MFC中实现RTTI、动态创建的基础。

Ø  该类实现了Serialize函数。Serialize函数是对象真正保存数据的函数,是整个串行化的核心。实现方法和CWuyaDoc::Serialize()一样,利用CArchive::IsStoring()和CArchive::IsLoading() 判断当前的操作,并选择<<和>>来保存和读取对象。

Ø  该类在定义时使用了DECLARE_SERIAL宏。

Ø  在类的实现文件中使用了IMPLEMENT_SERIAL宏 。

Ø  这个类有一个不带参数的构造函数,或者某一个带参数的构造函数所有的参数都提供了缺省参数。

可串行化对象条件中没有包括简单类型,对于简单类型,CArchive基本都实现了运算符<<和>>的重载,所以可以直接使用串行化方式进行读写。

简单的可串行化的类如下:

头文件(Wuya.h)

  1. #pragma once  
  2.   
  3. class CWuya : public CObject  
  4. {  
  5.          DECLARE_SERIAL(CWuya)  
  6. public:  
  7.          CWuya();  
  8.          virtual ~CWuya();  
  9.          virtual void  Serialize(CArchive& ar);  
  10. private:  
  11.          CString              m_strText;  
  12.          int                  m_nXML;  
  13. };  

实现文件(wuya.cpp)

  1. #include "stdafx.h"  
  2. #include "Wuya.h"  
  3.   
  4. IMPLEMENT_SERIAL(CWuya,CObject,1 | VERSIONABLE_SCHEMA)  
  5. CWuya::CWuya() : m_nXML(0)  
  6. {  
  7. }  
  8. CWuya::~CWuya()  
  9. {  
  10. }  
  11. void CWuya::Serialize(CArchive& ar)  
  12. {  
  13.          // 调用基类的串行化函数  
  14.          CObject::Serialize(ar);  
  15.   
  16.         if(ar.IsStoring())        // 判断是否在进行存储操作  
  17.           {  
  18.                    ar << m_strText << m_nXML;  
  19.          }  
  20.          else  
  21.          {  
  22.                    ar >> m_strText >> m_nXML;  
  23.          }  
  24. }  

二、       串行化的版本控制

IMPLEMENT_SERIAL宏的第三个参数可以传入类的当前版本。倘若类wuya增加了一个成员变量CString m_strLine,同时修改Serialize函数:

  1. void CWuya::Serialize(CArchive& ar)  
  2. {  
  3.          // 调用基类的串行化函数  
  4.          CObject::Serialize(ar);  
  5.          if(ar.IsStoring())  
  6.          {  
  7.                    ar << m_strText << m_nXML << m_strLine;  
  8.          }  
  9.          else  
  10.          {  
  11.                    ar >> m_strText >> m_nXML >> m_strLine;  
  12.          }  
  13. }  

这时,将IMPLEMENT_SERIAL的第三个参数由1改为2。此时这个版本参数是没有任何作用的,CArchive按照一个对象一个对象读取,读到文件末尾,然后需要读入m_strLine对象,导致抛出CArchiveException异常,提示“意外的文件格式”。

1、  为了了解MFC对于串行化的整个流程,可以查看MFC的源代码,当点击菜单“打开”时:

1>     CDemoApp 中的消息映射ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen),导致调用CWinApp::OnFileOpen函数;

2>     调用m_pDocManager->OnFileOpen(),导致调用CDocManagerOnFileOpen()

3>     调用函数DoPromptFileName(),弹出选择文件对话框;

4>     同时调用AfxGetApp()->OpenDocumentFile(newName);

5>     调用CWinApp中的m_pDocManager->OpenDocumentFile(lpszFileName);

6>     调用CDocumentCDocManager::OpenDocumentFile(LPCTSTR lpszFileName)。

7>     函数之后调用pBestTemplate->OpenDocumentFile(szPath),其中pBestTemplateCDocTemplate的指针,将根据单、多文档,分别调用CSingleDocTemplateCMultiDocTemplate中的OpenDocumentFile()函数,将创建视图、文档、子框架对象。而后调用pDocument->OnOpenDocument(lpszPathName);

8>     BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)中调用Serialize()函数,并进行了异常处理。由于CDocument从COjbect派生,CDemoDoc从CDocument,而CDemoDoc中覆写了Serialize ()函数,调用CDocument中的Serialize函数。

9>     CDocument中的Serialize()函数调用发生异常,当前函数未进行处理,将栈展开到函数CDocument::OnOpenDocument中,从而提示意外的文件格式

10>  文件打开成功后,将继续执行CSingleDocTemplate::OpenDocumentFile()中函数InitialUpdateFrame(pFrame,pDocumentbMakeVisible),从而调用函数框架pFrame->InitialUpdateFrame(pDocbMakeVisible),而该函数中,对视图发送了WM_INITIALUPDATE消息,对视图进行初始化。

2、  若要是存储的内容可以读出,并没有“意外的文件格式”的提示,需要修改类的Serialize函数。修改如下

  1. void CWuya::Serialize(CArchive& ar)  
  2.                  {  
  3.                           // 调用基类的串行化函数  
  4.                           CObject::Serialize(ar);  
  5.                           if(ar.IsStoring())  
  6.                           {  
  7.                                     ar << m_strText << m_nXML << m_strLine;  
  8.                           }  
  9.                           else  
  10.                           {  
  11.                                     UINT uSchema = ar.GetObjectSchema();    ///< 读取存储的类版本  
  12.                                     switch(uSchema)  
  13.                                     {  
  14.                                     case 1:  
  15.                                              ar >> m_strText >> m_nXML;  
  16.                                              m_strLine.Empty();  
  17.                                              break;  
  18.                                     case 2:  
  19.                                              ar >> m_strText >> m_nXML >> m_strLine;  
  20.                                              break;  
  21.                                     default:  
  22.                                              AfxThrowArchiveException(CArchiveException::badSchema);  
  23.                                              break;  
  24.                                     }  
  25.                           }  
  26.                  }  

                 GetObjectSchema()函数将读取文件中该类的版本号,可以根据该版本号进行判断读取的时候应该采取什么方法。以上的Serialize函数可以保证当前版本2打开版本1存储的文件。

3、  解释

1>     IMPLEMENT_SERIAL宏展开之后,可以看到只针对操作符 >> 进行了重载。这是由于ReadObject()函数需要CRunTimeClass信息,而WriteObject()函数不需要。这两个函数分别在 >> 和 << 时调用。

2>     由于MFC的串行化涉及到动态创建,而动态创建的过程是使用new关键字创建的对象,在CDemoDoc的Serialize()函数中,串行化的对象都应该是指针对象,或是存放到CArray、CTypedPtrArray的指针数组。对于指针对象,应该在OnNewDocument()函数中进行初始化,并在DeleteContents()函数中进行内存释放,该函数在新建、打开、关闭时都将被调用。

3>     只通过Serialize函数对对象读写,而不使用ReadObject/WriteObject和运算符重载时,前面的可串行化条件不需要,只要实现Serialize 函数即可。或是对于现存的类,如果它没有提供串行化功能,可以通过使用重载友元operator <<和operator >>来实现。这两种方法也可以实现串行化,但如果这么做,将无法使用GetObjectSchema()获取版本号的功能。实现版本兼容将比较困难。

三、       MFC中默认文件菜单

看完了Serialize()函数中的文件读取,接着看看其他文件菜单的内部操作。在doccore.cpp中,可以看到CDocument类的消息映射中,存在如下代码:

        

  1. BEGIN_MESSAGE_MAP(CDocument, CCmdTarget)  
  2.                   //{{AFX_MSG_MAP(CDocument)  
  3.                   ON_COMMAND(ID_FILE_CLOSE, &CDocument::OnFileClose)  
  4.                   ON_COMMAND(ID_FILE_SAVE, &CDocument::OnFileSave)  
  5.                   ON_COMMAND(ID_FILE_SAVE_AS, &CDocument::OnFileSaveAs)  
  6.                   //}}AFX_MSG_MAP  
  7. END_MESSAGE_MAP()  

分别相应了“关闭”、“保存”和“另存为”菜单的操作。

1、 “关闭”菜单

1>     void CDocument::OnFileClose()函数调用OnCloseDocument()函数,此函数为虚函数,可以再CDemoDoc中进行覆写。默认的操作关闭所有的视图,并调用虚函数DeleteContents()做清除工作。DeleteContents()函数在打开文档、新建文档时都将先被调用。

2>     DeleteContents()函数为虚函数,可以再CDemoDoc中进行覆写。

2、 “保存”菜单

1>     void CDocument::OnFileSave()调用DoFileSave()函数。

2>     DoFileSave()函数调用DoSave()函数。

3>     DoSave()函数调用OnSaveDocument()函数,该函数为虚函数。

4>     默认的OnSaveDocument()函数中调用Serialize()函数进行串行化保存。

“另存为”操作与“保存”类似,直接调用了DoSave()函数进行保存。

3、 “新建”菜单

1>     CDemoApp的消息映射中:

ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)

将新建菜单映射为函数CWinApp的OnFileNew函数。

2>     经过类似“打开”操作的步骤,到调用BOOL CDocument::OnNewDocument()函数。

3>     此函数也为虚函数,可以对该函数进行覆写。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值