////////////////////////////////////////////////////////////////////////////////////
/********* 文章系列:MFC技術內幕系列***********/
/************MFC技術內幕系列之(三)***********/
/*文章題目:MFC執行期類型識別與動態創建技術內幕*/
/* Copyright(c)2002 bigwhite */
/* All rights Reserved */
/*******關鍵字:執行期類型識別,動態創建*******/
/* 時間: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消息映射與消息傳遞內幕》