【MFC】12.双缓冲序列化机制-笔记

双缓冲

双缓冲在之前写字符雨的时候,已经简单介绍过,今天我们来写一个简单的程序来体会双缓冲机制

我们实现一个在屏幕上画直线的功能:

在类中添加变量,保存起点坐标和终点坐标:

cpp 复制代码
//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;CPoint m_begin;
CPoint m_end;

在对话框上添加WM_MOUSEMOVE,WM_LBUTTONDOWM,WM_LBUTTONUP消息处理函数:

cpp 复制代码
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
  C双缓冲View* pDoc = GetDocument();
  ASSERT_CALID(pDoc);
  if(!pFoc)
    return;
    
  //画直线
  pDC->MoveTo(m_begin);
  pDC->LineTo(m_end);
  
  CVIew::OnMouseMove(nFlags,point);
}
cpp 复制代码
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){
  if(nFlags&MK_LBUTTON){
    m_end = point;
    IncalidateRect(NULL,TRUE);
  }
  CView::OnMouseMoce(nFlags,point);
}
cpp 复制代码
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){
  //记录开始坐标
  CVIew::OnLButtonDown(nFlags,point);
  m_begin = point;
}
cpp 复制代码
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){
  //记录终点坐标
  m_end = point;
  CVIew::OnLButtonUp(nFlags,point);
  ReleaseCapture();
}

这样写完之后,没有反应,这是因为没有无效区域,我们将OnLButtonUp函数中添加无效区域就可以了:

cpp 复制代码
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){
  //记录终点坐标
  m_end = point;
  CVIew::OnLButtonUp(nFlags,point);
  InvalidateRect(NULL,TRUE);
}

只有有了无效区域,绘图消息才会产生

然后我们完善:

cpp 复制代码
//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;
cpp 复制代码
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
  C双缓冲View* pDoc = GetDocument();
  ASSERT_CALID(pDoc);
  if(!pFoc)
    return;
    
  pDC->MoveTo(m_begin);
  pDC->LineTo(m_end);
    
  auto pos = m_list.GetHeadPossition();
  while(pos){
    auto Lint = m_list.GetNext(pos);
    //画直线
    pDC->MoveTo(Line.first);
    pDC->LineTo(line.second);
  }
  
  CVIew::OnMouseMove(nFlags,point);
}
cpp 复制代码
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){
  if(nFlags&MK_LBUTTON){
    m_end = point;
    IncalidateRect(NULL,TRUE);
  }
  CView::OnMouseMoce(nFlags,point);
}
cpp 复制代码
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){
  //记录开始坐标
  CVIew::OnLButtonDown(nFlags,point);
  m_begin = point;
  //捕捉客户区外鼠标消息
  SetCapture();
}
cpp 复制代码
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){
  //记录终点坐标
  m_end = point;
  
  m_list.AddTail(Line(m_begin,m_end));
  
  CVIew::OnLButtonUp(nFlags,point);
  InvalidateRect(NULL,TRUE);
  ReleaseCapture();
}

这样写完了之后,确实可以画出来直线,但是这是我们直接操作外设的,所以会出现闪屏的情况

这时候就需要我们的双缓冲了

双缓冲就是我们操作内存,将直线画在内存上,然后将内存完整拷贝到外设上,这样就可以避免操作慢,闪屏的问题:

Win32中,能操作设备的,只有设备句柄hdc

而在MFC中封装了:

CDC------->对应Win32::GetDC

CMetaFileDC 矢量图,位图(了解一下就行了)

CPainDC:WM_PAINT::BEGINPAINT

CWindowDC:桌面窗口的HDC

这几种对象,能代表他们各自的东西,肯定是内部有绑定机制Attah()

双缓冲:内存DC,这里的CDC就代表了内存DC

然后我们修改代码:

cpp 复制代码
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
  C双缓冲View* pDoc = GetDocument();
  ASSERT_CALID(pDoc);
  if(!pFoc)
    return;
    
  //双缓冲绘图
  //1.创建内存DC
  CDC dcMem;
  dcMem.CreateCompatibleDC(pDC);
  
  CRect rect;
  GetClintRect(rect);
  
  //2.创建一张屏幕DC一样的位图
  CBitmap bitmap;
  bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
  
  //3.送到内存DC中
  dcMem.SeleObject(bitmap);
  dcMem.FillSolidRect(rect,RGB(255,255,255));
  
  //然后我们使用内存DC绘图
  dcMem->MoveTo(m_begin);
  dcMem->LineTo(m_end);
    
  auto pos = m_list.GetHeadPossition();
  while(pos){
    auto Lint = m_list.GetNext(pos);
    //画直线
    dcMem->MoveTo(Line.first);
    dcMem->LineTo(line.second);
  }
  
  //4.拷贝到设备
  pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);
  
  CVIew::OnMouseMove(nFlags,point);
}

修改完这个函数后,将绘制无效区域的函数,不再擦除背景

  • 画壁画刷位图

    在Win32中都是GDI句柄

    MFC封装了GDI对象

    Win32什么流程

    MFC就还是什么流程

    cpp 复制代码
    void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
      C双缓冲View* pDoc = GetDocument();
      ASSERT_CALID(pDoc);
      if(!pFoc)
        return;
        
      //双缓冲绘图
      //1.创建内存DC
      CDC dcMem;
      dcMem.CreateCompatibleDC(pDC);
      
      CRect rect;
      GetClintRect(rect);
      
      //2.创建一张屏幕DC一样的位图
      CBitmap bitmap;
      bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
      
      CPen pen;
      pen.CreatePen(PS_DOT,55,RGB(0,255,255));
      //把画笔送到内存DC
      deMem.SelectObject(pen);
      
      //3.送到内存DC中
      dcMem.SeleObject(bitmap);
      dcMem.FillSolidRect(rect,RGB(255,255,255));
      
      //然后我们使用内存DC绘图
      dcMem->MoveTo(m_begin);
      dcMem->LineTo(m_end);
        
      auto pos = m_list.GetHeadPossition();
      while(pos){
        auto Lint = m_list.GetNext(pos);
        //画直线
        dcMem->MoveTo(Line.first);
        dcMem->LineTo(line.second);
      }
      
      //4.拷贝到设备
      pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);
      
      CVIew::OnMouseMove(nFlags,point);
    }

序列化

  • 为什么要有序列化:

    我们在绘图应用程序上绘制的图形,可以保存起来,我们之后还可以打开

    而我们上面写的程序,是不能保存的,这就是序列化的基本功能

  • 简单使用以下序列化:

    1. 首先继承于CObject
    2. 类内添加声明宏DECLARE_SERTAL
    3. 类外实现宏IMPLEMENT_SERIAL
    cpp 复制代码
    //直线类
    class Cline:public Cobject{
    public:
      CLine(){};
      CLine(int x,int y,CString type):x(x),y(y),str(type){};
      virtual coid Setialize(CArchice* ar){
        if(ar,IsLoading()){
          //加载
          ar>>x>>y>>str;
        }else{
          //保存
          ar<<x<<y<<str;
        }
      }
      int x;
      int y;
      int color;
      CString str;
      DECLARE_SERTAL(CLine)
    }
    IMPLEMENT_SERIAL(CLine,Cobject,1)
    cpp 复制代码
    int main(){
      int nRetCode = 0;
      
      HMODULE hModule = ::GEtModuleHandle(nullptr);
      AfxWinInit(hModule,nullptr,::GetCommandLine(),0);
      CLine line(40,40,"直线");
      
      CFile file;
      file.Open(R"(文件路径\文件名)",CFile::modeCreate|CFile::modeWrite);
      CArchive ar(&file,CArchice::store,4096);
      ar<<&line;
      ar.Close();
      file.Close();
      
      return nRetCode;
    }

我们这样实现后,发现,我们只写了三个成员,但是文件中有很多,我们来简单追踪有一下序列化是如何实现的:

序列化过程

CArchive 归档类对象

cpp 复制代码
CArchive& AFXAPI operator>>(CArchive& ar, CLine* &pOb)
{
	pOb = (CLine*) ar.ReadObject(RUNTIME_CLASS(CLine)); 
			return ar; 
}

CArchive ar(&file, CArchive::store,4096)
{
	m_nMode = nMode;//模式
	m_pFile = pFile;//文件句柄
	m_nBufSize = nBufSize	//缓冲区大小
	this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小
	m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址
	m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}

struct 
{
	类版本
	类大小
	x
}

void CArchive::WriteObject(const CObject* line)
{
	//获取类信息地址
	CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
	//写类信息
	WriteClass(pClassRef);
	
	((CObject*)pOb)->Serialize(ar对象/*归档类对象*/);
	{
		ar << x
		{
			return CArchive::operator<<((char)x); 
			{
				if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) 
					Flush();
				
				*(UNALIGNED LONG*)m_lpBufCur = x;
				m_lpBufCur += sizeof(LONG);
				return *this; 
			}
		}
		
		AfxWriteStringLength(*this, 长度, 判断字符串是否是多字节
		{
			ar<<(BYTE)nLength;
		}
		
		:memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), lpBuf, nTemp)
		
	}
}


ar,close
{
	Flush();
	{	
		m_pFile->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart)
		{
		  //写文件
			::WriteFile(m_hFile, lpBuf, nCount, &nWritten, NULL)
			m_lpBufCur = m_lpBufStart;
		}
		
	}
}

反序列化:

cpp 复制代码
int main(){
  int nRetCode = 0;
  
  HMODULE hModule = ::GEtModuleHandle(nullptr);
  AfxWinInit(hModule,nullptr,::GetCommandLine(),0);
  CLine* line;
  
  CFile file;
  file.Open(R"(文件路径\文件名)",CFile::modeCreate|CFile::modeRead);
  CArchive ar(&file,CArchice::store,4096);
  ar >> line;
  ar.Close();
  file.Close();
  
  std::cout<<line->str;
  std::coutd<<line-x;
  std::cout<<line->y;
  
  return nRetCode;
}
cpp 复制代码
CArchive ar(&file, CArchive::store,4096)
{
	m_nMode = nMode;//模式 读
	m_pFile = pFile;//文件句柄
	m_nBufSize = nBufSize	//缓冲区大小
	this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小
	m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址
	m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}

ar.ReadObject(直线类信息)
{
	//读类信息
	CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);
	{
		BYTE* lpTemp = m_lpBufStart + nPreviouslyFilled;
			do
			{
				//读文件 全部读上来
				nBytesRead = m_pFile->Read(lpTemp, nLeftToRead);
				lpTemp = lpTemp + nBytesRead;
				nTotalInBuffer += nBytesRead;
				nLeftToRead -= nBytesRead;
			}
			while (nBytesRead > 0 && nLeftToRead > 0 && nTotalInBuffer < nTotalSizeWanted);
			
			m_lpBufCur = m_lpBufStart;
			m_lpBufMax = m_lpBufStart + nTotalInBuffer;
	}
	//动态创建对象
	pOb = pClassRef->CreateObject();
	//回到我们代码
	pOb->Serialize(*this)
	{
		CArchive::operator>>((LONG&)x);
		{
			if (m_lpBufCur + sizeof(LONG) > m_lpBufMax)
				FillBuffer(UINT(sizeof(LONG) - (m_lpBufMax - m_lpBufCur)));
			i = *(UNALIGNED LONG*)m_lpBufCur;
			m_lpBufCur += sizeof(LONG);
			return *this; 
		}
	}
}

了解了序列化和反序列化过程,我们就可以将我们画的图保存起来了:

  1. 添加一个直线类

    cpp 复制代码
    #pragma once
    #include <afx.h>
    class Cline:public CObject{
    public:
      DECLARE_SERTAL(CLine)
      virtual void Serialize(CArchive& ar);
      CPoint m_begin;
      CPoint m_end;
    };
    
    //在cpp中实现:
    #include "pch.h"
    #include "CLine.h"
    
    IMPLEMENT_SERIAL(CLine,CObject,1)
    
    void Serialize(CArchive& ar){
      CObject::Serialize(ae);
      if(ar.IsLoading()){
        ar>>m_begin.x>>m_begin.y;
        ar>>m_end.x>>m_end.y;
      }else{
        ar<<m_begin.x<<m_begin.y;
        ar<<m_end.x<<m_end.y;
      }
    }
    
    我们还需要将直线的链表存储到文档中:
    在文档类.cpp中:
    #include "CLine.h"
    
    为文档类添加变量:
    CList<CLine>m_list;
  2. 我们将OnDraw消息稍作修改:就是直线的数据获取位置不一样了,我们需要修改:

cpp 复制代码
  auto pos = pDoc->m_list.GetHeadPossition();
  while(pos){
    auto Lint = pDoc->m_list.GetNext(pos);
    //画直线
    dcMem->MoveTo(Line.m_begin);
    dcMem->LineTo(line.m_end);
  }
  1. 这样修改之后,我们还需要将直线类的拷贝构造函数修改,因为默认是浅拷贝,我们在OnDraw函数中使用的时候,显示已经释放:
cpp 复制代码
#pragma once
#include <afx.h>
class Cline:public CObject{
public:

  CLine(){};
  
  CLine& operator_(const& line){
    this->m_begin = line.m_begin;
    this->m_end = list.m_red;
    return *this;
  }
  
  CLine(const CLine& line){
    this->m_begin = line.m_begin;
    this->m_end = list.m_red;
  }
  
  CLine(CPoint begin,CPoint end){
    this->m_begin = begin;
    this->m_end = end;
  }
  
  DECLARE_SERTAL(CLine)
  virtual void Serialize(CArchive& ar);
  CPoint m_begin;
  CPoint m_end;
};
  1. 然后我们去文档类中处理:
cpp 复制代码
void C...DOC::Serialize(CArchive& ar){
  if(ar.IsStoring()){
    ar<<m_list.getSize();
    auto pos = m_list.GetHeadPosition();
    while(pos){
      //ar<<&m_list.GetNext(pos);
      CLine*p = m_listl.GetNext(pos);
      ar<<&p;
      //这里第二种方式,看似跟上面的方式没有什么区别,但是运行发现,只保存了一条直线
      //这是因为每一次p都是一个地址
    }else{
      int nSize;
      ar>>nSize;
      CObject* shape;
      while(nSize--){
        ar>>shape;
        m_list.AddTail(*(CLine*)shape);
      }
    }
  }
}

双缓冲

双缓冲在之前写字符雨的时候,已经简单介绍过,今天我们来写一个简单的程序来体会双缓冲机制

我们实现一个在屏幕上画直线的功能:

在类中添加变量,保存起点坐标和终点坐标:

cpp 复制代码
//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;CPoint m_begin;
CPoint m_end;

在对话框上添加WM_MOUSEMOVE,WM_LBUTTONDOWM,WM_LBUTTONUP消息处理函数:

cpp 复制代码
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
  C双缓冲View* pDoc = GetDocument();
  ASSERT_CALID(pDoc);
  if(!pFoc)
    return;
    
  //画直线
  pDC->MoveTo(m_begin);
  pDC->LineTo(m_end);
  
  CVIew::OnMouseMove(nFlags,point);
}
cpp 复制代码
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){
  if(nFlags&MK_LBUTTON){
    m_end = point;
    IncalidateRect(NULL,TRUE);
  }
  CView::OnMouseMoce(nFlags,point);
}
cpp 复制代码
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){
  //记录开始坐标
  CVIew::OnLButtonDown(nFlags,point);
  m_begin = point;
}
cpp 复制代码
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){
  //记录终点坐标
  m_end = point;
  CVIew::OnLButtonUp(nFlags,point);
  ReleaseCapture();
}

这样写完之后,没有反应,这是因为没有无效区域,我们将OnLButtonUp函数中添加无效区域就可以了:

cpp 复制代码
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){
  //记录终点坐标
  m_end = point;
  CVIew::OnLButtonUp(nFlags,point);
  InvalidateRect(NULL,TRUE);
}

只有有了无效区域,绘图消息才会产生

然后我们完善:

cpp 复制代码
//定义一个容器,保存每次画的直线
using Lint = std::pair(CPoint,CPoint);
CList<>m_List;
cpp 复制代码
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
  C双缓冲View* pDoc = GetDocument();
  ASSERT_CALID(pDoc);
  if(!pFoc)
    return;
    
  pDC->MoveTo(m_begin);
  pDC->LineTo(m_end);
    
  auto pos = m_list.GetHeadPossition();
  while(pos){
    auto Lint = m_list.GetNext(pos);
    //画直线
    pDC->MoveTo(Line.first);
    pDC->LineTo(line.second);
  }
  
  CVIew::OnMouseMove(nFlags,point);
}
cpp 复制代码
void C双缓冲::OnMouseMove(UINT nFlags,CPoint point){
  if(nFlags&MK_LBUTTON){
    m_end = point;
    IncalidateRect(NULL,TRUE);
  }
  CView::OnMouseMoce(nFlags,point);
}
cpp 复制代码
void C双缓冲View::OnLButtonDown(UINT nFlags,CPoint point){
  //记录开始坐标
  CVIew::OnLButtonDown(nFlags,point);
  m_begin = point;
  //捕捉客户区外鼠标消息
  SetCapture();
}
cpp 复制代码
void C双缓冲View::OnLButtonUp(UINT nFlags,CPoint point){
  //记录终点坐标
  m_end = point;
  
  m_list.AddTail(Line(m_begin,m_end));
  
  CVIew::OnLButtonUp(nFlags,point);
  InvalidateRect(NULL,TRUE);
  ReleaseCapture();
}

这样写完了之后,确实可以画出来直线,但是这是我们直接操作外设的,所以会出现闪屏的情况

这时候就需要我们的双缓冲了

双缓冲就是我们操作内存,将直线画在内存上,然后将内存完整拷贝到外设上,这样就可以避免操作慢,闪屏的问题:

Win32中,能操作设备的,只有设备句柄hdc

而在MFC中封装了:

CDC------->对应Win32::GetDC

CMetaFileDC 矢量图,位图(了解一下就行了)

CPainDC:WM_PAINT::BEGINPAINT

CWindowDC:桌面窗口的HDC

这几种对象,能代表他们各自的东西,肯定是内部有绑定机制Attah()

双缓冲:内存DC,这里的CDC就代表了内存DC

然后我们修改代码:

cpp 复制代码
void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
  C双缓冲View* pDoc = GetDocument();
  ASSERT_CALID(pDoc);
  if(!pFoc)
    return;
    
  //双缓冲绘图
  //1.创建内存DC
  CDC dcMem;
  dcMem.CreateCompatibleDC(pDC);
  
  CRect rect;
  GetClintRect(rect);
  
  //2.创建一张屏幕DC一样的位图
  CBitmap bitmap;
  bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
  
  //3.送到内存DC中
  dcMem.SeleObject(bitmap);
  dcMem.FillSolidRect(rect,RGB(255,255,255));
  
  //然后我们使用内存DC绘图
  dcMem->MoveTo(m_begin);
  dcMem->LineTo(m_end);
    
  auto pos = m_list.GetHeadPossition();
  while(pos){
    auto Lint = m_list.GetNext(pos);
    //画直线
    dcMem->MoveTo(Line.first);
    dcMem->LineTo(line.second);
  }
  
  //4.拷贝到设备
  pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);
  
  CVIew::OnMouseMove(nFlags,point);
}

修改完这个函数后,将绘制无效区域的函数,不再擦除背景

  • 画壁画刷位图

    在Win32中都是GDI句柄

    MFC封装了GDI对象

    Win32什么流程

    MFC就还是什么流程

    cpp 复制代码
    void C双缓冲View::OnDraw(UINT nFlags,CPoint point){
      C双缓冲View* pDoc = GetDocument();
      ASSERT_CALID(pDoc);
      if(!pFoc)
        return;
        
      //双缓冲绘图
      //1.创建内存DC
      CDC dcMem;
      dcMem.CreateCompatibleDC(pDC);
      
      CRect rect;
      GetClintRect(rect);
      
      //2.创建一张屏幕DC一样的位图
      CBitmap bitmap;
      bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
      
      CPen pen;
      pen.CreatePen(PS_DOT,55,RGB(0,255,255));
      //把画笔送到内存DC
      deMem.SelectObject(pen);
      
      //3.送到内存DC中
      dcMem.SeleObject(bitmap);
      dcMem.FillSolidRect(rect,RGB(255,255,255));
      
      //然后我们使用内存DC绘图
      dcMem->MoveTo(m_begin);
      dcMem->LineTo(m_end);
        
      auto pos = m_list.GetHeadPossition();
      while(pos){
        auto Lint = m_list.GetNext(pos);
        //画直线
        dcMem->MoveTo(Line.first);
        dcMem->LineTo(line.second);
      }
      
      //4.拷贝到设备
      pDC.BitBit(0,0,rect.Width(),rect.Height(),&dcMem,0,DRCCOPY);
      
      CVIew::OnMouseMove(nFlags,point);
    }

序列化

  • 为什么要有序列化:

    我们在绘图应用程序上绘制的图形,可以保存起来,我们之后还可以打开

    而我们上面写的程序,是不能保存的,这就是序列化的基本功能

  • 简单使用以下序列化:

    1. 首先继承于CObject
    2. 类内添加声明宏DECLARE_SERTAL
    3. 类外实现宏IMPLEMENT_SERIAL

    ```cpp //直线类 class Cline:public Cobject{ public: CLine(){}; CLine(int x,int y,CString type):x(x),y(y),str(type){}; virtual coid Setialize(CArchice* ar){ if(ar,IsLoading()){ //加载 ar>>x>>y>>str; }else{ //保存 ar<

我们这样实现后,发现,我们只写了三个成员,但是文件中有很多,我们来简单追踪有一下序列化是如何实现的:

序列化过程

CArchive 归档类对象

cpp 复制代码
CArchive& AFXAPI operator>>(CArchive& ar, CLine* &pOb)
{
	pOb = (CLine*) ar.ReadObject(RUNTIME_CLASS(CLine)); 
			return ar; 
}

CArchive ar(&file, CArchive::store,4096)
{
	m_nMode = nMode;//模式
	m_pFile = pFile;//文件句柄
	m_nBufSize = nBufSize	//缓冲区大小
	this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小
	m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址
	m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}

struct 
{
	类版本
	类大小
	x
}

void CArchive::WriteObject(const CObject* line)
{
	//获取类信息地址
	CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
	//写类信息
	WriteClass(pClassRef);
	
	((CObject*)pOb)->Serialize(ar对象/*归档类对象*/);
	{
		ar << x
		{
			return CArchive::operator<<((char)x); 
			{
				if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) 
					Flush();
				
				*(UNALIGNED LONG*)m_lpBufCur = x;
				m_lpBufCur += sizeof(LONG);
				return *this; 
			}
		}
		
		AfxWriteStringLength(*this, 长度, 判断字符串是否是多字节
		{
			ar<<(BYTE)nLength;
		}
		
		:memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), lpBuf, nTemp)
		
	}
}


ar,close
{
	Flush();
	{	
		m_pFile->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart)
		{
		  //写文件
			::WriteFile(m_hFile, lpBuf, nCount, &nWritten, NULL)
			m_lpBufCur = m_lpBufStart;
		}
		
	}
}

反序列化:

cpp 复制代码
int main(){
  int nRetCode = 0;
  
  HMODULE hModule = ::GEtModuleHandle(nullptr);
  AfxWinInit(hModule,nullptr,::GetCommandLine(),0);
  CLine* line;
  
  CFile file;
  file.Open(R"(文件路径\文件名)",CFile::modeCreate|CFile::modeRead);
  CArchive ar(&file,CArchice::store,4096);
  ar >> line;
  ar.Close();
  file.Close();
  
  std::cout<<line->str;
  std::coutd<<line-x;
  std::cout<<line->y;
  
  return nRetCode;
}
cpp 复制代码
CArchive ar(&file, CArchive::store,4096)
{
	m_nMode = nMode;//模式 读
	m_pFile = pFile;//文件句柄
	m_nBufSize = nBufSize	//缓冲区大小
	this->m_lpBufStart = new BYTE[m_nBufSize]; //申请了缓冲区大小
	m_lpBufMax = m_lpBufStart + nBufSize;//缓冲区的尾地址
	m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
}

ar.ReadObject(直线类信息)
{
	//读类信息
	CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);
	{
		BYTE* lpTemp = m_lpBufStart + nPreviouslyFilled;
			do
			{
				//读文件 全部读上来
				nBytesRead = m_pFile->Read(lpTemp, nLeftToRead);
				lpTemp = lpTemp + nBytesRead;
				nTotalInBuffer += nBytesRead;
				nLeftToRead -= nBytesRead;
			}
			while (nBytesRead > 0 && nLeftToRead > 0 && nTotalInBuffer < nTotalSizeWanted);
			
			m_lpBufCur = m_lpBufStart;
			m_lpBufMax = m_lpBufStart + nTotalInBuffer;
	}
	//动态创建对象
	pOb = pClassRef->CreateObject();
	//回到我们代码
	pOb->Serialize(*this)
	{
		CArchive::operator>>((LONG&)x);
		{
			if (m_lpBufCur + sizeof(LONG) > m_lpBufMax)
				FillBuffer(UINT(sizeof(LONG) - (m_lpBufMax - m_lpBufCur)));
			i = *(UNALIGNED LONG*)m_lpBufCur;
			m_lpBufCur += sizeof(LONG);
			return *this; 
		}
	}
}

了解了序列化和反序列化过程,我们就可以将我们画的图保存起来了:

  1. 添加一个直线类

    cpp 复制代码
    #pragma once
    #include <afx.h>
    class Cline:public CObject{
    public:
      DECLARE_SERTAL(CLine)
      virtual void Serialize(CArchive& ar);
      CPoint m_begin;
      CPoint m_end;
    };
    
    //在cpp中实现:
    #include "pch.h"
    #include "CLine.h"
    
    IMPLEMENT_SERIAL(CLine,CObject,1)
    
    void Serialize(CArchive& ar){
      CObject::Serialize(ae);
      if(ar.IsLoading()){
        ar>>m_begin.x>>m_begin.y;
        ar>>m_end.x>>m_end.y;
      }else{
        ar<<m_begin.x<<m_begin.y;
        ar<<m_end.x<<m_end.y;
      }
    }
    
    我们还需要将直线的链表存储到文档中:
    在文档类.cpp中:
    #include "CLine.h"
    
    为文档类添加变量:
    CList<CLine>m_list;
  2. 我们将OnDraw消息稍作修改:就是直线的数据获取位置不一样了,我们需要修改:

cpp 复制代码
  auto pos = pDoc->m_list.GetHeadPossition();
  while(pos){
    auto Lint = pDoc->m_list.GetNext(pos);
    //画直线
    dcMem->MoveTo(Line.m_begin);
    dcMem->LineTo(line.m_end);
  }
  1. 这样修改之后,我们还需要将直线类的拷贝构造函数修改,因为默认是浅拷贝,我们在OnDraw函数中使用的时候,显示已经释放:
cpp 复制代码
#pragma once
#include <afx.h>
class Cline:public CObject{
public:

  CLine(){};
  
  CLine& operator_(const& line){
    this->m_begin = line.m_begin;
    this->m_end = list.m_red;
    return *this;
  }
  
  CLine(const CLine& line){
    this->m_begin = line.m_begin;
    this->m_end = list.m_red;
  }
  
  CLine(CPoint begin,CPoint end){
    this->m_begin = begin;
    this->m_end = end;
  }
  
  DECLARE_SERTAL(CLine)
  virtual void Serialize(CArchive& ar);
  CPoint m_begin;
  CPoint m_end;
};
  1. 然后我们去文档类中处理:
cpp 复制代码
void C...DOC::Serialize(CArchive& ar){
  if(ar.IsStoring()){
    ar<<m_list.getSize();
    auto pos = m_list.GetHeadPosition();
    while(pos){
      //ar<<&m_list.GetNext(pos);
      CLine*p = m_listl.GetNext(pos);
      ar<<&p;
      //这里第二种方式,看似跟上面的方式没有什么区别,但是运行发现,只保存了一条直线
      //这是因为每一次p都是一个地址
    }else{
      int nSize;
      ar>>nSize;
      CObject* shape;
      while(nSize--){
        ar>>shape;
        m_list.AddTail(*(CLine*)shape);
      }
    }
  }
}
相关推荐
捕鲸叉2 分钟前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer6 分钟前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq9 分钟前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
aloha_7891 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
青花瓷2 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
dsywws2 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零3 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉3 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
Dola_Pan4 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
yanlou2334 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法