MFC原理

来源:互联网 发布:淘宝比较真的法国代购 编辑:程序博客网 时间:2024/06/10 17:44

关于CObject

与框架有关的所有类都继承于 CObject 类,这也是很多开发设计中的基本理念

MFC继承关系图

这里写图片描述

模拟MFC框架

第一讲

为模拟 MFC 框架,我们创建以下几个类,继承关系如下

这里写图片描述

再 MFC 的工程下按下 F10 来到WinMain 的入口函数,入口函数处重新调用了微软自定义的 AfxWinMain。再入口除,看到了 AfxGetThread() 这个函数。这个函数是通过 TLS 来获取用户类的对象,赋值给 CWinThread 类,从而实现多态。CWinThread 类的指针调用 InitInstance() 这虚函数,即调用了用户类中的 InitInstance()。InitInstance() 自动添加了注册窗口类、更新显示窗口的代码,用户也可以在此自定义初始化代码,这是为用户提供的初始化的接口。

这里写图片描述

虚函数 InitInstance() 在类中的定义如下,在用户类中重写了 InitInstance()。

这里写图片描述

创建完窗口后,pThread 调用了 Run() 函数,其中是消息循环。

这里写图片描述

这里写图片描述

第二讲

在 MFC 框架中,CView 继承了 CWnd,与 CWnd 类不同的一点是,CView 中封装了与文档类(CDocument)的通信的方法(GetDocument)。文档类(CDocument)可以支持序列化,用于数据的存储。根据 MFC 的框架,我们又派生了以下的类:

这里写图片描述

如何在运行时动态new一个未知类

在序列化过程中,需要从文件中读取信息,然后还原现场,此时就需要根据类名动态创建对象,那么该如何做到呢?于是,在每个类中,都需要定义一个这样的结构体,信息包含 类名,用于确定是否是要创建该类;创建该类实例的 函数指针,如果确定是需要创建该类,则调用该函数指针,创建该类的实例。模仿 MFC 框架中的结构体,在每个类中定义一个这样的结构体:

这里写图片描述

//结构体简化struct CMyRuntimeClass{    // Attributes    LPCSTR m_lpszClassName;    int m_nObjectSize; //该结构体大小    UINT m_wSchema; // schema number of the loaded class    CMyObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class    CMyRuntimeClass* m_pBaseClass; //基类    // CRuntimeClass objects linked together in simple list    CMyRuntimeClass* m_pNextClass;       // linked list of registered classes    const void* m_pClassInit;};

在每个需要动态创建的类中,定义一个静态常成员变量 static const CMyRuntimeClass classCMyObject; 。因为一个类中只需要一个这样的变量,其他对象公用这一个变量,且初始化后不能被修改。在结构体中,还定义了这个类的基类指针,由此形成了链表的数据结构,得到该类的继承关系。

//定义virtual CMyRuntimeClass* GetRuntimeClass() const;//实现CMyRuntimeClass* CMyObject::GetRuntimeClass() const{  return ((CMyRuntimeClass*)(&CMyObject::classCMyObject));}

在每个类中,需要定义以上函数,来得到存储信息的结构体。为了便于修改,减少大量的冗余操作,将该函数和变量的定义定义成了宏,在每个类中添加这个宏,该宏的作用是为每个类添加 动态识别的功能

//定义常成员变量及函数声明宏#define MY_DECLARE_DYNAMIC(class_name) \public: \  static const CMyRuntimeClass class##class_name; \  virtual CMyRuntimeClass* GetRuntimeClass() const; \//初始化静态常成员变量宏,定义 GetRuntimeClass 函数实现代码宏#define MY_IMPLEMENT_DYNAMIC(class_name, base_class_name) \  MY_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)//MY_IMPLEMENT_DYNAMIC 宏的下一层#define MY_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \  const CMyRuntimeClass class_name::class##class_name = { \#class_name, sizeof(class class_name), wSchema, pfnNew, \  MY_RUNTIME_CLASS(base_class_name), NULL, class_init }; \  CMyRuntimeClass* class_name::GetRuntimeClass() const \{ return MY_RUNTIME_CLASS(class_name); }//返回该类的静态常成员结构体宏#define MY_RUNTIME_CLASS(class_name) ((CMyRuntimeClass*)(&class_name::class##class_name))

第三讲

为类添加 动态创建 的功能

上述已经完成 动态识别 的功能,接下来,若要根据类名来动态创建类,在每个类的 RTTI结构体 中,还需要添加创建该类的函数指针,指向该类添加的 CreateObject 函数,利用该函数创建该类的对象。

//模仿MFC定义的宏/**动态创建声明宏(定义了 RTTI 结构体,GetRuntimeClass 函数,CreateObject 函数**/#define MY_DECLARE_DYNCREATE(class_name) \  MY_DECLARE_DYNAMIC(class_name) \  static CMyObject* __stdcall CreateObject();/*动态创建定义宏(初始化RTTI结构体,定义 GetRuntimeClass 函数实现代码宏,定义CreateObject实现代码)*/#define MY_IMPLEMENT_DYNCREATE(class_name, base_class_name) \  CMyObject* class_name::CreateObject() \{ return new class_name; } \  MY_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \  class_name::CreateObject, NULL)

若要根据类名创建对象,需要记录所有可以动态创建的类名,从而可以根据类名确定该类名能否动态创建。我们可以使用链表来存储所有类名,利用一个类(CMyClassInit)的构造函数往链表中添加该类的RTTI结构体。在每个可以动态创建的类中,定义一个 CMyClassInit 类的静态对象,于是在每个类中,该对象只会初始化一次。

//存放类名的列表list<CMyRuntimeClass*> g_RuntimeList;//初始化类链表类class CMyClassInit{public:    CMyClassInit(CMyRuntimeClass* pRuntimeClass)    {        g_RuntimeList.push_back(pRuntimeClass);    }    ~CMyClassInit()    {    }};

MFC 中 CClassInit 的设计

在 MFC 中,该链表存放在了 TLS 中,且该类通过宏定义一个 CMyClassInit 这样的全局变量。

这里写图片描述

这里写图片描述

第四讲

这里写图片描述

构造 SingleDocTemplate

模仿在 MFC 工程中用户类的 InitInstance,首先创建 CSingleDocTemplate 类,以及其父类 CDocTemplate。从CSingleDocTemplate 类的构造函数中可以看到,实际上在 InitInstance 中传入的参数是在其父类 CDocTemplate 中赋值的。而 CMyDocTemplate 构造是保护,表示只能派生类来调用。

这里写图片描述

这里写图片描述

添加文档模板(AddDocTemplate)

AddDocTemplate() 将创建的文档模板保存在了文档管理类(CDocManager)的链表中。跟进 AddDocTemplate 函数,在 InitInstance 中先调用了其父类 CWinApp::AddDocTemplate(),在这个函数中检查文档管理类(CDocManager)是否已经创建,如果没有创建,则创建该实例。再调用文档管理类(CDocManager)中的AddDocTemplate函数将该模板指针存入到链表中。

这里写图片描述

这里写图片描述

创建窗口

跟进 MFC 的框架,可知 MFC 通过调用 CWinApp::OnFileNew() ,然后再经过一系列的调用,对窗口进行注册创建的。在 CWinApp::OnFileNew() 中,调用了模板管理类的 OnFileNew 函 数。在 CDocManager::OnFileNew() 中,拿出存储在链表中的模板指针,调用 OpenDocumentFile 函数。OpenDocumentFile 函数在 CDocTemplate 中是一个 纯虚函数,必须在子类 CSingleDocTemplate 中实现。

这里写图片描述

这里写图片描述

CreateNewDocument

在 CSingleDocTemplate::OpenDocumentFile() 中,调用父类 CDocTemplate 的成员函数 CreateNewDocument()。在 CreateNewDocument() 中,创建用户文档类的实例,赋值给CSingleDocTemplate 类中的 m_pOnlyDoc 成员变量,并保存 CDocTemplate 类自身的指针保存在 CDocument 类中的 m_pDocTemplate 成员变量中。

这里写图片描述

这里写图片描述

这里写图片描述

CreateNewFrame

在 CSingleDocTemplate::OpenDocumentFile() 中,继续调用父类 CDocTemplate 的成员函数 CreateNewFrame()。在 CreateNewFrame() 中,动态创建用户类框架对象,并调用 LoadFrame(),LoadFrame() 中调用 CFrameWnd::Create(),在其中加载菜单等。 CFrameWnd::Create() 中调用 CWnd::CreateEx() , CWnd::CreateEx() 中注册创建窗口。

这里写图片描述

在 OpenDocument 中,将主框架指针保存在 CWinThread 中,以便用户App类得以访问。

这里写图片描述

关于消息循环

由于 MFC 接管了消息循环,所以根据消息类别,在 CWnd 中添加消息处理的虚函数,如果用户需要处理某个消息,只要重写该消息的虚函数即可。

第五讲

由于每一个窗口都有自己的消息过程函数,如果用户使用框架创建了新的窗口,MFC 框架必须保证它能够接管这个新的窗口的过程函数。所以 MFC 在调用 CreateWindow API 之前,hook CreateWindow 这个 API。接管了窗口过程函数,并做了一张表,保存每个窗口对象所对应的窗口句柄,依此可以确定调用哪个窗口所对应的消息虚函数。

这里写图片描述

这里写图片描述

再窗口过程函数中,根据窗口句柄获取其窗口对象。再调用该窗口对象的窗口过程函数,从而将每个窗口的消息区分开来。

这里写图片描述

这里写图片描述

关于消息 WM_QUERYAFXWNDPROC
可以发送该消息来判断是否是 MFC 程序,因为 MFC 自定义的消息过程函数中如果收到该消息会返回值 1

第六讲 消息响应

MFC 通过做表来实现消息的响应。每个类中都有一张消息映射表。当消息来临,通过消息编号,查找表中对应的函数指针。如果该表中没有对应的编号,则在其父类的消息映射表中寻找。

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

原创粉丝点击