MFC的类层次结构与运行机制MFC的类层次结构如图所示(子类指向父类):其中:CObject:是MFC提供的绝大多数类的基类。
该类完成动态空间的分配与回收,支持一般的诊断、出错信息处理和文档序列化等。
CCmdTarget:主要负责将系统事件(消息)和窗口事件(消息)发送给响应这些事件的对象,完成消息发送、等待和派遣调度等工作,实现应用程序的对象之间的协调运行。
CWinApp:是应用程序的主线程类,它是从CWinThread类派生而来的。
CWinThread类用来完成对线程的控制,包括线程的创建、运行、终止和挂起等。
CDocument:是文档类,包含了应用程序在运行期间所用到的数据。
CWnd:是一个通用的窗口类,用来提供Windows中的所有通用特性、对话框和控件。
CFrameWnd是从CWnd类继承来的,并实现了标准的框架应用程序。
CDialog类用来控制对话框窗口。
CView:用于让用户通过窗口来访问文档。
CMDIFrameWnd和CMDIChildWnd:分别用于多文档应用程序的主框架窗口和文档子窗口的显示和管理。
CMiniFrameWnd类是一种简化的框架窗口,它没有最大化和最小化窗口按钮,也没有窗口系统菜单,一般很少用到它。
MFC运行机制在程序中,当定义一个类对象时,它会自动调用相应的构造函数。
所谓"类对象",就是用该类定义的"变量",这个"变量"又称为类的一个实例。
例如,theApp就是类CSimpApp的一个对象。
MFC正是利用类的这种"自动调用相应的构造函数"特性,使得WinMain()函数的调用变成了应用程序框架内部的调用,所以我们在代码中看不到每个Windows程序所必须有的WinMain()函数。
当应用程序运行到"CSimpApp theApp;"时,系统就会先调用基类CWinApp构造函数,进行一系列的内部初始化操作,然后自动调用CSimpApp的虚函数InitInstance(),该函数会进一步调用相应的函数来完成主窗口的构造和显示工作。
下面来看看上述程序中InitInstance的执行过程。
首先执行的是:m_pMainWnd = new CMainFrame;该语句用来创建从CFrameWnd类派生而来的用户框架窗口CMainFrame类对象,继而调用该类的构造函数,使得Create函数被调用,完成了窗口创建工作。
然后执行后面两句:m_pMainWnd->ShowWindow(m_nCmdShow);m_pMainWnd->UpdateWindow();用作窗口的显示和更新。
接下来调用:m_pMainWnd->MessageBox("你好,欢迎进入MFC世界!");最后返回TRUE,表示窗口创建成功。
由于应用程序类CWinApp是用来调用WinMain以及实例的初始化,因此每一个MFC应用程序有且只能一个这样的应用程序类,且需要一个全局的对象实例,如上述程序中的theApp,当然也可换一个对象名。
InitInstance()完成初始化工作之后,接下来就是调用基类CWinApp的成员函数Run(),执行应用程序的消息循环,即重复执行接收消息并转发消息的工作。
当Run()检查到消息队列为空时,将调用基类CWinApp 的成员函数OnIdle进行空闲时的后台处理工作。
若消息队列为空且又没有后台工作要处理时,则应用程序一直处于等待状态,一直等到有消息为止。
当程序结束后,调用基类CWinApp的成员函数ExitInstance(),完成终止应用程序的收尾工作。
这就是MFC应用程序的运行机制。
MFC消息响应机制分析---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析,可以帮助程序开发人员对MFC的消息映射机制有一个比较透彻的了解。
1.引言---- VC++的MFC类库实际上是Windows下C++编程的一套最为流行的类库。
MFC的框架结构大大方便了程序员的编程工作,但是为了更加有效、灵活的使用MFC编程,了解MFC的体系结构往往可以使编程工作事半功倍。
它合理的封装了WIN32 API函数,并设计了一套方便的消息映射机制。
但这套机制本身比较庞大和复杂,对它的分析和了解无疑有助于我们写出更为合理的高效的程序。
这里我们简单的分析MFC的消息响应机制,以了解MFC是如何对Windows的消息加以封装,方便用户的开发。
2. SDK下的消息机制实现---- 这里简单的回顾一下SDK下我们是如何进行Windows的程序开发的。
一般来说,Windows的消息都是和线程相对应的。
即Windows会把消息发送给和该消息相对应的线程。
在SDK的模式下,程序是通过GetMessage函数从和某个线程相对应的消息队列里面把消息取出来并放到一个特殊的结构里面,一个消息的结构是一个如下的STRUCTURE。
typedef struct tagMSG {HWND hwnd;UINT message;WPARAM wParam;LPARAM lParam;DWORD time;POINT pt;}MSG;---- 其中hwnd表示和窗口过程相关的窗口的句柄,message表示消息的ID号,wParam和lParam表示和消息相关的参数,time表示消息发送的时间,pt表示消息发送时的鼠标的位置。
---- 然后TranslateMessage函数用来把虚键消息翻译成字符消息并放到响应的消息队列里面,最后DispatchMessage函数把消息分发到相关的窗口过程。
然后窗口过程根据消息的类型对不同的消息进行相关的处理。
在SDK编程过程中,用户需要在窗口过程中分析消息的类型和跟消息一起的参数的含义,做不同的处理,相对比较麻烦,而MFC把消息调用的过程给封装起来,使用户能够通过ClassWizard方便的使用和处理Windows的各种消息。
3.MFC的消息实现机制---- 我们可以看到,在MFC的框架结构下,可以进行消息处理的类的头文件里面都会含有DECLARE_MESSAGE_MAP()宏,这里主要进行消息映射和消息处理函数的声明。
可以进行消息处理的类的实现文件里一般都含有如下的结构。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass)//{{AFX_MSG_MAP(CInheritClass)//}}AFX_MSG_MAPEND_MESSAGE_MAP()---- 这里主要进行消息映射的实现和消息处理函数的实现。
---- 所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。
CCmdTarget类是MFC处理命令消息的基础和核心。
---- 同时MFC定义了下面的两个主要结构:AFX_MSGMAP_ENTRYstruct AFX_MSGMAP_ENTRY{UINT nMessage; // windows messageUINT nCode; // control code or WM_NOTIFY codeUINT nID;// control ID (or 0 for windows messages)UINT nLastID;// used for entries specifying a range of control id'sUINT nSig;// signature type (action) or pointer to message #AFX_PMSG pfn; // routine to call (or special value)};和AFX_MSGMAPstruct AFX_MSGMAP{#ifdef _AFXDLLconst AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();#elseconst AFX_MSGMAP* pBaseMap;#endifconst AFX_MSGMAP_ENTRY* lpEntries;};其中AFX_MSGMAP_ENTRY结构包含了一个消息的所有相关信息,其中nMessage为Windows消息的ID号nCode为控制消息的通知码nID为Windows控制消息的IDnLastID表示如果是一个指定范围的消息被映射的话,nLastID用来表示它的范围。
nSig表示消息的动作标识AFX_PMSG pfn 它实际上是一个指向和该消息相应的执行函数的指针。
---- 而AFX_MSGMAP主要作用是两个,一:用来得到基类的消息映射入口地址。
二:得到本身的消息映射入口地址。
---- 实际上,MFC把所有的消息一条条填入到AFX_MSGMAP_ENTRY结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的参数。
同时通过AFX_MSGMAP能得到该数组的首地址,同时得到基类的消息映射入口地址,这是为了当本身对该消息不响应的时候,就调用其基类的消息响应。
---- 现在我们来分析MFC是如何让窗口过程来处理消息的,实际上所有MFC的窗口类都通过钩子函数_AfxCbtFilterHook截获消息,并且在钩子函数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。
原来的窗口过程保存在成员变量m_pfnSuper中。
---- 所以在MFC框架下,一般一个消息的处理过程是这样的。
函数AfxWndProc接收Windows操作系统发送的消息。
函数AfxWndProc调用函数AfxCallWndProc进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd 对象的操作。
函数AfxCallWndProc调用CWnd类的方法WindowProc进行消息处理。
注意AfxWndProc和AfxCallWndProc 都是AFX的API函数。
而WindowProc已经是CWnd的一个方法。
所以可以注意到在WindowProc中已经没有关于句柄或者是CWnd的参数了。
方法WindowProc调用方法OnWndMsg进行正式的消息处理,即把消息派送到相关的方法中去处理。
消息是如何派送的呢?实际上在CWnd类中都保存了一个AFX_MSGMAP的结构,而在AFX_MSGMAP结构中保存有所有我们用ClassWizard生成的消息的数组的入口,我们把传给OnWndMsg的message和数组中的所有的message进行比较,找到匹配的那一个消息。