65.9K
CodeProject is changing. Read more.
Home

Typesafe Windows Message Passing Using MFC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (7 votes)

Nov 4, 2010

CPOL

3 min read

viewsIcon

31486

Easy to use macros provide typesafe Windows message passing with call semantics.

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:

// 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 )

Using the Macros

The macros are used as follows:

  1. 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.
  2. Add message handlers to your CWnd-derived class definitions using the TYPESAFE_MESSAGE_HANDLER macro.
  3. Add entries to the MFC MESSAGE_MAP using the TYPESAFE_MAPENTRY macro.
  4. 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 );
// where hwnd is a window handle to a MyClass window 

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.