概述
从MFC到ATL,充斥着Map映射机制,似乎没有了这个Map机制,就玩不转啦。在WebBrower控件中,也存在着事件映射;在COM中,在IDispatch中也存在着自定义的函数映射。
以前,只要一谈到映射机制,总是让我闻风丧胆,退而求自保,暂且如此而已,记住就可以啦。现在想来,只要是跨不去过的坎,若没有认真面对和解决,那就永远无法逾越,成为心中永远的痛。最终,只能作茧自缚而唯唯诺诺。既然老天爷,又给了我一次机会,那我就好好抓住这次机会啦。
轰轰烈烈的开场白讲完了,让我们回归主题:“映射机制”
格式
Windows消息的Map格式
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; }
从中我们可以看到,头文件的map宏,确实有3个define所构成,其本质是定义了一个ProcessWindowMessage函数。在
#define MESSAGE_HANDLER(msg, func) \中,msg和func由用户选择,所以就暴露这2个参数。若有3个参数有用户选择的话,则肯定会暴露3个参数啦。
一般来说,最后一个参数是函数名称或函数地址,而它之前的参数一般都是它的参数。这样就解决了,只用一个宏,就可以解决所有相同个数和类型的输入参数,但不同操作的一般化函数调用,即程序中的映射机制。
通常情况下,映射一词有照射的含义,是一个动词。在数学上,映射则是个术语,指两个元素集之间元素相互“对应”的关系,名词;也指“形成对应关系”这一个动作,动词。
(摘自百度百科)
说白了,就是一种对应关系嘛,就这么简单,没有啥可说的。
模板类的Map格式
在头文件中,我们定义如下格式的映射关系:
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<class T> 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<theClassName> * GetMap() { static ST_XXX_ENTRY<theClassName> theMap[] = { #define XXX_ENTRY(string, identify, func) { string, identify, &theClassName::func},\ // 此处应用了类函数指针的获取方法 #define END_XXX_MAP() { NULL, 0, ST_XXX_ENTRY<theClassName>::Function_Name(NULL)} } }
(2)调用GetMap函数
在父类模板中,必然定义了如何使用GetMap中的函数映射关系。那么此时的调用,必然是直接使用T::GetMap()来获得静态容器的指针,然后对它进行遍历和筛选,以期获得我们想要点对应函数或对应函数上的处理结果。
非常棒,到这里,我们已经基本讲完了如何关联map和实例化map,以及变量在结构体中的定义。呵呵,感觉越写越有感觉,越写越明白里面map机制的奥秘在哪里已经如何外化出这个奥秘。
客户(界面)代码
客户代码是使用map宏的代码,它会继承一个模板类,而模板类所需要的实例类便是客户类,为什么会是这个样子呢?
其实原因很简单,因为此处模板类就是将公共函数提取出来,并且统一处理map宏中的转换关系,从而精简客户代码。而客户类完全可以按照客户所想定义的方式定义,想如何命名类名就如何命名类名,很*。唯一要做的就是继承一下模板类,并且添加自己喜欢的对应关系即可,想用什么函数名就用什么函数名,想用什么id就用什么id,因为map的实例化只是引用函数指针,跟名字一点关系都没有。够爽了吧,一个“牛爽”。
可话又说回来,所有的模板类不正是可以容纳各中类而存在,并且统一化处理流程的嘛。原来,我们从实践中,再次感受到模板的优点,或者说它的使命:
(1)模板更有助于编写。我们只需创建类或函数的一个泛型版本,而不是手动创建专用化;
(2)模板是类型安全的。 由于模板操作的类型在编译时是已知的,因此编译器可以在发生错误之前执行类型检查;
(3)由于可通过模板直接提取信息,因此模板更易于理解。(当然是这样的,若仅仅查看模板的话,显得比较抽象,若通过模板来实例化一个对象后,则提取信息变得可视化,确实易于理解。)
这三个优点,我是从msdn上摘的,不过稍微润色了一下,使得主旨更加明晰(毕竟翻译e文,仁者见仁哦)。
到此,我已经讲完了映射机制,Windows的所有映射机制,大抵如此,照葫芦画瓢。
真没有想到,居然写了这么多。不过真心体会,写完这篇blog之后,感觉对映射机制如释重负,感觉从未有过的轻松自在。越发觉得,写blog是一个很不错的深入学习的体验。只有在写得过程中,才会感受到那种顺藤摸瓜的感觉。