MFC框架


第一點:類別型錄網的搭建:

類別型錄網搭建的目的是為了實現所謂的"執行期類型識別",也就是在程序運行的時候識別出某個對象是否是某個類的實例(基類也可以)。這里還不是很明白為什么需要實現"執行期類型識別",這種技巧具體被應用在哪里。

例如在MFC中CView繼承於CWnd,那么可以進行這樣的判斷:

CView view;

bool result = view.IsKindOf(CWnd); // result == true

如上,通過調用IsKindOf函數,可以判斷出view對象是一個CWnd類的實例。

   

MFC通過建立一張型錄網來實現這樣的功能。也就是把類登記到一個表中,傳入特定的參數之后在這張表上進行查找比對,從而實現"執行期類型識別"。

具體說來比對過程如下:

為了在不同的對象和類之間互相比對,肯定類里得有一個特殊的標識才行,MFC通過為每個類添加一個CRuntimeClass類型的靜態成員來作為這個標識。

注意這里是靜態成員,其原因不言自明,如果是普通成員的話,不同的對象之間成員都不一樣,無法實現比對。

   

每個類都有了特殊標識之后,僅僅能進行一對一的比對,也就是說只能進行CView.IsKindOf(CView)這樣的操作,無法判斷一個CView對象是否也是一個CWnd對象。

MFC實現這種功能的方法類似於鏈表的實現:鏈表中有一個指針專門指向它下一個成員的位置,遍歷時依靠這個指針來不斷指向下一個。

CRuntimeClass類包含一個指針叫m_pBaseClass,對於CView,它有一個CRuntimeClass成員(就是之前說的特殊標識),只要使得這個成員的m_pBaseClass指針指向CWnd的CRuntimeClass成員,那么就建立起了類似鏈表的結構。

當判斷CView.IsKindOf(CWnd)時,首先判斷CView的CRuntimeClass成員和CWnd的CRuntimeClass成員是不是一致,發現不一致之后,在CView的CRuntimeClass成員中根據m_pBaseClass來得到CView的父類CWnd的CRuntimeClass成員,之后再進行比對,發現是一致的,因此可以判斷CView.IsKindOf(CWnd)為真。

   

下面介紹MFC中對上述機制的具體實現方法:

1.為每個類添加特定標識CRuntimeClass成員:

使用DECLARE_DYNAMIC宏:

class CView : public CWnd

        DECLARE_DYNAMIC(CView)

如上,使用了DECLARE_DYNAMIC宏之后,CView類中多了一個CRuntimeClass類型的靜態成員 classCView(名為classXXXX,也就是在類名之前加一個class),也就是之前所說的具有比較功能的"特殊標識"

2.建立類別型錄表:

也就是初始化classCView,使它的m_pBaseClass指針指向父類

IMPLEMENT_DYNCREATE(CView, CWnd)

如上,靜態成員的初始化需要在實現文件中進行,在實現文件中使用了IMPLEMENT_DYNAMIC宏之后,classCView的m_pBaseClass指針指向了CWnd的classCWnd成員

3.實現類型識別IsKindOf:

this->IsKindOf(RUNTIME_CLASS(CWnd))

如上,IsKindOf函數的參數有點特別,是一個RUNTIME_CLASS宏,這個宏的功能其實非常簡單,其實就是一個函數調用:RUNTIME_CLASS(CWnd)等價於CWnd::GetThisClass(),這個函數的返回值就是CWnd的CRuntimeClass成員,也就是CWnd的"特殊標識",把這個特殊標識傳遞給IsKindOf函數之后,事情就好辦許多,逐個提取CView及其父類的CRuntimeClass成員與這個標識進行比對就可以達到判斷的目的了。因為是靜態變量,所以只存有一份拷貝,可以直接把指針作為比較時的參照。

   

   

第二點:消息映射表的搭建:

搭建消息映射表的目的是為了找到一個消息對應的消息處理函數。對於一個CMyView窗口來說,它的某個消息處理函數有可能並不是存在於CMyView類中,而是存在於它的父類甚至是別的類里面(例如數據操作應該放在CDoc類里面處理比較合適),MFC為了找到正確的消息處理函數,遂給每個類都建立一個表來存儲這個類所擁有的消息處理函數,並通過指針連接起來,這樣就可以通過遍歷查找來找到正確的那個消息處理函數。

之前類別型錄網的搭建是以CRuntimeClass作為一個類的特別標識,而這里需要標識的則是消息和它對應的消息處理函數。也就是說,每個類里都存儲一張表,表里包括了這個類可以處理的消息和對應的處理函數,這樣對於每個類,得到一條消息之后,將這條消息和表里的條目進行比對,如果比對成功,調用對應的處理函數就可以了。

除此之外,還需要一個指針來指向這個類的父類

   

下面介紹MFC對上面機制的具體實現方法:

1.為每個類添加消息條目和指針成員:

AFX_MSGMAP_ENTRY[] 數組用來存儲消息和對應的消息響應函數指針

AFX_MSGMAP 結構,其中包含一個AFX_MSGMAP類型的指針指向基類,以及一個AFX_MSGMAP_ENTRY指針,指向之前的數組。

這樣一個類就需要兩個靜態成員就行了,一個是AFX_MSGMAP類型,一個是AFX_MSGMAP_ENTRY數組。

MFC中使用DECLARE_MESSAGE_MAP宏來實現為一個類添加這兩個成員的功能。

2.建立消息映射表:

也就是為AFX_MSGMAP_ENTRY[]數組添加成員,並且把指針指向基類的AFX_MSGMAP靜態成員:

BEGIN_MESSAGE_MAP(theClass, baseClass)

ON_COMMAND(MSGID,msgpfn)

END_MESSAGE_MAP()

另外,MFC還為每個類添加了一個虛函數GetMessageMap用於得到這個類的AFX_MSGMAP靜態成員指針,由此指針即可進行遍歷。

   

   

第三點:命令繞行

命令繞行的目的是找到一個消息正確的消息處理函數。

對於WM_LBUTTONDOWN這樣的消息來說,消息處理函數都在本窗口類(或者父類)里面定義,使用GetMessageMap得到消息映射表指針之后遍歷映射表就能找到對應的消息處理函數。

但對於WM_COMMAND消息來說,消息處理函數不一定是在本類里面,CFrameWnd窗口接收到的WM_COMMAND消息,其消息處理函數有可能在CView里面。之所以會這樣應該是與MFC Frame\View\Doc框架有關,具體原因以后進一步來理解,這里主要講解一下繞行的實現機制。

繞行的實現機制其實非常簡單,就是一個if語句的判斷,例如對於CFrameWnd,先看看CView里有沒有這個消息的處理函數,如果沒有,再遍歷自己的映射表看看有沒有,如果還是沒有,就看看CWinApp里有沒有,再沒有的話,就交給默認函數處理。

下面先給出消息繞行時的路徑:

MFC消息必然是屬於某個窗口的(MSG結構里還有個HWND字段呢),也就是說在MFC框架中,窗口的產生者只能是CWnd的派生類(CView和CFrameWnd等)。

而這些窗口所使用的窗口過程函數其實都是同一個全局函數AfxWndProc,也就是說消息產生之后都會被放到AfxWndProc中進行處理。

省去中間的調用步驟,AfxWndProc在接收到不同窗口的消息之后會調用CWnd->WindowProc()函數:

1.如果是WM_XXXX函數,直接使用GetMessageMap得到消息映射表指針,遍歷查找消息處理函數。

2.如果是WM_COMMAND函數,則調用CWnd::OnCommand(),對於不同的窗口對象,由於多態的原因,調用的也不會是同一個OnCommand()函數,例如有CFrameWnd::OnCommand()等等。這個函數其實算不上重點,真正起作用的是CWnd::OnCmdMsg()函數。

OnCmdMsg()函數是CCmdTarget類里的函數,其中的關鍵代碼就是遍歷消息映射表找到消息處理函數。在CCmdTarget的子類中有幾個類重寫了這個函數,這幾個類分別是CFrameWnd,CView,CDoc。

假如現在是CFrameWnd窗口接收到了WM_COMMAND消息,如下是CFrameWnd::OnCmdMsg()的主要代碼:

 

BOOL  CFrameWnd::OnCmdMsg( UINT  nID, int  nCode, void * pExtra,
     AFX_CMDHANDLERINFO* pHandlerInfo)
{
     CPushRoutingFrame push( this );
 
     // 調用CView的OnCmdMsg函數
     CView* pView = GetActiveView();
     if  (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
         return  TRUE;
 
     // 實際上是調用CCmdTarget的OnCmdMsg函數,也就是遍歷自身消息映射表
     if  (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
         return  TRUE;
 
     // 調用CWinApp的OnCmdMsg函數,實際上也是遍歷了CWinApp自身的消息映射表
     CWinApp* pApp = AfxGetApp();
     if  (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
         return  TRUE;
 
     return  FALSE;
}

 

如上,在CFrameWnd的OnCmdMsg函數里,分別調用了CView,CWnd(並沒有重載,所以實際上調用的是CCmdTarget的OnCmdMsg函數),CMyApp類的OnCmdMsg函數。也就是分別在CView、CWnd(其實就是CCmdTarget)、CWinApp里尋找消息處理函數。一旦找到消息處理函數之后,就調用之,然后OnCmdMsg函數返回。

如上,即解釋了來自CFrameWnd窗口的WM_COMMAND消息是如何繞行的,其實也就是分別調用CView、CWnd(CCmdTarget)、CWinApp的OnCmdMsg函數而已。

同理,CView和CWinApp以及CDocument類都以類似的方式來對OnCmdMsg函數來進行調用,從而實現命令繞行機制。

   

   

   

   

以面向對象的思想理解MFC

最開始的MFC框架只是用兩個類來對原本的SDK流程進行封裝,MFC使用兩個類來抽象這個流程。一個是CWinApp,封裝了創建窗口(通過實例化一個Frame對象來實現),消息循環的主流程;一個是CMainFrame,封裝了窗口注冊,創建,及消息處理等內容。

CWinApp(也許說CWinThread更合適一點)類封裝了傳統Win32程序的主流程。一般來說一個Win32程序里會有注冊窗口類,創建窗口,進行消息循環這幾個步驟。這幾個步驟在CWinApp類里都存在着,其中注冊窗口類並創建窗口的過程被封裝到InitInstance成員函數里面,消息循環被封裝到了Run成員函數里面。

CMainFrame(也許說CWnd更合適一點)類封裝了Win32程序中有關窗口的那部分東西,具體說來就是窗口類的注冊,窗口的創建,以及窗口消息的處理。其中窗口注冊被封裝在PreCreateWindow函數里面,窗口的創建則被封裝在Create函數里面(更准確地說是封裝在CWnd::CreateEx函數里),還有一個窗口過程函數在哪里這個暫時還沒有搞清楚。

   

后來之所以有了CView和CDoc,是因為原來的CFrame負擔了過多的責任。這里把數據的管理交給了CDoc類來負責,把數據的顯示交給了CView類來負責,明確了各自的責任。

這里CView類仍然是一個單獨的窗口,從功能上來說應該和CFrame處於同等地位的,仍然有自己的窗口過程函數。只是從職責上來講CView只負責數據的顯示,而CFrame作為CView外部的一個框架提供別的一些功能。

由上可知,程序的功能實際上是被封裝到了Frame/View/Doc三個類里,其實是這三個類來合作完成程序的某個功能,正因為這樣,對於一個COMMEND消息,就可以交給這三個類里的某一個類來處理,不管這個消息是來源於Frame還是View。比如在菜單上單擊一個"更新"的選項。本來這個消息是由Frame接收到的,但是由View來處理會更簡單,因為View類本身持有更新時所需要的一些信息。

從這點上可以理解,Frame/View/Doc三個類是相互關聯的一個整體。

   

   

   

接着上面的闡述多說兩句:

MFC之所以創造了消息機制是為了實現其Frame/View/Doc三位一體的架構。

Frame/View/Doc架構的意義在於將處理消息的職責分配到合理的類中去處理,例如在菜單上點擊一個"保存"選項,處理這個消息就應該交給Doc類來實現,而如果點擊"更新"選項,則將這個消息交給View類來處理更方便一些。

MFC的消息機制就負責將消息交給合適的類去處理。下面解釋消息機制的實現思路:

1.比對思路和消息表:為每一個類建立一個消息表,這個消息表里包括了這個類能夠處理的消息有哪些,消息和其處理函數也一一對應。這樣的話就可以遍歷這張消息表並進行比對來知道這個類可以處理那些消息,並能夠一個消息的處理函數。

2.遍歷思路:要讓Frame里產生的消息在Doc類里進行處理,其思路也是簡單的遍歷:先看看View里有沒有能處理的,要是沒有,再看看自己類里能不能處理,要是再不行,再看看Doc類里有沒有對應的處理函數。

3.Frame/View/Doc三位一體:這個我也沒記清楚,貌似在創建一個Frame的時候都會順帶創建出其對應的View和Doc(單文檔的情況,多文檔貌似要創建多個)。所以上面可以由View找到其對應的Doc。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM