MFC技术内幕系列之(三)MFC执行期类型识别与动态..

/********* 文章系列:MFC技术内幕系列***********/
                     /************MFC技术内幕系列之(三)***********/
                     /*文章题目:MFC执行期类型识别与动态创建技术内幕*/
                     /*                                        /*******关键字:执行期类型识别,动态创建*******/
                     /*                          时间:2002.7.23                            */
                     /*    注释:本文所涉及的程序源代码均在Microsoft    */
                     /          Visual Studio.Net Enterprise Architect Edition      /
                     /*                  开发工具包提供的源代码中                  */
                    

////////////////////////////////////////////////////////////////////////////////////////////////
引言:
    众所周知,微软的MFC Application Framework建立在一系列先进的程序设计技术上的。比如:消息映射机制,命令传递机制,执行期类型识别与动态创建技术及文档序列化技术等。其中执行期类型识别与动态创建技术是其中最重要的技术之一。微软在MFC中用一些神秘的宏实现了这种机制,但是对于学习MFC程序设计的初学者来说它却成为了一大难点,所以在这篇文章中我将详细地为大家挖掘其中的内幕。

正文:
    MFC执行期类型识别与动态创建技术是借助CRuntimeClass结构和一系列神秘的宏实现的。而动态创建技术 的前提是执行期类型识别网的建立。下面就让我们来看看执行期类型识别网是如何建立起来的?
                       ///////////////////////////////////////////////////
                       /*一. 1.CRuntimeClass结构总览 */
                       ///////////////////////////////////////////////////
   注释:CRuntimeClass结构定义在..\Visual Studio.NET\vc7\atlmfc\include\Afx.h中
// object type information  
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
// Operations
 CObject* CreateObject();
 BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

 // dynamic name lookup and creation
 static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);//for ANSI
 static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);//for Unicode
 static CObject* PASCAL CreateObject(LPCSTR lpszClassName);  for ANSI
 static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);//for Unicode
// 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;
};

                       /////////////////////////////////////////////////////////
                       /* 一.2.执行期类型识别网组成部分 */
                       //////////////////////////////////////////////////////////
  在CRuntimeClass结构中与执行期类型识别网建立有关的成员如下:
  struct CRuntimeClass
  { 
      ...//
      LPCSTR m_lpszClassName;
      CRuntimeClass* m_pBaseClass;
            
   }


                       //////////////////////////////////////////////////////////
                       /*一.3.执行期类型识别网的连接建立*/
                       //////////////////////////////////////////////////////////
   MFC在每一个具有执行期类型识别能力的类的.h和.cpp文件中都添加了两个宏。他们是:
//in xx.h
  class class_name
  {
      DECLARE_DYNAMIC(class_name)
      ...//
  }
//in xx.cpp
  IMPLEMENT_DYNAMIC(class_name, base_class_name)
  ...//
  这两个宏展开后是什么样子呢?让我们看看其源代码吧!
  注释:这两个宏定义在..\Visual Studio.NET\vc7\atlmfc\include\Afx.h中
 
  #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 RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
  #define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
 
  看清了吧!这两个宏互相配合着在每个类中都塞进了点东东,至于什么东东,自己看呗儿!
 
  下面我以一个具体的类来展示以下执行期类型识别网是如何连接建立的。
  以一个MDI应用程序的CMainFrame类为例:
  //in MaimFrm.h
  class CMainFrame : public CMDIFrameWnd
 {
 DECLARE_DYNAMIC(CMainFrame)
         ...//
 }

  //in MaimFrm.cpp
  IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
  ...//

  展开后得:
  //in MaimFrm.h
  class CMainFrame : public CMDIFrameWnd
 {
  public:
 static const CRuntimeClass classCMainFrame;
 virtual CRuntimeClass* GetRuntimeClass() const;
    ...//
 }

  //in MaimFrm.cpp
 
  AFX_COMDAT const CRuntimeClass CMainFrame::classCMainFrame ={CMainFrame,sizeof(classCMainFrame  ),0xFFFF,NULL,(CRuntimeClass*)(&CMDIFrameWnd::classCMDIFrameWnd),NULL,NULL};

  CRuntimeClass* CMainFrame::GetRuntimeClass() const
  { return &CMainFrame::classCMainFrame; }
  有上面的代码可以看到IMPLEMENT_DYNAMIC宏初始化和实现了DECLARE_DYNAMIC宏为CMainFrame添加的CRuntimeClass classCMainFrame和CRuntimeClass* GetRuntimeClass() const静态函数。
其中最重要的一点是:   CMainFrame类的classCMainFrame对象的CRuntimeClass* m_pBaseClass被“间接的”赋值为&CMDIFrameWnd::classCMDIFrameWnd,也就是它基类的静态CRuntimeClass classCMDIFrameWnd成员。
以此类推一:
   CMDIFrameWnd::classCMDIFrameWnd的成员CRuntimeClass* m_pBaseClass被“间接的”赋值为
其基类&CFrameWnd::classCFrameWnd;
   ....
   ....
   CCmdTarget::classCCmdTarget的成员CRuntimeClass* m_pBaseClass被“间接的”赋值为&CObject::classCObject;
   CObject没有基类,那么CObject::classCObject的成员CRuntimeClass* m_pBaseClass被赋予什么值呢?看看what is the special runtime-class structure for CObject (no base class) in MFC Framework?

  注释:CMainFrame类的基类顺序以此是CMDIFrameWnd--〉CFrameWnd-->CWnd-->CCmdTarget-->CObject

//in Afx.h
// class CObject is the root of all compliant objects
 class AFX_NOVTABLE CObject
 {
   public:
   virtual CRuntimeClass* GetRuntimeClass() const;
   static const CRuntimeClass classCObject;
   ...//
 }
 
//in objcore.cpp
 // special runtime-class structure for CObject (no base class)
 const struct CRuntimeClass CObject::classCObject =
 { "CObject", sizeof(CObject), 0xffff, NULL, NULL, NULL };

 CRuntimeClass* CObject::GetRuntimeClass() const
 {
 return _RUNTIME_CLASS(CObject);
 }
 
                       ///////////////////////////////////////////////////////
                       /* 一.4.执行期类型识别网的应用 */
                       //////////////////////////////////////////////////////
    执行期类型识别网建立好了,它将在MFC Framework中发挥重要作用,这里举一个其应用的例子:
    CObject::IsKindOf函数就是利用该网完成的函数。下面看看其源代码:
  //in objcore.cpp
  BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
  {
 ASSERT(this != NULL);
 // it better be in valid memory, at least for CObject size
 ASSERT(AfxIsValidAddress(this, sizeof(CObject)));
        // simple SI case
 CRuntimeClass* pClassThis = GetRuntimeClass();
 return pClassThis->IsDerivedFrom(pClass);
  }
  BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const
 {
 ASSERT(this != NULL);
 ASSERT(AfxIsValidAddress(this, sizeof(CRuntimeClass), FALSE));
 ASSERT(pBaseClass != NULL);
 ASSERT(AfxIsValidAddress(pBaseClass, sizeof(CRuntimeClass), FALSE));

 // simple SI case
 const CRuntimeClass* pClassThis = this;
   #ifdef _AFXDLL
 for (;;)
   #else
 while (pClassThis != NULL)
   #endif
 {
  if (pClassThis == pBaseClass)
   return TRUE;
   #ifdef _AFXDLL
  if (pClassThis->m_pfnGetBaseClass == NULL)
   break;
  pClassThis = (*pClassThis->m_pfnGetBaseClass)();
   #else
  pClassThis = pClassThis->m_pBaseClass;
   #endif
 }
 return FALSE;       // walked to the top, no match
}

                       //////////////////////////////////////////////////
                       /*  二.1. 执行期动态创建技术   */
                       //////////////////////////////////////////////////
   谈完了执行期类型识别网建立后,就来看看另一项重要的技术-----执行期动态创建技术 ,该技术应用可谓是甚广,在你的SDI中的主框架,视图和其对应的文档以及MDI中的子框架,视图和其对应的文档都是利用动态创建技术生成的。
   执行期动态创建技术利用的也是CRuntimeClass结构和一对对应的宏DECLARE_DYNCREATE(class_name)
和IMPLEMENT_DYNCREATE(class_name, base_class_name);
   下面看看这两个宏的定义吧:

   #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, NULL)
   应该看出来执行期类型识别网是执行期动态创建技术的基础。
                    
                       ///////////////////////////////////////////////////////////
                       /* 二.2 执行期动态创建技术组成部分*/
                       ///////////////////////////////////////////////////////////
                      
   下面将列出与执行期动态创建技术有关的CRuntimeClass的结构成员:
    // object type information  
   struct CRuntimeClass
   {
       CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
       CObject* CreateObject();
       ...//
   };
  
                       ////////////////////////////////////
                       /*      二.3.宏展开       */
                       ////////////////////////////////////                 
   这回我们以SDI程序中的CMainFrame的动态创建为例,下面看看上面的两个宏在xx.h和xx.cpp文件中展开后的样子:
   //in MainFrm.h
   class CMainFrame
   { 
      public:
        static const CRuntimeClass classCMainFrame;
 virtual CRuntimeClass* GetRuntimeClass() const;
        static CObject* PASCAL CreateObject();
        ...//
   };
   //in MainFrm.cpp
    CObject* PASCAL CMainFrame::CreateObject() { return new class_name; }
    AFX_COMDAT const CRuntimeClass CMainFrame::classCMainFrame    ={
CMainFrame,sizeof(classCMainFrame),0xFFFF,CMainFrame::CreateObject,(CRuntimeClass*)(&CMDIFrameWnd::classCMDIFrameWnd),NULL,NULL};

    CRuntimeClass* CMainFrame::GetRuntimeClass() const
  { return &CMainFrame::classCMainFrame; }
    看出来着两个宏展开后与前面介绍的那两个宏的不同了吧;DECLARE_DYNCREATE在头文件中加入了一个函数static CObject* PASCAL CreateObject();IMPLEMENT_DYNCREATE的展开也有所不同。下面将详述。
 
 
                         ////////////////////////////////////////////
                         /* 二.4.如何进行动态创建  */
                         ///////////////////////////////////////////
    让我们看看CMainFrame的构造函数的access control吧!protected:疑惑了吧?受保护的构造函数是不能够得实例化的。那么CMainFrame是如何被构造出来的呢?这就是微软利用CRuntimeClass进行动态创建的原因。下面我用一个简单的例子来诠释一下构造方法:
   //in A.h
   class A
   {
      protected:A(){}
                ...//something else
      public:static A*CreateObject();
   };
   //in A.cpp
   A*A::CreateObject()
   {  return new A();}
   //in test.cpp
   int main(void)
   {
      A*pNewObj=A::CreateObject();//OK
      return 0;
   }
   诠释完了后就让我们一一对应的看看微软是如何做的吧,我们回头再看看DECLARE_DYNCREATE为CMainFrame
添加了什么吧。static CObject* PASCAL CreateObject(); 眼睛亮了吧!是的它就相当于上个例子中的 
static A*CreateObject();那么是谁调用了它呢?再回头看看IMPLEMENT_DYNCREATE作了什么吧?

AFX_COMDAT const CRuntimeClass CMainFrame::classCMainFrame    ={
CMainFrame,sizeof(classCMainFrame),0xFFFF,CMainFrame::CreateObject,(CRuntimeClass*)(&CFrameWnd::classFrameWnd),NULL,NULL};
   原来该宏把m_pfnCreateObject赋值为CMainFrame::CreateObject;
   说到这你可能还是不很清楚,那么让我们看看MFC的代码吧!
    CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CMyDoc),
  RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口
  RUNTIME_CLASS(CMyView));
    我们在MFC技术内幕系列之(二)----《MFC文档视图结构内幕 》中曾经说过,所以这里不详述,
    CSingleDocTemplate::OpenDocumentFile调用了CreateNewFrame
    CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)//部分源代码
    {
 ...//
       CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
        ...//
    }
    m_pFrameClass是CRuntimeClass*指针它指向CMainFrame::classCMainFrame;CreateNewFrame函数调用了
(CFrameWnd*)m_pFrameClass->CreateObject();而CObject* CRuntimeClass::CreateObject()代码如下:
                  
  //in objcore.cpp
  CObject* CRuntimeClass::CreateObject()
 {
 if (m_pfnCreateObject == NULL)
 {
  TRACE(traceAppMsg, 0,
   _T("Error: Trying to create object which is not ")
   _T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"),
   m_lpszClassName);
  return NULL;
 }

 CObject* pObject = NULL;
 TRY
 {
  pObject = (*m_pfnCreateObject)();
 }
 END_TRY

 return pObject;
 }
  它调用了 (*m_pfnCreateObject)();即 CMainFrame::CreateObject();并返回了动态创建的 CMainFrame对象的指针。看到着你应该明白了吧。    
                       /////////////////////////////////////////
                       /*        三.下期预告         */
                       /////////////////////////////////////////
   MFC技术内幕系列之(四)-----《MFC消息映射与消息传递内幕》
 

作者: yearnquiet   发布时间: 2010-12-05