從MFC到ATL,充斥著Map映射機制,似乎沒有了這個Map機制,就玩不轉啦。在WebBrower控件中,也存在著事件映射;在COM中,在IDispatch中也存在著自定義的函數映射。
以前,只要一談到映射機制,總是讓我聞風喪膽,退而求自保,暫且如此而已,記住就可以啦。現在想來,只要是跨不去過的坎,若沒有認真面對和解決,那就永遠無法逾越,成為心中永遠的痛。最終,只能作繭自縛而唯唯諾諾。既然老天爺,又給了我一次機會,那我就好好抓住這次機會啦。
轟轟烈烈的開場白講完了,讓我們回歸主題:“映射機制”
map代碼,如下所示:
BEGIN_MSG_MAP(CTestDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) MESSAGE_HANDLER(WM_CLOSE, OnClose) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP()其實是三個define所構成的,如下所示:
#define BEGIN_MSG_MAP(theClass) \ public: \ BOOL ProcessWindowMessage(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam,\ _In_ LPARAM lParam, _Inout_ LRESULT& lResult, _In_ DWORD dwMsgMapID = 0) \ { \ BOOL bHandled = TRUE; \ (hWnd); \ (uMsg); \ (wParam); \ (lParam); \ (lResult); \ (bHandled); \ switch(dwMsgMapID) \ { \ case 0:
#define MESSAGE_HANDLER(msg, func) \ if(uMsg == msg) \ { \ bHandled = TRUE; \ lResult = func(uMsg, wParam, lParam, bHandled); \ if(bHandled) \ return TRUE; \ }
#define END_MSG_MAP() \ break; \ default: \ ATLASSERT(FALSE); \ break; \ } \ return FALSE; \ }
#define MESSAGE_HANDLER(msg, func) \中,msg和func由用戶選擇,所以就暴露這2個參數。若有3個參數有用戶選擇的話,則肯定會暴露3個參數啦。
一般來說,最後一個參數是函數名稱或函數地址,而它之前的參數一般都是它的參數。這樣就解決了,只用一個宏,就可以解決所有相同個數和類型的輸入參數,但不同操作的一般化函數調用,即程序中的映射機制。
通常情況下,映射一詞有照射的含義,是一個動詞。在數學上,映射則是個術語,指兩個元素集之間元素相互“對應”的關系,名詞;也指“形成對應關系”這一個動作,動詞。
(摘自百度百科)
說白了,就是一種對應關系嘛,就這麼簡單,沒有啥可說的。
在頭文件中,我們定義如下格式的映射關系:
BEGIN_XXX_MAP(CClassName)
XXX_ENTRY(String1, Identify1, OnFunctionName1)
XXX_ENTRY(String2, Identify2, OnFunctionName2)
... ...
XXX_ENTRY(StringN, IdentifyN, OnFunctionNameN)
END_XXX_MAP()
那麼,我們現在可以理解為OnFunctionName函數需要String和Identify這兩個變量。由於有若個這樣的XXX_ENTRY,那麼就會有相應個函數,暫且成為函數容器。
這時我們就會想到兩種情況來解釋個函數容器:
一種是:上面所說的“Windows消息映射”,它只是將消息和函數進行一一對應,則程序更富有表現力,同時隱藏了不必要的代碼。並且對應關系比較簡單,就是一個類函數指針的代理。
另一種是:若個函數作為函數容器出現,以便在對應的模板類中對容器中的各個函數進行輪詢,以便決定是否使用具有特定碼的函數。它不再作為一個代理的角色出現,而更多地是扮演成員變量數組的角色出現。
這樣做的好處是,讓模板類可以更加靈活的處理這個數組,以便完成特定的處理效果。解放了數據和函數,分別進行了處理。咱們職責分明,秋毫無犯嘛,呵呵。
注意事項:
(1)定義GetMap()函數
它一般被const staic所修飾,其返回值為指向模板數組的一個指針;這樣在函數中引用該GetMap時,只需要使用T::GetMap即可,因為它是靜態函數啊!如下為保存和顯示三個變量關系的結構體:
template結構體固然重要,但是這裡更為重要的是展現三個變量關系之間的靜態函數。struct ST_XXX_ENTRY { typedef void(T::*Function_Name)(); LPSTR string; UINT identify; Function_Name func; static void ProcessFunc1() { //... } static void ProcessFunc2() { // ... } };
map的實例化代碼如下所示:
#define BEGIN_XXX_MAP(theClassName)\ static const ST_XXX_ENTRY* GetMap() \ { \ static ST_XXX_ENTRY theMap[] = \ { #define XXX_ENTRY(string, identify, func) \ { string, identify, &theClassName::func},\ // 此處應用了類函數指針的獲取方法 #define END_XXX_MAP() \ { NULL, 0, ST_XXX_ENTRY ::Function_Name(NULL)} \ } \ }
在父類模板中,必然定義了如何使用GetMap中的函數映射關系。那麼此時的調用,必然是直接使用T::GetMap()來獲得靜態容器的指針,然後對它進行遍歷和篩選,以期獲得我們想要點對應函數或對應函數上的處理結果。
非常棒,到這裡,我們已經基本講完了如何關聯map和實例化map,以及變量在結構體中的定義。呵呵,感覺越寫越有感覺,越寫越明白裡面map機制的奧秘在哪裡已經如何外化出這個奧秘。
客戶代碼是使用map宏的代碼,它會繼承一個模板類,而模板類所需要的實例類便是客戶類,為什麼會是這個樣子呢?
其實原因很簡單,因為此處模板類就是將公共函數提取出來,並且統一處理map宏中的轉換關系,從而精簡客戶代碼。而客戶類完全可以按照客戶所想定義的方式定義,想如何命名類名就如何命名類名,很自由。唯一要做的就是繼承一下模板類,並且添加自己喜歡的對應關系即可,想用什麼函數名就用什麼函數名,想用什麼id就用什麼id,因為map的實例化只是引用函數指針,跟名字一點關系都沒有。夠爽了吧,一個“牛爽”。
可話又說回來,所有的模板類不正是可以容納各中類而存在,並且統一化處理流程的嘛。原來,我們從實踐中,再次感受到模板的優點,或者說它的使命:
(1)模板更有助於編寫。我們只需創建類或函數的一個泛型版本,而不是手動創建專用化;
(2)模板是類型安全的。 由於模板操作的類型在編譯時是已知的,因此編譯器可以在發生錯誤之前執行類型檢查;
(3)由於可通過模板直接提取信息,因此模板更易於理解。(當然是這樣的,若僅僅查看模板的話,顯得比較抽象,若通過模板來實例化一個對象後,則提取信息變得可視化,確實易於理解。)
這三個優點,我是從msdn上摘的,不過稍微潤色了一下,使得主旨更加明晰(畢竟翻譯e文,仁者見仁哦)。
到此,我已經講完了映射機制,Windows的所有映射機制,大抵如此,照葫蘆畫瓢。
真沒有想到,居然寫了這麼多。不過真心體會,寫完這篇blog之後,感覺對映射機制如釋重負,感覺從未有過的輕松自在。越發覺得,寫blog是一個很不錯的深入學習的體驗。只有在寫得過程中,才會感受到那種順籐摸瓜的感覺。