MFC的运行机制和消息响应机制 - 图文

更新时间:2023-09-18 23:05:01 阅读量: 小学教育 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

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()函数。

当应用程序运行到\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_MAP END_MESSAGE_MAP()

---- 这里主要进行消息映射的实现和消息处理函数的实现。

---- 所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。 ---- 同时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 {

#ifdef _AFXDLL

const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); #else

const AFX_MSGMAP* pBaseMap; #endif

const AFX_MSGMAP_ENTRY* lpEntries; };

其中AFX_MSGMAP_ENTRY结构包含了 一个消息的所有相关信息,其中 nMessage为Windows消息的ID号 nCode为控制消息的通知码 nID为Windows控制消息的ID

nLastID表示如果是一个指定范围的消息被映射的话, 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进行比较,找到匹配的那一个消息。实际上系统是通过函数AfxFindMessageEntry来实现的。找到了那个message,实际上我们就得到一个AFX_MSGMAP_ENTRY结构,而我们在上面已经提到

AFX_MSGMAP_ENTRY保存了和该消息相关的所有信息,其中主要的是消息的动作标识和跟消息相关的执行函数。然后我们就可以根据消息的动作标识调用相关的执行函数,而这个执行函数实际上就是通过ClassWizard在类实现中定义的一个方法。这样就把消息的处理转化到类中的一个方法的实现上。举一个简单的例子,比如在View中对WM_LButtonDown消息的处理就转化成对如下一个方法的操作。 void CInheritView::OnLButtonDown (UINT nFlags, CPoint point) {

// TODO: Add your message

handler code here and/or call default CView::OnLButtonDown(nFlags, point); }

注意这里CView::OnLButtonDown(nFlags, point)实际上就是调用CWnd的Default()方法。 而Default()方法所做的工作就是调用DefWindowProc对消息进行处理。这实际上是调用原来的窗口过程进行缺省的消息处理。

如果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(表示键值)由小写字母的值该为大写字母的值就实现了这个功能。

---- 继续上面的例子,根据我们对MFC消息机制的分析,我们很容易得到除了上面的方法,我们至少还可以在另外两个地方进行操作。

---- 一:在消息的处理方法里面即OnChar中,当然最后我们不再调用CEdit::OnChar(nChar, nRepCnt, nFlags),而是直接调用DefWindowProc(WM_CHAR,nChar,MAKELPARAM (nRepCnt,nFlags))。因为从我们上面的分析可以知道CEdit::OnChar(nChar, nRepCnt, nFlags)实际上也就是对DefWindowProc方法的调用。

本文来源:https://www.bwwdw.com/article/zv8h.html

Top