Introduction
The Microsoft Foundation Classes reduce, but do not eliminate, the need for passing Windows messages using the Windows ::SendMessage and ::PostMessage functions. Unfortunately, passing message parameters using these functions is not type safe. You must cast your parameters to a WPARAM or LPARAM data type in the call, then cast back to the desired data type in the handler. This is bad enough when passing numeric values, but even worse when passing pointers. The macros defined below allow simple type safe message passing in an MFC application. As an added benefit, the macros provide HWND-based message passing using method call semantics.
The Macro Definitions
Place the following macro definitions in your code:
#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 ) ) ; }
#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 );
#define TYPESAFE_MAPENTRY( NAME ) ON_MESSAGE( MSG__##NAME, ON__##NAME )
Using the Macros
The macros are used as follows:
- Create a header file to contain the global message descriptions (e.g., app_messages.h). Messages are defined at global scope using the
TYPESAFE_MESSAGE macro.
- Add message handlers to your
CWnd-derived class definitions using the TYPESAFE_MESSAGE_HANDLER macro.
- Add entries to the MFC
MESSAGE_MAP using the TYPESAFE_MAPENTRY macro.
- Write the body of the message handler methods defined by the
TYPESAFE_MESSAGE_HANDLER macro.
The TYPESAFE_MESSAGE macro is defined as:
TYPESAFE_MESSAGE( name, code, result_type, wparam_type, lparam_type )
where:
name is the name of the message
code is the UINT message code used in ::SendMessage and ::PostMessage calls
result_type is the data type of the result returned by ::SendMessage.
wparam_type is the data type of wparam
lparam_type is the data type of lparam
result_type must be a data type that can be cast to LRESULT. wparam_type and lparam_type must be data types that can be cast to LPARAM.
The TYPESAFE_MESSAGE_HANDLER macro is defined as:
TYPESAFE_MESSAGE_HANDLER( name, result_type, wparam_type, lparam_type )
where name, result_type, wparam_type, and lparam_type must match the corresponding values used in the TYPESAFE_MESSAGE macro.
The TYPESAFE_MAPENTRY macro is defined as:
TYPESAFE_MAPENTRY( name )
where name must match the name used in the above two macros.
Example
Assume you want to create a message named on_foo with the message code WM_APP+200. Further, you want on_foo to take as arguments a const int and a const CWnd*, and you want on_foo to return a float. Finally, you want the class MyClass to have a handler for on_foo.
In the file app_messages.h, define:
TYPESAFE_MESSAGE( on_foo, WM_APP+200, float, const int, const CWnd* )
In the file MyClass.h, define:
class MyClass : public CWnd
{
...
TYPESAFE_MESSAGE_HANDLER( on_foo, float, const int, const CWnd* )
...
};
In the file MyClass.cpp, define:
BEGIN_MESSAGE_MAP(MyClass, CWnd)
...
TYPESAFE_MAPENTRY( on_foo )
END_MESSAGE_MAP()
float MyClass::on_foo( const int a, const CWnd* b )
{
...
}
That's it. Now you can send an on_foo message to any MyClass window and invoke the MyClass::on_foo method by using the code:
float x = on_foo( hwnd, a, b );
Alternatively, you can post an on_foo message using the code:
BOOL b = post_on_foo( hwnd, a, b );
Notice that in both cases, you are using method call semantics for message passing.
How it Works
In the above example, the TYPESAFE_MESSAGE macro creates the following code at global scope:
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 ) ) ); }
The TYPESAFE_MESSAGE_HANDLER macro produces the following code in the MyClass header:
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 ); }
And, the TYPESAFE_MAPENTRY macro produces the following code in the MyClass MFC message map:
ON_MESSAGE( MSG__on_foo, ON__on_foo )
In MyClass, the ON__on_foo MFC message handler receives the on_foo message, casts the arguments to the proper data types, and invokes the on_foo method.
The method on_foo___compile_test() is never called. Its sole purpose is to generate a call to ::on_foo( hwnd, a, b ) and force the compiler to do a compile-time check to make sure that the data types specified in TYPESAFE_MESSAGE_HANDLER match those in TYPESAFE_MESSAGE.
History
- 11/8/2010 - Corrected a couple of typos.