本文是关于看《深入浅出MFC》RTTI的一些理解和想法。由于知识水平的有限,有错误的地方请大家指正。
正在看侯捷的《深入浅出MFC》在第三章中关于RTTI类型表的构建中,侯捷所模拟的代码是所有存在于一个exe文件中的!其实在mfc头文件中关于CRuntimeClass类的声明如下:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
CRuntimeClass* m_pBaseClass;
#endif
// Operationsa
CObject* CreateObject();
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
// dynamic name lookup and creation
static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);
static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);
static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);
// 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
const AFX_CLASSINIT* m_pClassInit;
};
--------------------------------------------------------
以上是类CRuntimeClass的完整声明,这里有个特别的地方,下面几行代码:
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
CRuntimeClass* m_pBaseClass;
#endif
当声明宏_AFXDLL与没有的时候,CRuntime的类结构是不同的。当是用在DLL中时,类的表项是一个函数指针这个函数指针指向的函数的 功能是获取父类的CRuntime的地址。而不是DLL时,它只是一个指向父类CRuntime的指针。而这个指针的初始化代码如下:
-----代码--------------------------------------------
#ifdef _AFXDLL
#define DECLARE_DYNAMIC(class_name)
protected:
static CRuntimeClass* PASCAL _GetBaseClass();
public:
static const CRuntimeClass class##class_name;
static CRuntimeClass* PASCAL GetThisClass();
virtual CRuntimeClass* GetRuntimeClass() const;
#else
#define DECLARE_DYNAMIC(class_name)
public:
static const CRuntimeClass class##class_name;
virtual CRuntimeClass* GetRuntimeClass() const;
#endif
---------------------------------------------------------------
以上是在类中声明动态的代码。不同点:在dll版本的类是多了两个函数
static CRuntimeClass* PASCAL _GetBaseClass();
static CRuntimeClass* PASCAL GetThisClass();
在非dll版本除了少了两个函数外,其余都是一样的。
@问题提出:这样说明了什么问题?
$问题的回答:RTTI链表的构建节点类有两类:DLL版本和非DLL版本(exe版本)。在类中添加的CRuntimeClass信息时,DLL版本多添加了两个上面的函数。
那么这两个函数有什么作用的呢?看关于RTTI表项的初始化代码,如下:
-------代码----------------------------------------------------
#ifdef _AFXDLL
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init)
CRuntimeClass* PASCAL class_name::_GetBaseClass()
{ return RUNTIME_CLASS(base_class_name); }
AFX_COMDAT const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), wSchema, pfnNew,
&class_name::_GetBaseClass, NULL, class_init };
CRuntimeClass* PASCAL class_name::GetThisClass()
{ return _RUNTIME_CLASS(class_name); }
CRuntimeClass* class_name::GetRuntimeClass() const
{ return _RUNTIME_CLASS(class_name); }
#else
#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); }
#endif
-----------------------------------------------------------------
从代码可以看出,两个版本的代码都由两部分组成:1.CRuntimeClass的初始化代码。2.函数的实现代码;
首先,分析第一部分的两个版本的不同点。
DLL版本:
AFX_COMDAT const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), wSchema, pfnNew,
&class_name::_GetBaseClass, NULL, class_init };
非DLL版本:
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 };
从前面可以知道,DLL版本和非DLL版本的CRuntimeClass的不同点,所以DLL版本的代码&class_name:: _GetBaseClass是初始化函数指针m_pfnGetBaseClass;而非DLL版本的代码RUNTIME_CLASS (base_class_name)是初始化指针m_pBaseClass;
在这里要看看宏RUNTIME_CLASS的代码了。代码如下:
-------代码--------------------------------------
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
#ifdef _AFXDLL
#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())
#else
#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
#endif
---------------------------------------------------
DLL版本是通过调用GetThisClass()函数间接(!是间接)返回该类的CRuntimeClass的指针。
非DLL版本的是直接使用宏_RUNTIME_CLASS直接(!是直接)获取到该类的CRuntimeClass的指针。
宏_RUNTIME_CLASS的代码(CRuntimeClass*)(&class_name::class##class_name)使用取地址直接获取静态变量classclass_name的地址。
@问题的提出:同样是获取到CRuntimeClass的指针,为什么要分别用两种方式获取呢?
$问题的答案:要回答这个问题就需要知道DLL和非DLL(这里是说exe)的不同之处了。
DLL是地址可重定位的模块,即是在运行之前其地址是不确定的,在此时使用&class_name::class##class_name是获取不到它的真实地址的。
此时class##class_name还是一个相对地址。
而非DLL则可以直接获取到它的地址。
@一些理解:其实两个版本都是可以通过调用class_name::GetThisClass()函数来获取CRuntimeClass的地址的,这样的话代码也是比较容易理解的,
我认为是由于效率的问题,把代码设计成现在的样子,这样在非DLL本版的CRuntimeClass的时间效率和空间效率比DLL版本的高,DLL版本因为
地址重定位的原因,只能通过函数的形式来获取。
@总结
由于DLL重定位的原因,使得类CRuntimeClass,宏DECLARE_DYNAMIC和宏IMPLEMENT_RUNTIMECLASS都有两个版本。