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. HDFS设置配额的命令

    1 文件个数限额 #查看配额信息 hdfs dfs -count -q -h /user/root/dir1 #设置N个限额数量,只能存放N-1个文件 hdfs dfsadmin -setQuota ...

  2. unbutu的dpkg被中断的解决办法

    直接sudo apt update进行重新配置就行

  3. Error message: Failed to spawn: unable to access process with pid 413 due to system restrictions; try `sudo sysctl kernel.yama.ptrace_scope=0`, or run Frida as root

    Android 8.0 在frida中使用 -f 参数报错, Error message: Failed to spawn: unable to access process with pid 413 ...

  4. 6、MyBatis教程之日志实现

    7.日志实现 思考:我们在测试SQL的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率? 如果一个 数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题. 对于以 ...

  5. 灵魂拷问!浏览器输入「xxxxhub」的背后.....

    Hey guys 各位读者姥爷们大家好,这里是程序员 cxuan 计算机网络连载系列的第 13 篇文章. 到现在为止,我们算是把应用层.运输层.网络层和数据链路层都介绍完了,那么现在是时候把这些内容都 ...

  6. golang 性能优化分析:benchmark 结合 pprof

    前面 2 篇 golang 性能优化分析系列文章: golang 性能优化分析工具 pprof (上) golang 性能优化分析工具 pprof (下) 一.基准测试 benchmark 简介 在 ...

  7. VIM 编辑器操作详解

    1 vim 使用介绍 1.1 vim 安装 # CentOS 安装: yum install -y vim # Ubuntu 安装: sudu apt-get install vim 安装完成后,可使 ...

  8. Python的flask接收前台的ajax的post数据和get数据

    ajax向后台发送数据: ①post方式 ajax: @app.route("/find_worldByName",methods=['POST']) type:'post', d ...

  9. python基础(〇):注释

    单行注释 Python中单行注释以 # 开头,例如: # 这是一个注释1 print("Hello, World!") # 这是一个注释2 多行注释 单引号(''') ''' 这是 ...

  10. 茫茫内存,我该如何用 windbg 找到你 ?

    一:背景 1. 讲故事 前天wx上有个朋友丢给我一个dump,让我帮忙鉴定一下某些敏感信息在内存中是否也是加密的,现在数据安全很重要,不仅数据库中的信息要加密,灌到内存后数据同样也需密文存储,随用随解 ...