现在的位置: 首页 > 综合 > 正文

深入分析MFC消息映射

2014年06月10日 ⁄ 综合 ⁄ 共 6164字 ⁄ 字号 评论关闭

在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就是打电话,必须处理完消息才返回。

抱歉!评论已关闭.