The ketchup message map library

Author: MB
Contact: mb2act@yahoo.co.jp
License:Distributed under the Common Public License 1.0
Version: 0.995

Contents

Preface

Four yeas ago, I made a program. Everything WTL originally had was useless except win32 thin wrappers. As CUpdateUI was the one of them, I made the replacement by macros. Later, I read the book, C++ Template Metaprogramming, and I was inspired by the sample code, the finite state machine. That could remove the marcos and the result was named ketchup, intended to replace BEGIN_MSG_MAP of ATL/WTL.

In time, the experience of making biscuit gave me the way of avoiding compile-time crashes. Now that ketchup is the type-safe synonym of BEGIN_MSG_MAP.

Introduction

ketchup is a message map generator implemented using class templates. The templates allow us to write type-safe BEGIN_MSG_MAP.

A simple BEGIN_MSG_MAP macro snippet:

BEGIN_MSG_MAP(CMDIFrame)
  MESSAGE_HANDLER(WM_CREATE, OnCreate)
  COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
  COMMAND_ID_HANDLER(ID_FILE_NEWBOUNCE, OnBounce)
  CHAIN_MSG_MAP(CMDIFrameWindowImpl<CMDIFrame>)
END_MSG_MAP()

is approximated using ketchup's facilities as seen in this code snippet:

struct msg_map : ketchup::entry_set<CMDIFrame>
{
  typedef sequence<
    message_handler<WM_CREATE, &_::OnCreate>,
    command_id_handler<ID_APP_EXIT, &_::OnFileExit>,
    command_id_handler<ID_FILE_NEWBOUNCE, &_::OnBounce>,
    chain_msg_map< CMDIFrameWindowImpl<_> >
  > type;
};

Requirements

Quick Start

  1. Include headers:

    #include "ketchup/ketchup.hpp"
    
    class CMainFrame : 
      public CFrameWindowImpl<CMainFrame>,
      public CUpdateUI<CMainFrame>,
      public CMessageFilter, public CIdleHandler
    {
    
  2. Define message handlers as you did:

    private:
      LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
      {
        CreateSimpleToolBar();
        CreateSimpleStatusBar();
        // ...
      }
      // ...
    
  3. Define your message map:

      struct msg_map : ketchup::entry_set<CMainFrame>
      {
        typedef sequence<
          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< CUpdateUI<_> >,
          chain_msg_map< CFrameWindowImpl<_> >
        > type;
      };
    
  4. Finally, override CMessageMap::ProcessWindowMessage as BEGIN_MSG_MAP insidiously did, using ketchup::process_window_message:

    public:
      virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg,
        WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)
      {
        return ketchup::process_window_message<msg_map>(
          *this, hWnd, uMsg,wParam, lParam, lResult, dwMsgMapID);
      }
    };
    

Bear in mind that declarations of message handlers should be placed before the entry, and the entry should be placed before ProcessWindowMessage. An entry is not a macro but a type. C++ Standard doesn't allow you to abbreviate the syntax of member function pointers.

Basic Concepts

Metafunction

A metafunction is a class or a class template that represents a function invocable at compile-time. Further documents are available at Boost.MPL.

Message Processor

A message processor is any type that has the 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 from ATL.

Message Map

A message map is any type that is a nullary metafunction which returns an entry.

Derived

A derived is any type of object that is passed to ketchup::process_window_message as the first argument. This name is somewhat historical.

Chain Class

A chain class is any message processor from which a derived is derived. This concept also comes from ATL.

Entry

An entry is any type that has the static member function:

static bool process(Derived& derived, HWND hWnd, UINT uMsg,
  WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID);

id

An id is any static constant of a window message id or command id.

func

A func is any member function of a derived.

Predefined Entry Wrappers

All the entries and wrappers are in the scope of ketchup::entry_set<derived>, which defines a nested _ type that refers to the derived.

message_handler

message_handler makes an entry of message handler from id and func:

template< UINT id, LRESULT (Derived::*func)(UINT,WPARAM,LPARAM,BOOL&) >
struct message_handler;

sequence

sequence makes an entry from entries. The maximum size of template argument arity is thirty. If the size of your entries comes greater than thirty, you can chain the entries. Keep in mind that the entry made by sequence is also an entry:

struct msg_map : ketchup::entry_set<CHelloView>
{
  struct cmd_ui_map_sub;  // forward declaration also works fine.
    
  struct cmd_ui_map : sequence<
    cmd_ui_handler<ID_BLACK, &_::OnUpdateBlack>,
    cmd_ui_handler<ID_RED, &_::OnUpdateRed>,
    cmd_ui_handler<ID_GREEN, &_::OnUpdateGreen>,
    cmd_ui_map_sub        // chain
  > { };

  struct cmd_ui_map_sub : sequence<
    cmd_ui_handler<ID_BLUE, &_::OnUpdateBlue>,
    sequence<             // chain
      cmd_ui_handler<ID_WHITE, &_::OnUpdateWhite>,
      cmd_ui_handler<ID_CUSTOM, &_::OnUpdateCustom>
    >
  > { };

  typedef sequence<
    cmd_ui_map,           // chain
    msg_wm_paint<&_::OnPaint>,
    command_range_handler<ID_BLACK, ID_WHITE, &_::OnColor>,
    command_id_handler<ID_CUSTOM, &_::OnCustomColor>
  > type;
};

and...

ketchup has every synonym of the BEGIN_MSG_MAP macro.

Cracked Handlers

ketchup supports cracked handlers of WTL:

LRESULT OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
{
  m_pView = new CHelloView();
  RECT rect = { 0, 0, 1, 1 };
  m_hWndClient = m_pView->Create(m_hWnd, rect, NULL, WS_CHILD | WS_VISIBLE, WS_EX_CLIENTEDGE);

  // SetMsgHandled(FALSE);
  return 1;
}

struct msg_map : ketchup::entry_set<CHelloWnd>
{
  typedef sequence<
    msg_wm_create_not_handled<&_::OnCreate>, // cracked!
    chain_client_commands,
    chain_client_cmd_ui,
    chain_msg_map< CMDIChildWindowImpl<_> >
  > type;
};

SetMsgHandled of WTL was rejected for transparency. Instead of that, you can refer using entry. By referring at compile-time using entry, program size becomes a little smaller. If you want to decide whether or not to handle a message at runtime, you must use message_handler.

MFC Update Command UI

ketchup supports Updating Command UI mechanism of MFC and the limited automatic-disable:

void OnUpdateViewStatusBar(ketchup::cmd_ui& ui)
{
  ui.set_check(::IsWindowVisible(m_hWndStatusBar));
}

virtual BOOL OnIdle()
{
  ketchup::update_toolbar_cmd_ui(m_hWnd, m_wndToolBar);
  return FALSE;
}

struct msg_map : ketchup::entry_set<CMDIFrame>
{
  typedef ketchup::id_set<
    ID_BLACK, ID_RED, ID_GREEN, ID_BLUE, ID_WHITE,
    ID_CUSTOM, ID_SPEED_SLOW, ID_SPEED_FAST
  > child_cmd_ids;

  typedef sequence<
    cmd_ui_update_menu,
    cmd_ui_handler<ID_VIEW_TOOLBAR, &_::OnUpdateViewToolBar>,
    cmd_ui_handler<ID_VIEW_STATUS_BAR, &_::OnUpdateViewStatusBar>,
    cmd_ui_enable_if_handled<chain_mdi_child_cmd_ui, child_cmd_ids>,
    // ...
  > type;
};

This is a replacement for CUpdateUI of WTL. cmd_ui_update_menu generates a ketchup::cmd_ui object from WM_INITMENUPOPUP. cmd_ui_enable_if_handled enables a ketchup::cmd_ui object if it is handled; otherwise, disables it.

Compatibility

ketchup is compatible with BEGIN_MSG_MAP. KETCHUP_CHAIN_MSG does it. You can enjoy and test ketchup with no harm:

struct msg_map : ketchup::entry_set<CBounceWnd>
{
  typedef sequence<
    message_handler<WM_CREATE, &_::OnCreate>,
    chain_client_cmd_ui
  > type;
};

BEGIN_MSG_MAP(CBounceWnd)
  // MESSAGE_HANDLER(WM_CREATE, OnCreate)
  KETCHUP_CHAIN_MSG(msg_map)
  CHAIN_CLIENT_COMMANDS()
  CHAIN_MSG_MAP(CMDIChildWindowImpl<CBounceWnd>)
END_MSG_MAP()

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. VC++7.1 generates the same size program as the original one, because ketchup's message map is almost same as BEGIN_MSP_MAP. But VC++7.1 can't inline message handlers, unlike BEGIN_MSG_MAP. Could this be a problem of the speed?

Well, ketchup is based on the finite state machine of C++ Template Metaprogramming. ketchup couldn't adopt the simple CRTP found at the book, because message processor may be base class and the multiple inheritance makes ambiguity, hundreds of entries. Now, yet another layer of abstraction of ketchup is a nested metafunction, message map. ketchup::entry_set<derived> seems to be constants that the metafunction can use. By deriving from it, all the entries are hidden and live only in the scope. ketchup favors type composition over CRTP as you favor object composition over class inheritance.

By the way, ketchup must be the first application using Boost.Xpressive for the implementation.

References

Release Notes

The version 0.960 or later no longer depends on MPL Sequence of Boost.MPL. Instead of that, it depends on partial class template specialization, so you can see and easily debug your code.