MFC源码实战分析(二)——消息响应与处理机制初探

在上一节中,我们借助MFC框架中的两个类,编写了一个Windows窗口应用程序。本节我们来探究MFC的消息映射机制,在上一节程序的基础上增加响应鼠标WM_LBUTTONDOWN的消息。

增加消息处理能力

Win32原始消息机制

在此之前,我们先来回忆一下Win32SDK程序中与消息处理相关的部分:

  1. WinMain函数中,注册窗口类时,传入了窗口过程WndProc的地址。然后创建窗口、显示窗口、更新窗口。
  2. 完成上述步骤后,进入消息循环。调用user32!GetMessage或user32!PeekMessage、user32!TranslateMessage完成消息的获取和整理后,调用user32!DispatchMessage,将控制权交还给系统,由系统回调第一步注册窗口类时传入的WndProc函数。
  3. WndProc被系统调用,根据传入的消息不同,分别加以处理,形式上是一个switch-case或多级的if-else。对于我们不关心的消息,再交调用DefWindowProc交给系统处理。(另外,在创建窗口、显示窗口、更新窗口的时候,虽然尚未进入消息循环,但系统会直接调用我们的窗口过程处理一些与窗口创建相关的消息)
  4. 当关闭窗口时,调用DestroyWindow销毁窗口,然后调用PostQuitMessage退出消息循环,程序结束。

MFC消息映射机制初探

而在MFC中,处理消息的机制被称为消息映射机制,通过一组宏来完成,下面热烈欢迎它们登场:

DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(CChild, CBase)
END_MESSAGE_MAP()
ON_COMMAND()
ON_WM_XXX()

其中:
1. DECLARE_MESSAGE_MAP用于声明该类具有处理消息的资格和能力,放在该类的类声明中。
2. BEGIN_MESSAGE_MAP和END_MESSAGE_MAP成对出现,自成一体。BEGIN_MESSAGE_MAP有两个参数,分别是本类名称和父类名称。
在二者中间用ON_WM_XXX或ON_COMMAND将要处理的消息和消息处理函数绑定。
3. 这些消息映射的宏和函数的名字是固定的,因为这些是常用的系统消息。有这样的名字的函数和宏会自己关联。
4. 任何CCmdTarget及其派生类都可以通过添加上述的宏从而进行消息处理。但CCmdTarget类能处理的消息类型有限,大多数消息都需要由CWnd及其派生类来处理。

增补代码

在上节代码基础上,增加以下部分:

class CMyWnd : public CFrameWnd
{
public:
     //类原有内容   
    void OnLButtonDown(UINT nFlags, CPoint point)
    {
        MessageBox(TEXT("In CMyWnd::OnLButtonDown"), TEXT("In CMyWnd::OnLButtonDown"), MB_OK);
    }

    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyWnd, CFrameWnd)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

需要注意的是上述所有宏都要紧跟一对括号。

一探究竟

DECLARE_MESSAGE_MAP()宏

下面看看DECLARE_MESSAGE_MAP()宏的定义:

#define DECLARE_MESSAGE_MAP() \
protected: \
    static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
    virtual const AFX_MSGMAP* GetMessageMap() const; \

这个宏的作用就是在类声明中加入了两个函数,其中一个名为GetThisMessageMap的函数是静态函数,另一个名为GetMessageMap的函数为虚函数,两个函数都没有参数。注意到其返回值都是AFX_MSGMAP的指针,那这又是什么鬼呢?

struct AFX_MSGMAP
{
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
    const AFX_MSGMAP_ENTRY* lpEntries;
};

该结构包含两个成员,第一个成员是一个函数指针,由名称猜出应该是得到基类中该结构的指针。
第二个成员又是一个结构指针,看名称中lpEntries用了复数,估计是指向了一个结构数组。先看看该结构:

#ifndef AFX_MSG_CALL
#define AFX_MSG_CALL
#endif
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);

struct AFX_MSGMAP_ENTRY
{
    UINT nMessage;   // windows message
    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)
};

在其中,我们发现了nMessage和pfn,这应该对应的是一种映射关系,将消息与处理消息函数的入口地址建立一个映射关系。且pfn是CCmdTarget的成员函数。这也从侧面应证了CCmdTarget及其子类具有消息处理的能力。
在该宏中,我们没有找到GetThisMessageMap以及GetMessageMap的定义,预知后事如何,且看BEGIN_MESSAGE_MAP()宏的解析。

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

哈,这两个宏真有意识。原来在两个宏中间定义的代码全部在GetThisMessageMap函数体内,准确的说是在GetThisMessageMap函数体内定义的AFX_MSGMAP_ENTRY类型的结构数组_messageEntries的条目中。那么ON_WM_LBUTTONDOWN应该是在这个数组中插入了一个新条目喽,查看其宏定义加以验证:

enum AfxSig
{
    //省略
    AfxSig_v_u_p,                // void (UINT, CPoint)
    AfxSig_vwp = AfxSig_v_u_p,            // void (UINT, CPoint)
    //省略
}

#define ON_WM_LBUTTONDOWN() \
    { WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, \
        (AFX_PMSG)(AFX_PMSGW) \
        (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > ( &ThisClass :: OnLButtonDown)) },

到这里我们终于知道了为什么添加了ON_WM_LBUTTONDOWN()宏后就能与CMyWnd类中的OnLButtonDown函数建立起联系了。原来这名字是MFC框架的编写者们早就定好的了。

并且我们还注意到,WM_LBUTTONDOWN消息对应的处理函数必须在CWnd类中加以定义(或其子类中),也就是说,只有CWnd及其子类才能处理WM_LBUTTONDOWN消息。翻看afxmsg_.h中的定义,绝大多数Windows消息都只能由CWnd类或其子类来处理。

只有WM_COMMAND消息和WM_NOTIFY消息可由CCmdTarget或其子类处理。此外,我们还看到了MFC定义的WM_REFLECT_BASE消息,即所谓的反射类消息。关于这些内容我们将在后续章节加以介绍。

在END_MESSAGE_MAP中,将AFX_MSGMAP结构中的pfnGetBaseMap函数指针设定为了父类的GetThisMessageMap函数。

因此,虚函数GetThisMessageMap中定了该类的消息-处理函数映射表并返回其指针,而GetMessageMap就是该类的虚函数(意味着可以用其基类的指针调用),在其中调用了该类的静态函数GetThisMessageMap。

小结

总结一下:
* DECLARE_MSG_MAP宏在类声明中添加了两个成员函数:GetThisMessageMap(静态函数)与GetMessageMap(虚函数)的声明
* BEGIN_MSG_MAP宏中实现了GetMessageMap,就是调用GetThisMessageMap(静态函数);部分实现了GetThisMessageMap函数:创建了一个AFX_MSGMAP_ENTRY的结构数组,该数组用于将消息和处理消息的函数一一对应。
* ON_WM_XXX宏和ON_COMMAND等宏其实就是在AFX_MSGMAP_ENTRY结构数组中增加了一个条目,将目标消息和已经定义好名称和函数原型的函数(该函数需要在类中实现)对应起来
* END_MSG_MAP宏在AFX_MSGMAP结构数组的末尾增加了一个全0的条目作为结尾,然后构造了一个静态的AFX_MSGMAP结构,将其中的pfnGetBaseMap字段填入父类的GetThisMessageMap函数地址,在lpEntries字段中填入刚构建的AFX_MSGMAP_ENTRY结构数组的地址并返回。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值