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

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.
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.
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.
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.
Any type which is derived from ketchup::message_processor<Derived>
any MessageProcessor from which a Derived is derived
An MPL ForwardSequence of an 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.
A static constant of a window message id or command id
A member function of a Derived
A model of an Entry, and the type of func is compatible with ATL.
A model of an Entry, created from MessageMap
A model of an Entry, created from ChainClass
every message map entry which BEGIN_MSG_MAP can have.
The minimum code requires four steps.
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.Define message handlers as you did.
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
CreateSimpleToolBar();
CreateSimpleStatusBar();
// ...
}
OnCreate conforms to a func.
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.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.
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>
> { };
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.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...,
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.
Remove the debug-time compiler options /Z7,/Zi and /ZI.
A legacy but funny stuff, CTraceFn supported a tracing with stack-sensitive indentation. ketchup::class_trace does his job. It is not a macro, but the optimizer can easily remove it. That should be the modernism.
debug_entry<Entry> becomes empty on release-compile.
typedef ketchup::get_class_trace<CAboutDlg, true>::type trace;
LRESULT OnCloseCmd(WORD, WORD wID, HWND, BOOL&)
{
trace text(_T("OnCloseCmd"));
EndDialog(wID);
return 0;
}
struct message_map : boost::mpl::vector<
message_handler<WM_INITDIALOG, &CAboutDlg::OnInitDialog>,
debug_entry< command_id_handler<IDOK, &CAboutDlg::OnCloseCmd> >,
command_id_handler<IDCANCEL, &CAboutDlg::OnCloseCmd>
> { };
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.
23 May 2005 - version 0.910 (Initial Release)
27 May 2005 - version 0.940
30 May 2005 - version 0.950 (class_trace and debugging_entry added)
12 Jun 2005 - version 0.951 (debugging_entry removed; empty_entry, assert_entry and debug_entry added)