概述
从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
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)} \ } \ }
(2)调用GetMap函数
在父类模板中,必然定义了如何使用GetMap中的函数映射关系。那么此时的调用,必然是直接使用T::GetMap()来获得静态容器的指针,然后对它进行遍历和筛选,以期获得我们想要点对应函数或对应函数上的处理结果。
非常棒,到这里,我们已经基本讲完了如何关联map和实例化map,以及变量在结构体中的定义。呵呵,感觉越写越有感觉,越写越明白里面map机制的奥秘在哪里已经如何外化出这个奥秘。
客户(界面)代码
客户代码是使用map宏的代码,它会继承一个模板类,而模板类所需要的实例类便是客户类,为什么会是这个样子呢?
其实原因很简单,因为此处模板类就是将公共函数提取出来,并且统一处理map宏中的转换关系,从而精简客户代码。而客户类完全可以按照客户所想定义的方式定义,想如