Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / MFC

Typesafe Windows Message Passing Using MFC

Rate me:
Please Sign up or sign in to vote.
4.50/5 (7 votes)
8 Nov 2010CPOL3 min read 30.4K   17   13
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:

C++
// 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:

C++
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:

C++
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:

C++
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:

C++
TYPESAFE_MESSAGE( on_foo, WM_APP+200, float, const int, const CWnd* )

In the file MyClass.h, define:

C++
class MyClass : public CWnd
{
  ...
  TYPESAFE_MESSAGE_HANDLER( on_foo, float, const int, const CWnd* )
  ...
};

In the file MyClass.cpp, define:

C++
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:

C++
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:

C++
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:

C++
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:

C++
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:

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionPassing pointers in 64 bit? Pin
Stefan_Lang9-Nov-10 1:29
Stefan_Lang9-Nov-10 1:29 
AnswerRe: Passing pointers in 64 bit? Pin
Lee Henderson9-Nov-10 6:43
Lee Henderson9-Nov-10 6:43 
GeneralMy vote of 5 Pin
Ken Earle8-Nov-10 14:24
Ken Earle8-Nov-10 14:24 
GeneralMy vote of 3 Pin
xComaWhitex5-Nov-10 22:07
xComaWhitex5-Nov-10 22:07 
GeneralRe: My vote of 3 Pin
Lee Henderson6-Nov-10 5:23
Lee Henderson6-Nov-10 5:23 
GeneralRe: My vote of 3 Pin
BobMiller8-Nov-10 11:29
BobMiller8-Nov-10 11:29 
GeneralRe: My vote of 3 Pin
xComaWhitex8-Nov-10 11:32
xComaWhitex8-Nov-10 11:32 
GeneralRe: My vote of 3 Pin
BobMiller8-Nov-10 11:56
BobMiller8-Nov-10 11:56 
GeneralRe: My vote of 3 Pin
xComaWhitex8-Nov-10 12:03
xComaWhitex8-Nov-10 12:03 
GeneralRe: My vote of 3 Pin
BobMiller8-Nov-10 12:41
BobMiller8-Nov-10 12:41 
GeneralRe: My vote of 3 Pin
xComaWhitex8-Nov-10 12:48
xComaWhitex8-Nov-10 12:48 
GeneralRe: My vote of 3 Pin
BobMiller8-Nov-10 13:10
BobMiller8-Nov-10 13:10 
GeneralRe: My vote of 3 Pin
FrankLaPiana16-Nov-10 7:27
FrankLaPiana16-Nov-10 7:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.