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 a 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
models of an Entry, and the type of func is compatible with ATL.
models of an Entry, created from MessageMap
models 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(); // ... }
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.
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
27 May, 2005 - version 0.940