原创 MFC的串行化编程 收藏

2011-6-15 07:56 2988 12 13 分类: 工程师职场

原创  MFC的串行化编程 收藏

 

串行化是微软提供的用于对对象进行文件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();

 

可见,对于一个文件而言,如果文件内对象的排列顺序是固定的,那么对于文件读和写从形式上只有使用的运算符的不同。在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;

        }

同样,当用户选择菜单文件/文件保存(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

        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;

             }

        }

 

可串行化对象(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

 

        #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为例,实现串行化功能:

1. 从CPicture派生CLine,在CLine类定义中增加如下成员变量:

2. CPoint m_ptStart,m_ptEnd;

3. 在该行下一行增加如下宏:

4. DECLARE_SERIAL(CLine)

5. 实现Serialize函数

6. void CLine::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<<m_ptStart.x<<m_ptStart.y<<m_ptEnd.x<<m_ptEnd.y;

14.      }

15. }

16. 在CPP文件中增加

17. IMPLEMENT_SERIAL(CLine,CPicture,TYPE_LINE);

这样定义的CLine就具有串行化功能,其他图形类可以类似定义。

 

文章评论1条评论)

登录后参与讨论

用户377235 2012-4-26 11:06

HAO
相关推荐阅读
用户159579 2013-07-04 16:44
gerber文件转换 bl bo bs ko tl to ts
s ...
用户159579 2013-01-11 15:10
【博客大赛】PLC与上位机通信显示系统(四)
  一、     固件实现 1.       资源分析 1)      Ram资源 STM32F103VCT6共48K Ram资源,分配个内部变量和程序栈空间。 2)     ...
用户159579 2013-01-11 15:07
【博客大赛】PLC与上位机通信显示系统(三)
  一、     硬件电路设计 硬件电路的设计将在系统总体方案确立的基础上进行电路板的设计。其中,硬件电路设计主要应用ALTIUM公司的PROTEL软件,首先建立元件封装库,设计原理图绘制...
用户159579 2013-01-11 15:06
【博客大赛】PLC与上位机通信显示系统(二)
  一、     系统总体方案 根据系统的设计要求和需要的功能,对系统的总体方案进行选择,其中包括系统的显示方案、主芯片的选择、显示驱动的选择等等,将对最终确定的方案进行介绍。 1. ...
用户159579 2013-01-11 15:04
【博客大赛】PLC与上位机通信显示系统(一)
一、     系统概述 PLC与上位机通信显示系统主要完成与西门子PLC进行通信,将结果使用7寸彩屏进行显示,在系统上预留出与上位机(PC机)通信的接口和协议,可以保证上位机与系统的通信通畅。主要的...
用户159579 2012-08-18 16:45
PAL,NTSC,还有SECAM
  PAL,NTSC,还有SECAM,这是全球现行的三种模拟技术彩色电视的制式。 NTSC(National Television System Committee)制是最早的彩电制式,195...
我要评论
1
12
关闭 站长推荐上一条 /2 下一条