使用 MFC 进行类型安全的 Windows 消息传递






4.50/5 (7投票s)
易于使用的宏提供具有调用语义的类型安全的 Windows 消息传递。
引言
Microsoft 基础类减少了,但并没有消除使用 Windows ::SendMessage
和 ::PostMessage
函数传递 Windows 消息的需要。不幸的是,使用这些函数传递消息参数不是类型安全的。您必须将参数转换为 WPARAM
或 LPARAM
数据类型,然后在处理程序中再转换回所需的数据类型。在传递数值时这已经很糟糕了,而在传递指针时更糟。下面定义的宏允许在 MFC 应用程序中进行简单的类型安全的消息传递。 作为一个额外的优势,这些宏使用方法调用语义提供基于 HWND
的消息传递。
宏定义
将以下宏定义放置在您的代码中
// Macro to define the message and the global calling functions
// This macro is used at global scope
#define TYPESAFE_MESSAGE( NAME, MSG, RESULT, ARG1, ARG2 ) \
const UINT MSG__##NAME( ( MSG ) ) ; \
inline RESULT NAME( const HWND hwnd, ARG1 a , ARG2 b ) { \
return (RESULT)( ::SendMessage( hwnd, (MSG), WPARAM( a ), LPARAM( b ) ) ) ; } \
inline BOOL post__##NAME( const HWND hwnd, ARG1 a , ARG2 b ) { \
return ::PostMessage( hwnd, (MSG), WPARAM( a ), LPARAM( b ) ) ; }
//
// Macro to define the message handler
// This macro is used in the class definition of an MFC CWnd-derived class
#define TYPESAFE_MESSAGE_HANDLER( NAME, RESULT, ARG1, ARG2 ) \
virtual RESULT NAME( ARG1, ARG2 ); \
afx_msg LRESULT ON__##NAME( WPARAM a, LPARAM b )
{ return LRESULT( NAME( (ARG1) a, (ARG2) b ) ); } \
void NAME##___compile_test() const { RESULT x = ::NAME( (HWND) 0, (ARG1) 0, (ARG2) 0 );
//
// macro to define the message map entry
#define TYPESAFE_MAPENTRY( NAME ) ON_MESSAGE( MSG__##NAME, ON__##NAME )
使用宏
宏的使用方法如下
- 创建一个头文件来包含全局消息描述(例如,app_messages.h)。使用
TYPESAFE_MESSAGE
宏在全局范围内定义消息。 - 使用
TYPESAFE_MESSAGE_HANDLER
宏将消息处理程序添加到您的CWnd
派生类定义中。 - 使用
TYPESAFE_MAPENTRY
宏将条目添加到 MFCMESSAGE_MAP
中。 - 编写由
TYPESAFE_MESSAGE_HANDLER
宏定义的消息处理程序方法的正文。
TYPESAFE_MESSAGE
宏定义如下
TYPESAFE_MESSAGE( name, code, result_type, wparam_type, lparam_type )
其中
name
是消息的名称code
是::SendMessage
和::PostMessage
调用中使用的UINT
消息代码result_type
是::SendMessage
返回的结果的数据类型。wparam_type
是wparam
的数据类型lparam_type
是lparam
的数据类型
result_type
必须是可以转换为 LRESULT
的数据类型。wparam_type
和 lparam_type
必须是可以转换为 LPARAM
的数据类型。
TYPESAFE_MESSAGE_HANDLER
宏定义如下
TYPESAFE_MESSAGE_HANDLER( name, result_type, wparam_type, lparam_type )
其中 name
、result_type
、wparam_type
和 lparam_type
必须与 TYPESAFE_MESSAGE
宏中使用的对应值匹配。
TYPESAFE_MAPENTRY
宏定义如下
TYPESAFE_MAPENTRY( name )
其中 name
必须与上述两个宏中使用的名称匹配。
示例
假设您想创建一个名为 on_foo
的消息,其消息代码为 WM_APP+200
。此外,您希望 on_foo
接受 const int
和 const CWnd*
作为参数,并且您希望 on_foo
返回一个 float
。最后,您希望类 MyClass
具有 on_foo
的处理程序。
在文件 app_messages.h 中,定义
TYPESAFE_MESSAGE( on_foo, WM_APP+200, float, const int, const CWnd* )
在文件 MyClass.h 中,定义
class MyClass : public CWnd
{
...
TYPESAFE_MESSAGE_HANDLER( on_foo, float, const int, const CWnd* )
...
};
在文件 MyClass.cpp 中,定义
BEGIN_MESSAGE_MAP(MyClass, CWnd)
...
TYPESAFE_MAPENTRY( on_foo )
END_MESSAGE_MAP()
float MyClass::on_foo( const int a, const CWnd* b )
{
...
}
就这样。现在,您可以通过使用代码将 on_foo
消息发送到任何 MyClass
窗口并调用 MyClass::on_foo
方法
float x = on_foo( hwnd, a, b );
// where hwnd is a window handle to a MyClass window
或者,您可以使用以下代码发布 on_foo
消息
BOOL b = post_on_foo( hwnd, a, b );
请注意,在这两种情况下,您都在使用方法调用语义进行消息传递。
工作原理
在上面的例子中,TYPESAFE_MESSAGE
宏在全局范围内创建以下代码
const UINT MSG__on_foo( WM_APP + 200 );
inline float on_foo( const HWND hwnd, const int a, const CWnd* b )
{ return float( ::SendMessage( hwnd, WM_APP+200, WPARAM( a ), LPARAM( b ) ) ); }
inline BOOL post_on_foo( const HWND hwnd, const int a, const CWnd* b )
{ return ::PostMessage( hwnd, WM_APP+200, WPARAM( a ), LPARAM( b ) ) ); }
TYPESAFE_MESSAGE_HANDLER
宏在 MyClass
标头中生成以下代码
virtual float on_foo( const int, const CWnd* );
afx_msg LRESULT ON__on_foo( WPARAM a, LPARAM b )
{ return LRESULT( on_foo( const int a, const CWnd* b ) ); }
void on_foo__compile_test() const
{ float x = ::on_foo( (HWND) 0, (const int) 0, (const CWnd*) 0 ); }
并且,TYPESAFE_MAPENTRY
宏在 MyClass
MFC 消息映射中生成以下代码
ON_MESSAGE( MSG__on_foo, ON__on_foo )
在 MyClass
中,ON__on_foo
MFC 消息处理程序接收 on_foo
消息,将参数转换为适当的数据类型,并调用 on_foo
方法。
方法 on_foo___compile_test()
从未被调用。其唯一目的是生成对 ::on_foo( hwnd, a, b )
的调用,并强制编译器进行编译时检查,以确保在 TYPESAFE_MESSAGE_HANDLER
中指定的数据类型与 TYPESAFE_MESSAGE
中的数据类型匹配。
历史
- 2010 年 11 月 8 日 - 修正了一些拼写错误。