《深入浅出MFC》– 永久保存、消息映射、命令传递

1.永久保存机制,其实就是用来保存我们创建的对象。由于每一个对象都有不同的状态,所以我们需要把对象的这些状态保存起来,这个和NEW出来一个对象是不同的,NEW出来的对象中的数据都是一些初始化的数据,有的时候我们已经对数据做出了修改,所以我们需要把我们自己用的对象给保存起来

我们希望有一个专门负责Serialization的函数,就叫做Serialize好了,假设现在我的Document类名称为CScribDoc,我希望有这么便利的程序方法:

void  CScribDoc::Serialize(CArchive&  ar)

{

    if(ar.IsStoring())

           ar<<m_sizeDoc;

    else

          ar>>m_sizeDoc;

    m_graphList.Serialize(ar);

}

void CObList::Serialize(CArchive&  ar)

{

    if(ar.IsStoring()){

       ar<<(DWORD)m_nCount;

       for(CNode* pNode = m_pNodeHead;pNode != NULL;pNode = pNode->pNext)

             ar<<pNode->data;

   }else{

        WORD    nNewCount;

        ar>>nNewCount;

       while(nNewCount—){

             CObject*    newData;

            ar>>newData;

            AddTail(newData);

       }

   }

}

每一个可写到文件或从文件中读出的类,都应该有它自己的Serailize函数,负责它自己的数据读写文件操作。此类并且应该改写<<运算符和>>运算符,把数据导流到archive中。archive是什么?一个与文件息息相关的缓冲区,暂时你可以想象它就是文件的化身。

要将<<和>>两个运算符重载,还要让Serialize函数神不知鬼不觉地放入类声明之中,最好的做法仍然是使用宏。

类之所以能够进行文件读写操作,前提是拥有动态创建的能力,所以,MFC设计了两个宏DECLARE_SERIAL和IMPLEMENT_SERIAL:

#define DECLARE_SERIAL(class_name) \

   DECLARE_DYNCREATE(class_name) \

   friend  CArchive&  AFXAPI  operator>>(CArchive& ar,class_name*  &pOb);

#define IMPLEMENT_SERIAL(class_name,base_class_name,wSchema) \

   CObject*  PASCAL  class_name::CreateObject() \

      {return  new  class_name;}  \

  _IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema, \

   class_name::CreateObject)  \

  CArchive&  AFXAPI  operator>>(CArchive& ar,class_name* &pOb) \

   {pOb = (class_name*)ar.ReadObject(RUNTIME_CLASS(class_name)); \

       return  ar;} \

为了在每一个对象被处理之前,能够处理琐屑的工作,诸如判断是否第一次出现,记录版本号码、记录文件名等工作,CRuntimeClass需要两个函数Load和Store:

struct CRuntimeClass
{

LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema;
CObject *(PASCAL *m_pfnCreateObject)();
CRuntimeClass *m_pBaseClass;
static CRuntimeClass *pFirstClass;
CRuntimeClass *m_pNextClass;

void Store (CArchive &ar) const;

static CRuntimeClass *PASCAL Load(CArchive &ar,UINT *pwSchemaeNum);

};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//读取一个类
CRuntimeClass *PASCAL CRuntimeClass::Load(CArchive &ar,UINT *pwSchemaNUm)
{
       WORD nLen;
       char szClassName[64];
       CRuntimeClass *pClass;
       //得到类的长度
       ar >> (WORD)(*pwSchemaNum)>>nLen;
       if(nLen >= sizeof(szClassName) || ar.Read(szClassName,nLen)!=nLen)
       {
              return NULL;
       }
       szClassName[nLen] = ‘\0;
       //逐个比较读取的泪是否在这个类型录中
       for(pClass=pFirstClass;pClass!=NULL;pClass=pClass->m_pNextClass)
       {
              //如果在类型录中
              if(lstrcmp(szClassName,pClass->m_lpszClassName)==0)
              {
                     Return pClass;
              }
       }
       return NULL;
}
 
//存储
void CRuntimeClass::Stroe(CArchive &ar) const
{
       WORD nLen = (WORD)lstrlenA(m_lpszClassName);
       ar << (WORD)m_wSchema<<nLen;
       ar.Write(m_lpszClassName,nLen*sizeof(char));
}

 

2.Message Mapping(消息映射)

SDK编程中,一般处理消息的方法就是使用switch/case判断消息的类型,然后进行响应。更模块化的方法消息映射表的方法,把消息和消息处理函数关联起来。

应该为每个需要处理消息的类构建一个消息映射表,并将基类与派生类的消息映射表连接起来。当窗口函数比较消息时,就沿着这条继承路线传递下去。

首先定义数据结构:

struct AFX_MSGMAP

{

  AFX_MSGMAP *  pBaseMessageMap;//指向基类的本结构。

  AFX_MSGMAP_ENTRY*  lpEntries;//本类的消息映射表。

};

struct AFX_MSGMAP_ENTRY

{

  UINT nMessage;

  UINT nCode;

  UINT nID;

  UINT nLastID;

  UINT nSig;

  AFX_PMSG pfn;

};

其中的AFX_PMSG定义为函数指针:

typedef  void  (CCmdTarget::*ZFX_PMSG)(void);

然后我们定义一个宏:

#define DECLARE_MESSAGE_MAP()\ 
    static AFX_MSGMAP_ENTRY  _messageEntries[];\ 
    static AFX_MSGMAP  messageMap;\ 
    virtual AFX_MSGMAP*  GetMessageMap()  const;

于是DECLARE_MESSAGE_MAP就相当于声明了这样一个数据结构:

image

这个数据结构的内容填塞工作又三个宏完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define BEGIN_MESSAGE_MAP(class_name,base_class)\  
    AFX_MSGMAP*class_name::GetMessageMap()const\  
       {return &class_name::messageMap;}\  
    AFX_MSGMAP class_name::messageMap={&base_class::messageMap,class_name::_messageEntries};\  
    AFX_MSGMAP_ENTRY class_name::_messageEntries[]=\  
      {  
 
#define ON_COMMAND(id,memFunc)\  
      {WM_COMMAND,0,(WORD)id,(WORD)id,AfxSig_vv,(AFX_PMSG)memFunc },\  
 
#define END_MESSAGE_MAP()\  
       {0,0,0,0,AfxSig_end,(AFX_PMSG)0}\  
        };

其中的AfxSig_end定义为:

enum  AfxSig{

      AfxSig_en = 0,

      AfxSig_vv,

};

图例:

image

那么我们可以根据这个和原来的重要类构成一个消息传递网:

image

3.Command Routing(命令传递)

MFC对于消息循环的规定是:

①:如果是一般的Windows消息(WM_xxx),则一定是由派生类流向基类,没有旁流的可能。

②:如果是命令消息WM_COMMAND,那就有奇特的路线了。

当CMyFrameWnd对象获得一个WM_COMMAND时,导致的函数调用次数:

2014-07-06_191420

直接看消息传递路线来得直接些:

image

转载请标注来源:http://www.alonemonkey.com

By:AloneMonkey

本文链接:http://www.alonemonkey.com/mfc-four.html