本机环境
IDE:VC6.0
OS:Win7
一,创建工程、设置环境、编写测试代码
创建工程:
新建一个Win32 Application,选择A Simple Win32 Application,这里我的工程名为:MFCMsg
设置环境:
Project->Settings->MicroSoft Foundation Classes:(Use MFC in a Static Library)
编写分析测试代码:
来到StdAfx.h修改
#include <windows.h>
为:
#include <afxwin.h>
回到MFCMsg.cpp中编写如下分析测试代码:
#include "stdafx.h"
class CMyFrameWnd : public CFrameWnd
{
DECLARE_MESSAGE_MAP ()
public:
LRESULT OnTest (WPARAM wParam, LPARAM lParam);
};
BEGIN_MESSAGE_MAP (CMyFrameWnd, CFrameWnd)
ON_MESSAGE (WM_CREATE, OnTest)
END_MESSAGE_MAP ()
LRESULT CMyFrameWnd::OnTest (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
........// 消息映射
END_MESSAGE_MAP
为了方便后面的跟踪分析,先把这三个宏展开,这三个宏的替换可以右键转到定义中查看,这里直接拷贝过来并稍加修改
DECLARE_MESSAGE_MAP
宏替换为:
private:
static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
static AFX_DATA const AFX_MSGMAP messageMap;
virtual const AFX_MSGMAP* GetMessageMap() const;
BEGIN_MESSAGE_MAP
宏替换为:
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
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[] =
{
ON_MESSAGE (WM_CREATE, OnTest)
宏替换为:
{ WM_CREATE,0, 0, 0, AfxSig_lwl,(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&OnCgh },
END_MESSAGE_MAP
宏替换为
{0,0,0,0, AfxSig_end, (AFX_PMSG)0}
};
那么测试代码整体替换后:
#include "stdafx.h"
class CMyFrameWnd : public CFrameWnd
{
/*
DECLARE_MESSAGE_MAP ()
*/
///DECLARE_MESSAGE_MAP ()
private:
static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
static AFX_DATA const AFX_MSGMAP messageMap;
virtual const AFX_MSGMAP* GetMessageMap() const;
/
public:
LRESULT OnTest (WPARAM wParam, LPARAM lParam);
};
/*
BEGIN_MESSAGE_MAP (CMyFrameWnd, CFrameWnd)
ON_MESSAGE (WM_CREATE, OnTest)
END_MESSAGE_MAP ()
*/
//BEGIN_MESSAGE_MAP//
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
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[] =
{
///ON_MESSAGE (WM_CREATE, OnCgh)
{ WM_CREATE,0, 0, 0, AfxSig_lwl,(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&OnTest },
/END_MESSAGE_MAP/
{0,0,0,0, AfxSig_end, (AFX_PMSG)0}
};
//
LRESULT CMyFrameWnd::OnTest (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;
}
为了追踪我们的消息的调用流程,在OnTest的入口处下断,调试并运行到该断点
然后通过VC的查看调用堆栈功能找到OnTest函数的调用流程:
操作:View->Debug Windows->Call Stack
双击AfxWndProc那一行就定位到AfxWndProc函数,在该函数的入口点设置断点
先停止当前调试,然后再次调试启动程序运行到AfxWndProc入口的断点处,按F5注意观察nMsg的值,当nMsg的值为1时为止(因为这里我们要以WM_CREATE消息为例分析)
跟踪其调用流程(只分析核心代码):
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
.........................
// 通过消息产生窗口的句柄拿到框架窗口对象指针pFrame
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
.........................
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
AfxWndProc->AfxCallWndProc
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{// pWnd === pFrame
.........................
// WindowProc为虚函数,这里可能发生重写回到子类(pFrame)代码,这里我并没有重写窗口过程函数
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
.........................
}
AfxWndProc->AfxCallWndProc->WindowProc
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{// 内部this指针为pFrame
........................................
// 重点分析OnWndMsg
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
AfxWndProc->AfxCallWndProc->WindowProc->OnWndMsg
为了更清晰的解释OnWndMsg这个函数先来认识其中的几个数据结构
注意到在DECLARE_MESSAGE_MAP的宏展开后的静态数组变量 AFX_MSGMAP_ENTRY _messageEntries[]以及静态变量AFX_MSGMAP messageMap
数组元素_messageEntries的类型说明:
struct AFX_MSGMAP_ENTRY (宏展开静态数组每个元素的类型)
{
UINT nMessage; // windows message,消息ID
UINT nCode; // control code or WM_NOTIFY code, 通知码(只有控件才发)
UINT nID; // control ID (or 0 for windows messages) 命令ID(菜单,加速键)/控件ID
UINT nLastID; // used for entries specifying a range of control id's 最后一个控件ID
UINT_PTR nSig; // signature type (action) or pointer to message # ,消息处理函数的类型
AFX_PMSG pfn; // routine to call (or special value), 消息处理函数的指针
};
通过分析ON_MESSAGE (WM_CREATE, OnTest)和END_MESSAGE_MAP宏展开
AFX_MSGMAP_ENTRY CMyFrameWnd::_messageEntries[] =
{
{ WM_CREATE,0, 0, 0, AfxSig_lwl,(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&OnTest },
{0,0,0,0, AfxSig_end, (AFX_PMSG)0}
};
发现数组中的每一条元素正是我们写在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的ON_XXX的消息ID和对应的消息处理函数,结尾用一条0元素标识数组结束
messageMap的类型说明
struct AFX_MSGMAP (宏展开的静态变量的类型)
{
const AFX_MSGMAP* pBaseMap; // 保存了父类静态变量地址(负责连接链表)
const AFX_MSGMAP_ENTRY* lpEntries;// 指向相应类的静态数组首地址
};
可以很清晰的看到静态成员messageMap的第二个成员lpEntries正是指向数组成员_messageEntries类型的指针
而BEGIN_MESSAGE_MAP展开的一条复制语句也说明了这点
const AFX_MSGMAP CMyFrameWnd::messageMap ={ &CFrameWnd::messageMap,&CMyFrameWnd::_messageEntries[0]};// 注意第二个数组元素正是_messageEntries的首地址
并且我们可以通过宏展开个GetMessageMap()获取该静态变量
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
return &CMyFrameWnd::messageMap;
}
union MessageMapFunctions
{
AFX_PMSG pfn; // 这变量提供我们一个简单的名字供我们对该联合体进行读/写操作
// 下面保存了上百个函数类型的定义
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_D)(CDC*);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_b)(BOOL);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_u)(UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_h)(HANDLE);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_u_u)(CWnd*, UINT, UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_b_W_COPYDATASTRUCT)(CWnd*, COPYDATASTRUCT*);
................................................
}
AfxWndProc->AfxCallWndProc->WindowProc->OnWndMsg
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
.....................
if (message == WM_COMMAND)
{// WM_COMMAND消息的特别处理
if (OnCommand(wParam, lParam))
{
lResult = 1;
goto LReturnTrue;
}
return FALSE;
}
// special case for notifies
if (message == WM_NOTIFY)
{// WM_NOTIY消息的特别处理
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
goto LReturnTrue;
return FALSE;
}
.........................
// 获取保存在类对象(能处理消息的,添加了宏和映射)中的messageMap
// 也即消息映射链表的节点指针
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
...........................
for (/* pMessageMap already init'ed */; pMessageMap != NULL;pMessageMap = pMessageMap->pBaseMap)
{// 次循环遍历链表
// 拿着WM_CREATE到pMessageMap->lpEntries静态数组中匹配每个元素
// 如果找到了返回数组元素地址,没找到返回NULL
........................
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0)) != NULL)
{
pMsgCache->lpEntry = lpEntry;
AfxUnlockGlobals(CRIT_WINMSGCACHE);
goto LDispatch;
}
LDispatch:
ASSERT(message < 0xC000);
union MessageMapFunctions mmf;
// 给联合体的第一个元素赋值也就是给所有的元素赋值了
// 给联合体的第一个元素赋值为找到的那条数组记录中的相应的消息处理函数的地址
mmf.pfn = lpEntry->pfn;
.................................
int nSig;
// nSig标识函数的类型
nSig = lpEntry->nSig;
switch (nSig)
{
.........
case AfxSig_lwl:
lResult = (this->*mmf.pfn_lwl)(wParam, lParam);//调用获取到的消息处理函数(OnTest)
break;
.........
}
}
这里值得一提的是消息的处理函数的地址和原型的获取过程
// 定义该联合体,其中保存了所有可能的消息处理函的类型(参数列表和返回值类型)
union MessageMapFunctions mmf;
// 该联合体中第一个成员pfn名字较为简洁方便我们赋值
// 这里把匹配的消息处理函数地址存放到联合体中
mmf.pfn = lpEntry->pfn;
// AFX_MSGMAP_ENTRY结构体的倒数第二个成员nSig保存了本消息处理函数的类型对应的数值编号
int nSig = lpEntry->nSig;
// 通过编号还原结构体中保存的真是的函数类型,然后调用之
switch (nSig)
{
.........
case AfxSig_lwl:
lResult = (this->*mmf.pfn_lwl)(wParam, lParam);//调用获取到的消息处理函数(OnTest)
break;
..........
}
这行代码上F11直接回到我们的消息响应函数OnTest()
LRESULT CMyFrameWnd::OnTest (WPARAM wParam, LPARAM lParam)
{
AfxMessageBox ("WM_CREATE消息处理");
return 0;
}
然后就回到我们的消息处理函数OnTest中了,到这里WM_CREATE消息的响应过程就分析完毕了。
特别值得注意的就是拿到消息ID(WM_CREATE)找对应的消息处理函数的过程,其实MFC内部正是维护了一个链表(目前来看是)。所有派生自CCmdTarget并且添加了消息哦声明宏和实现的类中都有那两个静态成员。messageMap的第一个元素pBaseMap指向父类messageMap,messageMap的第二个元素lpEntries指向本类的另一个静态成员
_messageEntries。而静态成员 _messageEntries中包含了一条条消息ID(如:WM_CREAT)和其对应的处理函数的地址(如:OnTest)等信息。这样就形成了一个以本类的messageMap成员为头指针的链表,而宏展开的GetMessageMap()正是用来获取该头结点的。
根据上述大概绘制了这张图:
到这里消息的响应机制就基本分析完毕了
总结下上面的流程:
// 以WM_CREATE为例 (WM_PAINT, WM_COMMAND 类似)
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
................................................
// all other messages route through message map
// 获取和hWnd绑定在一起框架类对象地址pWnd === pFrame
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
.................................................
AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam)
{// 参数pWnd === pFrame
pWnd->WindowProc(nMsg, wParam, lParam)
{// 函数内部this为pWnd === pFrame
OnWndMsg(message, wParam, lParam, &lResult)
{// 内部this指针为pWnd === pFrame, 在这个函数里(WM_PAINT,WM_CREATE)和 WM_COMMAND
// 开始区分处理
// 获取本类静态变量地址(链表头节点)
const AFX_MSGMAP* pMessageMap = GetMessageMap();
// const AFX_MSGMAP_ENTRY* lpEntry;
for (/* pMessageMap already init'ed */; pMessageMap != NULL;pMessageMap = pMessageMap->pBaseMap)
{// 次循环遍历链表
// 拿着WM_CREATE到pMessageMap->lpEntries静态数组中匹配每个元素
// 如果找到了返回数组元素地址,没找到返回NULL
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,WM_CREATE, 0, 0)) != NULL)
{
// 跳出for循环
goto LDispatch;
}
}// for
LDispatch:
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn; // 获取&CMyFrameWnd::OnCgh
int nSig = lpEntry->nSig;//AfxSig_lwl
switch (nSig)
{
case AfxSig_lwl:
lResult = (this->*mmf.pfn_lwl)(wParam, lParam);
break;
}
}
}
}
}
当然我们如果只是想处理我们的消息那就十分简单了
1 在类中声明消息宏
2 在类外添加实现宏
BEGIN_MESSAGE_MAP
END_MESSAGE_MAP
3 在实现宏之间写上消息和对应的处理函数
BEGIN_MESSAGE_MAP
ON_MESSAGE (WM_XXX, OnXXX)
END_MESSAGE_MAP
4 在类中声明消息处理函数注意一般格式(可到MSDN中查询, ON_MESSAGE、ON_COMMAND、ON_PAINT、ON_TIMER等他们的原型可能不太一样)
ON_MESSAGE( <message>, <memberFxn> ) afx_msg LRESULT memberFxn(WPARAM, LPARAM);
ON_COMMAND(<id>, <memberFxn>) afx_msg void memberFxn( );
......
MFC的消息分为标准消息、命令消息和用于自定义消息
1 标准消息:键盘/鼠标/定时器... ON_WM_XX(ON_WM_CREATE 、ON_WM_PAINT)
以ON_WM_PAINT为例分析下,通过查看定义发现如下宏替换
#define ON_WM_PAINT() { WM_PAINT, 0, 0, 0, AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint },
其实就是把一条数据存放到我们定义的静态成员 _messageEntries中,而这条数据中包含了消息的ID(WM_PAINT), 以及处理函数OnPaint
2 自定义消息 ON_MESSAGE
记得加消息宏定义
#define WM_MYMESSAGE WM_USER + n (n<=31743) 0x400~31743
然后我们可以通过SendMessage或PostMessage发消息
3 命令消息 WM_COMMAND
ON_COMMAND (命令ID, 处理函数)
以本文最后的练习代码中的ON_COMMAND (1001, OnTest)为例,观察其宏代换如下:
#define ON_COMMAND(id, memberFxn) { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
WM_COMMAND ----- UINT nMessage; // windows message,消息ID
CN_COMMAND ----- UINT nCode; // control code or WM_NOTIFY code, 通知码(只有控件才发)</span>
(WORD)id ------ UINT nID; // control ID (or 0 for windows messages) 命令ID(菜单,加速键)/控件ID
(WORD)id ------ UINT nLastID; // used for entries specifying a range of control id's 最后一个控件ID
AfxSig_vv ------ UINT_PTR nSig; // signature type (action) or pointer to message # ,消息处理函数的类型
(AFX_PMSG)&member ---------FxnAFX_PMSG pfn; // routine to call (or special value), 消息处理函数的指针
注:其中的消息映射ON_MESSAGE是通用的可以用来处理用户自定义消息和标准消息
当然还有一些专用的消息映射使用起来更便捷
比如:ON_WM_CREATE () ON_WM_PAINT () ON_WM_MOUSEMOVE ()
其实这些宏在编译时被替换,且其消息处理函数名规定好了不可更改
ON_WM_CREATE () ------- OnCreate ------------- afx_msg int OnCreate( LPCREATESTRUCTlpCreateStruct); ON_WM_PAINT () -------- OnPaint -------------- afx_msg void OnPaint( ); ON_WM_MOUSEMOVE () ------ OnMouseMove ------------ afx_msg void OnMouseMove( UINTnFlags, CPointpoint);
下面是我写好的一个练习的列子可以很好的说明它们的用法
// MFCCmd.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#define WM_MYMESSAGE WM_USER + 1001
class CMyFrameWnd : public CFrameWnd
{
DECLARE_MESSAGE_MAP ()
public:
afx_msg int OnCreate( LPCREATESTRUCT pcs);
afx_msg void OnPaint( );
afx_msg void OnMouseMove( UINT nFlags, CPoint point );
afx_msg void OnTest( );
int m_x;
int m_y;
LRESULT OnMyMessage (WPARAM wParam, LPARAM lParam);
};
BEGIN_MESSAGE_MAP (CMyFrameWnd, CFrameWnd)
ON_WM_CREATE ()
ON_WM_PAINT ()
ON_WM_MOUSEMOVE ()
ON_MESSAGE (WM_MYMESSAGE, OnMyMessage)
ON_COMMAND (1001, OnTest)
END_MESSAGE_MAP ()
int CMyFrameWnd::OnCreate( LPCREATESTRUCT pcs)
{
::CreateWindowEx (0, "BUTTON", "OK", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
100, 100, 100, 40, m_hWnd, (HMENU)1001, AfxGetInstanceHandle(), NULL);
AfxMessageBox ("CMyFrameWnd::OnCreate");
::PostMessage (m_hWnd, WM_MYMESSAGE, 1, 2);
return CFrameWnd::OnCreate (pcs);
}
void CMyFrameWnd::OnPaint ()
{
//AfxMessageBox ("CMyFrameWnd::OnPaint");
PAINTSTRUCT ps;
HDC hDC = ::BeginPaint (m_hWnd, &ps);
HBRUSH hBrush = (HBRUSH)GetStockObject (BLACK_BRUSH);
HGDIOBJ hOldBrush = SelectObject (hDC, hBrush);
Ellipse (hDC, m_x - 50, m_y - 50, m_x + 50, m_y + 50);
SelectObject (hDC, hOldBrush);
DeleteObject (hBrush);
::EndPaint (m_hWnd, &ps);
}
void CMyFrameWnd::OnMouseMove( UINT nFlags, CPoint point )
{
m_x = point.x;
m_y = point.y;
::InvalidateRect (m_hWnd, NULL, TRUE);
}
LRESULT CMyFrameWnd::OnMyMessage (WPARAM wParam, LPARAM lParam)
{
CString str;
str.Format ("wParam = %d,lParam = %d", wParam, lParam);
AfxMessageBox (str);
// AfxMessageBox ("CMyFrameWnd::OnMyMessage");
return 0;
}
void CMyFrameWnd::OnTest ()
{
AfxMessageBox ("OK按钮被点击了");
}
class CMyWinApp : public CWinApp
{
public:
virtual BOOL InitInstance ();
};
CMyWinApp theApp;
BOOL CMyWinApp::InitInstance ()
{
CMyFrameWnd *pFrame = new CMyFrameWnd;
pFrame->Create (NULL, "MFCCmd");
m_pMainWnd = pFrame;
pFrame->ShowWindow (SW_SHOW);
pFrame->UpdateWindow ();
return TRUE;
}