MFC的CRuntimeClass分析

MFC 专栏收录该内容
22 篇文章 0 订阅

一、CRuntimeClass背景介绍

MSDN:对于MFC中每个从CObject派生的类来说,都有一个相关的CRuntimeClass结构体,在程序运行时可以访问该结构体来获取对象及其基类的运行时信息。

CRuntimeClass是一个结构体,并且其本身并没有基类。在运行时确定一个对象的类型是很重要的,尤其是在做类型检查时;而C++语言本身并不支持运行时类信息。CObject和CRuntimeClass是MFC中两个非常重要的类/结构,绝大部分MFC类都是以CObject做为基类, CRuntimeClass结构同CObject密不可分,了解它们对于深入理解MFC具有重要意义。

二、CRuntimeClass 的结构

CRuntimeClass是一个结构体,它的定义如下:

struct CRuntimeClass
{
// Attributes
//类名,一般是指包含CRuntimeClass对象的类的名称
LPCSTR m_lpszClassName;


//包含CRuntimeClass对象的类sizeof的大小,不包括它分配的内存
int m_nObjectSize; 


// schema number of the loaded class分类编号(对不可分类的类,该值为-1)
UINT m_wSchema; 


// NULL => abstract class 指向一个建立实例的构造函数,创建一个类的对象,抽象类则返回NULL(只有//在类支持动态创建时才有效;否则,返回NULL)。PASCAL不用管,是个过时了的calling convension。
CObject* (PASCAL* m_pfnCreateObject)(); 


//mfc用于动态对象创建的CRuntimeClass结构的一个成员变量 
CObject*(* m_pfnCreateObject)(); 

#ifdef _AFXDLL


//是一个指向函数的指针,该函数返回基类的CRuntimeClass结构。
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();  


#else

//m_pBaseClass的指针(函数)是MFC运行时确定类层次的关键,它一个简单的单向链表
CRuntimeClass* m_pBaseClass;  
   
#endif

// Operations

/*
这个函数给予CObject 派生类运行时动态建立的能力
从CObject派生的类可以支持动态创建,这是在运行时创建一个指定类的对象的能力。例如,文档,视和框架类就应该支持动态创建。CreateObject成员函数可以用来实现这个功能,在运行时为这些类创建对象。
*/
CObject* CreateObject(); 


/*
这个函数使用 m_pBaseClass或 m_pfnGetBaseClass遍历整个类层次确定是否pBaseClass指向的类是基类,使用它可以判断某类是否是从pBaseClass指向的类在派生来。
*/
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;   //判断是不是一个基类的子类



// Implementation
void Store(CArchive& ar) const;


static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);


//单向链表,每个类的CRuntimeClass结构体对象的 m_pNextClass 都指向其直接基类的CRuntimeClass结构体对象
CRuntimeClass* m_pNextClass; // linked list of registered classes
};

UML图如下所示

每一个类拥有这样一个CRuntimeClass成员变量,并且有一定的命名规则(例如在类名称之前冠以“class”作为它的名称),然后通过某种手段将整个类库构建好,“类别型录网”能呈现类似的风貌

三、CObject类

 CRuntimeClass与CObject关联密切。所以必须介绍一下CObject。它是MFC类的大多数类的基类,主要是通过它实现:
(1)、运行类信息;(2)、序列化;(3)、对象诊断输出;(4)、同集合类相兼容;
1、运行时类信息:
注意:要想使用CRuntimeClass结构得到运行时类信息,你必须在你的类中包括DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC、 DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE或DECLARE_SERIAL/IMPLEMENT_SERIAL。但你的类又必须是从CObject派生的才能使用这些宏, 因为通过DECLARE_DYNAMIC将定义一个实现如下的函数:

CRuntimeClass* PASCAL B::_GetBaseClass()
{
    return RUNTIME_CLASS(base_name);
}

该函数中的RUNTIME_CLASS是这样定义的

#define RUNTIME_CLASS( class_name ) \
(CRuntimeClass *)(&class_name::class##class_name);

即得到类中的CRuntimeClass对象指针。显而易见,如果没有基类你用IMPLEMENT_DYNAMIC时将得到一个编译错误。 除非你象CObject一样不用DECLARE_DYNAMIC而定义和实现了这些函数,CObject中的GetBaseClass只是简单的返回NULL。 实际的DECLARE_DYNAMIC在afx.h中声明如下:

#define DECLARE_DYNAMIC(classname) \
protected: \
static CRuntimeClass* PASCAL _GetBaseClass(); \

public: \

//##class_name 在预处理阶段会被自动翻译为后面class_name
static const AFX_DATA CRuntimeClass class##class_name; \

virtual CRuntimeClass* GetRuntimeClass() const; \

IMPLEMENT_DYNAMIC在afx.h中定义如下:

#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
 
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
CRuntimeClass* PASCAL class_name::_GetBaseClass() \
{ return RUNTIME_CLASS(base_class_name); } \
 
AFX_COMDAT const AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
&class_name::_GetBaseClass, NULL }; \
 
//重写GetRuntimeClass虚函数,这个函数最初的定义在CObject中,重写后,将返回子类自己的GetRuntimeClass变量
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \

 

总而言之,就是在预处理阶段自动生成(更准确说应该是宏翻译转换)用于类运行时检查的函数和变量,主要包括获取其基类的CRuntimeClass变量的函数_GetBaseClass,类本身的CRuntimeClass静态变量定义,同时将类本身的CRuntimeClass静态变量与其基类的CRuntimeClass静态变量绑定起来,使得子类的该变量可以直接访问到基类的变量,并且这种绑定是从子类到其基类递归进行的。在运行时判断类信息时,就可以通过类的CRuntimeClass静态变量来进行对比,从而判断出该类是否是相应的类型,这便是CObject中接口函数IsKindOf函数的作用,其实现类似于下面的过程:

BOOL CObject::IsKindOf(const CRuntimeClass *pClass) const
{
     CRuntimeClass *pClassThis = GetRuntimeClass();
     while (pClassThis != NULL)
     {
     // 检查CRuntimeClass静态变量是否相同,若相同,则表示所检查的pClass是对的

      if (pClassThis == pClass)
         {
              return TRUE;
         }
         pClassThis = pClassThis->m_pBaseClass;
     }
     return FALSE;
}


参考:

https://www.cnblogs.com/liqilei/archive/2010/09/18/1830381.html

https://blog.csdn.net/liuchen1206/article/details/7653573

  • 1
    点赞
  • 0
    评论
  • 5
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

物随心转

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值