Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1646958
  • 博文数量: 268
  • 博客积分: 8708
  • 博客等级: 中将
  • 技术积分: 3764
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-06 15:58
文章分类

全部博文(268)

文章存档

2014年(1)

2013年(15)

2012年(23)

2011年(60)

2010年(51)

2009年(12)

2008年(59)

2007年(47)

分类: C/C++

2007-08-13 11:23:42

MFC消息响应机制分析
1 引言
微软公司提供的MFC基本类库(Microsoft Foundation Classes),是进行可视化编程时使用最为流行的一个类
库。MFC封装了大部分Windows API函数和Windows控件,使得程序的开发变得简单,极大的缩短了程序的开发
周期。MFC独创的Document/View框架结构,能够将管理数据的代码和显示数据的程序代码分开,并且设计了
一套方便的消息映射和命令传递机制,方便程序员的开发使用。其中消息映射机制本身比较庞大和复杂,对
它的分析和了解无疑有助于我们写出更为合理的高效的程序。这里我们分析一下MFC的消息映射机制,以了解
MFC是如何对Windows的消息加以封装,方便用户的开发。
2 SDK下的消息机制实现
首先,简单回顾一下SDK下我们是如何进行Windows的程序开发的。Windows程序的运行是依靠外部发生的事件
来驱动的,事件由操作系统捕捉,以消息的形式进入消息队列,然后通过消息循环从队列中不断取出消息,
送到对应的窗口过程里处理。相对于DOS程序,Windows是以WinMain作为程序的入口点,以下就是一个简化的
Win32程序的主体,通过while语句实现消息循环:
WinMain(…)
{
   MSG msg;
   RegisterClass(…);           // 注册窗口类
   CreateWindow(…);            // 创建窗口
   ShowWindow(…);              // 显示窗口
   UpdateWindow(…);
   While(GetMessage(&msg,…)){   // 消息循环
TranslateMessage(…);
DispatchMessage(…);
}
return msg.wParam;
}
其中,msg代表消息,程序是通过GetMessage函数从和某个线程相对应的消息队列里面把消息取出来并放到消
息变量msg里面。然后TranslateMessage函数用来把键盘消息转化并放到响应的消息队列里面,最后
DispatchMessage函数把消息分发到相关的窗口过程去处理。窗口过程根据消息的类型对不同的消息进行相关
的处理。在SDK编程过程中,用户需要在窗口过程中分析消息的类型及其参数的含义,然后做不同的处理,相
对比较麻烦;而MFC把消息调用的过程给封装起来,使用户能够通过ClassWizard方便的使用和处理Windows的
各种消息。
3 MFC中的消息映射机制
在MFC的框架结构下,“消息映射”是通过巧妙的宏定义,形成一张消息映射表格来进行的。这样一旦消息发
生,Framework就可以根据消息映射表格来进行消息映射和命令传递。
首先在需要进行消息处理的类的头文件(.H)里,都会含有DECLARE_MESSAGE_MAP()宏,声明该类拥有消息映
射表格:
class CscribbleDoc:public Cdocument
{
    …
DECLARE_MESSAGE_MAP()
};
然后在类应用程序文件(.CPP)实现这一表格
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)
          //{{AFX_MSG_MAP(CInheritClass)
         ON_COMMAND(ID_EDIT_COPY,OnEditCopy)
         ………
          //}}AFX_MSG_MAP
END_MESSAGE_MAP()
----这里主要进行消息映射的实现,把它和消息处理函数联系在一起。其中出现三个宏,第一个宏是
BEGIN_MESSAGE_MAP有两个参数,分别是拥有消息表格的类,及其父类。第二个宏是ON_COMMAND,指定命令消
息的处理函数名称。第三个宏是END_MESSAGE_MAP()作为结尾符号。中间的奇怪符号//}}和//{{,是
ClassWizard产生的,对程序无影响。
观察DECLARE_MESSAGE_MAP的定义:
#define DECLARE_MESSAGE_MAP () \
private: \
static const AFX_MESSAGE_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
里面又包含了MFC新定义的两个数据结构,如下:
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 nSig;       // signature type (action) or pointer to message #
          AFX_PMSG pfn;    // routine to call (or special value)
};
和AFX_MSGMAP
struct AFX_MSGMAP
{
          const AFX_MSGMAP* pBaseMap;
          const AFX_MSGMAP_ENTRY* lpEntries;
};
其中AFX_MSGMAP_ENTRY结构包含了一个消息的所有相关信息,而AFX_MSGMAP主要作用有两个,一是用来得到基
类的消息映射入口地址。二是得到本身的消息映射入口地址。
实际上,MFC把所有的消息一条条填入到AFX_MSGMAP_ENTRY结构中去,形成一个数组,该数组存放了所有的消
息和与它们相关的参数。同时通过AFX_MSGMAP能得到该数组的首地址,同时得到基类的消息映射入口地址。
当本身对该消息不响应的时候,就可以上溯到基类的消息映射表寻找对应的消息响应。
现在我们来分析MFC是如何让窗口过程来处理消息的,实际上所有MFC的窗口类都通过钩子函数
_AfxCbtFilterHook截获消息,并且在钩子函数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。原来的
窗口过程保存在成员变量m_pfnSuper中。
在MFC框架下,一般一个消息的处理过程是这样的。
(1)函数AfxWndProc接收Windows操作系统发送的消息。
(2)函数AfxWndProc调用函数AfxCallWndProc进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd对
象的操作。
(3)函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。
(4)WindowProc调用OnWndMsg进行正式的消息处理,即把消息派送到相关的方法中去处理。在CWnd类中都保存
了一个AFX_MSGMAP的结构,而在AFX_MSGMAP结构中保存有所有我们用ClassWizard生成的消息的数组的入口,
我们把传给OnWndMsg的message和数组中的所有的message进行比较,找到匹配的那一个消息。实际上系统是
通过函数AfxFindMessageEntry来实现的。找到了那个message,实际上我们就得到一个AFX_MSGMAP_ENTRY结
构,而我们在上面已经提到AFX_MSGMAP_ENTRY保存了和该消息相关的所有信息,其中主要是消息的动作标识
和相关的执行函数。然后我们就可以根据消息的动作标识调用相关的执行函数,而这个执行函数实际上就是
通过ClassWizard在类实现中定义的一个方法。这样就把消息的处理转化到类中的一个方法的实现上。
(5)如果OnWndMsg方法没有对消息进行处理的话,就调用DefWindowProc对消息进行处理。这是实际上是调用
原来的窗口过程进行缺省的消息处理。 所以如果正常的消息处理的话,MFC窗口类是完全脱离了原来的窗口
过程,用自己的一套体系结构实现消息的映射和处理。即先调用MFC窗口类挂上去的窗口过程,再调用原先的
窗口过程。用户面对的消息参数将不再是固定的wParam和lParam,而是和消息类型具体相关的参数。比如和消
息WM_LButtonDown相对应的方法OnLButtonDown的两个参数是nFlags和point。nFlags表示在按下鼠标左键的
时候是否有其他虚拟键按下,point更简单,就是表示鼠标的位置。同时MFC窗口类消息传递中还提供了两个
函数,分别为WalkPreTranslateTree和PreTranslateMessage。我们知道利用MFC框架生成的程序,都是从
CWinApp开始执行的,而CWinapp实际继承了CWinThread类。在CWinThread的运行过程中会调用窗口类中的
WalkPreTranslateTree方法。而WalkPreTranslateTree方法实际上就是从当前窗口开始查找愿意进行消息翻
译的类,直到找到窗口没有父类为止。在WalkPreTranslateTree方法中调用了PreTranslateMessage方法。实
际上PreTranslateMessage最大的好处是我们在消息处理前可以在这个方法里面先做一些事情。举一个简单的
例子,比如我们希望在一个CEdit对象里,把所有的输入的字母都以大写的形式出现。我们只需要在
PreTranslateMessage方法中判断message是否为WM_CHAR,如果是的话,把wParam(表示键值)由小写字母的值
该为大写字母的值就实现了这个功能。
4 小结
MFC通过巧妙的宏定义把消息调用的过程给封装起来,使用户能够通过ClassWizard方便的使用和处理Windows
的各种消息。通过对MFC消息映射机制的分析,不仅能够使我们更好的使用MFC类库,同时,对于我们自己设
计程序框架和类,无疑也有相当大的帮助。
-----------------------------------------------------------------------------------------------
MFC程序框架的剖析
和Win32平台创建Windows程序作对比:
MFC有个theApp全局变量来代表程序的本身.
1.WinMain
寻找WinMain入口:
在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码。
WinMain在APPMODUL.CPP中实现:
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
注意:(#define _tWinMain WinMain)
_tWinMain函数中通过调用AfxWinMain()函数来完成 它要完成的功能。(Afx*前缀代表这是应用程序框架函
数,是一些全局函数,应用程序框架是一套辅助生成应用程序的框架模型,把一些类做一些有机的集成, 我
们可根据这些类函数来设计自己的应用程序)。  
AfxWinMain()函数在WINMAIN.CPP中:
在AfxWinMain()函数中:
CWinApp* pApp = AfxGetApp();
说明:pApp存储的是指向WinApp派生类对象(theApp)的指针。
//_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
// { return afxCurrentWinApp; }
调用pApp->InitApplication():MFC类的一些内部初始化管理。
调用pThread->InitInstance()
说明:pThread也指向theApp,由于基类中virtual BOOL InitInstance()定义为虚函数,所以调用pThread-
>InitInstance()的时候,调用的是派生类CTestApp的InitInstance()函数。
 
在InitInstance()调用了注册窗口类AfxEndDeferRegisterClass();
AfxEndDeferRegisterClass()在WINCORE.CPP中实现:
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister){…}
说明:设计窗口类:在MFC中事先设计好了几种缺省的窗口类,根据不同的应用程序的选择,调用
AfxEndDeferRegisterClass()函数注册所选择的窗口类。
 
说明:这是在单文档情况下,先AfxEndDeferRegisterClass()注册,出于文档管理,注册提前。正常是在
CMainFrame::PreCreateWindow中调用AfxEndDeferRegisterClass()进行注册的。
 
CMainFrame的PreCreateWindow()://主要是注册窗口类,以及在创建窗口之前让用户有机会对style进行修

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
return TRUE;
}
说明:
CFrameWnd::PreCreateWindow()函数所在文件:WINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
//判断AFX_WNDFRAMEORVIEW_REG型号窗口类是否注册,如果没有注册则注册
cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
//把注册后的窗口类名赋给cs.lpszClass
}
if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
cs.style |= FWS_PREFIXTITLE;
if (afxData.bWin4)
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
其中:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);//PreCreateWindow()是个虚函数,如果子类有则调用子
类的。
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW;//WINCORE.CPP文件中,定义为全局数组。
//#define AFX_WNDFRAMEORVIEW AFX_WNDCLASS(”FrameOrView”)
 
在InitInstance()隐含调用的CFrameWnd::Create.
这里只讲解了框架窗口的创建,它的Create()函数在WINFRM.CPP中:
CFrameWnd::Create(…){

CreateEx(…);//从父类继承来的,调用CWnd::CreateEx().

}
CWnd::CreateEx()函数在WINCORE.CPP中:
BOOL CWnd::CreateEx(…){

if (!PreCreateWindow(cs))//虚函数,如果子类有调用子类的。
{
PostNcDestroy();
return FALSE;
}

HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

}
说 明:CreateWindowEx()函数与CREATESTRUCT结构体参数的对应关系,使我们在创建窗口之前可以通过
PreCreateWindow(cs)修改cs结构体成员来修改所要的窗口外观。PreCreateWindow(cs))//是虚函数,如果子
类有调 用子类的。
 
InitInstance()隐含调用
m_pMainWnd->ShowWindow(SW_SHOW);//显示窗口,m_pMainWnd指向框架窗口
m_pMainWnd->UpdateWindow();//更新窗口
大概运行步骤:
调 试:CWinApp::CWinApp();->CTestApp theApp;(->CTestApp ::CTestApp())->CWinApp::CWinApp()-
>CTestApp::CTestApp()-> _tWinMain(){}//进入程序
->AfxWinMain();->pApp->InitApplication();->pThread->InitInstance()//父类InitInstance虚函数;
->CTestApp::InitInstance()//子类实现函数;
->AfxEndDeferRegisterClass(LONG fToRegister)->CFrameWnd::Create(…)->Wnd::CreateEX(…)-
>m_pMainWnd->ShowWindow(SW_SHOW)->m_pMainWnd->UpdateWindow()!
---------------------------------------------------------------------------------------------
阅读(5973) | 评论(1) | 转发(1) |
1

上一篇:lib和dll的例子

下一篇:#pragma指令

给主人留下些什么吧!~~