我們知道MFC的作用在于封裝Windows的編程接口,并提供應(yīng)用程序框架的開發(fā)模式。為了完成從前者到后者的過(guò)渡,MFC實(shí)現(xiàn)了幾種基本機(jī)制,它們是消息映射,命令傳遞,運(yùn)行時(shí)類信息(RTCI),動(dòng)態(tài)創(chuàng)建和序列化。
消息映射和命令傳遞是對(duì)SDK程序交互機(jī)制的封裝。序列化是應(yīng)用程序需要的一種基本特性,即把數(shù)據(jù)保存到磁盤和從磁盤打開數(shù)據(jù)。通過(guò)RTCI和動(dòng)態(tài)創(chuàng)建,可以把軟件的對(duì)象數(shù)據(jù)保存到磁盤,反過(guò)來(lái)從這些數(shù)據(jù)識(shí)別和恢復(fù)對(duì)象,從而實(shí)現(xiàn)對(duì)象的序列化。基于數(shù)據(jù)庫(kù)的序列化機(jī)制和這種方式不同,應(yīng)用程序和數(shù)據(jù)庫(kù)之間有一個(gè)約定,以什么樣的格式保存什么樣的數(shù)據(jù),再以同樣的方式打開,并且如何重建對(duì)象數(shù)據(jù)也定下來(lái)了,在打開數(shù)據(jù)時(shí),應(yīng)用程序不需要有適應(yīng)性,不需要識(shí)別數(shù)據(jù)類型,也不需要根據(jù)在運(yùn)行期才確定的類型名稱創(chuàng)建其對(duì)象。
動(dòng)態(tài)創(chuàng)建就是創(chuàng)建某種類型的對(duì)象,具體類型在運(yùn)行時(shí)確定,編譯時(shí)可能不知道。比如運(yùn)行時(shí)用戶輸入一個(gè)類型名稱,如果該類型是程序類型體系中的一員,則程序中將能夠創(chuàng)建該類型的對(duì)象。下面的代碼是使用MFC動(dòng)態(tài)創(chuàng)建機(jī)制的一個(gè)簡(jiǎn)化的例子:
CRuntimeClass* g_pFirstClass;
void func()
{
char szClassName[64];
CRuntimeClass* pClass;
CObject* pObject;
cout << "enter a class name... ";
cin >> szClassName;
for (pClass = g_pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (strcmp(szClassName, pClass->m_lpszClassName) == 0)
pObject = pClass->CreateObject();
}
}
實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建的思路是把動(dòng)態(tài)的類型名稱與程序類型體系中的每一個(gè)進(jìn)行比較,與某個(gè)類型吻合時(shí)讓該類型創(chuàng)建自身的對(duì)象。這樣,支持動(dòng)態(tài)創(chuàng)建的類庫(kù)中的每一個(gè)類都要額外實(shí)現(xiàn)一些功能,即判別一個(gè)名稱是否與自身相符,以及創(chuàng)建自身的對(duì)象。
判別一個(gè)名稱是否與自身相符,這是運(yùn)行時(shí)類識(shí)別的內(nèi)容,所以MFC動(dòng)態(tài)創(chuàng)建是在RTCI基礎(chǔ)上實(shí)現(xiàn)的。
RTCI是一個(gè)對(duì)象能夠判定自己是否屬于某種類型,該類型的名稱在運(yùn)行時(shí)確定,編譯時(shí)可能不知道。從下面的例子很容易理解RTCI,
void Func()
{
char szClassName[64];
CDocument* pDoc = new CDocument;
cout << "enter a class name... ";
cin >> szClassName;
cout << pDoc->IsKindOf(szClassName); //是返回1,否返回0
}
有一點(diǎn)需要說(shuō)明的是,因?yàn)镃Document派生于CObject,所以IsKindOf對(duì)于CObject也要返回1。因?yàn)槲覀兪菑膭?dòng)態(tài)創(chuàng)建出發(fā)的,所以如果是這樣可能會(huì)有一點(diǎn)背離初衷。但是RTCI明顯和動(dòng)態(tài)創(chuàng)建有密切聯(lián)系,RTCI也可能有單獨(dú)的價(jià)值,所以先把RTCI實(shí)現(xiàn)起來(lái)。
實(shí)現(xiàn)RTCI的思路是讓每一個(gè)類記錄自身的類型信息,并提供IsKindOf(char*)函數(shù)進(jìn)行所給類型與自身類型的比較,而且還要能訪問(wèn)基類的類型信息,進(jìn)行比較,一直到根類。所以記錄的類型信息要按繼承關(guān)系連起來(lái),每個(gè)類的IsKindOf()還要調(diào)用基類的IsKindOf()。MFC把要記錄的類型信息抽取到一個(gè)CRuntimeClass結(jié)構(gòu)體中,每個(gè)類中加入一個(gè)CRuntimeClass成員即可。
現(xiàn)在回到動(dòng)態(tài)創(chuàng)建,在RTCI建立的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)上將可實(shí)現(xiàn)它。動(dòng)態(tài)創(chuàng)建從不同于IsKindOf()的角度使用這一數(shù)據(jù)結(jié)構(gòu),它要遍歷所有類型的CRuntimeClass。那么僅僅有繼承關(guān)系的類的CRuntimeClass相連還不夠,要把所有類的CRuntimeClass連成一個(gè)鏈表。其實(shí)動(dòng)態(tài)創(chuàng)建并不關(guān)心類間的繼承關(guān)系,它平等看待每個(gè)類。現(xiàn)在以CRuntimeClass為結(jié)點(diǎn)構(gòu)成一個(gè)縱橫兩個(gè)方向的鏈表,IsKindOf()和動(dòng)態(tài)創(chuàng)建分別使用它不同的側(cè)面。
序列化的概念是在文件中存儲(chǔ)對(duì)象信息,并能根據(jù)它恢復(fù)對(duì)象。對(duì)于文檔視圖結(jié)構(gòu)的軟件,用戶需要保存所編輯的文檔和打開已編輯的文檔,這正是序列化的應(yīng)用,所以序列化是非常重要的一種特性。在序列化恢復(fù)對(duì)象時(shí),就可以用到動(dòng)態(tài)創(chuàng)建。
使用MFC序列化的例子如下,
void CMyDocument::Serialize(CArichive &ar)
{
if (ar.IsStoring())
{
ar << m_pMyClass; //CMyClass m_pMyClass;
}
else
{
ar >> m_pMyClass;
}
}
一個(gè)支持序列化的類提供Serialize(CArchive &)函數(shù),重載<<和>>操作。注意兩者是不同的,在上例中,CMyDocument類的信息并不被序列化,而CMyClass類的信息被序列化。實(shí)際上一個(gè)序列化類的<<和>>操作,其不涉及類信息的部分是調(diào)用Serialize()完成的,它必須同時(shí)實(shí)現(xiàn)這兩者。
按照MFC的要求,需要在支持序列化的類定義中使用DECLARE_SERIAL宏,在類實(shí)現(xiàn)中使用IMPLEMENT_SERIAL宏。我們看一下這兩個(gè)宏實(shí)現(xiàn)了什么,
#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
主要是加入了對(duì)>>的重載,但是沒(méi)有重載<<,MFC僅提供了CObject對(duì)<<的重載,如下,
_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ ar.WriteObject(pOb); return ar; }
這是因?yàn)樵谛蛄谢x和寫的時(shí)候,都需要具體類的CRuntimeClass信息。相應(yīng)的GetRuntimeClass()是一個(gè)虛函數(shù),CObject重載<<,在寫類信息時(shí)調(diào)用到該函數(shù),由于虛函數(shù)機(jī)制,寫入的是具體類的信息。但是這里隱含著一個(gè)條件,就是調(diào)用<<和GetRuntimeClass()時(shí),具體類對(duì)象已經(jīng)存在了,而調(diào)用>>和讀入類信息時(shí),該類對(duì)象還未被創(chuàng)建,所以無(wú)法利用這種機(jī)制,只能在每個(gè)具體類中都重載一次>>。我覺(jué)得《深入解析MFC》對(duì)這個(gè)問(wèn)題的解釋不正確。
這里有一個(gè)問(wèn)題需要明確一下,序列化為什么要寫入類信息?一是它應(yīng)該保存完整的能夠獨(dú)立恢復(fù)對(duì)象的信息,二是在程序讀入對(duì)象時(shí),要把它的類信息和程序中期望的(所能處理的)類信息相比較,進(jìn)行檢驗(yàn)。
看IMPLEMENT_SERIAL宏對(duì)重載>>的實(shí)現(xiàn),是提供一個(gè)期望的CRuntimeClass結(jié)構(gòu)(用于檢驗(yàn)),委托CArchive進(jìn)行對(duì)象讀取。因?yàn)樽x對(duì)象時(shí)首先要跟文件打交道,所以交給CArchive處理,隨后把讀出的數(shù)據(jù)寫入對(duì)象時(shí),CArchive再調(diào)用具體類的Serialize(),如此合作是十分恰當(dāng)?shù)摹T谶@里,CArchive還負(fù)責(zé)了讀出和檢驗(yàn)類信息,然后創(chuàng)建對(duì)象的過(guò)程。因?yàn)橐环矫婢唧w類對(duì)象還不存在,另一方面這些操作對(duì)所有具體類都沒(méi)有分別,應(yīng)該提出來(lái),在類級(jí)別實(shí)現(xiàn)或者讓合作者實(shí)現(xiàn)。實(shí)際上,MFC先把這個(gè)過(guò)程交給CArchive::ReadClass(),后者又調(diào)用CRuntimeClass::Load()。
對(duì)于序列化來(lái)說(shuō),搞清它的概念以后,就是實(shí)現(xiàn)Serialzie(),重載<<和>>。對(duì)<<和>>的重載涉及很多工作,MFC已經(jīng)幫我們實(shí)現(xiàn)了,我們也看見(jiàn)了大概的設(shè)計(jì),主要是與CArchive分工合作,其次是CRuntimeClass。
現(xiàn)在看到CRuntimeClass結(jié)構(gòu)體在MFC對(duì)RTCI,動(dòng)態(tài)創(chuàng)建和序列化的實(shí)現(xiàn)中都起著重要的作用,重新認(rèn)識(shí)一下這個(gè)數(shù)據(jù)結(jié)構(gòu)很有必要。
CRuntimeClass包含了關(guān)于類的各種信息和有關(guān)操作。把類及其基類的CRuntimeClass連成一個(gè)鏈表,就可以很方便地實(shí)現(xiàn)RTCI的IsKindOf();把所有類的CRuntimeClass連成一個(gè)鏈表,再加上一個(gè)簡(jiǎn)單的CreateObject函數(shù),就可以對(duì)以任意類名進(jìn)行動(dòng)態(tài)創(chuàng)建的企圖做出反應(yīng);CRuntimeClass還實(shí)現(xiàn)了向文件讀寫類信息的Load(),Store(),配合序列化的實(shí)現(xiàn)。
在分析消息映射和命令傳遞機(jī)制之前,需要對(duì)Windows程序模型有很好的理解。
未完待續(xù)...
參考:
《深入解析MFC》/中國(guó)電力出版社
《深入淺出MFC》/華中科大出版社
《Windows程序設(shè)計(jì)》/北大出版社