MFC框架

第一点:类别型录网的搭建:

类别型录网搭建的目的是为了实现所谓的"执行期类型识别",也就是在程序运行的时候识别出某个对象是否是某个类的实例(基类也可以)。这里还不是很明白为什么需要实现"执行期类型识别",这种技巧具体被应用在哪里。

例如在MFC中CView继承于CWnd,那么可以进行这样的判断:

CView view;

bool result = view.IsKindOf(CWnd); // result == true

如上,通过调用IsKindOf函数,可以判断出view对象是一个CWnd类的实例。

MFC通过建立一张型录网来实现这样的功能。也就是把类登记到一个表中,传入特定的参数之后在这张表上进行查找比对,从而实现"执行期类型识别"。

具体说来比对过程如下:

为了在不同的对象和类之间互相比对,肯定类里得有一个特殊的标识才行,MFC通过为每个类添加一个CRuntimeClass类型的静态成员来作为这个标识。

注意这里是静态成员,其原因不言自明,如果是普通成员的话,不同的对象之间成员都不一样,无法实现比对。

每个类都有了特殊标识之后,仅仅能进行一对一的比对,也就是说只能进行CView.IsKindOf(CView)这样的操作,无法判断一个CView对象是否也是一个CWnd对象。

MFC实现这种功能的方法类似于链表的实现:链表中有一个指针专门指向它下一个成员的位置,遍历时依靠这个指针来不断指向下一个。

CRuntimeClass类包含一个指针叫m_pBaseClass,对于CView,它有一个CRuntimeClass成员(就是之前说的特殊标识),只要使得这个成员的m_pBaseClass指针指向CWnd的CRuntimeClass成员,那么就建立起了类似链表的结构。

当判断CView.IsKindOf(CWnd)时,首先判断CView的CRuntimeClass成员和CWnd的CRuntimeClass成员是不是一致,发现不一致之后,在CView的CRuntimeClass成员中根据m_pBaseClass来得到CView的父类CWnd的CRuntimeClass成员,之后再进行比对,发现是一致的,因此可以判断CView.IsKindOf(CWnd)为真。

下面介绍MFC中对上述机制的具体实现方法:

1.为每个类添加特定标识CRuntimeClass成员:

使用DECLARE_DYNAMIC宏:

class CView : public CWnd

        DECLARE_DYNAMIC(CView)

如上,使用了DECLARE_DYNAMIC宏之后,CView类中多了一个CRuntimeClass类型的静态成员 classCView(名为classXXXX,也就是在类名之前加一个class),也就是之前所说的具有比较功能的"特殊标识"

2.建立类别型录表:

也就是初始化classCView,使它的m_pBaseClass指针指向父类

IMPLEMENT_DYNCREATE(CView, CWnd)

如上,静态成员的初始化需要在实现文件中进行,在实现文件中使用了IMPLEMENT_DYNAMIC宏之后,classCView的m_pBaseClass指针指向了CWnd的classCWnd成员

3.实现类型识别IsKindOf:

this->IsKindOf(RUNTIME_CLASS(CWnd))

如上,IsKindOf函数的参数有点特别,是一个RUNTIME_CLASS宏,这个宏的功能其实非常简单,其实就是一个函数调用:RUNTIME_CLASS(CWnd)等价于CWnd::GetThisClass(),这个函数的返回值就是CWnd的CRuntimeClass成员,也就是CWnd的"特殊标识",把这个特殊标识传递给IsKindOf函数之后,事情就好办许多,逐个提取CView及其父类的CRuntimeClass成员与这个标识进行比对就可以达到判断的目的了。因为是静态变量,所以只存有一份拷贝,可以直接把指针作为比较时的参照。

第二点:消息映射表的搭建:

搭建消息映射表的目的是为了找到一个消息对应的消息处理函数。对于一个CMyView窗口来说,它的某个消息处理函数有可能并不是存在于CMyView类中,而是存在于它的父类甚至是别的类里面(例如数据操作应该放在CDoc类里面处理比较合适),MFC为了找到正确的消息处理函数,遂给每个类都建立一个表来存储这个类所拥有的消息处理函数,并通过指针连接起来,这样就可以通过遍历查找来找到正确的那个消息处理函数。

之前类别型录网的搭建是以CRuntimeClass作为一个类的特别标识,而这里需要标识的则是消息和它对应的消息处理函数。也就是说,每个类里都存储一张表,表里包括了这个类可以处理的消息和对应的处理函数,这样对于每个类,得到一条消息之后,将这条消息和表里的条目进行比对,如果比对成功,调用对应的处理函数就可以了。

除此之外,还需要一个指针来指向这个类的父类

下面介绍MFC对上面机制的具体实现方法:

1.为每个类添加消息条目和指针成员:

AFX_MSGMAP_ENTRY[] 数组用来存储消息和对应的消息响应函数指针

AFX_MSGMAP 结构,其中包含一个AFX_MSGMAP类型的指针指向基类,以及一个AFX_MSGMAP_ENTRY指针,指向之前的数组。

这样一个类就需要两个静态成员就行了,一个是AFX_MSGMAP类型,一个是AFX_MSGMAP_ENTRY数组。

MFC中使用DECLARE_MESSAGE_MAP宏来实现为一个类添加这两个成员的功能。

2.建立消息映射表:

也就是为AFX_MSGMAP_ENTRY[]数组添加成员,并且把指针指向基类的AFX_MSGMAP静态成员:

BEGIN_MESSAGE_MAP(theClass, baseClass)

ON_COMMAND(MSGID,msgpfn)

END_MESSAGE_MAP()

另外,MFC还为每个类添加了一个虚函数GetMessageMap用于得到这个类的AFX_MSGMAP静态成员指针,由此指针即可进行遍历。

第三点:命令绕行

命令绕行的目的是找到一个消息正确的消息处理函数。

对于WM_LBUTTONDOWN这样的消息来说,消息处理函数都在本窗口类(或者父类)里面定义,使用GetMessageMap得到消息映射表指针之后遍历映射表就能找到对应的消息处理函数。

但对于WM_COMMAND消息来说,消息处理函数不一定是在本类里面,CFrameWnd窗口接收到的WM_COMMAND消息,其消息处理函数有可能在CView里面。之所以会这样应该是与MFC Frame\View\Doc框架有关,具体原因以后进一步来理解,这里主要讲解一下绕行的实现机制。

绕行的实现机制其实非常简单,就是一个if语句的判断,例如对于CFrameWnd,先看看CView里有没有这个消息的处理函数,如果没有,再遍历自己的映射表看看有没有,如果还是没有,就看看CWinApp里有没有,再没有的话,就交给默认函数处理。

下面先给出消息绕行时的路径:

MFC消息必然是属于某个窗口的(MSG结构里还有个HWND字段呢),也就是说在MFC框架中,窗口的产生者只能是CWnd的派生类(CView和CFrameWnd等)。

而这些窗口所使用的窗口过程函数其实都是同一个全局函数AfxWndProc,也就是说消息产生之后都会被放到AfxWndProc中进行处理。

省去中间的调用步骤,AfxWndProc在接收到不同窗口的消息之后会调用CWnd->WindowProc()函数:

1.如果是WM_XXXX函数,直接使用GetMessageMap得到消息映射表指针,遍历查找消息处理函数。

2.如果是WM_COMMAND函数,则调用CWnd::OnCommand(),对于不同的窗口对象,由于多态的原因,调用的也不会是同一个OnCommand()函数,例如有CFrameWnd::OnCommand()等等。这个函数其实算不上重点,真正起作用的是CWnd::OnCmdMsg()函数。

OnCmdMsg()函数是CCmdTarget类里的函数,其中的关键代码就是遍历消息映射表找到消息处理函数。在CCmdTarget的子类中有几个类重写了这个函数,这几个类分别是CFrameWnd,CView,CDoc。

假如现在是CFrameWnd窗口接收到了WM_COMMAND消息,如下是CFrameWnd::OnCmdMsg()的主要代码:

BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,

    AFX_CMDHANDLERINFO* pHandlerInfo)

{

    CPushRoutingFrame push(this);

    // 调用CView的OnCmdMsg函数

    CView* pView = GetActiveView();

    if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

        return TRUE;

    // 实际上是调用CCmdTarget的OnCmdMsg函数,也就是遍历自身消息映射表

    if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

        return TRUE;

    // 调用CWinApp的OnCmdMsg函数,实际上也是遍历了CWinApp自身的消息映射表

    CWinApp* pApp = AfxGetApp();

    if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

        return TRUE;

    return FALSE;

}

如上,在CFrameWnd的OnCmdMsg函数里,分别调用了CView,CWnd(并没有重载,所以实际上调用的是CCmdTarget的OnCmdMsg函数),CMyApp类的OnCmdMsg函数。也就是分别在CView、CWnd(其实就是CCmdTarget)、CWinApp里寻找消息处理函数。一旦找到消息处理函数之后,就调用之,然后OnCmdMsg函数返回。

如上,即解释了来自CFrameWnd窗口的WM_COMMAND消息是如何绕行的,其实也就是分别调用CView、CWnd(CCmdTarget)、CWinApp的OnCmdMsg函数而已。

同理,CView和CWinApp以及CDocument类都以类似的方式来对OnCmdMsg函数来进行调用,从而实现命令绕行机制。

以面向对象的思想理解MFC

最开始的MFC框架只是用两个类来对原本的SDK流程进行封装,MFC使用两个类来抽象这个流程。一个是CWinApp,封装了创建窗口(通过实例化一个Frame对象来实现),消息循环的主流程;一个是CMainFrame,封装了窗口注册,创建,及消息处理等内容。

CWinApp(也许说CWinThread更合适一点)类封装了传统Win32程序的主流程。一般来说一个Win32程序里会有注册窗口类,创建窗口,进行消息循环这几个步骤。这几个步骤在CWinApp类里都存在着,其中注册窗口类并创建窗口的过程被封装到InitInstance成员函数里面,消息循环被封装到了Run成员函数里面。

CMainFrame(也许说CWnd更合适一点)类封装了Win32程序中有关窗口的那部分东西,具体说来就是窗口类的注册,窗口的创建,以及窗口消息的处理。其中窗口注册被封装在PreCreateWindow函数里面,窗口的创建则被封装在Create函数里面(更准确地说是封装在CWnd::CreateEx函数里),还有一个窗口过程函数在哪里这个暂时还没有搞清楚。

后来之所以有了CView和CDoc,是因为原来的CFrame负担了过多的责任。这里把数据的管理交给了CDoc类来负责,把数据的显示交给了CView类来负责,明确了各自的责任。

这里CView类仍然是一个单独的窗口,从功能上来说应该和CFrame处于同等地位的,仍然有自己的窗口过程函数。只是从职责上来讲CView只负责数据的显示,而CFrame作为CView外部的一个框架提供别的一些功能。

由上可知,程序的功能实际上是被封装到了Frame/View/Doc三个类里,其实是这三个类来合作完成程序的某个功能,正因为这样,对于一个COMMEND消息,就可以交给这三个类里的某一个类来处理,不管这个消息是来源于Frame还是View。比如在菜单上单击一个"更新"的选项。本来这个消息是由Frame接收到的,但是由View来处理会更简单,因为View类本身持有更新时所需要的一些信息。

从这点上可以理解,Frame/View/Doc三个类是相互关联的一个整体。

接着上面的阐述多说两句:

MFC之所以创造了消息机制是为了实现其Frame/View/Doc三位一体的架构。

Frame/View/Doc架构的意义在于将处理消息的职责分配到合理的类中去处理,例如在菜单上点击一个"保存"选项,处理这个消息就应该交给Doc类来实现,而如果点击"更新"选项,则将这个消息交给View类来处理更方便一些。

MFC的消息机制就负责将消息交给合适的类去处理。下面解释消息机制的实现思路:

1.比对思路和消息表:为每一个类建立一个消息表,这个消息表里包括了这个类能够处理的消息有哪些,消息和其处理函数也一一对应。这样的话就可以遍历这张消息表并进行比对来知道这个类可以处理那些消息,并能够一个消息的处理函数。

2.遍历思路:要让Frame里产生的消息在Doc类里进行处理,其思路也是简单的遍历:先看看View里有没有能处理的,要是没有,再看看自己类里能不能处理,要是再不行,再看看Doc类里有没有对应的处理函数。

3.Frame/View/Doc三位一体:这个我也没记清楚,貌似在创建一个Frame的时候都会顺带创建出其对应的View和Doc(单文档的情况,多文档貌似要创建多个)。所以上面可以由View找到其对应的Doc。

时间: 2024-10-26 11:14:50

MFC框架的相关文章

C++开发人脸性别识别教程(9)——搭建MFC框架之显示图片

在之前的博客中我们已经实现读取用户选定的文件夹,并将其路径保存在相应的变量中,在这篇博文中我们将介绍如何借助CvvImage类将图片显示在picture控件中,并自动读取文件夹下的其他图片. 一.添加“下一张”按钮 由于我们需要读取文件夹下的所有图像文件,而非某一张文件,因此有必要添加一个按钮来进行控制,具体功能就是:每单击一次这个按钮,程序就会自动读取下一张图片并显示在界面上.由于之前已经详细介绍了MFC中添加Button控件的方式,这里不再赘述.添加一个按钮,命名为“下一张”,将ID更改为I

介绍MFC框架中涉及到的设计模式(一)

最近看了几篇有关MFC框架中涉及到的设计模式的论文,为帮助大家深入了解MFC框架特此总结,希望能帮助到大家. 简单介绍设计模式 设计模式是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 设计模式分为三种类型:创建型模式.结构型模式,行为型模式. 设计模式分为三种类型,共23种. 创建型模式:单例模式.抽象工厂模式.建造者模式.工厂模式.原型模式. 结构型模式:适配器模式.桥接模式.装饰模式.组合模式.外观模

C++开发人脸性别识别教程(7)——搭建MFC框架之界面绘制

在之前的博客中我们已经将项目中用到的算法表述完毕,包括人脸检测算法以及四种性别识别算法,在这篇博客中我们将着手搭建基本的MFC框架. 一.框架概况 在这篇博文中我们将搭建最基本的MFC框架,绘制MFC界面. 二.搭建流程 1.新建一个MFC工程并配置OpenCv 打开VS,按下“ctrl+n”,在新建窗口中选择“MFC应用程序”,命名为GenderRecognitionMFC: 单击确定,程序类型选择“基于对话框”,MFC使用选择“在静态库中使用MFC”: 直接单击“完成”,创建完毕.OpenC

MFC框架类、文档类、视图类相互访问的方法

1.获取应用程序指针 CMyApp* pApp=(CMyApp*)AfxGetApp(); 2.获取主框架指针 CWinApp 中的公有成员变量 m_pMainWnd 就是主框架的指针 CMainFrame* pMainFrame = (CMainFrame*)(AfxGetApp()->m_pMainWnd); 或者 CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd(); 3.获取菜单指针 CMenu* pMenu = AfxGetMain

C++开发人脸性别识别教程(8)——搭建MFC框架之读取文件夹信息

在上一篇博客中我们已经绘制了MFC界面,在这篇博客中我们将添加响应代码,为MFC框架添加一个最基本的功能:打开一个文件夹. 一.添加相关头文件 这里头文件主要包含三类:opencv头文件.批量读取文件相关的头文件.CvvImage.这里需要强调CvvImage这个头文件,这个是用来关联OpenCv和picture控件,并且这个头文件是隶属于OpenCv1.x的,在2.x版本中已经将这个类移除,因此需要手动下载这两个文件(CvvImage.h和CvvImage.cpp),下载地址:CvvImage

介绍MFC框架中涉及到的设计模式(二)

接着上一篇<介绍MFC框架中涉及到的设计模式(一)>介绍 单例模式(Singleton Pattern) 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案. 单例模式的要点有三个: 1.某个类只能有一个实例: 2.它必须自行创建这个实例: 3.它必须自行向整个系统提供这个实例. 单例模式典

MFC框架剖析和消息机制

即便是基于MFC的应用程序,建立窗口类也是会遵循如下的过程: 设计窗口类->注册窗口类->生成窗口->显示窗口->更新窗口->消息循环->消息路由到窗口过程函数处理.下面就剖析一下在MFC中是如何完成上述过程的. (1)每个应用程序都有且仅有一个应用类的全局变量theApp,全局变量先于WinMain函数进行处理. (2)WinMain函数体在APPMODUL.CPP文件中,定义如下: extern "C" int WINAPI _tWinMain(

MFC框架程序剖析

一.           MFC MFC(Microsoft Foundation Class,微软基础类库)是微软为了简化程序员的开发工作所开发的一套C++类的集合,是一套面向对象的函数库,以类的方式提供给用户使用.利用这些类,可以有效发帮助程序员完成Windows应用程序的开发 二.           theAPP theApp代表应用程序实例. 在C×××App类中 ,有 theApp 这个变量,它是CWinApp类的派生类的对象,是一个全局变量. 全局变量在WinMain()前被创建.

MFC框架仿真&lt;一&gt;

六个关键仿真,在去年的某个时候研究过一段时间,并且对这个仿真做了自己的二次仿真,并且做了笔记,可遗憾的是写的代码没有保存下来. 接触的所谓框架不多,但是说实在话,MFC一开始接触真的很难!这次给我极大的教训,就是研究完一个东西,一定要详细的记录下来,包括流程和完整代码!头文件MFC.H #include <iostream.h> class CObject/*根类*/ { public: CObject() {cout << "CObject 构造 \n";}