MFC的六大机制

  • 程序的初始化过程
  • 运行时类型识别
  • 动态创建
  • 永久保存
  • 消息映射
  • 命令传递

运行时类型识别

MFC的运行时类型识别就是在程序运行过程中判断某个对象是否属于某个类,MFC通过为需要进行运行时类型识别的类添加一个静态CRuntimeClass类对象,其属于此类而不是属于某一特定对象,其在没有实例化对象的时候就已经存在并且可以被使用。

struct CRuntimeClass
{
//省略其他无关的成员
LPCSTR m_lpszClassName; //类的名称
int m_nObjectSize; //类的大小
CRuntimeClass* m_pBaseClass; //指向基类的CRuntimeClass对象
CRuntimeClass* m_pNextClass; //指向同类型对象的CRuntimeClass对象
};

然后提供了一个能返回类自身CRuntimeClass对象地址的函数GetRuntimeClass()

CRuntimeClass* class_name::GetRuntimeClass() const/
{ return &class_name::class##class_name;}

CObject类提供一个IsKindof虚拟函数,此函数通过从对象自己的CRuntimeClass成员对象开始往基类遍历,判断是否有CRuntimeClass成员对象等于待判断类的CRuntimeClass成员对象,有则说明此对象属于此类(也就是说此对象是此类或其派生类的实例),无则说明不属于此类。

BOOL CObject::IsKindof(const CRuntimeClass *pClass) const
{
CRuntimeClass* pClassThis=GetRuntimeClass();
while(pClassThis != NULL)
{
if(pClassThis==pClass)
return TRUE;
pClassThis=pClassThis->m_pBaseClass;
}
return FALSE;
}

例如:IsKindof()最后就会返回TRUE,因为MFC自己定义的类都实现了运行时类型识别,而所有的类都是从CObject中派生的,所以CWnd类的对象实例属于CObject类。注意RUNTIME_CLASS()宏,其直接返回对应类的CRuntimeClass对象指针。

CWnd cwnd;
cwnd.IsKindof(RUNTIME_CLASS(CObject));

一般MFC预定义的类都支持运行时类型识别,要想在我们从MFC预定义类中继承的类支持运行时类识别需要将DECLARE_DYNAMIC()写在类定义中,将IMPLEMENT_DYNAMIC()宏写在源文件中。

DECLARE_DYNAMIC()宏参数为类名称,其就是为类添加一个CRuntimeClass类对象,覆盖GetRuntimeClass()。

#define DECLARE_DYNAMIC(class_name)/
public:/
static CRuntimeClass class##class_name;/
virtual CRuntimeClass* GetRuntimeClass() const;

IMPLEMENT_DYNAMIC()宏是将CRuntimeClass对象的一些数据成员初始化,然后实现GetRuntimeClass()

#define IMPLEMENT_DYNAMIC(class_name,bass_class_name)/
_IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,0xFFFF,NULL) #define _IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema,pfnNew)/
static char _lpsz##class_name[]= #class_name;/
CRuntimeClass class_name::class##class_name = {/
_lpsz##class_name,sizeof(class_name),wSchema,pfnNew,/
RUNTIME_CLASS(base_class_name),NULL};/
static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name);/
CRuntimeClass* class_name::GetRuntimeClass() const/
{ return &class_name::class##class_name;}

动态创建

MFC能够在我们自己没有实例化类的情况下使用这个类的对象,实际是MFC在运行时创建这个类的对象。

struct CRuntimeClass
{
LPCSTR m_lpszClassName; //类的名称
int m_nObjectSize; //类的大小
CRuntimeClass* m_pBaseClass; //指向基类的CRuntimeClass对象
CRuntimeClass* m_pNextClass; //指向同类型链表的下一个类的CRuntimeClass对象 CObject* (PASCAL* m_pfnCreateObject)(); //函数指针,指向创建对象的函数
CObject* CreateObject(); //利用m_pfnCreateObject创建对象 static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum); //遍历整个同类型链表(沿着m_pNextClass遍历),查询符合条件的类并创建对象
};

值得注意的是CRuntimeClass类的m_pNextClass成员,当我们从同一个类中派生出两个类时此成员就会排上用场。

CClass1:public CWnd
{ }
//此时同类型链表的头指针pFirstClass指向CClass1类,而此链表中只有CClass1一个类,所以pNextClass为NULL。 CClass2:public CWnd
{ }
//当又从同一类中继承另一个类后执行如下操作(前提加入IMPLEMENT_DYNCREATE宏),即链表头指向新的类,在链表中添加一个新的元素(头插法)。
pNextClass = pFirstClass;
pFirstClass = CClass2;

CObject* CreateObject()成员函数是利用m_pfnCreateObject函数指针创建的对象。

CObject* CRuntimeClass::CreateObject()
{
  CObject* pObject = NULL;
  pObject = (*m_pfnCreateObject)();
  return pObject;
}

要想使我们的类能够动态创建,需要在类定义中使用DECLARE_DYNCREATE()宏,在源文件中使用IMPLEMENT_DYNCREATE()宏。

DECLARE_DYNCREATE()宏包含了DECLARE_DYNAMIC()宏,声明了静态的CreateObject()函数(此函数与CRuntimeClass类中的CreateObject()成员函数不一样,此函数实际就是CRuntimeClass的成员m_pfnCreateObject函数指针指向的函数)。

#define DECLARE_DYNCREATE (class_name) \
    DECLARE_DYNAMIC (class_name) \
    static CObject* PASCAL CreateObject();

IMPLEMENT_DYNCREATE宏是包含了CreateObject()函数的实现,可以看到其就是new了一个指定的类对象并将其返回。以及IMPLEMENT_RUNTIMECLASS宏是对CRuntimeClass的成员的定义和初始化。

#define IMPLEMENT_DYNCREATE (class_name, base_class_name) \
    CObject* PASCAL class_name::CreateObject() \
    { return new class_name; } \
    IMPLEMENT_RUNTIMECLASS (class_name, base_class_name, 0xFFFF, \
    class_name::CreateObject, NULL)

所以我们可以得知在MFC中动态创建对象的大致过程为,以下面实例代码为例,先得到CWnd类的CRuntimeClass成员对象,然后调用CRuntimeClass类的CreateObject成员函数,此成员函数通过m_pfnCreateObject函数指针调用Cwnd类的静态成员函数CreateObject()。从而new了一个对象,实现了动态创建。

RUNTIME_CLASS(CWnd)->CreateObject();
//因为类(CWnd)的CRuntimeClass成员对象是静态的所以直接可以通过类(CWnd)得到并使用,此处调用了CRuntimeClass类的成员函数CreateObject(),因为类(CWnd)的CreateObject()函数也是静态的所以可以直接调用而不用先实例化对象。

所以要想让我们的类能够动态创建需要满足以下条件

  • 定义一个不带参数的构造函数(默认构造函数)。因为我们是用CreateObject()动态创建,它只有一条语句就是return new XXX,不带任何参数。所以我们要有一个无参构造函数。
  • 在类定义中加入DECLARE_DYNCREATE(),在源文件中加入IMPLEMENT_DYNCREATE()宏其还会在链表中加入一个新的元素(头插法)。

例如我们在MDI多文档和多视图程序设计时,我们在使用文档模板对象进行动态创建对象时。文档对象就会字内部调用对应类的CRuntimeClass类对象的CreateObject函数来动态创建对应的类对象(CMyDoc,CMainFrame,CMyView)。

RUNTIME_CLASS(CMyDoc)
RUNTIME_CLASS(CMainFrame)
RUNTIME_CLASS(CMyView)

永久保存

MFC的永久数据保存也称为序列化(串行化和并行化)

MFC利用档案类CArchive进行串行化和并行化,对于基本的数据类型()可以直接进行串行化和并行化。

//ar 为一个CArchive类对象
int a;
DWORD b;
ar << a << b;
ar >> a >> b;

对于一些MFC预定义的数据类MFC为其重载了<< 和 >> 运算符所以也可以直接串行化和并行化

CString string;
ar << string;
ar >> string;

对于自定义类可以通过类指针进行串行化和并行化,当然必须对自定义类必须满足一些条件才可以。

那么一个满足条件的自定义类是如何进行串行化和并行化的呢?首先说串行化。

CMyClass * pMyclass;
ar << pMyclass;

首先会先调用CArchive::WriteObject()函数,并将自定义类对象的指针作为CObject类对象的指针传递给它。接着会调用GetRuntimeClass()获得自身的CRuntimeClass对象,然后条用WriteClass()将类信息写入档案中。最后调用虚拟函数Serialize()进行类基本数据的串行化。

_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ ar.WriteObject(pOb); return ar; } void CArchive::WriteObject(const CObject* pOb)
{
//省略无关代码
CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
WriteClass(pClassRef);
((CObject*)pOb)->Serialize(*this);
}

接着看看并行化过程。

CMyClass * pMyclass;
ar >> pMyclass;

首先其会先调用CArchive::ReadObject()函数,接着ReadObject()函数会调用ReadClass()函数读取档案中的类信息,并判断版本号是否等于对应pOb类的版本号,如果相等返回对应类的CRuntimeClass,否则返回空说明模式号不匹配。如果模式号不匹配就会判断pOb类是否包含VERSIONABLE_SCHEMA标志从而支持多版本号,如果支持则将模式编号保存到GetObjectSchema()可以检索到的地方,否则产生异常。如果一开始版本号就匹配的话,则会调用CRuntimeClass的CreateObject()进行动态创建对象。接着将调用动态创建对象的Serialize()函数进行并行化,从而将对象的成员进行初始化。

_AFX_INLINE CArchive& AFXAPI operator>>(CArchive& ar, const CObject*& pOb)
{ pOb = ar.ReadObject(NULL); return ar; } CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
//省略部分代码
UINT nSchema;
DWORD obTag;
CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag); CObject* pOb=NULL;
if (pClassRef == NULL)
{
if (obTag > (DWORD)m_pLoadArray->GetUpperBound())
AfxThrowArchiveException(CArchiveException::badIndex, m_strFileName); //此处将模式编号保存到GetObjectSchema()可以检索到的地方
}
else
{
pOb = pClassRef->CreateObject();
pOb->Serialize(*this);
}
}

根据上面自定义类串行化和并行化过程,我们可以得知MFC中自定义的类指针要能进行串行化和并行化需要满足以下条件。

  • 直接或间接得到CObject的派生类(因为在调用WriteObject和ReadObject函数的时候都是将自定义类指针转换为CObject类指针使用)
  • 在类说明中写入MFC的DECLARE_SERIAL()宏。在类实现中要写入IMPLEMENT_SERIAL()宏
  • 覆盖CObject类的虚函数Serialize,并串行化/并行化派生类的数据成员。(因为最后会调用Serialize函数进行并行化或并行化)
  • 如果派生类没有默认的构造函数(无参数),则应该添加一个。(因为在并行化时,其会在得到对应类的CRuntimeClass后通过调用CreateObject函数动态创建一个对象,我们知道动态创建对象的时候是通过CRuntimeClass的函数指针成员m_pfnCreateObjcet调用对应类中的静态成员函数CreateObject,而此函数是通过new XXX创建的对象,其new没有传任何参数所以需要一个无参的构造函数供其调用)

最后你可能会疑惑为什么我们自定义的对象并没有使用在定义能动态创建对象需要使用的宏,但是其却能动态创建对象呢?

实际是DECLARE_SERIAL()包含了DECLARE_DYNCREATE(),并且其将 >> 运算符进行了重载(声明为此自定义类的友元)

#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);

同样 IMPLEMENT_SERIAL()也是同样的道理,并且其包含了 << 运算符重载函数的实现。

#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
extern AFX_CLASSINIT _init_##class_name; \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject, &_init_##class_name) \
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; }

命令传递

命令传递实现将“命令消息”和“菜单更新命令”通过命令的形式传递到其他类中处理。MFC中的命令消息指的是WM_COMMAND(当选择菜单项,加速键,工具栏时将产生此消息),MFC菜单更新命令(MFC自定义)也可以进行命令传递。

WM_COMMAND消息的命令传递

因为菜单和工具栏资源一般都是加载到框架窗口上,所以WM_COMMAND消息应该是首先被传送到框架窗口处理。WM_COMMAND消息的消息处理函数为OnCommand()。

BOOL CFrameWnd::OnCommand(WPARAM wParam,LPARAM lParam)
{
//省略部分代码
return CWnd:: OnCommand(wParam,lParam);
}

我们看到CFrameWnd的OnCommand函数最后会调用CWnd::OnCommand函数,我们继续查看此函数。发现CWnd::OnCommand函数,此函数会调用虚函数OnCmdMsg,因为一般都会在CFrameWmd中覆盖OnCmdMsg函数,所以实际调用的是CFrameWnd::OnCmdMsg函数。

BOOL CWnd::OnCommand(WPARAM wParam,LPARAM lParam)

{

//省略部分代码

return OnCmdMsg(nID,nCode,NULL,NULL);

}

我们查看CFrame::OnCmdMsg函数,发现其会先让活动视图先处理,如果活动视图处理不了或不存在活动视图就交给框架窗口自己处理,如果框架窗口自己处理不了就交给应用程序对象处理。应用程序对象最后会调用消息默认处理函数::DefWindowProc()处理。

BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
CPushRoutingFrame push(this); // pump through current view FIRST
CView* pView = GetActiveView();
if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE; // then pump through frame
if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE; // last but not least, pump through app
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE; return FALSE;
}

注意活动视图在处理WM_COMMAND消息的时候如果不能处理,就会先交给文档对象处理,如果文档对象处理不了就会交给文档模板处理。如果也处理不了就会返回。所以MFC的命令传递让处理WM_COMMAND消息十分自由,可以在框架窗口,视图窗口,文档中任何一个地方处理。最后得出MFC命令传递路径如下图所示。

MFC菜单更新命令的命令传递

MFC通过ON_UPDATE_COMMAND_UI宏实现菜单的更新处理。其原理是当一个从框架窗口类(CFrameWnd)派生出来的类的窗口菜单被点击且并没有显示时其会接收到WM_INITMENUPOPUP消息,如果其没有处理WM_INITMENUPOPUP消息,那么消息就会交给CFrame::OnInitMenuPopup()处理。此函数会为所有菜单调用DoUpdate()函数来更新菜单。



DoUpdate()函数会利用CCmdTarget*指针调用OnCmdMsg虚函数,因为我们类继承了CFrameWnd类,而此类重写了虚函数OnCmdMsg(),所以会进而去调用CFrameWnd::OnCmdMsg。从而回到了WM_COMMAND消息一样的处理过程。

MFC的六大机制的更多相关文章

  1. MFC消息映射机制以及画线功能实现

    ---此仅供用于学习交流,切勿用于商业用途,转载请注明http://www.cnblogs.com/mxbs/p/6213404.html. 利用VS2010创建一个单文档标准MFC工程,工程名为Dr ...

  2. MFC编程入门之五(MFC消息映射机制概述)

    在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作.比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应. 一.什 ...

  3. VS2010/MFC编程入门之五(MFC消息映射机制概述)

    VS2010/MFC编程入门之五(MFC消息映射机制概述)-软件开发-鸡啄米 http://www.jizhuomi.com/software/147.html 上一讲鸡啄米为大家简单分析了MFC应用 ...

  4. MFC消息响应机制 q

    MFC消息响应机制分析 1 引言微软公司提供的MFC基本类库(Microsoft Foundation Classes),是进行可视化编程时使用最为流行的一个类 库.MFC封装了大部分Windows ...

  5. MFC消息映射机制

    1.MFC应用框架主要类之间的关系 MFC自动生成的框架中重要的类有:C-App.CMainFrame.C-Doc和C-View. 其他的类如CClassView.CFileView等都是在框架窗口( ...

  6. MFC消息响应机制分析

    ---- 摘要: ---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析 ...

  7. MFC的消息机制

    MFC的消息循环(::GetMessage,::PeekMessage)消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情 分两个步骤完成: 1 “ ...

  8. MFC消息反射机制

    消息反射机制要解决什么问题呢? 消息反射机制主要是为了控件而实现的.每当控件需要某些资讯(比如,绘制自身背景的画刷,显示字体的颜色等等)时,都会频繁地向其父窗口发送通告消息(notification ...

  9. VS2010-MFC(MFC消息映射机制概述)

    转自:http://www.jizhuomi.com/software/147.html 前面已经说过,Windows应用程序是消息驱动的.在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息 ...

随机推荐

  1. 使用C# (.NET Core) 实现模板方法模式 (Template Method Pattern)

    本文的概念内容来自深入浅出设计模式一书. 项目需求 有一家咖啡店, 供应咖啡和茶, 它们的工序如下: 咖啡: 茶: 可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, ...

  2. vue 快速入门 系列 —— 侦测数据的变化 - [基本实现]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [基本实现] 在 初步认识 vue 这篇文章的 hello-world 示例中,我们通过修改数据(app.seen = false),页面中 ...

  3. Spring的循环依赖

    本文简要介绍了循环依赖以及Spring解决循环依赖的过程 一.定义 循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环.这里的对象特指单例对象. 二.表现形式 对象之间的循环 ...

  4. 快速上手阿里云oss SDK

    使用阿里云oss SDK 依赖安装: pip install oss2 pip install aliyun-python-sdk-sts 版本最好是 2.7.5 或以上 如果要开启 crc64 循环 ...

  5. 第7 章 : 应用编排与管理:Job & DaemonSet

    应用编排与管理:Job & DaemonSet 本节课程要点 Job & CronJobs 基础操作与概念解析: DaemonSet 基础操作与概念解析. Job 需求来源 Job 背 ...

  6. java面试-生产环境服务器变慢,谈谈你的诊断思路

    1.uptime:查询linux系统负载 11:16:16 系统当前时间 up 64 days, 19:23 从上次启动开始系统运行的时间3 users 连接数量,同一用户多个连接的时候算多个load ...

  7. 在Android、iOS、Web多平台使用AppGallery Connect性能管理服务

    性能管理(App Performance Management,简称APM)是华为应用市场AppGallery Connect(简称AGC)质量系列服务中的其中一项,可以提供分钟级应用性能监控能力,支 ...

  8. All in All UVA - 10340

     You have devised a new encryption technique which encodes a message by inserting between its charac ...

  9. Java并发-同步容器篇

    作者:汤圆 个人博客:javalover.cc 前言 官人们好啊,我是汤圆,今天给大家带来的是<Java并发-同步容器篇>,希望有所帮助,谢谢 文章如果有问题,欢迎大家批评指正,在此谢过啦 ...

  10. odoo字段属性列举

    罗列一些Odoo中的字段属性,基本包含大部分的属性. 常用字段属性 平平无奇常用字段属性 string:字段的标题,在UI视图标签中使用.它是可选项,如未设置,会通过首字母大写及将空格替换成为下划线来 ...