Title:       GOOD BYE BEGIN_MSG_MAP!
Author:      MB
Email:       mb2act@yahoo.co.jp
Environment: Microsoft Visual C++ .NET Version 7.1, Windows XP
Keywords:    ATL, WTL, MPL, boost, C++, metaprogramming
Level:       Intermediate
Description: A replacement for BEGIN_MSG_MAP macros, using the Boost.MPL library
Section      WTL, C++
SubSection   Doc/View

Sample Image - maximum width is 600 pixels

Introduction

Four yeas ago, I made a program. Everything WTL originally had was useless, except win32 thin wrappers. CUpdateUI was the one of them, so I made the replacement by macros.

Later, it was extended to support general window messages with no macro and named ketchup, intended to replace BEGIN_MSG_MAP of ATL/WTL.

Background

I read the book, C++ Template Metaprogamming, and I was inspired by the sample code, the finite state machine. WTL is the "Template" library..., so it's time to tie.

Requirements

Microsoft Visual C++ .NET Version 7.1, WTL 7.5 and Boost C++ libraries(No build required).
I did test the demo by Visual C++ .NET Standard Edition with Visual C++ Toolkit 2003.
ketchup itself is a header-only template library.

Concepts

MessageProcessor

A type which has a member function, whether virtual or not,

BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg,
  WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID);
The return value is TRUE if the message is fully handled; otherwise, it is FALSE.
This is the concept of ATL.

Derived

Any type which is derived from ketchup::message_processor<Derived>

ChainClass

any MessageProcessor from which a Derived is derived

MessageMap

An MPL ForwardSequence of a Entry

Entry

A type which has a static member function,

static bool process(Derived& derived, HWND hWnd, UINT uMsg,
  WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID, BOOL& bHandled);
The return value is true if the message is fully handled; otherwise, it is false.
I think that process has to be able to do everything BEGIN_MSG_MAP entries can, so dwMsgMapID and bHandled stand.

ID

A static constant of a window message id or command id

func

A member function of a Derived

Predefined Entries

message_handler<ID, func>

models of an Entry, and the type of func is compatible with ATL.

chain_msg<MessageMap>

models of an Entry, created from MessageMap

chain_msg_map<ChainClass>

models of an Entry, created from ChainClass

and...

every message map entry which BEGIN_MSG_MAP can have.

Using the code

The minimum code requires four steps.

step.1

Include headers and derive from ketchup::message_processor.

#include <boost/mpl/vector.hpp>
#include "ketchup/ketchup.hpp"

class CMainFrame : public CFrameWindowImpl<CMainFrame>,
    public CMessageFilter, public CIdleHandler,
    public ketchup::message_processor<CMainFrame>
{
CMainFrame conforms to a Derived.
CFrameWindowImpl<CMainFrame> conforms to a ChainClass.

step.2

Define message handlers as you did.

  LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
  {
    CreateSimpleToolBar();
    CreateSimpleStatusBar();
    // ...
  }

step.3

Define a MessageMap.

  struct message_map : boost::mpl::vector<
    message_handler<WM_CREATE, OnCreate>,
    command_id_handler<ID_APP_EXIT, OnFileExit>,
    command_id_handler<ID_FILE_NEW, OnFileNew>,
    command_id_handler<ID_VIEW_TOOLBAR, OnViewToolBar>,
    command_id_handler<ID_VIEW_STATUS_BAR, OnViewStatusBar>,
    command_id_handler<ID_APP_ABOUT, OnAppAbout>,
    chain_msg_map< CFrameWindowImpl<CMainFrame> >
  > { };
message_map conforms to a MessageMap.

step.4

Finally, override CMessageMap::ProcessWindowMessage as BEGIN_MSG_MAP did, by process_window_message provided by ketchup::message_processor.

  virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg,
    WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)
  {
    return process_window_message<message_map>(hWnd, uMsg,
      wParam, lParam, lResult, dwMsgMapID);
  }
Bear in mind that declarations of message handlers should be placed before message_map, and the message_map should be placed before ProcessWindowMessage. message_map is not a macro but a type.

Cracked Handlers

If cracked handlers not supported, nobody would call ketchup type-safe. I did write a <atlcrack.h> converter, using Boost.Xpressive. You can find the followings from the demo.

  struct message_map : boost::mpl::vector<
    chain_msg<cmd_ui_map>,
    msg_wm_paint<&CHelloView::OnPaint>, // cracked!
    command_range_handler<ID_BLACK, ID_WHITE, &CHelloView::OnColor>,
    command_id_handler<ID_CUSTOM, &CHelloView::OnCustomColor>
  > { };

MFC Update Command UI

ketchup also supports Updating Command UI mechanism of MFC, and the limited automatic-disable. You will find the following code from the demo.

  virtual BOOL OnIdle()
  {
    ketchup::update_toolbar_cmd_ui(m_hWnd, m_wndToolBar);
    return FALSE;
  }
  // ...
  
  void OnUpdateViewStatusBar(ketchup::cmd_ui& ui)
  {
    ui.set_check(::IsWindowVisible(m_hWndStatusBar));
  }

  struct message_map : boost::mpl::vector<
    menu_cmd_ui_generator,
    cmd_ui_handler_auto_enable<
      boost::mpl::vector_c<UINT,
          ID_BLACK, ID_RED, ID_GREEN, ID_BLUE,
          ID_WHITE, ID_CUSTOM, ID_SPEED_SLOW, ID_SPEED_FAST
      >,
      chain_mdi_child_cmd_ui
    >,
    cmd_ui_handler<ID_VIEW_TOOLBAR, &CMDIFrame::OnUpdateViewToolBar>,
    cmd_ui_handler<ID_VIEW_STATUS_BAR, &CMDIFrame::OnUpdateViewStatusBar>,
    // ...
  > { };
menu_cmd_ui_generator makes a cmd_ui object from WM_INITMENUPOPUP.
cmd_ui_handler_auto_enable makes a cmd_ui object enable if it is handled; otherwise, makes it disable.

Static Crash

The compiler generates a big if-statement from the MessageMap. ketchup has no runtime-map, but fatal compile-time errors may occur about the heap memory. I guess it is not ketchup's issue and if you encounter the problem...,

Decrease the preprocessor's work

Never generate boost::mpl::vector using BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS macro.
The default boost::mpl::vector can have up-to 20 elements. If you want a big boost::mpl::vector, you can chain MessageMaps with chain_msg.

Decrease debugging-time informations

Remove the debug-time compiler options /Z7,/Zi and /ZI.

Points of Interest

The last point is the performance. The demo is a WTL's sample, MDIDocVw from which BEGIN_MSG_MAPs are removed(the same bug, a confusion between m_hWndToolbar and commandbar, also stands). The program size seems not to be a problem. /O2 optimization generates the same size program as the original one. But VC++7.1 can't inline message handlers, unlike BEGIN_MSG_MAP. Could this be a problem of the speed? I guess... not.

History

23 May, 2005 - version 0.910
27 May, 2005 - version 0.940