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就相当于声明了这样一个数据结构:
这个数据结构的内容填塞工作又三个宏完成:
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,
};
图例:
那么我们可以根据这个和原来的重要类构成一个消息传递网:
3.Command Routing(命令传递)
MFC对于消息循环的规定是:
①:如果是一般的Windows消息(WM_xxx),则一定是由派生类流向基类,没有旁流的可能。
②:如果是命令消息WM_COMMAND,那就有奇特的路线了。
当CMyFrameWnd对象获得一个WM_COMMAND时,导致的函数调用次数:
直接看消息传递路线来得直接些:
转载请标注来源:http://www.alonemonkey.com
By:AloneMonkey