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)