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. 学习Python的书籍——入门到进阶

    入门读物 <Python基础教程>(Beginning Python From Novice to Professional) <Python学习手册>(Learning Py ...

  2. 11、Spring教程之声明式事务

    1.回顾事务 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎! 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性. 事务就是把一系列的动作当成一个独立的工作单元,这 ...

  3. Java 学习记录

    •Eclipse相关 Eclipse常用设置 解决 Eclipse 项目中有红色感叹号的详细方法(图文) JRE System Library [JavaSE-1.8](unbound) •Java ...

  4. 写个小程序01 | 注册微信小程序

    出于兴趣和学习目的,我想自己做一个基于"子弹笔记(Bullet Journal)"的小程序.由于个人开发经验很有限,只在课程作业中写过 web 前端,所以也不知道多久能写出来(逃) ...

  5. 第17 章 : 深入理解 etcd:etcd 性能优化实践

    深入理解 etcd:etcd 性能优化实践 本文将主要分享以下五方面的内容: etcd 前节课程回顾复习: 理解 etcd 性能: etcd 性能优化 -server 端: etcd 性能优化 -cl ...

  6. Ansible-Playbook中的变量使用

    变量名:仅能由字母.数字和下划线组成,且只能以字母开头 变量来源: 1.ansible all -m setup 远程主机的所有变量都可直接调用 #显示所有变量 ansible all -m setu ...

  7. jQuery核心函数和静态方法

    jQuery核心函数 从jQuery文档中可以看出,jQuery核心函数一共3大类4小类 jQuery(callback) 当DOM加载完成后执行传入的回调函数 <script> $(fu ...

  8. redis常用数据类型对应的数据结构

    redis的数据类型都是通过多种数据结构来实现,主要是出于时间和空间的考虑,当数据量小的时候通过数组下标访问最快,占用内存最小[压缩列表是数组的变种,允许存储的数据大小不同] 因为数组需要占用连续的内 ...

  9. “知疫”疫情防控可视化平台——NABCD分析

    "知疫"疫情防控可视化平台 项目 内容 这个作业属于那个课程 2021春季学期软件工程(罗杰.任健) 这个作业的要求在哪里 初次邂逅,需求分析 1 NABCD分析 NEED 目前多 ...

  10. 通过Dapr实现一个简单的基于.net的微服务电商系统

    本来想在Dpar 1.0GA时发布这篇文章,由于其他事情耽搁了放到现在.时下微服务和云原生技术如何如荼,微软也不甘示弱的和阿里一起适时推出了Dapr(https://dapr.io/),园子里关于da ...