MFC六大關鍵技術包括:
- MFC Initialization —— MFC程序的初始化過程
- RTTI(Runtime Type Information)—— 運行時類型識別
- Dynamic Creation —— 動態創建
- Persistence ——永久保存(串行化、序列化)
- Message Mapping —— 消息映射
- Message Routing —— 消息傳遞
MFC程序的初始化過程
首先,我們用VS2010建立一個Win32應用程序,在項目的配置屬性中鏈接MFC庫,並輸入以下代碼:
#include <afxwin.h> class MyApp : public CWinApp { public: BOOL InitInstance() //②程序入點 { CFrameWnd *Frame=new CFrameWnd();//構造框架 m_pMainWnd=Frame; //將m_pMainWnd 設定為Frame; Frame->Create(NULL,_T("最簡單的窗口"));//建立框架 Frame->ShowWindow(SW_SHOW); //顯示框架 return true; //返回 } }; MyApp theApp; //①建立應用程序。
運行結果:
然后再更換為以下代碼:
#include <afxwin.h> class MyApp : public CWinApp { public: BOOL InitInstance() //②程序入點 { AfxMessageBox(_T("程序依然可以運行!")); return true; //返回 } }; MyApp theApp; //①建立應用程序。
程序運行結果為:
我們知道,C++控制台程序的入口點函數為main()函數,而Windows應用程序的入口點函數為WinMain()。然而,上述程序並沒有main()或WinMain()函數,也能運行。實際上,在main()或WinMain()函數執行之前,全局對象會先運行。在上述程序中我們定義了全局對象theApp,程序會首先執行theApp。只要我們構造了CWinApp 對象,就可以執行WinMain()函數。
整個過程大體為:theApp對象初始化-->調用類名構造函數-->調用AfxWinMain函數-->調用CWinApp類的成員函數完成各種初始化(包括InitApplication 、InitInstance 、Run(包含消息循環))-->Winmain函數執行。
例如,我們再建立一個Win32控制台程序,代碼如下:
#include<iostream> using namespace std; class test { public: test() { cout<<"請改變你對main()函數的看法!"<<endl; } }; test test1; int main() { system("pause"); return 0; }
運行結果:
程序首先執行了全局對象test1的構造函數。
在MFC中,InitApplication()和InitInstance()為CWinApp的兩個虛函數,前者負責”每個程序只做一次“的操作,后者負責”每個例程都得做一次“的操作。在Windows應用程序中,如果我們想改變窗口的屬性,只需改寫初始化函數InitInstance()即可。
運行時類型識別
這方面請閱讀我的另一篇博客:RTTI(運行時類型識別):http://www.cnblogs.com/gaohongchen01/p/4085908.html
動態創建
MFC中很多地方都使用了動態創建技術,動態創建就是在程序運行時創建指定類的對象。例如MFC的單文檔程序中,文檔模板類的對象就動態創建了框架窗口對象、文檔對象和視圖對象。動態創建技術對於希望了解MFC底層運行機制的朋友來說,非常有必要弄清楚。
要做到把自己的類交給MFC,MFC用同一方法把不同的類一一准確創建,我們就要用到鏈表,記錄各類的關鍵信息,在動態創建的時候找出這些信息。
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); 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; };
簡單地說m_pfnCreateObject保存了一個函數的地址,將會創建一個對象,m_pfnCreateObject指向不同的函數,我們就會創建不同類型的對象。CreateObject()即為m_pfnCreateObject指向的函數。這樣,我們用函數指針m_pfnCreateObject,就隨時可new新對象了。
在設計CRuntimeClass類時,只有類名(和基類名)的不同,這正是我們想要的,因為動態創建也象RTTI那樣用到兩個宏,只要傳入類名和基類作宏參數,就可以滿足條件。類聲明中使用DECLARE_DYNCREATE(CLASSNMAE)宏和在類的實現文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏來為我們加入鏈表。
m_pBaseClass指針只會沿着基類上去,會漏掉其它分支。在動態創建時,必需檢查整個鏈表,看有多少個要動態創建的對象,即是說要從表頭(pFirstClass)開始一直遍歷到表尾(m_pNextClass=NULL),不能漏掉一個CRuntimeClass對象。所以每當有一個新的鏈表元素要加入鏈表時,就要使新的鏈表元素成為表頭,且m_pNextClass指向原來鏈表的表頭,即像下面那樣(當然,這些不需要我們操心,是RTTI宏幫助我們完成的):
pNewClass->m_pNextClass=CRuntimeClass::pFirstClass;//新元素的m_pNextClass指針指向想加入的鏈表的表頭。 CRuntimeClass::pFirstClass=pNewClass;//鏈表的頭指針指向剛插入的新元素。
有了上面的鏈表,我們就可以分析動態創建了。
動態創建的步驟:
有了一個包含類名,函數指針,動態創建函數的鏈表,我們就可以知道應該按什么步驟去動態創建了:
- 獲得一要動態創建的類的類名(假設為A)
- 將A跟鏈表里面每個元素的m_lpszClassName指向的類名作比較
- 若找到跟A相同的類名就返回A所屬的CRuntimeClass元素的指針
- 判斷m_pfnCreateObject是否有指向創建函數,有則創建對象,並返回該對象
代碼演示如下(以下兩個函數都是CRuntimeClass類函數):
///////////////以下為根據類名從表頭向表尾查找所屬的CRuntimeClass對象//////////// CRuntimeClass* PASCAL CRuntimeClass::Load() { char szClassXXX[64]; CRuntimeClass* pClass; cin>>szClassXXX; //假定這是我們希望動態創建的類名 for(pClass=pFirstClass;pClass!=NULL;pClass=pClass->m_pNextClass) { if(strcmp(szClassXXX,pClass->m_lpszClassName)==0) return pClass; } return NULL; } ///////////根據CRuntimeClass創建對象/////////// CObject* CRuntimeClass::CreateObject() { if(m_pfnCreateObject==NULL) return NULL; CObject *pObject; pObject=(* m_pfnCreateObject)(); //函數指針調用 return pObject; }
有了上面兩個函數,我們在程序執行的時候調用,就可以動態創建對象了。
簡單實現動態創建:
我們還可以更簡單地實現動態創建,大家注意到,就是在我們的程序類里面有一個RUNTIME_CLASS(class_name)宏,作用就是得到類的RunTime信息,即返回class_name所屬CRuntimeClass的對象。這個宏在MFC里定義為:
RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
在我們的應用程序類(CMyWinApp)的InitInstance()函數下面的CSingleDocTemplate函數中,有:
RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMainFrame), // main SDI frame window RUNTIME_CLASS(CMyView)
構造文檔模板的時候就用這個宏得到文檔、框架和視的RunTime信息。有了RunTime信息,我們只要一條語句就可以動態創建了,如:
classMyView->CreateObject(); //對象直接調用用CRuntimeClass本身的CreateObject()
總結:
最后再總結和明確下動態創建的具體步驟:
- 定義一個不帶參數的構造函數(默認構造函數);因為我們是用CreateObject()動態創建,它只有一條語句就是return new XXX,不帶任何參數。所以我們要有一個無參構造函數。
- 類說明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;和在類的實現文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏;這個宏完成構造CRuntimeClass對象,並加入到鏈表中。
- 使用時先通過宏RUNTIME_CLASS得到類的RunTime信息,然后使用CRuntimeClass的成員函數CreateObject創建一個該類的實例。
- CObject* pObject = pRuntimeClass->CreateObject();//完成動態創建。
文檔永久保存(串行化、序列化)
我們可以利用CArchive類將對象數據保存到永久設備上,這樣,即使應用程序關閉,我們也可以將從磁盤文件中讀取對象數據,然后在內存中重新構建相應的對象,這種讓對象數據持久性的過程,即MFC的連續存儲機制稱之為序列化(Serialize)。
MFC文檔的序列化過程包括:創建空文檔、打開文檔、保存文檔和關閉文檔四個操作。
從單文檔的序列化過程可以看出:打開和保存文檔時,系統都會調用Serialize函數。事實上,MFC AppWizard在創建文檔應用程序框架時已在文檔類中重載了Serialize函數,通過在該函數中添加代碼可達到實現數據序列化的目的。
消息映射、消息傳遞