上篇我们通过重写WindowProc函数来处理消息,今天我们采用下面的方法实现同样的功能
1.建一个win32简单应用程序,不要认为这样就不能写出MFC程序,因为是不是MFC程序取决于调没调MFC函数。
2. 删除入口函数,只留下#include "stdafx.h"
3.将stdafx.h中的头文件 <windows.h> 更改为 <afxwin.h>。4.Project-->Settings菜单项中设置使用MFC库
5.编写代码:
// MFCMsg.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
class CMyFrameWnd :public CFrameWnd
{
DECLARE_MESSAGE_MAP();
public:
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("WM_CREATE");
return 0;
}
class CMyWinApp:public CWinApp
{
public:
virtual BOOL InitInstance();
};
CMyWinApp theApp;
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd *pFrame=new CMyFrameWnd;
pFrame->Create(NULL,"MFCMsg");
m_pMainWnd=pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
注意:
1 自己的类必须派生自 CCmdTarget
2 自己的类内 必须添加声明宏 DECLARE_MESSAGE_MAP
3 自己的类外 必须添加实现宏
BEGIN_MESSAGE_MAP( theClass, baseClass )
END_MESSAGE_MAP( )
为什么我们自己定义的OnCreate函数就能处理消息???答案我会在文章最后给出,现在我们给出分析过程:
1. 为DECLARE_MESSAGE_MAP,BEGIN_MESSAGE_MAP,END_MESSAGE_MAP分别做宏代换并作整理,(选中点go,西红柿插件)
/*private:
static const AFX_MSGMAP_ENTRY _messageEntries[]; //1185粘贴过来的
//静态数组 的每个变量都是AFX_MSGMAP_ENTRY类型的结构体
protected:
static AFX_DATA const AFX_MSGMAP messageMap; //静态变量 messageMap是AFX_MSGMAP的对象
virtual const AFX_MSGMAP* GetMessageMap() const; */
/*const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const // 1206粘过来的 BEGIN_MESSAGE_MAP 点go
{
return &CMyFrameWnd::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP
CMyFrameWnd::messageMap =
{
&CFrameWnd::messageMap,
&CMyFrameWnd::_messageEntries[0]
};
AFX_COMDAT const AFX_MSGMAP_ENTRY
CMyFrameWnd::_messageEntries[] = //END_MESSAGE_MAP点go
{
{ WM_CREATE, 0, 0, 0, AfxSig_lwl, // ON_MESSAGE 点go
(AFX_PMSG)(AFX_PMSGW)
(LRESULT (AFX_MSG_CALL CWnd::*)
(WPARAM, LPARAM))&OnCreate },
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};*/
通过上边的代码我们可以看出里边包含了一个静态数组,一个静态变量,还有一个虚函数:
_messageEntries[] - 静态数组
数组中每个元素的类型AFX_MSGMAP_ENTRY
数组中每个元素保存的是 消息ID 对应的 处理函数
messageMap - 静态变量
类型 :AFX_MSGMAP
第一个成员保存的是父类的静态变量地址(用于连接链表)
第二个成员保存的是本类的静态数组首地址。
GetMessageMap - 虚函数
返回本类静态变量地址(获取链表头节点)
通过下边这两个图我们可以分许这三者的联系:第一幅图的左边是表示的是静态变量的第二个成员指向的是本类的静态数组首地址,第一个成员保存的是父类的静态变量地址,父子类关系图通过第二幅图能清楚的看出来,静态数组里面装的是的是 消息ID 以及对应的 处理函数等,我们也可以清楚的看出数组里有一行0,0,0,0,表示数组末尾。现在就形成了条链表,头结点在我们自己定义的类中,可以通过虚函数得到,尾节点是NULL(因为CObject类不含这三个变量)。
2. 现在我们已经搞清楚这条链表的来历了,下面我们来用断点调试和调用堆栈来分析消息的具体处理过程,断点加 AfxMessageBox("WM_CREATE");这行,调试运行,通过调用堆栈定位到AfxWndProc函数中,写出伪代码:
AfxWndProc(...)
{
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
//获取 和 hWnd绑定在一起的框架类对象地址pFrame<==>pWnd
AfxCallWndProc(pWnd..)//参数的pWnd<==>pFrame
{
pWnd->WindowProc(...)//函数内部this为pWnd<==>pFrame
{
OnWndMsg(...)//函数内部this为pWnd<==>pFrame
{
AFX_MSGMAP* pMessageMap = GetMessageMap();
//获取了本类静态变量地址(链表头节点)
const AFX_MSGMAP_ENTRY* lpEntry;
for (; pMessageMap != NULL;
pMessageMap = pMessageMap->pBaseMap)
//遍历整个链表
{
lpEntry = AfxFindMessageEntry(
pMessageMap->lpEntries,message..);
if( lpEntry != NULL )
{
goto LDispatch;//跳转出for循环(不再遍历链表)
}
}
LDispatch:
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
//将匹配到的数组元素的最后一个成员(&OnCreate)
//保存到 联合体mmf的第一成员中(相当于所有成员)
int nSig = lpEntry->nSig;(AfxSig_lwl)
switch (nSig)
{
case AfxSig_lwl:
lResult = (this->*mmf.pfn_lwl)(wParam, lParam);
//调用 OnCreate成员函数,回到自己的代码
break;
}
}
}
}
}
总结上边程序的执行流程,也就是消息映射机制的原理
1 利用本类的对象(pFrame)调用GetMessageMap成员 虚函数,获取本类静 态变量地址(pMessageMap)即链表头节点。
2 利用pMessageMap的第二个成员获取 相应的静态数组首地址,然后匹配数组 中的每个元素,一旦找到执行4
3 如果找不到,利用pMessageMap的第一个成员,获取 父类静态变量地址, 如果为NULL,结束查找,如果不为NULL执行2
4 利用找到的 数组元素的第六个成员,并调用这个成员中保存的成员函数指针, 完成消息的处理。
5 如果遍历整个链表未找到 利用 DefWindowProc处理。
通俗的讲其实很简单,绕来绕去他的本质就是:拿到链表头结点,然后for循环遍历链表,在节点对应的数组里找东西,找到就跳出循环,拿到对应数组里的最后一个成员,调用这个成员,完成对消息的处理,找不到调用 DefWindowProc处理。
思考:重写WindowProc速度快为什么非得用消息映射机制???
答:因为用起来特别简单,只要添加ON_MESSAGE(消息类型,处理函数),在类内声明处理函数,类外实现就可以了。