MFC 序列化机制
作者:mmseoamin日期:2024-01-21

目录

文件操作相关类

序列化机制相关类

序列化机制使用

序列化机制执行过程

序列化类对象


文件操作相关类

CFile:文件操作类,封装了关于文件读写等操作,常见的方法:

  • CFile::Open:打开或者创建文件
  • CFile::Write/Read:写/读文件
  • CFile::Close:关闭文件
  • CFile::SeekToBegin/SeekToEnd/Seek:从 开始/结束/任意 位置设置文件读写位置

    代码如下:

    #include 
    #include 
    using namespace std;
    void File( ){
    	CFile file;
    	// 没有文件就创建,然后可读可写
    	file.Open( "E:/MFC/Day07/file.txt", CFile::modeCreate|CFile::modeReadWrite );
    	char str[] = "hello file";
    	file.Write( str, strlen(str) );
    	file.SeekToBegin( );// 设置文件读写位置
    	char buf[256] = { 0 };
    	long nLen = file.Read( buf, 255 ); // 返回值是实际读到的数据
    	cout << buf << ' ' << nLen << endl;
    	file.Close();
    }
    int main(){
    	File();
    	return 0;
    }

    序列化机制相关类

    序列化作用:以二进制流形式读写硬盘文件,效率很高。

    • CFile:文件操作类,完成硬盘文件的读写操作
    • CArchive:归档类,完成内存数据的读写操作,维护了一个缓冲区

      先把数据放到缓冲区,再放到硬盘上

      序列化机制使用

      序列化:往硬盘上写数据;

      1. 创建或打开文件  CFile::Open
      2. 定义归档类对象  CArchive ar;
      3. 数据序列化(存储/写)  ar<<数据   把数据读入缓冲区
      4. 关闭归档类对象,释放缓冲区
      5. 关闭文件

      反序列化:从硬盘上读取数据。

      1. 打开文件  CFile::Open
      2. 定义归档类  CArchive  ar;
      3. 数据反序列化(加载/读)  ar>>变量
      4. 关闭文档类对象   ar.close()
      5. 关闭文件    CFile::Close()

      代码如下:

      #include 
      #include 
      using namespace std;
      void Store( ){//序列化(存储、写)数据
      	CFile file;
      	file.Open( "E:/MFC/Day07/serial.txt", CFile::modeCreate | CFile::modeWrite );
      	CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区。
      	long age = 18;
      	ar << age;//将18保存当前指向的位置,并向后移动当前指向,相应字节数。
      	float score = 88.5;
      	ar << score;//将88.5保存当前指向的位置,并向后移动当前指向,相应字节数。
      	CString name = "zhangsan";  
      	ar << name;
      	ar.Close( );
      	file.Close( );
      }
      void Load( ){//反序列化(加载/读)
      	CFile file;
      	file.Open( "E:/MFC/day07/serial.txt", CFile::modeRead );
      	CArchive ar( &file, CArchive::load, 4096 );//维护一个buff,大小4096字节
      	long age;
      	ar >> age;//当反序列化第一个数据时候,内部将文件中所有数据读入ar维护的buff中
      	float score;
      	ar >> score;//当反序列化后续数据时候,不需要到硬盘文件中读取,直接到ar维护的buff中读取
      	CString name;
      	ar >> name;//当反序列化后续数据时候,不需要到硬盘文件中读取,直接到ar维护的buff中读取
      	ar.Close( );
      	file.Close( );
      	cout << age << ' ' << score << ' ' << name << endl;
      }
      int main(){
      	Store( );
      	Load( );
      	return 0;
      }

      问题:数据一共16字节,为啥是17个字节

      MFC 序列化机制,第1张

      原因:首先排除\0,如果在内存中会有这个符号,但是再硬盘不会有。多出的一个字节是描述字符串大小的。前四个字节是Int,后四个字节是float,中间一个字节是字符串长度为8

      MFC 序列化机制,第2张

      Windows记事本解析文件是按照字符解析,所以前面的数字乱码了。

      序列化机制执行过程

      数据结构:

      class CArchive
      {	
          enum Mode;            // {store = 0,load = 1……}
          BOOL m_nMode;         // 访问方式
      	int m_nBufSize;       // buff的大小
      	CFile* m_pFile;       // 操作的文件对象
      	BYTE* m_lpBufCur;     // 当前指向
      	BYTE* m_lpBufMax;     // 终止指向
      	BYTE* m_lpBufStart;   // 开始指向
      }

      CArchive ar(&file, CArchive::store, 4096);  构造函数伪代码如下:

      CFile file;
      file.Open( "E:/MFC/Day07/serial.txt", CFile::modeCreate | CFile::modeWrite );
      CArchive ar(&file, CArchive::store, 4096) === CArchive::CArchive(&file,0, 4096)
      {
        m_nMode = CArchive::store; // 0
        m_pFile = &file;//“E:/....serial.txt”
        m_nBufSize = 4096;
        m_lpBufStart = new BYTE[m_nBufSize];  // 开辟一块堆内存,指向首地址
        m_lpBufMax = m_lpBufStart + 4096;
        m_lpBufCur =  m_lpBufStart;
      }

      初始时

      MFC 序列化机制,第3张

      如何把数据存入缓冲区,伪代码如下:

      long age = 18;
      ar << age === CArchive::operator<<(age)//函数内部this为&ar
      {
        if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) 
        {
          Flush();
        }
        *m_lpBufCur = age;
        m_lpBufCur += sizeof(LONG); 
      } // 把18存入缓冲区,并且指针后移4个字节
      float score = 88.5;
      ar << score === CArchive::operator<<(score)//函数内部this为&ar
      {
        if (m_lpBufCur + sizeof(float) > m_lpBufMax) 
        {
          Flush();
        }
        *m_lpBufCur = score;//88.5 
        m_lpBufCur += sizeof(float);
      }
      CString name = "zhangsan";  
      ar << name === CArchive::operator<<(name)//函数内部this为&ar
      {
        AfxWriteStringLength(ar, 8 )
        {
          ar<<(unsigned char)nLength;//8
        }
        Write(name, 8)//函数内部this为&ar
        {
          memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), name, 8);
          m_lpBufCur += 8;
        }
      }

      序列化三个数据后的缓冲区:

      MFC 序列化机制,第4张

      关闭文档类对象,释放缓冲区伪代码:把当前数据导入硬盘上,再重置当前指向

      ar.Close( )//函数内部this为&ar
      {
        Flush()//函数内部this为&ar
        {
          &file->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart));  // 往硬盘写数据
          m_lpBufCur = m_lpBufStart;//重置当前指向
        }
      }

      如何需要写入4个字节,但是只剩3个字节的空间,怎么办?

      会调用flush(),把缓冲区的数据写入到硬盘空间,再重置当前指针指向。再重新写入数据,相当于从头覆盖写入。

      序列化执行过程总结:

      1. ar对象维护一个缓冲区
      2. 将各个数据依次序列化(存储)到ar对象维护的缓冲区中,并将m_lpBufCur的指针指向移动相应字节
      3. 如果ar维护的缓冲区不足,则将ar维护的缓冲区的数据写入硬盘文件,并重置m_lpBufCur为开始指向
      4. 当关闭ar对象时,将ar对象维护的缓冲区数据写入硬盘文件,并释放ar对象维护的缓冲区。

      反序列化执行过程总结:

      1. ar对象维护一个缓冲区
      2. 当反序列化第一个数据时,将文件数据全部读取到ar维护的缓冲区,并将第一个数据反序列化到第一个变量,并将m_lpBufCur移动相应的字节数
      3. 依次反序列化每个数据到变量中
      4. 当关闭ar对象时,释放ar维护的缓冲区

      序列化类对象

      序列化类对象的使用:

      • 类必须派生自CObject
      • 类内必须添加声明宏  DECLARE_SERIAL(theClass)
      • 类外必须添加实现宏   IMPLEMENT_SERIAL(theClass,baseClass,1)
      • 类必须重写虚函数 Serialize

        当类具备以上四个条件时,类对象就可以序列化到文件保存了。

        序列化类对象,除了要把类对象成员变量还有类对象的信息,类对象大小和版本等。

        完整测试代码如下:

        #include 
        #include 
        using namespace std;
        class CMyDoc : public CDocument{
        	DECLARE_SERIAL( CMyDoc )
        public:
        	CMyDoc(int age=0, float score=0.0, CString name=""):m_age(age),m_score(score),m_name(name){}
        	int m_age;
        	float m_score;
        	CString m_name;
        	virtual void Serialize( CArchive& ar );
        };
        IMPLEMENT_SERIAL( CMyDoc, CDocument, 1 )
        void CMyDoc::Serialize( CArchive& ar ){
        	if( ar.IsStoring() ){
         		ar << this->m_age << this->m_score << this->m_name; //序列化基本类型变量  
        	}else{
        		ar >> m_age >> m_score >> m_name;//反序列化基本类型变量
        	}
        }
        void Store( ){//序列化(存储、写)数据
        	CFile file;
        	file.Open("E:/MFC/Day08/serial.txt", CFile::modeCreate|CFile::modeWrite);
        	CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区。
        	CMyDoc data(18, 88.5, "zhangsan");
        	ar << &data; //序列化对象,就是将对象的各个成员变量序列化。
        	ar.Close( );
        	file.Close( ); 
        }
        void Load( ){//反序列化(加载/读)
        	CFile file;
        	file.Open( "E:/MFC/day08/serial.txt", CFile::modeRead );
        	CArchive ar( &file, CArchive::load, 4096 );//维护一个buff,大小4096字节
        	CMyDoc* pdata = NULL;
        	ar >> pdata;
        	ar.Close( );
        	file.Close( );
        	cout << pdata->m_age << ' ' << pdata->m_score << ' ' << pdata->m_name << endl;
        }
        int main(){
        	Store( );
        	Load( );
        	return 0;
        }

        测试结果:CMyDoc后面是成员变量的数据,前面是类对象信息

        MFC 序列化机制,第5张

        把 DECLARE_SERIAL( CMyDoc ) 宏展开

        MFC 序列化机制,第6张

        再进一步展开宏  _DECLARE_DYNCREATE

        MFC 序列化机制,第7张

        更进一步进入 _DECLARE_DYNAMIC

        MFC 序列化机制,第8张

        所以可以认为就是动态创建机制宏

        MFC 序列化机制,第9张

        实现宏展开

        MFC 序列化机制,第10张

        相当于动态创建机制加上一个操作符重载

        下断点,分析一下类对象序列化执行

        MFC 序列化机制,第11张

        伪代码如下:

        CFile file;
        file.Open("E:/MFC/Day08/serial.txt", CFile::modeCreate|CFile::modeWrite);
        CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区。
        CMyDoc data(18, 88.5, "zhangsan");
        ar << &data === operator<<(ar, const &data)
        {
          ar.WriteObject(&data)//函数内部this为&ar
          {
            CRuntimeClass* pClassRef = &data->GetRuntimeClass();//文档类静态变量
            WriteClass(pClassRef);//将类的相关信息(类名/类大小/类版本)存入ar维护的buff中
            (&data)->Serialize(ar)//函数内部this为&data
            {
              ar << this->m_age << this->m_score << this->m_name; //序列化基本类型变量
            }
          }
        }
        CFile file;
        file.Open( "E:/MFC/day08/serial.txt", CFile::modeRead );
        CArchive ar( &file, CArchive::load, 4096 );//维护一个buff,大小4096字节
        CMyDoc* pdata = NULL;//????????????
        ar >> pdata === operator>>(ar, pdata) 
        {
          pdata = ar.ReadObject(RUNTIME_CLASS(CMyDoc))//函数内部this为&ar
          {
            CRuntimeClass* pClassRef = ReadClass(RUNTIME_CLASS(CMyDoc),...);
                   //从文件读取 类的相关信息,和 RUNTIME_CLASS(CMyDoc)中信息进行比对,
                   //如果相同返回RUNTIME_CLASS(CMyDoc),如果不同返回NULL
            CObject*pOb = RUNTIME_CLASS(CMyDoc)->CreateObject();
                   //动态创建CMyDoc类的对象,并返回对象地址
            pOb->Serialize(ar)//函数内部this为刚刚创建的CMyDoc类对象(pOb)
            {
              ar >> m_age >> m_score >> m_name;//反序列化基本类型变量
            }
            return pOb;
          }
        }