views:

572

answers:

4

How could I create similar structure to handle Win32 Messages like it is in MFC?

In MFC;

BEGIN_MESSAGE_MAP(CSkinCtrlTestDlg, CDialog)
    //{{AFX_MSG_MAP(CSkinCtrlTestDlg)
    ON_BN_CLICKED(IDC_BROWSE, OnBrowse)
    ON_BN_CLICKED(IDC_DEFAULTSKIN, OnChangeSkin)
    ON_WM_DRAWITEM()
    ON_WM_MEASUREITEM()
    ON_WM_COMPAREITEM()
    ON_BN_CLICKED(IDC_CHECK3, OnCheck3)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

BEGIN_MESSAGE_MAP macro handles this behaviour. What to do for pure Win32?

A: 

You could use something like a std::map< short, MessageFn >. Where short is the window message and MessageFn is the function that handles the message. You can then handle messages as follows:

if ( messageMap.find( uMsg ) != messageMap.end() )
{
   messageMap[uMsg]( wParam, lParam );
}

It won't be quite as neat but would be pretty easy to implement, though you'd be defining the message map at runtime rather than at compile time.

Another solution is to read through the MFC macro code and see how microsoft did it ...

Another solution, if you want MFC like behaviour without the overhead, is using ATL. You can also have a look at ATL's macro definitions to see how they did it ....

Edit: You can solve WM_COMMAND or WM_NOTIFY handling by storing a CommandMap and a NotifyMap as well. You then set the WM_COMMAND handler to a function that then does a similar thing and passes the command on through the CommandMap.

Your biggest issue is the fact that you don't get anything in the message that identifies a specific class instance. This isn't a problem if you only need the hWnd but youj may need to store a further global map of HWNDs to class instances ...

This is only 1 solution. You can solve the problem in many different ways. I'm just throwing 1 idea out there for you.

Goz
But, There is something missing here, for example I want to bind a function to specific button click event. In your suggestion every event goes to a single function. There should be another parameter but I think map is not enough?
whoi
A: 

The MFC message map doesn't use a regular WndProc per se. IIRC, it is based on some kind of hook mechanism.

However, I guess it shouldn't be very hard to adapt the macros to an usual WndProc.

The 1st approach that comes to mind is to let the macros create an array of Message id/Handler function pairs. Even better: Use a map for improved performance.

Your WndProc would loop through that array to identify the given WM and execute the corresponding handler.

You might also want the BEGIN_MESSAGE_MAP macros to mimic a switch statement where each ON_BLAH() line would be a case line inside the switch().

That should be too hard.

Serge - appTranslator
A: 

It's difficult to do this with something like an std::map. In particular, that requires that every item in the map has the same type, but different messages have handlers that take different numbers of parameters, so pointers to them aren't the same type.

You might want to take a look at the message cracker macros (especially HANDLE_MSG) in windowsx.h though. Though this really just generates case's for a switch statement, it still lets you write code that looks something like an MFC message map.

Jerry Coffin
That's not a real problem; you can hand each message handler the raw WPARAM/LPARAM you got. That way, you move the different casts to the appropriate message handler.
MSalters
+2  A: 

Here is a breif summary of the code I use to do this in the Zeus programmer's editor:

Step 1: Define a couple of message structure to hold the Windows message details:

typedef struct
{
  MSG     msg;
  LRESULT lResult;
} xMessage;

struct xWM_COMMAND
{
  HWND hwnd;
  UINT Msg;
  WORD ItemID;
  WORD NotifyCode;
  HWND Ctl;
  LRESULT lResult;
};

//-- unpack a message buffer
#define MSG_UNPACK(var, id, msg) x##id *var = (x##id *)(msg);

Step 2: Define a base window class with a few special methods:

class xWindow
{
protected:
  //-- windows callback function
  static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, 
                                  WPARAM wParam, 
                                  LPARAM lParam);

  //-- a message dispatch method
  void dispatch(HWND hwnd, UINT uMessageID, WPARAM wParam, 
                LPARAM lParam, LRESULT &Result);

  //-- method for command message dispatching
  virtual void dispatchToCmdMap(xMessage *pMessage);

  //-- method for windows message dispatching
  virtual void dispatchToMsgMap(xMessage *pMessage);
};

Step 3: Define a few macros to do the dispatching of the Windows messages:

#define BEGIN_MSG_MAP                          \
   protected:                                  \
   virtual void dispatchToMsgMap(xMessage *msg)\
   {                                           \
     if (msg->msg.message == WM_NULL)          \
     {                                         \
       return;                                 \
     }

#define MSG_HANDLER(meth, wm_msg)              \
     else if (msg->msg.message == wm_msg)      \
     {                                         \
       this->meth(msg);                        \
       return;                                 \
     }

#define END_MSG_MAP(base)                      \
     else if (msg->msg.message == WM_COMMAND)  \
     {                                         \                       
       this->dispatchToCmdMap(msg);            \                       
       return;                                 \                       
     }                                         \                       
     else if (msg->msg.message == WM_NOTIFY)   \                       
     {                                         \                       
       this->dispatchToNotifyMap(msg);         \                       
       return;                                 \                       
     }                                         \                       
                                               \                       
     base::dispatchToMsgMap(msg);              \                       
   };

#define BEGIN_CMD_MAP                          \
   virtual void dispatchToCmdMap(xMessage *msg)\
   {                                           \                              
     MSG_UNPACK(Cmd, WM_COMMAND, msg);         \                              
                                               \                              
     if (Cmd->ItemID == 0)                     \                              
     {                                         \                              
        /* not allowed */                      \                              
     }                                                                        

#define CMD_HANDLER(meth, cmd_id)              \
     else if (Cmd->ItemID == cmd_id)           \
     {                                         \                                
       this->meth(Cmd->ItemID);                \                                
     }                                                                          

#define END_CMD_MAP(base)                      \
     else                                      \                              
     {                                         \                              
       base::dispatchToCmdMap(msg);        \                              
     }                                         \                              
   };

Step 4: Define the dispatcher method:

void xWindow::dispatch(HWND, UINT uMessageID, WPARAM wParam, 
                       LPARAM lParam, LRESULT &Result)
{
  xMessage message;

  //-- build up a message packet
  message.msg.message = uMessageID;
  message.msg.wParam  = wParam;
  message.msg.lParam  = lParam;
  message.lResult     = 0;

  //-- dispatch the message
  this->dispatchToMsgMap(&message);
}

Step 5: Define the static window procedure method (NOTE: this method will need to be used as the Window procedure of the window class when the class is first registered):

LRESULT CALLBACK xWindow::wndProc(HWND hwnd, UINT msg, 
                                  WPARAM wParam, 
                                  LPARAM lParam)
{
  LRESULT lResult = 0;

  //-- look for the creation message
  if (msg == WM_NCCREATE)
  {
    CREATESTRUCT *pCreateData = (CREATESTRUCT*)lParam;

    //-- get the window object passed in
    xWindow *pWindow = (xWindow)pCreateData->lpCreateParams;

    if (pWindow)
    {
      //-- attach the window object to the hwnd
      SetWindowLong(hwnd, pWindow);

      //-- let the window object dispatch the message
      pWindow->dispatch(hwnd, msg, wParam, lParam, lResult);
    }
    else
    {
      //-- leave the message to windows
      lResult = DefWindowProc(hwnd, msg, wParam, lParam);
    }
  }
  else if (hwnd)
  {
    //-- get the object attached to the hwnd
    xWindow *pWindow = (xWindow *)GetWindowLong(hwnd);

    //-- check to see if we have an object window attached to the handle
    if (pWindow)
    {
      //-- let the window object dispatch the message
      pWindow->dispatch(hwnd, msg, wParam, lParam, lResult);
    }
    else
    {
      //-- leave the message to windows
      lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
    }
  }

  return lResult;
}

Now, using this base class it is possible to define a new window class that will look like this:

class MyWindow : public xWindow
{
protected:  
  //-- the WM_COMMAND message handlers
  virtual void onAdd(int);
  virtual void onDelete(int);

  //-- the WM_CLOSE message handler
  virtual void onClose(xMessage *pMessage);

  //-- the WM_SIZE message handler
  virtual void onSize(xMessage *pMessage);

public:
  //-- ctor and dtor
  MyWindow();
  virtual ~MyWindow();

  BEGIN_MSG_MAP
    //-- command message handlers
    CMD_HANDLER(onAdd   , IDPB_ADD   )
    CMD_HANDLER(onDelete, IDPB_DELETE)

    //-- other message handling
    MSG_HANDLER(onClose , WM_CLOSE)
    MSG_HANDLER(onSize  , WM_SIZE )
  END_MSG_MAP(xWindow)
};

Edit: How this code works.

The secret to understanding how this code works is to remember the wndProc in the xWindow class is nothing but a Win32 Window procedure passed to RegisterClassEx when the Win32 Window is registered.

Now if you look at the wndProc code you will see it does a bit of setting up and checking but generally it does nothing more than send the Windows message to the dispatch method.

The dispatch method is even simpler as it does nothing more than pack the Windows message into an easy to move structure and then sends it off to the dispatchToMsgMap method.

Now look at the MyWindow class an you will see this code:

BEGIN_MSG_MAP    
   //-- command message handlers    
   CMD_HANDLER(onAdd   , IDPB_ADD   )    
   CMD_HANDLER(onDelete, IDPB_DELETE)    

   //-- other message handling    
   MSG_HANDLER(onClose , WM_CLOSE)    
   MSG_HANDLER(onSize  , WM_SIZE )  
END_MSG_MAP(xWindow)

This code is just using the macros defined earlier. If you take a close look at these macros you will see the code above is in fact creating a dispatchToMsgMap method. This is the exact same dispatchToMsgMap method that was called by the dispatch method.

I know this method of handling Windows messages does work as I use this exact same approach in the Zeus for Windows editor.

jussij
Well, your answer seems best at all with all the features, thank you
whoi
Hi again, Have you compiled this source? When I included this to my project I have got many errors? Should there be any compile considerations?
whoi
The code in my posting posting above is taken from a Win32 library that I created when I wrote the Zeus for Windows editor. I only copied only a very small portion of what is a very big library code so I would not expect this code to compile. For example xWindow class shown above is in fact some 2000+ lines of code so naturally it has been cut this down considerably. But what I tried to post was the basic concept on how I map Windows messages to C++ methods.
jussij
I edited the posting and added a few words in an attempt to better describe how this code actually works.
jussij