MFC的六大机制
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的六大机制的更多相关文章
- MFC消息映射机制以及画线功能实现
---此仅供用于学习交流,切勿用于商业用途,转载请注明http://www.cnblogs.com/mxbs/p/6213404.html. 利用VS2010创建一个单文档标准MFC工程,工程名为Dr ...
- MFC编程入门之五(MFC消息映射机制概述)
在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作.比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应. 一.什 ...
- VS2010/MFC编程入门之五(MFC消息映射机制概述)
VS2010/MFC编程入门之五(MFC消息映射机制概述)-软件开发-鸡啄米 http://www.jizhuomi.com/software/147.html 上一讲鸡啄米为大家简单分析了MFC应用 ...
- MFC消息响应机制 q
MFC消息响应机制分析 1 引言微软公司提供的MFC基本类库(Microsoft Foundation Classes),是进行可视化编程时使用最为流行的一个类 库.MFC封装了大部分Windows ...
- MFC消息映射机制
1.MFC应用框架主要类之间的关系 MFC自动生成的框架中重要的类有:C-App.CMainFrame.C-Doc和C-View. 其他的类如CClassView.CFileView等都是在框架窗口( ...
- MFC消息响应机制分析
---- 摘要: ---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析 ...
- MFC的消息机制
MFC的消息循环(::GetMessage,::PeekMessage)消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情 分两个步骤完成: 1 “ ...
- MFC消息反射机制
消息反射机制要解决什么问题呢? 消息反射机制主要是为了控件而实现的.每当控件需要某些资讯(比如,绘制自身背景的画刷,显示字体的颜色等等)时,都会频繁地向其父窗口发送通告消息(notification ...
- VS2010-MFC(MFC消息映射机制概述)
转自:http://www.jizhuomi.com/software/147.html 前面已经说过,Windows应用程序是消息驱动的.在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息 ...
随机推荐
- MySQL按天备份二进制日志
#!/usr/bin/env python # -*- coding:utf-8 -*- # Author:guozhen.zhang import MySQLdbimport timeimp ...
- 一键自签本地 TLSv3 多域名 SAN 域名证书工具 HTTPS(最新版 Chrome 浏览器策略测试通过)
一键自动生成本地自签名SAN域名证书工具 原生OpenSSL生成自签名SAN CA域名(V3签名),在Linux.MacOS系统下签发测试通过. 用于一键快速生成开发和测试场景证书,内部平台授权和私有 ...
- 小白的第一次sql实战
去年发的有一篇sql注入忘记粘贴过来了,今天想起了就fuzz过来一下 有id尝试sql注入 找这种sql注入的站用sql检索就行了,但是最好挂代理用谷歌搜索,百度的话搜sql注入的很多被别人打过了,导 ...
- 基于开源的GOCW和Directshow.net,实现摄像头预览、采集、录像等操作
本文基于开源的GOCW和Directshow.net,实现图像采集等操作.最为关键的部分在于可以实现摄像头的控制,同时关于视频采集进行了实现. 具体的内容请关注首发于51CTO的课程<基于Csh ...
- Python基础之异常定义
技术背景 在各类python的项目中,总会涉及到项目自身相关的一些约束条件.这些约束条件体现在,当用户输入的参数或者文件不符合项目要求时,就拒绝这个参数的输入并且播报出来,提醒用户自行修改,而这一过程 ...
- SQL注入靶场实战-小白入门
目录 SQL注入 数字型 1.测试有无测试点 2.order by 语句判断字段长,查出字段为3 3.猜出字段位(必须与内部字段数一致)(用union联合查询查看回显点为2,3) 4.猜数据库名,用户 ...
- 批量实现SSH无密码登陆认证脚本
批量实现SSH无密码登陆认证脚本 问题背景 使用为了让linux之间使用ssh不需要密码,可以采用了数字签名RSA或者DSA来完成.主要使用ssh-key-gen实现. 1.通过 ssh-key-ge ...
- Oracle 存储结构
数据库是存储数据的容器,它的主要功能是保存和共享数据. oracle数据库的存储结构可以分为逻辑存储结构和物理存储结构,对于这两种存储结构,oracle是分别进行管理的. 逻辑存储结构:oracle内 ...
- [Fundamental of Power Electronics]-PART II-9. 控制器设计-9.2 负反馈对网络传递函数的影响
9.2 负反馈对网络传递函数的影响 我们已经知道了如何推导开关变换器的交流小信号传递函数.例如,buck变换器的等效电路模型可以表示为图9.3所示.这个等效电路包含三个独立输入:控制输入变量\(\ha ...
- 2021 小白版,360 行行行转 IT
hey guys ,我是 cxuan,这一篇文章我就要和你聊聊编程如何学习,这一篇文章涉及的内容简直太多了,我将从入门开始,一步一步到如何提高,然后到一些学习的相关问题,还有一些计算机相关的术语等,干 ...