下面几节将分析MFC的消息机制的实现原理和消息处理的过程。为此,首先要分析ClassWizard实现消息映射的内幕,然后讨论MFC的窗口过程,分析MFC窗口过程是如何实现消息处理的。1.消息映射的定义和实现1.MFC处理的三类消息根据处理函数和处理过程的不同,MFC主要处理三类消息:Windows消息,前缀以“WM_”打头,WM_COMMAND例外。Windows消息直接送给MFC窗口过程处理,窗口过程调用对应的消息处理函数。一般,由窗口对象来处理这类消息,也就是说,这类消息处理函数一般是MFC窗口类的成员函数。控制通知消息,是控制子窗口送给父窗口的WM_COMMAND通知消息。窗口过程调用对应的消息处理函数。一般,由窗口对象来处理这类消息,也就是说,这类消息处理函数一般是MFC窗口类的成员函数。需要指出的是,Win32使用新的WM_NOFITY来处理复杂的通知消息。WM_COMMAND类型的通知消息仅仅能传递一个控制窗口句柄(lparam)、控制窗ID和通知代码(wparam)。WM_NOTIFY能传递任意复杂的信息。命令消息,这是来自菜单、工具条按钮、加速键等用户接口对象的WM_COMMAND通知消息,属于应用程序自己定义的消息。通过消息映射机制,MFC框架把命令按一定的路径分发给多种类型的对象(具备消息处理能力)处理,如文档、窗口、应用程序、文档模板等对象。能处理消息映射的类必须从CCmdTarget类派生。在讨论了消息的分类之后,应该是讨论各类消息如何处理的时候了。但是,要知道怎么处理消息,首先要知道如何映射消息。1.MFC消息映射的实现方法MFC使用ClassWizard帮助实现消息映射,它在源码中添加一些消息映射的内容,并声明和实现消息处理函数。现在来分析这些被添加的内容。在类的定义(头文件)里,它增加了消息处理函数声明,并添加一行声明消息映射的宏DECLARE_MESSAGE_MAP。在类的实现(实现文件)里,实现消息处理函数,并使用IMPLEMENT_MESSAGE_MAP宏实现消息映射。一般情况下,这些声明和实现是由MFC的ClassWizard自动来维护的。看一个例子:在AppWizard产生的应用程序类的源码中,应用程序类的定义(头文件)包含了类似如下的代码://{{AFX_MSG(CTttApp)afx_msgvoidOnAppAbout();//}}AFX_MSGDECLARE_MESSAGE_MAP()应用程序类的实现文件中包含了类似如下的代码:BEGIN_MESSAGE_MAP(CTApp,CWinApp)//{{AFX_MSG_MAP(CTttApp)ON_COMMAND(ID_APP_ABOUT,OnAppAbout)//}}AFX_MSG_MAPEND_MESSAGE_MAP()头文件里是消息映射和消息处理函数的声明,实现文件里是消息映射的实现和消息处理函数的实现。它表示让应用程序对象处理命令消息ID_APP_ABOUT,消息处理函数是OnAppAbout。为什么这样做之后就完成了一个消息映射?这些声明和实现到底作了些什么呢?接着,将讨论这些问题。2.在声明与实现的内部1.DECLARE_MESSAGE_MAP宏:首先,看DECLARE_MESSAGE_MAP宏的内容:#ifdef_AFXDLL#defineDECLARE_MESSAGE_MAP()\private:\staticconstAFX_MSGMAP_ENTRY_messageEntries[];\protected:\staticAFX_DATAconstAFX_MSGMAPmessageMap;\staticconstAFX_MSGMAP*PASCAL_GetBaseMessageMap();\virtualconstAFX_MSGMAP*GetMessageMap()const;\#else#defineDECLARE_MESSAGE_MAP()\private:\staticconstAFX_MSGMAP_ENTRY_messageEntries[];\protected:\staticAFX_DATAconstAFX_MSGMAPmessageMap;\virtualconstAFX_MSGMAP*GetMessageMap()const;\#endifDECLARE_MESSAGE_MAP定义了两个版本,分别用于静态或者动态链接到MFCDLL的情形。2.BEGIN_MESSAE_MAP宏然后,看BEGIN_MESSAE_MAP宏的内容:#ifdef_AFXDLL#defineBEGIN_MESSAGE_MAP(theClass,baseClass)\constAFX_MSGMAP*PASCALtheClass::_GetBaseMessageMap()\{return&baseClass::messageMap;}\constAFX_MSGMAP*theClass::GetMessageMap()const\{return&theClass::messageMap;}\AFX_DATADEFconstAFX_MSGMAPtheClass::messageMap=\{&theClass::_GetBaseMessageMap,&theClass::_messageEntries[0]};\constAFX_MSGMAP_ENTRYtheClass::_messageEntries[]=\{\#else#defineBEGIN_MESSAGE_MAP(theClass,baseClass)\constAFX_MSGMAP*theClass::GetMessageMap()const\{return&theClass::messageMap;}\AFX_DATADEFconstAFX_MSGMAPtheClass::messageMap=\{&baseClass::messageMap,&theClass::_messageEntries[0]};\constAFX_MSGMAP_ENTRYtheClass::_messageEntries[]=\{\#endif#defineEND_MESSAGE_MAP()\{0,0,0,0,AfxSig_end,(AFX_PMSG)0}\};\对应地,BEGIN_MESSAGE_MAP定义了两个版本,分别用于静态或者动态链接到MFCDLL的情形。END_MESSAGE_MAP相对简单,就只有一种定义。3.ON_COMMAND宏最后,看ON_COMMAND宏的内容:#defineON_COMMAND(id,memberFxn)\{\WM_COMMAND,\CN_COMMAND,\(WORD)id,\(WORD)id,\AfxSig_vv,\(AFX_PMSG)memberFxn\};1.消息映射声明的解释在清楚了有关宏的定义之后,现在来分析它们的作用和功能。消息映射声明的实质是给所在类添加几个静态成员变量和静态或虚拟函数,当然它们是与消息映射相关的变量和函数。1.成员变量有两个成员变量被添加,第一个是_messageEntries,第二个是messageMap。第一个成员变量的声明:AFX_MSGMAP_ENTRY_messageEntries[]这是一个AFX_MSGMAP_ENTRY类型的数组变量,是一个静态成员变量,用来容纳类的消息映射条目。一个消息映射条目可以用AFX_MSGMAP_ENTRY结构来描述。AFX_MSGMAP_ENTRY结构的定义如下:structAFX_MSGMAP_ENTRY{//Windows消息IDUINTnMessage;//控制消息的通知码UINTnCode;//WindowsControl的IDUINTnID;//如果是一定范围的消息被映射,则nLastID指定其范围UINTnLastID;UINTnSig;//消息的动作标识//响应消息时应执行的函数(routinetocall(orspecialvalue))AFX_PMSGpfn;};从上述结构可以看出,每条映射有两部分的内容:第一部分是关于消息ID的,包括前四个域;第二部分是关于消息对应的执行函数,包括后两个域。在上述结构的六个域中,pfn是一个指向CCmdTarger成员函数的指针。函数指针的类型定义如下:typedefvoid(AFX_MSG_CALLCCmdTarget::*AFX_PMSG)(void);当使用一条或者多条消息映射条目初始化消息映射数组时,各种不同类型的消息函数都被转换成这样的类型:不接收参数,也不返回参数的类型。因为所有可以有消息映射的类都是从CCmdTarge派生的,所以可以实现这样的转换。nSig是一个标识变量,用来标识不同原型的消息处理函数,每一个不同原型的消息处理函数对应一个不同的nSig。在消息分发时,MFC内部根据nSig把消息派发给对应的成员函数处理,实际上,就是根据nSig的值把pfn还原成相应类型的消息处理函数并执行它。第二个成员变量的声明AFX_MSGMAPmessageMap;这是一个AFX_MSGMAP类型的静态成员变量,从其类型名称和变量名称可以猜出,它是一个包含了消息映射信息的变量。的确,它把消息映射的信息(消息映射数组)和相关函数打包在一起,也就是说,得到了一个消息处理类的该变量,就得到了它全部的消息映射数据和功能。AFX_MSGMAP结构的定义如下:structAFX_MSGMAP{//得到基类的消息映射入口地址的数据或者函数#ifdef_AFXDLL//pfnGetBaseMap指向_GetBaseMessageMap函数constAFX_MSGMAP*(PASCAL*pfnGetBaseMap)();#else//pBaseMap保存基类消息映射入口_messageEntries的地址constAFX_MSGMAP*pBaseMap;#endif//lpEntries保存消息映射入口_messageEntries的地址constAFX_MSGMAP_ENTRY*lpEntries;};从上面的定义可以看出,通过messageMap可以得到类的消息映射数组_messageEntries和函数_GetBaseMessageMap的地址(不使用MFCDLL时,是基类消息映射数组的地址)。1.成员函数_GetBaseMessageMap()用来得到基类消息映射的函数。GetMessageMap()用来得到自身消息映射的函数。1.消息映射实现的解释消息映射实现的实质是初始化声明中定义的静态成员函数_messageEntries和messageMap,实现所声明的静态或虚拟函数GetMessageMap、_GetBaseMessageMap。这样,在进入WinMain函数之前,每个可以响应消息的MFC类都生成了一个消息映射表,程序运行时通过查询该表判断是否需要响应某条消息。1.对消息映射入口表(消息映射数组)的初始化如前所述,消息映射数组的元素是消息映射条目,条目的格式符合结构AFX_MESSAGE_ENTRY的描述。所以,要初始化消息映射数组,就必须使用符合该格式的数据来填充:如果指定当前类处理某个消息,则把和该消息有关的信息(四个)和消息处理函数的地址及原型组合成为一个消息映射条目,加入到消息映射数组中。显然,这是一个繁琐的工作。为了简化操作,MFC根据消息的不同和消息处理方式的不同,把消息映射划分成若干类别,每一类的消息映射至少有一个共性:消息处理函数的原型相同。对每一类消息映射,MFC定义了一个宏来简化初始化消息数组的工作。例如,前文提到的ON_COMMAND宏用来映射命令消息,只要指定命令ID和消息处理函数即可,因为对这类命令消息映射条目,其他四个属性都是固定的。ON_COMMAND宏的初始化内容如下:{WM_COMMAND,CN_COMMAND,(WORD)ID_APP_ABOUT,(WORD)ID_