关于MFC中RTTI的理解

来源:互联网 发布:助跑跳远技巧知乎 编辑:程序博客网 时间:2024/05/24 07:09

 本文是关于看《深入浅出MFCRTTI的一些理解和想法。由于知识水平的有限,有错误的地方请大家指正。

 
正在看侯捷的《深入浅出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都有两个版本。

 
原创粉丝点击