CRuntimeClass在MFC中是至关重要的一个结构。
CRuntimeClass没有基类。
每个由CObject派生的类都与一个CRuntimeClass结构相联系,用户可以使用该结构获取一个对象及其基类的运行时信息。当需要额外的函数参数检查时,或当用户必须根据一个对象的类编写特殊目的代码时,在运行时确定该对象的类就非常有用。C++并不直接支持运行时类的信息。
CRuntimeClass是MFC实现的RTTI(运行时类型信息),MFC中的很多类需要由框架动态创建(比如文档、视图、框架窗口类等等),所以从CObject继承的类如果需要这种能力,必须实现它的CRuntimeClass,包括CreateObject静态方法(这个方法简单调用new CMyClass)。而做到这个很简单,使用DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏就自动拥有这个特性,看看这两个宏的定义就能理解其原理。
CRuntimeClass在MFC中定义为一个数据结构,在文件AFX.H中声明,它是用来串起MFC从COBJECT继承下来的所有类(相当于一根绳,只要你牵住绳的一头你就可以得到绳上的所有数据),你也可以把你自己写的类加入这根绳。
每个从CObject中派生的类都有一个CRuntimeClass对象同它关联以完成在运行时得到类实例的信息或者是它的基类。 在afx.h中CRuntimeClass的定义如下:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName; //类名,一般是指包含CRuntimeClass对象的类的名称
int m_nObjectSize; //包含CRuntimeClass对象的类sizeof的大小,不包括它分配的内存
UINT m_wSchema; // schema number of the loaded class分类编号(对不可分类的类,该值为-1)。
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class 指向一个建立实例的构造函数,创建一个类的对象,抽象类则返回NULL(只有在类支持动态创建时才有效;否则,返回NULL)。
//PASCAL不用管,是个过时了的calling convension。 CObject*(* m_pfnCreateObject)(); 指向函数的指针 如:int (*fn)();
//这是mfc用于动态对象创建的CRuntimeClass结构的一个成员变量
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)(); //是一个指向函数的指针,该函数返回基类的CRuntimeClass结构。
#else
CRuntimeClass* m_pBaseClass; // 如果你的应用程序是静态地链接到MFC的,则是一个指向基类的CRuntimeClass结构的指针。 #endif
//以上m_pBaseClass的指针(函数)是MFC运行时确定类层次的关键,它一个简单的单向链表
// Operations
CObject* CreateObject(); //这个函数给予CObject 派生类运行时动态建立的能力
//从CObject派生的类可以支持动态创建,这是在运行时创建一个指定类的对象的能力。例如,文档,视和框架类就应该支持动态创建。CreateObject成员函数可以用来实现这个功能,在运行时为这些类创建对象。
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; //判断是不是一个基类的子类
/*如果IsDerivedFrom类成员的类是从基类派生而来,该基类的CRuntimeClass结构作为一个参数给出,则返回TRUE。IsDerivedFrom从该成员的类开始向上沿派生类链经过所有的类直到顶端,并且只有在没有与基类匹配的类时才返回FALSE。
//这个函数使用 m_pBaseClass或 m_pfnGetBaseClass遍历整个类层次确定是否pBaseClass指向的类是基类,
//使用它可以判断某类是否是从pBaseClass指向的类在派生来。*/
// Implementation
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
CRuntimeClass* m_pNextClass; // linked list of registered classes
};
也就是说每新建一个类我们都可以加入MFC的继承表中,就是每个类声明中加入一个CRuntimeClass但是这个数据结构中有很多成员变量和函数。而且在实现文件中也要加入一些成员变量和函数的初始化或者成员函数的定义工作,每个类都要这么做会花很多行代码。还好微软给我们定义了几个宏让我们可以很方便的把我们定义的类加入到MFC继承的大绳中去。
类别型录网
1、DECLEAR_DYNAMIC(classname)//这个宏在声明文件中出现
#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name; \ 在类中建立一个CRuntimeClass的结构
virtual CRuntimeClass* GetRuntimeClass() const; 在类中建立一个虚拟函数
##表示把字符串连接起来
2.IMPLEMENT_DYNAMIC//这个宏出现在实现文件中
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
而IMPLEMENT_RUNTIMECLASS宏的定义是
#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 }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
有了这个大网之后我们就可以判断一个类是不是另一个类的派生类.
为了在得到一个类名后可以创建一个该类对象,我们不以直接NEW出一个来,因为MFC不知道这是一个类,而CRUNTIMECLASS中可以加入一个构造函数,这样就可以支持动态创建一个已知类名的类的对象.
而MFC又提供给我们新的宏定义
在声明文件中需要DECLARE_DYNCREATE宏
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL CreateObject();//往类中加入一个函数
在定义文件中需加入另一个宏
#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)
二、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_DYNAM
IC时将得到一个编译错误。 除非你象CObject一样不用DECLARE_DYNAMIC而定义和实现了
这些函数,CObject中的GetBaseClass只是简单的返回NULL。 实际的DECLARE_DYNAMIC在
afx.h中声明如下:
#define DECLARE_DYNAMIC(class_name) \
protected: \
static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
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_n
ame =
{ \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
&class_name::_GetBaseClass, NULL }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
其中的CRuntimeClass* GetRuntimeClass() const;被定义为虚函数,以完成在类层次上
的重载。 这也是MFC利用多态实现运行时动态类信息的方法。
另外两个DECLARE_DYNCREATE和DECLARE_SERIAL类似。只不过它们多定义和实现了一些函
数,对于使用DECLARE_DYNCREATE 要注意的是类必须要有一个无参数的缺省构造函数,
因为在DECLARE_DYNCREATE中定义了一个CreateObject函数 用以在动态的建立对象,它
只是一条简单的return new class_name。
我们先来看一下序列化:
,CObject实现这些功能绝大部分是通过它里面的CRuntimeClass对象classObject实现的
,
CObject不支持多重继承,即表示以CObject为基类的类层次中只能有一个CObject基类。
之所以会这样,就是因为CRuntimeClass对象的成员m_pBaseClass的关系。因为它只是一
个单链表。
以下是它在afx.h中的定义:
/
// class CObject is the root of all compliant objects
#ifdef _AFXDLL
class CObject
#else
class AFX_NOVTABLE CObject
#endif
{
public:
// Object model (types, destruction, allocation)
virtual CRuntimeClass* GetRuntimeClass() const;
// 上面的函数的实现只是很简单的return RUNTIME_CLASS(classObject);
//调用GetRuntimeClass这个函数,返回一个CRuntimeClass指针常量
virtual ~CObject(); // virtual destructors are necessary
// Diagnostic allocations
void* PASCAL operator new(size_t nSize);
void* PASCAL operator new(size_t, void* p);
void PASCAL operator delete(void* p);
#if _MSC_VER >= 1200
void PASCAL operator delete(void* p, void* pPlace);
#endif
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
// for file name/line number tracking using DEBUG_NEW
void* PASCAL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#if _MSC_VER >= 1200
void PASCAL operator delete(void *p, LPCSTR lpszFileName, int nLine);
#endif
#endif
// Disable the copy constructor and assignment by default so you will
get
// compiler errors instead of unexpected behaviour if you pass objects
// by value or assign objects.
protected:
CObject();
private:
CObject(const CObject& objectSrc); // no implementation
void operator=(const CObject& objectSrc); // no implementation
// Attributes
public:
BOOL IsSerializable() const; // 对
对象进行序列化
BOOL IsKindOf(const CRuntimeClass* pClass) const; //判是否是
// Overridables
virtual void Serialize(CArchive& ar);
#if defined(_DEBUG) || defined(_AFXDLL)
// Diagnostic Support
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
// Implementation
public:
static const AFX_DATA CRuntimeClass classCObject;
#ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();
#endif
};
如果你在你的类的实现和定义中使用可选宏的其中一个,你必须了解从CObject派生的好
处。
第一级的宏是DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC,它允许你在运行时处理类名和类层
次中的位置,允许你做有意义的诊断Dump。
第二级的宏是DECLARE_SERIAL/IMPLEMENT_SERIAL,它包括第一级宏所有的功能,允许你
进行对象的序列化。
/,//
DECLARE_DYNAMIC就是.h中声明的。IMPLEMENT_DYNAMIC 放在 cpp实现用。看IMPLEMENT_DYNAMIC的实现 AFX_COMDAT const CRuntimeClass class_name::class##class_name =是创建了一个CRuntimeClass的实例(对象),这个实例里放了当前类的名字m_lpszClassName和基类的指针,大量的宏眼都花了,其实说白了就是一个链表,每个类里加DECLARE_DYNAMIC后就生成一个CRuntimeClass的静态变量,保存了自己的类名和基类的静态CRuntimeClass指针。其它那些函数都是用这两个遍历查找。对应基类的名字,或对应名字的基类指针,在程序全局中也有一个CRuntimeClass链表,里面有所有加了运行类支持的类信息。
##用于将连接
#用于去类型化。
DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏
#define DECLARE_DYNAMIC(class_name) \
public: \
static const CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
#define AFX_COMDAT __declspec(selectany)
#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
值得注意的是:CRuntimeClass结构体的静态成员pFirstClass指针属于类,所以从一开始它就记录了所有生成的类的类别型录,整个程序中仅此一份,于是便可方便的进行IsKindOf操作。如下:
void PrintAllClasses()
{
CRuntimeClass* pClass;
for(pClass = CRuntimeClass::pFirstClass; pClass!=NULL;pClass = pClass->m_pNextClass)
{
cout << pClass->m_lpszClassName << "\n";
cout << pClass->m_nObjectSize << "\n";
cout << pClass->m_wSchema << "\n";
}
}
注意,while循环所追踪的是“同宗”路线,也就是凭借着m_pBaseClass而非m_pNextClass。