MFC的消息机制

时间:2022-12-28 05:05:28

今天重新整理MFC的消息机制,最终的结果应该是利用win32程序模拟一个MFC的消息链。

1.标准消息 

除WM_COMMAND之外,所有以WM_开头的消息。 从CWnd派生的类,都可以接收到这类消息。 

2.命令消息 

来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。 CCmdTarget派生的类,都可以接收到这类消息。 

3.通告消息

由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。 例如当你在ListBox上选择其中一个项目,ListBox就会产生LBN_SELCHANGE传送给父视窗。从CCmdTarget派生的类,都可以接收到这类消息。

万流归宗 Command Target(CCmdTarget)

一个Command Target 物件如何知道它可以处理某个讯息?答案是它會看看自己的讯息映
射表。訊息映射表使得讯息和函式的对映关系形成㆒份表格,进而全体形成一张网,当
Command Target 物件收到某个讯息,便可由表格得知其处理函式的名称。

 

三个特别的宏:

在MFC中,我们可以找到如下三个宏

1、DECLARE_MASSAGE_MAP()

2、BEGINE_MASSAGE_MAP(CLASS, BASSCLASS)

3、END_MASSAGE_MAP()

下面来分析这三个宏

1  DECLARE_MESSAGE_MAP()

作用:为一个消息响应类声明必需的成员变量和成员函数。

#define DECLARE_MESSAGE_MAP()

private:

     static const AFX_MSGMAP_ENTRY _messageEntries[];

protected:

     static const AFX_MSGMAP messageMap;

     virtual const AFX_MSGMAP* GetMessageMap() const;

可以看出DECLARE_MESSAGE_MAP() 宏中定义了两个静态成员函数和一个重载的虚函数

AFX_MSGMAP_ENTRY 根据题意,可以看出这是一个消息入口(消息和消息函数之间的映射)

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)
};

再看看AFX_MESSAGE结构

struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};

可见结构体AFX_MSGMAP中定义了两个指针,pBaseMap指向另一个AFX_MSGMAP,lpEntries指向一个消息入口表。可以推想,在响应消息时,一定是在lpEntries指向的消息入口表中寻找响应函数,也可能会在pBaseMap指向的结构体中做同样的响应函数寻找操作(有点不理解)。

重载的虚函数GetMessageMap,可以猜测只是用来返回成员messageMap的地址而已

2 BEGIN_MESSGAE_MAP(CLASS, BASECLASS)

作用:定义DECLARE_MESSAGE_MAP宏声明的静态变量。

#define BEGIN_MESSAGE_MAP(theClass, baseClass) 
const AFX_MSGMAP* theClass::GetMessageMap() const
         { return &theClass::messageMap; } 
     AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
     { &baseClass::messageMap, &theClass::_messageEntries[0] }; 
     AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
     {

BEGIN_MESSAGE_MAP宏有两个参数,theClass表示为当前类,bassClass为当前类的父类。

BEGIN_MESSAGE_MAP宏首先定义了函数GetMessageMap的函数体,如前文所述,直接返回当前类的成员变量messageMap的地址。

const AFX_MSGMAP* theClass::GetMessageMap() const
         { return &theClass::messageMap; }

然后初始化了当前类的成员变量messageMap。messageMap的pBaseMap指针指向其父类的messageMap成员,lpEntries指针指向当前类的_messageEntries数组的首地址。

AFX_COMDAT const AFX_MSGMAP theClass::messageMap =
     { &baseClass::messageMap, &theClass::_messageEntries[0] };

最后,定义了_messageEntries数组初始化代码的开始部分。

AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
     {

3、END_MESSAGE_MAP()

作用:定义_messageEntries数组初始化代码的结束部分。

#define END_MESSAGE_MAP() 
         {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } 
     }; 
在DECLARE_MESSAGE_MAP和END_MESSAGE_MAP之间还有一些宏,如ON_COMMAND、ON_WM_CREATE等,这些宏最终都会被生成一条AFX_MSGMAP_ENTRY结构体数据,并成为_messageEntries消息映射表数据的一个元素。我们以常见的ON_COMMAND宏为例。ON_COMMAND宏的源代码为:

#define ON_COMMAND(id, memberFxn) 
     { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, 
         static_cast<AFX_PMSG> (memberFxn) },

通过以上分析,我们可以得到一个链表式的数据结构,子类的messageMap成员为链表的头节点。链表的每个节点都包含一个消息入口表。MFC的消息系统的标准备消息处理函数CCmdTarget::OnCmdMsg正是通过这样一个链表查找到消息的响应函数,并调用该函数来响应消息。

 

实例展开前面的三个宏:

假设有这样的视图类class CTestmfcView : public CView,里面有一个消息响应函数:OnMenuTest,则展开宏后,头文件形如:

class CTestmfcView : public CView
{

//。。。
protected:
    afx_msg void OnMenuTest();
private:
    static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
    static AFX_DATA const AFX_MSGMAP messageMap;
    virtual const AFX_MSGMAP* GetMessageMap() const;

};

实现文件形如:

const AFX_MSGMAP* CTestmfcView::GetMessageMap() const
{
    return &CTestmfcView::messageMap;
}
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CTestmfcView::messageMap =
{
    &CView::messageMap, &CTestmfcView::_messageEntries[0]
};
AFX_COMDAT const AFX_MSGMAP_ENTRY CTestmfcView::_messageEntries[] =
{
    //ON_COMMAND(ID_MENU_TEST, OnMenuTest)
    { WM_COMMAND, CN_COMMAND, (WORD)ID_MENU_TEST, (WORD)ID_MENU_TEST, AfxSig_vv, (AFX_PMSG)&OnMenuTest },
    {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};

void CTestmfcView::OnMenuTest()
{
    // TODO: Add your command handler code here
    AfxMessageBox("dfd");
}

由以上代码即可发现,实际上程序通过虚函数得到本类的指针AFX_MSGMAP,而这个指针包含两个值,一个是指向基类的AFX_MSGMAP*,一个是指向本类函数入口的指针AFX_MSGMAP_ENTRY*,这样,当程序运行到CWnd::OnWndMsg()里面,通过如下代码:

const AFX_MSGMAP* pMessageMap;

pMessageMap = GetMessageMap();//注意,此为虚函数,实际调用的为各子类的函数

即可得到当前类的AFX_MSGMAP* ,然后在CCmdTarget::OnCmdMsg中通过以下代码:

const AFX_MSGMAP_ENTRY* lpEntry;

lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, nMsg, nCode, nID);可找到消息函数的入口地址,至此便可调用函数OnMenuTest()了(调用此函数是通过函数指针实现)。

在MFC消息流中,还有消息的上溯,拐弯等一系列的变化,这些还需要进一步深入研究。