绪论
在MFC中,框架类(CFrameWnd)代表着Windows中一个窗口,它负责窗口的注册和创建,并可以将控件包括按钮、菜单、工具栏等都可以挂载在CFrameWnd中。它功能非常强大,管理着窗口的一切。本文将通过pFrame来详细研究MFC是如何进行消息处理的。
窗口的创建是通过pFrame->Create()进行创建,它主要分为如下几个步骤:
第一步:加载菜单
第二步:利用pFrame->CreateEx()中的PreCreateWindow注册窗口
第三步:利用pFrame->CreateEx()中的 AfxHookWindowCreate设置钩子
第四步:在pFrame->CreateEx()中调用win32的CreateWindowEx()创建
第四步:窗口创建成功后,就会调用钩子处理函数AfxCbtFilterHook()将框架对象与窗口句柄进行绑定,并更改消息处理函数为AfxWndProc();
MFC的窗口创建的过程详细介绍请阅读MFC的窗口创建机制
在窗口创建的第四步,MFC更改了消息处理函数,那么MFC是如何处理消息的,接下来将进一步进行介绍。
MFC消息处理
在CFrameWnd有一个消息处理函数WindowProc()。利用它可以对消息进行处理,我们重写一下WindowProc().然后利用调用堆栈的方法,找到消息最终的处理函数AfxWndProc().来研究MFC的消息处理过程。
重写WindowProc()的内容如下:
LRESULT CMyFrameWnd::WindowProc (UINT msgID, WPARAM wParam, LPARAM lParam)
{
switch (msgID)
{
case WM_ CREATE:
{
AfxMessageBox(_T("消息被处理了"));
}break;
}
//调用父类的WindowProc
return CFrameWnd::WindowProc(msgID, wParam, lParam);
}
MFC消息处理流程
利用调用堆栈找到AfxWndProc();以WM_CREATE为例来跟踪消息处理的过程。
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
//通过窗口句柄获取pFramed对象,通过pFrame对象调用pFrame的WindowProc函数进行窗口消息的处理
//FromHandlePermanent(hWnd)获取pFrame的执行过程如下: 参数为窗口句柄
CWnd* pWnd = CWND::FromHandlePermanent(hWnd);
{
//获取保存窗口句柄与Frame对应的map集合 afxMapHWND()的执行过程如下
CHandleMap* pMap = afxMapHWND();
{
//获取全局变量 AFX_MODULE_THREAD_STATE*
// pThreadState 当前程序线程状态信息
AFX_MODULE_THREAD_STATE8* pState =
AfxGetModuleThreadState();
return pState->m_pmapHWND;
//此时可能会有疑问,为什么可以通过全局变量
//就可以获取保存窗口句柄和框架类的map集合?
//这是因为窗口创建时就将两者的集合保存到了全局变量
}
//通过窗口句柄在map中获取Frame对象
PWnd = (CWnd*)pMap->LookupPermanent(hWnd);
//返回Frame对象
return PWnd;
}
//通过AfxCallWndProc将消息分发给不同的Frame的窗口消息处理函数,AfxCallWndProc的执行过程如下:
return AfxCallWndProc(pWnd,hWnd,mMsg,wParam,lParam);
{
//直接调用了Frame的窗口消息处理函数
lResult = pWnd->WindowProc(nMsg,wParam,lParam);//回到自己的代码
}
}
总结
- 当收到消息时,进入AfxWinProc函数
- AfxWndProc函数根据消息的窗口句柄吗,查询对应框架类Frame
- 利用Frame调用Frame成员虚函数WindowProc,完成消息的处理
消息映射机制
经过上述分析所有的消息都有经过AfxWinProc,然后分发给不同的重写消息处理函数。重写消息处理函数需要我们自己进行消息捕获和处理,如何我们能够不重写消息处理函数就能进行消息处理。MFC为我们提供了消息映射机制,这样我们就可以不用重写函数就可以对单一的消息进行处理。
消息映射机制的使用
类必须具备的条件
类内必须添加声明宏 DECLARE_MESSAGE_MAP()
类外必须添加实现宏
BEGIN_MESSAGE_MAP(theClass,baseClass)
消息处理宏 例如 ON_MESSAGE(消息ID,消息处理函数名)
END_MESSAGE_MAP()
消息机制的实施
以WM_CREATE消息为例
-
在 BEGIN_MESSAGE_MAP(theClass,baseClass)和END_MESSAGE_MAP()之间添加ON_MESSAGGE(WM_CREATE,OnCreate)的宏,其中OnCreate是自己定义的处理函数
-
在CMyFrameWnd(继承CFrameWnd的类)类内添加OnCreate函数的声明和定义
代码实现
class CMyFrameWnd : public CFrameWnd
{
//通过消息机制进行消息处理
DECLARE_MESSAGE_MAP()
LRESULT OnCreate(WPARAM wParam, LPARAM lParam);
}
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_MESSAGE(WM_CREATE, OnCreate)
END_MESSAGE_MAP()
LRESULT CMyFrameWnd::OnCreate(WPARAM wParam, LPARAM lParam)
{
AfxMessageBox(_T("消息被处理了"));
return LRESULT();
}
消息映射实现原理
为了能够更好的理解消息机制我们将上面消息机制的实施的宏代表的内容展开,其代码变为:
class CMyFrameWnd:public CFrameWnd
{
//通过消息机制进行消息处理
//DECLARE_MESSAGE_MAP()展开的内容
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;
LRESULT OnCreate(WPARAM wParam, LPARAM lParam);
}
//将BEGIN_MESSAGE_MAP(CMyFrameWnd,CFrameWnd) 展开的内容并且替换参数后的代码
PTM_WARNING_DISABLE
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
{
typedef CMyFrameWnd ThisClass;
typedef CFrameWnd TheBaseClass;
__pragma(warning(push))
__pragma(warning(disab1e:4640)) /* message maps can only be called by single threaded message pump */
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
//ON_MESSAGE(WM_CREATE, OnCreate)展开内容并且替换参数后的代码
{WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG) (AFX_PMSCW) (static_cast<LRESULT(AFX_MSG_CALL CWnd::*) (WPARAM, LPARAM)> (OnCreate))},
//将END_MESSAGE_MAP() 展开的内容
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
__pragma(warning(pop))
static const AFX_MSGMAP messageMap = { &TheBaseC1ass::GetThisMessageMap, &_messageEntries[0]};
return &messageMap;
}
PTM_WARNING_RESTORE
根据上面的宏展开内容可以得出
在类中有一个静态函数GetThisMessageMap() 和 一个虚函数GetMessageMap()且此虚函数直接调用了GetThisMessageMap()。
在静态函数GetThisMessageMap() 中有两个结构 AFX_MSGMAP_ENTRY 和 AFX_MSGMAP。为了更好的理解消息映射机制,我们先来介绍一下着两个结构体。
AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; //消息ID
UINT nCode; //control code or WM_ NOTIFY code
UINT nID; // control ID (or 0 for windows messages) 控件ID
UINT nLastID; //最后一个控件ID
UINT_ PTR nSig;//处理消息的函数类型
AFX_PMSG pfn; //处理消息的函数名
}
我们要重点关注该结构体的UINT nMessage; //消息ID 和 AFX_PMSG pfn; //处理消息的函数名
AFX_MSGMAP
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); //父类宏展开的静态变量地址
const AFX_MSGMAP_ENTRY* lpEntries; //本类宏展开的静态数组首地址
}
两个结构体分析
在上面的代码展开的静态函数GetThisMessageMap()中,由两个静态的变量,分别是AFX_MSGMAP_ENTRY类型的数组和AFX_MSGMAP类型的变量。
其中AFX_MSGMAP_ENTRY类型的数组存放了和消息相关的内容。
AFX_MSGMAP类型的变量的第一个成员存放的是父类的AFX_MSGMAP的变量的地址,第二个成员存放的是AFX_MSGMAP_ENTRY类型的数组的首地址。
依次类推,CMyFrameWnd的父类也由这两个静态成员,其中AFX_MSGMAP的第一个成员存放了CMyFrameWnd的父类的父类的AFX_MSGMAP的变量的地址,第二个成员存放CMyFrameWnd的父类的AFX_MSGMAP_ENTRY类型的数组。
因此就形成了一个单向的链表,根据MFC的继承结构可知链表的结构为:
图表说明:箭头的出发点为 AFX_MSGMAP 箭头的指向为箭头的标识
宏展开各部分的作用
-
GetThisMessageMap() 静态函数
作用:定义静态变量和静态数组,并返回本类静态变量的地址(获取链表头)
-
_messageEntries[] 静态数组
作用:数组的每个元素,保存的为消息ID和处理消息的函数名(地址)
-
messageMap 静态变量
作用:
第一个成员,保存父类宏展开的静态变量地址(负责连接链表) 第二个成员,保存本类的静态数组首地址
-
GetMessageMap() 虚函数
作用:返回本类静态变量地址(获取链表头)
消息映射机制的具体实现步骤
通过程序运行的过程理解消息映射原理,通过程序调试找到OnCreate调用的源头AfxWindowProc。
程序运行过程,以WM_CREATE为例
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
//通过窗口句柄获取pFramed对象,通过pFrame对象调用pFrame的WindowProc函数进行窗口消息的处理
CWnd* pWnd = CWND::FromHandlePermanent(hWnd);
//通过AfxCallWndProc将消息分发给不同的Frame的窗口消息处理函数,AfxCallWndProc的执行过程如下:
return AfxCallWndProc(pWnd,hWnd,mMsg,wParam,lParam);
{
//直接调用了Frame的窗口消息处理函数
lResult = pWnd->WindowProc(nMsg,wParam,lParam);
//此时我们没有重写WindowProc(),所它会执行父类CWnd的
//WindowProc(),**CWnd::WindowProc**的执行过程在下面
}
}
CWnd::WindowProc的执行过程
LRESULT CWnd::WindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
{
//通过OnWndMsg对不同的消息进行分析,并且变量原型链表,OnWndMsg的执行过程在如下
OnWndMsg(nMsg,wParam,lParam,&lResult)
{
//获取链表头
const AFX_MSGMAP* pMessageMap;
pMessageMap = GetMessageMap();
//定义AFX_MSGMAP_ENTRY* lpEntry的指针
//循环遍历链表
for( pMessageMap = GetMessageMap(); //头节点
pMessageMap->pfnGetBaseMap != NULL; //判断到达链表尾部
pMessageMap = (*pMessageMap->pfnGetBaseMap)() //链表的下一个节点
)
{
if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,message,0,0))!=NULL)
{
//找到了与message匹配的AFX_MSGMAP_ENTRY的数组
//跳出for循环,去执行AFX_MSGMAP_ENTRY的数组的OnCreate函数处理消息
//LDisPatch的执行过程在下面
goto LDisPatch;
}
}
}
}
LDisPatch:
//获取OnCreate的地址
mmf.pfn = lpEntry->pfn;
//调用OnCreate函数
lResult = (this->*mmf.pfn_l_W_1)(wParam,lParam);
总结:
- 消息产生后进入窗口处理函数(AfxWndProc)
- 根据已知窗口句柄,找到和它绑定在一起的框架类对象(Frame)
- 利用框架类对象(Frame)调用框架类成员虚函数WindowProc,本类中未重写调用父类的WindowProc
- 获取本类对应的静态变量,并找到对应数组中去匹配的消息Id
- 如果未找到,获取父类对应的静态变量,并到对应的数字中去匹配查找
- 如果找到了,利用找到的数组元素的最后一个成员(函数地址),并调用,完成消息处理。
消息的分类
MFC消息分类
注意:在MFC使用的ON_MESSAGE()是一个通用的宏,可以向AFX_MSGMAP_ENTRY数组中放置任何种类的消息以及它的处理函数
但是在一般的开发中我们不使用ON_MESSAGE的宏。
在MFC中不同种类的消息使用不同的消息宏和不同的消息处理函数。MFC的消息分类具体如下:
-
标准Windows消息宏
ON_WM_xxx xxx代表不同的消息
例如:ON_WM_CREATE标准的Windows消息宏的不同消息具有固定且不同的消息处理函数,具体可以在msdn中查询
-
自定消息宏
ON_MESSAGE(消息ID,处理消息函数的名)
处理消息函数的原型
LRESULT OnCreate(WPARAM wParam, LPARAM lParam);
-
命令消息宏
ON_COMMAND(资源的ID,处理消息函数的名)
处理消息函数的原型例子
void OnButton();
命令消息是一种特殊的消息,它在CWinApp(应用程序)、CFrameWnd(框架类)、CView(视图类)、CDocument(文档类)中都可以进行处理,且具有不同的执行顺序。具体内容将在后续的文章内介绍。
总结
通过以上的内容分析,我们可以了解MFC的消息映射机制是维护一个关于继承关系的链表,然后遍历链表来找到对应消息的消息处理函数,然后进入我们自定义的消息处理函数进行消息处理。其实CWinApp(应用程序)、CFrameWnd(框架类)、CView(视图类)、CDocument(文档类)都有一个关于继承关系的链表,本章只是以CFrameWnd为例进行介绍。
消息映射机制是MFC是六大机制之一,也是比较重要的机制,前面的文章介绍了MFC的程序启动机制和MFC的窗口创建机制。在下一篇文章将要介绍MFC另外的两大机制运行时类信息机制与动态创建机制