在MFC生成的类中,我们会发现几处使用宏的地方,如下
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(CMFCHelloDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()
声明的宏是什么?
这里就是提供消息循环的地方,这里可以避免使用冗长的虚表,因为如果每个类对他可能接收的消息都有一个虚函数,那么就需要过多的虚表,占用较多的内存空间。
而使用宏只需要将一个消息与与一个成员函数进行关联,也就是绑定,可以利用专门的算法进行快速匹配。
1. 声明消息映射:DECLARE_MESSAGE_MAP()
在库函数中我们可以找到这个宏的定义:
#define DECLARE_MESSAGE_MAP() \ protected: \ static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \
这里面就是就是定义为如下两个保护函数:
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); //一个static函数,返回消息映射的
virtual const AFX_MSGMAP* GetMessageMap() const; //一个虚函数,可以被子类继承并重写
这里面的AFX_MSGMAP如下:
struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); //定义了一个基类函数指针,返回值为const AFX_MSGMAP const AFX_MSGMAP_ENTRY* lpEntries; //定义了一个指向消息映射结构的指针,返回值为const AFX_MSGMAP_ENTRY };
而AFX_MSGMAP_ENTRY结构体定义了消息的参数和处理函数
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) 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) 处理函数 };
如绘图处理函数ON_WM_PAINT()
#define ON_WM_PAINT() \ { WM_PAINT, 0, 0, 0, AfxSig_vv, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },
展开后如下,可以看到WM_PAINT 与类中OnPaint进行了关联
{ WM_PAINT, 0, 0, 0, AfxSig_vv,
(AFX_PMSG)(AFX_PMSGW) (static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },
2.通过宏进行消息映射:将消息放置在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP两个宏之间,找到这两个宏定义如下
#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; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ return &messageMap; \ } \ PTM_WARNING_RESTORE
我们将下面的宏展开如下面的代码(去除了里面的警告消息)
BEGIN_MESSAGE_MAP(CMFCHelloDlg, CDialogEx) //子类 , 基类
END_MESSAGE_MAP():
const AFX_MSGMAP* CMFCHelloDlg::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMFCHelloDlg::GetThisMessageMap()
{
typedef CMFCHelloDlg ThisClass;
typedef CDialogEx TheBaseClass;
static const AFX_MSGMAP_ENTRY _messageEntries[] = {
....//这里面就是添加的消息和自定义的消息映射
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
static const AFX_MSGMAP messageMap = {
&TheBaseClass::GetThisMessageMap, &_messageEntries[0]
};
return &messageMap;
}
正好了对应了上面的定义的两个函数,先看第一个
return GetThisMessageMap();
默认隐含了 const this指针,调用this->GetThisMessageMap(); 就是调用上面宏展开后的第二个函数
这里主要看返回的结构体,里面第一项为基类的函数指针,第二项为消息映射表
static const AFX_MSGMAP messageMap = {
&TheBaseClass::GetThisMessageMap, &_messageEntries[0]
};
当处理消息时,默认是在当前类中的消息映射表中进行查找,如果找不到是才会调用基类的消息映射表进行查找
3.添加消息的处理:可以定义成员函数或者在默认消息处理函数中处理,如绘图函数的处理如下:
void CMFCHelloDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使图标在工作区矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } }
如何进行消息的匹配?
进过代码的DEBUG搜寻,发现消息的匹配的确是一个巨大的工程:以下为代码的运行过程
注意:缩进行表示被上层调用,同一个函数的不同调用在同一行
_tWinMain AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow) if (!pThread->InitInstance()) BOOL CMFCHelloApp::InitInstance() INT_PTR nResponse = dlg.DoModal(); INT_PTR CDialog::DoModal() if (!CreateRunDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), hInst) && !g_bClosedByEndDialog) hWnd = ::CreateDialogIndirect(hInst, lpDialogTemplate,pParentWnd->GetSafeHwnd(), AfxDlgProc); LRESULT CALLBACK AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) return AfxWndProc(hWnd, nMsg, wParam, lParam); return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); lResult = pWnd->WindowProc(nMsg, wParam, lParam); LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) if (!OnWndMsg(message, wParam, lParam, &lResult)) pMessageMap = GetMessageMap(); BEGIN_MESSAGE_MAP(CMFCHelloDlg, CDialogEx) ... END_MESSAGE_MAP() pMessageMap = (*pMessageMap->pfnGetBaseMap)() lResult = DefWindowProc(message, wParam, lParam); return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam); //返回
从上面的定位可以知道在MFC框架没有找到合适的消息时,就会调用GetMessageMap函数,在当前应用程序定义的消息映射表中寻
BEGIN_MESSAGE_MAP(CMFCHelloDlg, CDialogEx)
...
END_MESSAGE_MAP()
也就是下面这个结构体中寻找指定的函数,
static const AFX_MSGMAP_ENTRY _messageEntries[] = {
....//这里面就是添加的消息和自定义的消息映射
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
至于如何搜寻,在代码如下,其实就像一个线性存储的结构数组,感觉像散列算法,这个不追究了
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];
当在本类没有找到合适的消息时,就搜寻父亲类提供的消息映射表进行搜索
pMessageMap = (*pMessageMap->pfnGetBaseMap)();
注意:上面是个for循环,不断向上层基类搜寻,如果都没有找到就使用默认的消息处理函数
lResult = DefWindowProc(message, wParam, lParam);
当OnWndMsg消息也处理不了时,调用API提供的消息进行处理
::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);
如何定义自己的消息?
1)首先我们要定义一个消息,这个消息要是全局的,但是还不能跟系统定义的消息相冲突,所以一般选择WM_USER后面的消息,例如
#define WM_SHOWTEXT WM_USER + 1
2)声明消息映射的函数,按照如下格式
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
...
afx_msg HRESULT OnShowText(WPARAM wParam, LPARAM lPara);
DECLARE_MESSAGE_MAP()
一定要在DECLARE_MESSAGE_MAP()之前定义,afx_msg表示其行为很像虚函数但却不需要虚表项的函数,HRESULT为返回值,权当做一个long型就可以了
参数一定要设置两个,否则无法进行转换,里面参数类型就是long型,可以传递参数地址,当有多个参数可以使用结构体
3)定义消息映射表,如下
ON_MESSAGE(WM_SHOWTEXT,OnShowText)
#define ON_MESSAGE(message, memberFxn) \ { message, 0, 0, 0, AfxSig_lwl, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \ (memberFxn)) },
这里就可以看到刚才为什么必须定义两个参数,否则无法进行转换,另外还有
ON_COMMAND:将菜单和其他的UI事件与响应事件进行映射
ON_UPDATA_COMMAND_UI:将菜单选择项和其他UI对象连接到保持它们与应用程序状态”同步更新“
4)定义消息处理函数,对刚才的消息定义进行简单处理,如下
afx_msg HRESULT CMFCHelloDlg::OnShowText(WPARAM wParam, LPARAM lPara)
{
AfxMessageBox(_T("hello"));
return 0;
}
5) 在将要发送消息的地方定义消息,但是消息必须要发送到指定的窗口,默认消息是在主窗口进行处理,如下
SendMessage(WM_SHOWTEXT);
这里没有使用参数,默认参数为0;当然也可以使用下面这个函数PostMessage( )进行消息的发送,但是这个函数发送完毕,立刻返回,有点像发短信;而SendMessage就是打电话,必须处理完消息才返回。