程序架构
首先创建一个MFC应用程序
它的类结构如下图(省略了Ex后缀的类)
CObject是所有类的基类,提供了MFC的一些机制,比如RTTI(简单理解为运行期类信息识别)、串行化(简单理解为将应用程序数据持久存储到硬盘或从硬盘读取数据的能力)等
CCmdTarget类,接收命令消息的类的基类
CWinThread是线程类,有一个重要的函数run,用于消息分发
CWinApp是应用程序类,负责管理应用程序的初始化、运行和结束
CWnd类提供窗口的基本功能
接下来是文档/视图结构的组成部分:CFrameWnd为窗口框架,是应用程序顶层窗口,并充当视图的容器;CView提供文档(这里指数据)的可视化;CDocument用来保存数据
RTTI
CRuntimeClass
RTTI是运行阶段类型识别(Runtime Type Identification)的简称
在MFC中实现RTTI的核心结构体是CRuntimeClass
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;//类名
int m_nObjectSize;//对象大小
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
CRuntimeClass* m_pBaseClass;//基类
#endif
// Operations
CObject* CreateObject();
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
// dynamic name lookup and creation
static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);
static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);
static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);
// Implementation
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
CRuntimeClass* m_pNextClass; // linked list of registered classes
const AFX_CLASSINIT* m_pClassInit;
};
组织成的链表结构如下:
相关宏
//声明
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();
#define DECLARE_DYNAMIC(class_name) \
protected: \
static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
static const CRuntimeClass class##class_name; \
static CRuntimeClass* PASCAL GetThisClass(); \
__pragma(warning(suppress:26433)) \
virtual CRuntimeClass* GetRuntimeClass() const; \
//实现
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject, NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
CRuntimeClass* PASCAL class_name::_GetBaseClass() \
{ return RUNTIME_CLASS(base_class_name); } \ //#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())
AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
&class_name::_GetBaseClass, NULL, class_init }; \
CRuntimeClass* PASCAL class_name::GetThisClass() \
{ return _RUNTIME_CLASS(class_name); } \ //#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return _RUNTIME_CLASS(class_name); }
//CMySDIView宏展开
//声明
protected:
static CRuntimeClass* __stdcall _GetBaseClass();
public:
static const CRuntimeClass classCMySDIView;
static CRuntimeClass* __stdcall GetThisClass();
__pragma(warning(suppress:26433))
virtual CRuntimeClass* GetRuntimeClass() const;
static CObject* __stdcall CreateObject();
//实现
CObject* __stdcall CMySDIView::CreateObject() {
return new("D:\\VS\\VSrepos\\MySDI\\MySDI\\MySDIView.cpp", 23) CMySDIView;
} //运行期动态生成对象
CRuntimeClass* __stdcall CMySDIView::_GetBaseClass() {
return (CView::GetThisClass());
}
__declspec(selectany) const CRuntimeClass CMySDIView::classCMySDIView = { "CMySDIView", sizeof(class CMySDIView), 0xFFFF, CMySDIView::CreateObject, &CMySDIView::_GetBaseClass, 0, 0 };
CRuntimeClass* __stdcall CMySDIView::GetThisClass() {
return ((CRuntimeClass*)(&CMySDIView::classCMySDIView));
}
CRuntimeClass* CMySDIView::GetRuntimeClass() const {
return ((CRuntimeClass*)(&CMySDIView::classCMySDIView));
}
消息映射
消息映射是一个将消息和成员函数相互关联的表。消息映射是MFC避免使用冗长的虚表的一种方式
宏
#define DECLARE_MESSAGE_MAP() \
protected: \
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
const AFX_MSGMAP* GetMessageMap() const override; \
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
__pragma(warning(push)) \
__pragma(warning(disable: 4640)) /* message maps can only be called by single threaded message pump */ \
static const AFX_MSGMAP_ENTRY _messageEntries[] = \
{
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
__pragma(warning(pop)) \
static const AFX_MSGMAP messageMap = \
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
return &messageMap; \
}
//CMySDIView宏展开
protected:
static const AFX_MSGMAP* __stdcall GetThisMessageMap();
const AFX_MSGMAP* GetMessageMap() const override;
//BEGIN_MESSAGE_MAP(CMySDIView, CView)
//ON_WM_CONTEXTMENU()
//ON_WM_RBUTTONUP()
//END_MESSAGE_MAP()
const AFX_MSGMAP* CMySDIView::GetMessageMap() const {
return GetThisMessageMap();
}
const AFX_MSGMAP* __stdcall CMySDIView::GetThisMessageMap() {
typedef CMySDIView ThisClass; typedef CView TheBaseClass;
static const AFX_MSGMAP_ENTRY _messageEntries[] = {
{ 0x007B, 0, 0, 0, AfxSig_vWp, (AFX_PMSG)(AFX_PMSGW)(static_cast< void (CWnd::*) (CWnd*, CPoint) > (&ThisClass::OnContextMenu)) },
{ 0x0205, 0, 0, 0, AfxSig_vwp, (AFX_PMSG)(AFX_PMSGW)(static_cast< void (CWnd::*)(UINT, CPoint) > (&ThisClass::OnRButtonUp)) },
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };
static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
return &messageMap; }
消息映射结构体
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//父类映射表指针
const AFX_MSGMAP_ENTRY* lpEntries;
};
消息流动
以CMySDIView::OnRButtonUp为例
框架调用了从CWnd继承下来的虚拟WindowProc函数。WindowProc 调用OnWndMsg,而OnWndMsg对于标准消息又调用GetMessageMap来获取一个指向 CMySDIView::messageMap的指针,并搜索 CMySDIView::lpEntries来获取一个其消息ID与当前正等待 处理的消息的ID相匹配的条目。如果找到了该条目,对应的CMySDIView 函数(其地址与该消息ID一同存储在lpEntries数组中)就被调用。否则,OnWndMsg参考 CMySDIView::pfnGetBaseMap获得一个指向CView::messageMap的指针并为基类重复该过程。如果 基类没有该消息的处理程序,则框架将上升一个级别,参考基类的基类,相当系统地沿着继承链向上走,直到它找到一个消息处理程序或者将该消息传递给 Windows进行默认处理为止
//部分代码
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
// special case for commands
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = 1;
goto LReturnTrue;
}
return FALSE;
}
// special case for notifies
if (message == WM_NOTIFY)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
goto LReturnTrue;
return FALSE;
}
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
UINT iHash; iHash = (LOWORD((DWORD_PTR)pMessageMap) ^ message) & (iHashMax-1);
winMsgLock.Lock(CRIT_WINMSGCACHE);
AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
const AFX_MSGMAP_ENTRY* lpEntry;
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
{
// cache hit
lpEntry = pMsgCache->lpEntry;
winMsgLock.Unlock();
if (lpEntry == NULL)
return FALSE;
// cache hit, and it needs to be handled
if (message < 0xC000)
goto LDispatch;
else
goto LDispatchRegistered;
}
else
{
// not in cache, look for it
pMsgCache->nMsg = message;
pMsgCache->pMessageMap = pMessageMap;
for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
pMessageMap = (*pMessageMap->pfnGetBaseMap)())
{
// Note: catch not so common but fatal mistake!!
// BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
if (message < 0xC000)
{
// constant window message
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
message, 0, 0)) != NULL)
{
pMsgCache->lpEntry = lpEntry;
winMsgLock.Unlock();
goto LDispatch;
}
}
else
{
// registered windows message
lpEntry = pMessageMap->lpEntries;
while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
{
UINT* pnID = (UINT*)(lpEntry->nSig);
ASSERT(*pnID >= 0xC000 || *pnID == 0);
// must be successfully registered
if (*pnID == message)
{
pMsgCache->lpEntry = lpEntry;
winMsgLock.Unlock();
goto LDispatchRegistered;
}
lpEntry++; // keep looking past this one
}
}
}
pMsgCache->lpEntry = NULL;
winMsgLock.Unlock();
return FALSE;
}
如果消息类型是WM_COMMAND,消息流动如下
在CFrameWnd::OnCmdMsg函数中会分三路
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
CPushRoutingFrame push(this);
// pump through current view FIRST
CView* pView = GetActiveView();
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// then pump through frame
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
// last but not least, pump through app
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return FALSE;
}
在CView::OnCmdMsg函数中可以看出是先View后Document
CWnd::OnCmdMsg函数其实调用的是CCmdTarget::OnCmdMsg函数
//部分代码
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
// for backward compatibility HIWORD(nCode)==0 is WM_COMMAND
if (nMsg == 0)
nMsg = WM_COMMAND;
// look through message map to see if it applies to us
for (pMessageMap = GetMessageMap(); pMessageMap->pfnGetBaseMap != NULL;
pMessageMap = (*pMessageMap->pfnGetBaseMap)())
{
// Note: catches BEGIN_MESSAGE_MAP(CMyClass, CMyClass)!
ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);
if (lpEntry != NULL)
{
// found it
return _AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);
}
}
return FALSE; // not handled
}
CDocument中的函数
命令消息的调用次序
文档/视图结构
文档/视图结构是一种将数据与视图分离的程序设计模型,使程序模块化
Document、 View、 Frame三部分由CDocTemplate类管理(单文档视图是其派生类CSingleDocTemplate管理),InitInstance函数中的操作如下
//部分代码
BOOL CMySDIApp::InitInstance()
{
// 注册应用程序的文档模板。 文档模板
// 将用作文档、框架窗口和视图之间的连接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMySDIDoc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(CMySDIView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);//CDocManager中维护着CDocTemplate的指针链表
// 唯一的一个窗口已初始化,因此显示它并对其进行更新
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
CDocument有一个成员变量CDocTemplate*m_pDocTemplate,回指其DocumentTemplate;另外有一个成员变量CPtrList m_viewList,表示它可以同时维护一组Views
CFrameWnd有一个成员变量CView*m_pViewActive,指向当前活动 的View
CView有一个成员变量CDocument*m_pDocument,指向相关的Document
串行化(序列化)
序列化在面向对象的程序设计中出现的,它基于对象是可以连续的思想,在程序退出或者启动的时候,可以把对象顺序的存储在磁盘或者从磁盘读出,存储对象的过程叫做序列化,读出对象的过程叫反序列化
可串行化类
编写可串行化类的步骤:
继承自CObject
头文件中添加DECLARE_SERIAL宏
实现文件中添加 IMPLEMENT SERIAL宏
添加一个缺省构造函数
重写Serialize( CArchive& ar )函数
相关宏
#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \
AFX_API 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; } \
extern AFX_CLASSINIT _init_##class_name; \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject, &_init_##class_name) \
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; }
示例
#pragma once
#include <afx.h>
class MyClass:public CObject
{
DECLARE_SERIAL(MyClass)
public:
MyClass(){}
MyClass(int i,float j):m_iVal(i),m_fVal(j){}
virtual void Serialize(CArchive& ar);
public:
int m_iVal=0;
float m_fVal=0;
};
#include "MyClass.h"
IMPLEMENT_SERIAL(MyClass, CObject, 1)
//CObject* __stdcall MyClass::CreateObject() {
// return new MyClass;
//}
//extern AFX_CLASSINIT _init_MyClass;
//__declspec(selectany) CRuntimeClass MyClass::classMyClass = { "MyClass", sizeof(class MyClass), 1, MyClass::CreateObject, ((CRuntimeClass*)(&CObject::classCObject)), 0, &_init_MyClass };
//CRuntimeClass* MyClass::GetRuntimeClass() const {
// return ((CRuntimeClass*)(&MyClass::classMyClass));
//}
//AFX_CLASSINIT _init_MyClass(((CRuntimeClass*)(&MyClass::classMyClass)));
//CArchive& __stdcall operator>>(CArchive& ar, MyClass*& pOb) {
// pOb = (MyClass*)ar.ReadObject(((CRuntimeClass*)(&MyClass::classMyClass))); return ar;
}
void MyClass::Serialize(CArchive& ar)
{
if (ar.IsLoading())
{
//从文件中读取数据到内存中
ar >> m_iVal;
ar >> m_fVal;
}
else if (ar.IsStoring())
{
ar << m_iVal;
ar << m_fVal;
}
}
编写CMySDIDoc::Serialize函数
void CMySDIDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: 在此添加存储代码
ar << &myclass;
}
else
{
// TODO: 在此添加加载代码
CObject* p;
ar >> p;
delete p;
}
}
存储流程
点击保存后,调用堆栈如下
//部分代码
#define wNullTag ((WORD)0) // special tag indicating NULL ptrs
#define wNewClassTag ((WORD)0xFFFF) // special tag indicating new CRuntimeClass
#define wClassTag ((WORD)0x8000) // 0x8000 indicates class tag (OR'd)
#define dwBigClassTag ((DWORD)0x80000000) // 0x8000000 indicates big class tag (OR'd)
#define wBigObjectTag ((WORD)0x7FFF) // 0x7FFF indicates DWORD object tag
#define nMaxMapCount ((DWORD)0x3FFFFFFE) // 0x3FFFFFFE last valid mapCount
void CArchive::WriteObject(const CObject* pOb)
{
// object can be NULL
DWORD nObIndex;
// make sure m_pStoreMap is initialized
MapObject(NULL);
if (pOb == NULL)
else if ((nObIndex = (DWORD)(DWORD_PTR)(*m_pStoreMap)[(void*)pOb]) != 0)
else
{
// write class of object first
CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
WriteClass(pClassRef);//写入tag标记、版本号、类名大小、类名
// enter in stored object table, checking for overflow
CheckCount();
(*m_pStoreMap)[(void*)pOb] = (void*)(DWORD_PTR)m_nMapCount++;
// cause the object to serialize itself
((CObject*)pOb)->Serialize(*this);//写入对象数据
}
}
存储的数据如下
读取流程
点击打开最近文件
//部分代码
CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
// attempt to load next stream as CRuntimeClass
UINT nSchema;
DWORD obTag;
CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);
// check to see if tag to already loaded object
CObject* pOb=NULL;
if (pClassRef == NULL)
{
}
else
{
TRY
{
// allocate a new object based on the class just acquired
pOb = pClassRef->CreateObject();
if (pOb == NULL)
AfxThrowMemoryException();
// Serialize the object with the schema number set in the archive
UINT nSchemaSave = m_nObjectSchema;
m_nObjectSchema = nSchema;
pOb->Serialize(*this);
m_nObjectSchema = nSchemaSave;
ASSERT_VALID(pOb);
}
}
return pOb;
}
参考文档
MFC Windows程序设计(第2版)——Jeff Prosise
深入浅出MFC 第2版——侯捷