# 消息机制简述

我们知道,Windows 的窗口是由消息驱动的;
Windows 通过捕获鼠标、键盘等输入设备产生的动作,再生成对应的消息,并传递给相应的窗口所属的线程。

直接基于 Win32API 开发时,我们需要在注册窗口类时指定窗口过程函数,以告知 Windows 回调 (传递消息) 的入口。


# MFC 的消息映射

而 MFC 为我们提供了一种更为方便的机制,直接为一个消息与一个函数建立映射关系,在消息产生时,回调此函数,而不再需要我们去自行分发。

消息映射示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <afxwin.h>

class CMyFrameWnd : public CFrameWnd {
DECLARE_MESSAGE_MAP()

public:
LRESULT OnCreate(WPARAM wParam, LPARAM lParam) {
AfxMessageBox(L"Window Create!");
return 0;
}
};

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_MESSAGE(WM_CREATE, OnCreate) // 将WM_CREATE与CMyFrameWnd类的成员函数OnCreate建立映射
END_MESSAGE_MAP()




class CMyWinApp : public CWinApp {
public:
CMyWinApp() {

}

virtual BOOL InitInstance() {
CMyFrameWnd* frame = new CMyFrameWnd;
frame->Create(nullptr, L"MFCBase");
m_pMainWnd = frame;
frame->ShowWindow(SW_SHOW);
frame->UpdateWindow();
return TRUE;
}
};

CMyWinApp g_theApp;

# 消息映射宏

MFC 的消息映射是通过几个宏函数来实现的,我们以上述代码为例,将宏展开,一探究竟。

  • 声明宏
    MFC 的声明宏,就是在窗口类中声明两个受保护的成员函数。

    ECLARE_MESSAGE_MAP()
    1
    2
    3
    protected:
    static const AFX_MSGMAP* PASCAL GetThisMessageMap();
    virtual const AFX_MSGMAP* GetMessageMap() const;
  • 定义宏
    定义则稍显复杂,由多个宏函数组成,实际上是在实现声明宏声明的成员函数。

    BEGIN_MESSAGE_MAP()

    BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    PTM_WARNING_DISABLE
    const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
    {
    return GetThisMessageMap();
    }
    const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
    {
    typedef CMyFrameWnd ThisClass;
    typedef CFrameWnd TheBaseClass;
    __pragma(warning(push))
    __pragma(warning(disable: 4640))
    static const AFX_MSGMAP_ENTRY _messageEntries[] =
    {
    ON_MESSAGE()

    ON_MESSAGE(WM_CREATE, OnCreate)

    1
    2
    // 实际上是在为_messageEntries变量的初始化列表添加结构体元素
    { WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)>(OnCreate))},
    END_MESSAGE_MAP()
    1
    2
    3
    4
    5
    6
    7
    8
    		{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
    };
    __pragma(warning(pop))
    static const AFX_MSGMAP messageMap =
    { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
    return &messageMap;
    }
    PTM_WARNING_RESTORE
  • 整体展开

    消息映射示例-宏展开
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include <afxwin.h>

    class CMyFrameWnd :public CFrameWnd {
    protected:
    static const AFX_MSGMAP* PASCAL GetThisMessageMap();
    virtual const AFX_MSGMAP* GetMessageMap() const;

    public:
    LRESULT OnCreate(WPARAM wParam, LPARAM lParam) {
    AfxMessageBox(L"Window Create!");
    return 0;
    }
    };

    PTM_WARNING_DISABLE
    const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
    {
    return GetThisMessageMap();
    }
    const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
    {
    typedef CMyFrameWnd ThisClass;
    typedef CFrameWnd TheBaseClass;
    __pragma(warning(push))
    __pragma(warning(disable: 4640))
    static const AFX_MSGMAP_ENTRY _messageEntries[] =
    {
    { WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)>(OnCreate))},
    { 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
    };
    __pragma(warning(pop))
    static const AFX_MSGMAP messageMap =
    { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] };
    return &messageMap;
    }
    PTM_WARNING_RESTORE




    class CMyWinApp :public CWinApp {
    public:
    CMyWinApp() {

    }

    virtual BOOL InitInstance() {
    CMyFrameWnd* frame = new CMyFrameWnd;
    frame->Create(nullptr, L"MFCBase");
    m_pMainWnd = frame;
    frame->ShowWindow(SW_SHOW);
    frame->UpdateWindow();
    return TRUE;
    }
    };

    CMyWinApp g_theApp;

通过宏添加消息映射时,实际上是重写了 CFrameWnd 类的 GetMessageMap 成员虚函数;
GetMessageMap 函数初始化了一个名为 _messageEntriesmessageMap静态局部变量,并将该变量的地址返回。
我们简单猜测,与上一节重写 WindowProc 类似,微软在窗口过程函数 AfxWndProc 的某处,得到窗口对象之后,以窗口对象的身份调用其成员虚函数 GetMessageMap
由于成员虚函数被重写的原因,得到我们所设定的映射关系表,我们这里就把它叫做消息映射表
基于此消息映射表,就可以在窗口过程函数中,根据消息调用对应的成员函数


# 探索实现原理

我们基本上能够理解消息映射机制的工作原理,这小节我们就扒一下微软的源码,以便更清晰的了解消息映射机制。

AfxWndProc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
// special message which identifies the window as using AfxWndProc
if (nMsg == WM_QUERYAFXWNDPROC)
return 1;

// all other messages route through message map
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
ASSERT(pWnd != NULL);
ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);
if (pWnd == NULL || pWnd->m_hWnd != hWnd)
return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

调试上面提供的代码,首先在 AfxWndProc 函数设置断点;

继续走到 AfxCallWndProc

AfxCallWndProc
1
2
3
4
5
6
7
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = 0, LPARAM lParam = 0)
{
...
// delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
...

到此处停下,此时你可能会有些疑惑,诶,我们并没有重写 Cwnd::WindowProc ,为什么要到此处停下?
因为微软将消息映射机制放到了此函数内实现。

CWnd::WindowProc
1
2
3
4
5
6
7
8
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does most of the work, except for DefWindowProc call
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}

继续到进入 CWnd::OnWndMsg

CWnd::OnWndMsg
1
2
3
4
5
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
...
const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
...

上面的代码我们暂时不关心,只关心对 GetMessageMap 函数的调用;
还记得吗?我们将消息声明 / 定义宏展开后,就相当于我们为 CMyFrameWnd 类定义了两个函数:

1
2
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;

此处是以 CMyFrameWnd 类的对象的身份调用的 GetMessageMap ,因此实际上是在调用我们类中由消息映射宏重写的成员虚函数。

CMyFrameWnd::GetMessageMap
1
2
3
4
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
return GetThisMessageMap();
}

这里只是简单的调用了静态成员函数 GetThisMessageMap

CMyFrameWnd::GetThisMessageMap
1
2
3
4
5
6
7
8
9
10
11
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)>(&OnCreate)) },
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
static const AFX_MSGMAP messageMap =
{ &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
return &messageMap;
}

我这里对原先的代码做了一些简化,更方便看一点,实际上呢就是为当前函数的静态局部变量 _messageEntriesmessageMap 赋值,并返回 messageMap 的地址。

又回到 CWnd::OnWndMsg

CWnd::OnWndMsg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
...
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];
const AFX_MSGMAP_ENTRY* lpEntry;
if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
{
...
}
else
{
// not in cache, look for it
pMsgCache->nMsg = message;
pMsgCache->pMessageMap = pMessageMap;

for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
pMessageMap = (*pMessageMap->pfnGetBaseMap)())
{
...

使 pMessageMap 指向静态局部变量 messageMap

记住上述代码的第 19 ~ 20 行要进入循环;

剩下的我们暂时不关心,继续向下看。

CWnd::OnWndMsg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
pMessageMap = (*pMessageMap->pfnGetBaseMap)())
{
// Note: catch not so common but fatal mistake!!
// BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
if (message < 0xC000)
{
// constant window message
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,
message, 0, 0)) != NULL)
{
pMsgCache->lpEntry = lpEntry;
winMsgLock.Unlock();
goto LDispatch;
}
}

10 ~ 11 行是关键函数, pMessageMap->lpEntries 即是访问静态局部变量 messageMap 的第二个成员,也就是 _messageEntries 的地址;
message 就是本次窗口过程处理的消息 ID 了;

即传入消息映射表地址和消息,查找对应的条目

返回值即是 _messageEntries 中对应条目的地址,如果返回 NULL 表示消息映射表中不存在此消息。
我们假定此消息已经被我们影射了,跟随 goto LDispatch; 继续。

CWnd::OnWndMsg
1
2
3
4
5
6
7
8
9
10
11
12
LDispatch:
ASSERT(message < 0xC000);

mmf.pfn = lpEntry->pfn;

switch (lpEntry->nSig)
{
...
case AfxSig_l_w_l:
lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);
break;
...

最终会在此处以 CMyFrameWnd 类对象的身份调用我们指定的 OnCreate 成员函数。

# 消息映射表链表

整个流程基本上到这里就完成了,实际上我们留了一个小坑,即上面代码中我们暂时跳过的 for 循环。

实际上那是在遍历消息映射表链表,但是我们并没有提过,存在多个消息映射表这件事。

实际上, CFrameWnd 类也使用了消息映射机制,而 CMyFrameWnd::GetMessageMap 函数中的静态局部变量 messageMap ,第一个成员指向的就是静态成员函数 CFrameWnd::GetThisMessageMap

1
2
for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
pMessageMap = (*pMessageMap->pfnGetBaseMap)())

pMessageMap->pfnGetBaseMap ,就是在访问 messageMap 的第一个成员。

此 for 循环的工作:
从链表头 ( CMyFrameWnd ) 开始,即最子类向上遍历,依次查找是否存在与本次消息匹配的消息映射关系,存在则调用对应的成员函数并返回


# 消息映射分类

其实,除了 ON_MESSAGE 之外,微软还提供了更加方便的,直接映射指定消息的宏。

代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <afxwin.h>

class CMyFrameWnd :public CFrameWnd {
DECLARE_MESSAGE_MAP()
public:
int OnCreate(LPCREATESTRUCT pCs);
};

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()

int CMyFrameWnd::OnCreate(LPCREATESTRUCT pCs) {
AfxMessageBox(L"OnCreate!");
return 0;
}


class CMyWinApp :public CWinApp {
public:
CMyWinApp() {

}

virtual BOOL InitInstance() {
CMyFrameWnd* frame = new CMyFrameWnd;
frame->Create(nullptr, L"MFCBase");
m_pMainWnd = frame;
frame->ShowWindow(SW_SHOW);
frame->UpdateWindow();
return TRUE;
}
};

CMyWinApp g_theApp;

我们将 ON_WM_CREATE 宏展开,看看与 ON_MESSAGE 有什么区别。

ON_WM_CREATE
1
2
3
4
#define ON_WM_CREATE() \
{ WM_CREATE, 0, 0, 0, AfxSig_is, \
(AFX_PMSG) (AFX_PMSGW) \
(static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },
ON_MESSAGE
1
2
3
4
5
#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_WM_CREATE 不再要求我们提供参数消息 id 与回调函数名,而是直接指定为 WM_CREATEOnCreate
其次, ON_MESSAGEON_WM_CREATE 添加的消息映射条目 AFX_MSGMAP_ENTRY 元素的第 5 个成员 nSig ,分别是 AfxSig_l_w_lAfxSig_i_v_s

还记得吗,我们指定的消息映射函数,都是在 CWnd::OnWndMsg 函数内被调用的;
而先前通过 ON_MESSAGE 定义的消息映射,回调函数原型都是相同的。
而通过 ON_WM_CREATE 定义的消息映射,和 ON_MESSAGE 指定的消息映射函数的原型是不同的,那么 CWnd::OnWndMsg 是怎么区分不同的函数原型并进行回调的呢?

我们回到 CWnd::OnWndMsg 函数:

CWnd::OnWndMsg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
LDispatch:
ASSERT(message < 0xC000);

mmf.pfn = lpEntry->pfn;

switch (lpEntry->nSig)
{
...
case AfxSig_i_v_s:
lResult = (this->*mmf.pfn_i_s)(reinterpret_cast<LPTSTR>(lParam));
break;
...
case AfxSig_l_w_l:
lResult = (this->*mmf.pfn_l_w_l)(wParam, lParam);
break;
...

其中最关键的就是 switch (lpEntry->nSig)

  • 通过 ON_MESSAGE 定义的消息映射时,nSig 成员是 AfxSig_l_w_l
  • 通过 ON_WM_CREATE 定义的消息映射时,nSig 成员是 AfxSig_i_v_s

这就是为什么,函数原型不同,回调也能正确进行的缘故。