MFC框架

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

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

例如在MFC中CView继承于CWnd,那么能够进行这样的判断:框架

CView view;函数

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

如上,经过调用IsKindOf函数,能够判断出view对象是一个CWnd类的实例。spa

   

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

具体说来比对过程以下:code

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

注意这里是静态成员,其缘由不言自明,若是是普通成员的话,不一样的对象之间成员都不同,没法实现比对。blog

   

每一个类都有了特殊标识以后,仅仅能进行一对一的比对,也就是说只能进行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。

相关文章
相关标签/搜索