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
/li>
lparam_type
is the data type of lparam
/li>
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.