4. msg_struct的变异体
“变异体”这个名词不那么准确,但没有找到更好的名词之前,暂且先叫“变异体”。什么叫msg_struct的变异体,就是结构的大小和内存布局和msg_struct一样的,就叫做msg_struct的变异体。重温msg_struct的定义:
struct msg_struct
{
HWND hwnd;
UINT message;
union
{
WPARAM wParam;
struct
{
WORD wParamLo;
WORD wParamHi;
};
};
union
{
LPARAM lParam;
struct
{
WORD lParamLo;
WORD lParamHi;
};
};
LRESULT result;
};
所有的windows消息,都对应着这个结构体,message的不同,对应的wParam和lParam也不同。对WM_DESTROY消息来说,wParam和lParam是没意义的,我们可以引入一个msg_struct的别名,对应WM_DESTROY消息:
typedef msg_struct msg_destroy;
而WM_KEYDOWN消息,wParam和lParam是有意义的(具体意义请查阅MSDN),由此我们可以这么定义msg_key:
struct msg_key
{
HWND hWnd;
UINT message;
union
{
WPARAM key_code;
WPARAM wParm;
};
union
{
struct {
LPARAM repeat_count : 16;
LPARAM scan_code : 8;
LPARAM is_extended_key : 1;
LPARAM reserve : 4;
LPARAM context_code : 1; // The value is always 0 for a WM_KEYUP message
LPARAM previous_key_state : 1; // The value is always 1 for a WM_KEYUP message
LPARAM transition_state : 1; // The value is always 1 for a WM_KEYUP message
};
LPARAM lParam;
};
LRESULT result;
};
(为什么叫msg_key而不叫msg_keydown,因为对于按键消息来说,wParam和lParam是一样的解释。同理还有msg_mouse,对应所有的鼠标消息)
对比msg_key和msg_struct,会发现两者之间并无本质上的区别。msg_key把wParam和lParam解释成它对应的含义。在语法上,这两个结构是不存在关系,但在语义上,这两个结构是一回事。
对于大部分的windows消息来说,wParam和lParam都有含义,这就暗示着,对于消息映射函数来说,都需要wParam和lParam,而对于某一个windows消息来说,wParam和lParam要恢复成它的语义,才会方便使用。由此,我们可以规范消息映射函数的类型:
bool on_XXX(msg_struct &msg); // 为什么要有返回值bool,在讲继承的时候会说到这问题
对于WM_DESTROY消息,它映射的消息函数应该是:
bool on_destroy(msg_destroy &);
而对于WM_KEYDOWN消息,它映射的函数应该是:
bool on_keydown(msg_key &);
这定义的类型可以满足大部分的需求了。但凡事总有例外,比如,WM_PAINT消息。在WM_PAINT消息里,大多数情况下,都是要调用BeginPaint和EndPaint,若消息映射函数只有一个参数,这意味着在每个映射函数里,都有BeginPaint和EndPaint和代码,这未免太过于重复和乏味,所以我们要对这种情况做个特例:
bool on_paint(HDC hdc, const RECT &rtClip, msg_paint &);
既然消息映射函数的类型改了,那么map_XXX里的实现也要改了。
对于WM_DESTROY:
struct map_destroy : msgmap_t
{
template<typename T>
static inline size_t map_fun_addr(bool (T::*f)(msg_destroy &))
{
return *reinterpret_cast<size_t *>(&f);
}
static bool on_map(const msgmap_t &a, void * x, msg_struct &msg)
{
msgmap_t &o= *reinterpret_cast<msgmap_t *>(x);
typedef bool (msgmap_t::*fun_t)(msg_destroy &);
const fun_t &f = *reinterpret_cast<const fun_t *>(&a.on_message);
return (o.*f)(reinterpret_cast<msg_destroy &>(msg));
}
};
这里要注意这句代码:reinterpret_cast<msg_destroy &>(msg),因为msg_destroy是msg_struct的变异体,所以,这里的强行转换是没有问题的。
对于WM_PAINT:
struct map_paint : msgmap_t
{
template<typename T>
static inline size_t map_fun_addr(bool (T::*f)(HDC,const RECT &, msg_paint &))
{
return *reinterpret_cast<size_t *>(&f);
}
static bool on_map(const msgmap_t &a, void * x, msg_struct &msg)
{
struct paint_sentry : PAINTSTRUCT
{
HWND m_hWnd;
HDC hdc;
paint_sentry(HWND hWnd) : m_hWnd(hWnd)
{
hdc = ::BeginPaint(m_hWnd, this);
}
~paint_sentry()
{
::EndPaint(m_hWnd, this);
}
}ps(msg.hWnd);
msgmap_t &o= *reinterpret_cast<msgmap_t *>(x);
typedef bool (msgmap_t::*fun_t)(HDC, const RECT &, msg_paint &);
const fun_t &f = *reinterpret_cast<const fun_t *>(&a.on_message);
return (o.*f)(ps.hdc, ps.rcPaint,reinterpret_cast<msg_paint &>(msg));
}
};
在map_paint::on_map里,引入了一个局部类,是为了防止在回调函数中出现了异常而不能调用EndPaint。
宏WABC_ON_DESTROY和WABC_ON_PAINT不用做修改。
对于WM_KEYDOWN:
struct map_key : msgmap_t
{
template<typename T>
static inline size_t map_fun_addr(bool (T::*f)(msg_key &))
{
return *reinterpret_cast<size_t *>(&f);
}
};
#define WABC_ON_KEYDOWN(f) \
{ WM_KEYDOWN, map_key::map_fun_addr<map_class>(f), &map_destroy::on_map },
这里,不知道是否有人注意到,在map_key里,并没有on_map函数,它使用的是map_destroy::on_map!msg_key也是msg_struct的变异体,对于map_destroy::on_map来说,msg_destroy和msg_key没有区别,只要它们映射消息函数的类型是一致的。
同理,我们很快就能定义WM_KEYUP、WM_CHAR、WM_SYSKEYUP、WM_SYSKEYDOWN、WM_SYSKEYCHAR消息。
#define WABC_ON_KEYUP(f) \
{ WM_KEYUP, map_key::map_fun_addr<map_class>(f), &map_destroy::on_map },
#define WABC_ON_CHAR(f) \
{ WM_CHAR, map_key::map_fun_addr<map_class>(f), &map_destroy::on_map },
#define WABC_ON_SYSKEYDOWN(f) \
{ WM_SYSKEYDOWN, map_key::map_fun_addr<map_class>(f), &map_destroy::on_map },
#define WABC_ON_SYSKEYUP(f) \
{ WM_SYSKEYUP, map_key::map_fun_addr<map_class>(f), &map_destroy::on_map },
#define WABC_ON_SYSCHAR(f) \
{ WM_SYSCHAR, map_key::map_fun_addr<map_class>(f), &map_destroy::on_map },
鼠标消息类似。
总结:
在msgmap_t::on_message,我们弱化了on_message的语义,然后又规范了消息映射函数的类型,通过msg_struct的变异体,最大限度的在正确性、易用性和内存性能方面达到了完美的平衡。这一设计思想,不单纯可以应用于消息映射机制上,也可应用于在其它类似场合。
请点击这里下载'wabc'库的最终源码。