在Windows应用程序开发中,消息传递是非常重要的一部分。在你的应用程序中,你可能会需要响应多个系统相关的消息,如: WM_COMMAND、WM_PAINT、WM_SIZE、WM_MOUSEMOVE等。在MFC中,消息机制被高度封装提供了 CWnd::OnCommand、CWnd::OnPaint、CWnd::OnSize、CWnd::OnMouseMove等一系列成员函数,使得开发人员能以更高层次抽象方式去响应事件。
为了实现消息的分发和响应,MFC应用程序使用了消息映射表的概念。消息映射表是一张分派表,每一项对应着一个消息响应处理函数。开发人员可以使用 DECLARE_MESSAGE_MAP 宏去定义一个与窗口相关的消息映射表。
DECLARE_MESSAGE_MAP( )
官方文档给出了 DECLARE_MESSAGE_MAP 的定义:
```
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
```
目前,大部分Windows应用程序仍然在使用MFC。但在消息传递上,MFC 并不是完美的。
DECLARE_MESSAGE_MAP() 的定义是使用宏定义来实现的,使得开发人员可能使用大量消息映射表而不思考宏的使用背后的成本。在 MFC 消息映射面临的一个明显问题就是派生类会继承多个父类及其父类的消息处理函数,这样导致了大量的冗余,在MFC的消息响应函数中,调用者需要手动区分消息是由哪个控件产生的,这样使得在处理多个消息的时候复杂度增加,开发人员需要编写大量的代码来保证消息响应的正确性。MFC 表示了它的能力,当然,这些问题也不是没解决,反正也有一些开源的工具和库,如 WTL 等。
DECLARE_MESSAGE_MAP 的局限 manifest 在很久以前就被人认识到了,因此也出现了一些替代Declare_Message_Map的方案,例如:
TL,这是一个微软开源的GUI库,主要用于构建轻量级的 Windows 桌面应用程序。它以条约为基础,使用原生的 Windows API 来构建图形用户界面。
QT,一个跨平台的图形用户界面应用程序开发框架。
WxWidgets,轻量级的C++跨平台桌面软件开发框架
跳出框架,说说消息映射
现在我们回到 MFC 消息映射机制上,在声明一个类的时候,我们通常都会使用 DECLARE_MESSAGE_MAP 宏来声明一个消息映射表。但是,我们知道 DECLARE_MESSAGE_MAP 是一个宏定义。它只能在类/头文件中申明一次,不能在类的声明中重复使用。所以,需要在 .cpp 中使用 BEGIN_MESSAGE_MAP和 END_MESSAGE_MAP 来实现它的实现。
BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 实际上就是使用 C++ 语言中类的构造和析构函数的配置来创建和销毁消息映射的。
BEGIN_MESSAGE_MAP 的定义:
```
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
static const AFX_MSGMAP_ENTRY _messageEntries[] = \
{
```
END_MESSAGE_MAP 的定义:
```
{ 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
static const AFX_MSGMAP messageMap = \
{ \
&TheBaseClass::GetThisMessageMap, \
&_messageEntries[0], \
(UINT*)&_messageEntries[ \
sizeof(_messageEntries) / \
sizeof(AFX_MSGMAP_ENTRY) - 1 \
].nMessage, \
sizeof(AFX_MSGMAP_ENTRY), \
&afxMsgReg, \
&_messageEntries[ \
sizeof(_messageEntries) / \
sizeof(AFX_MSGMAP_ENTRY) - 1 \
] \
}; \
return &messageMap; \
}
```
当我们定义一个DECLARE_MESSAGE_MAP 的时候,实际上对应了一个BEGIN_MESSAGE_MAP ,这里会声明一些需要删除或修改的消息。消息结构体的主要几个字段是:
nID: 每个消息的唯一标识符。
nMessage: 每个消息的消息类型。
nSig: 每个消息处理函数的返回值。
用 C++11 移动语义优化
在 C++11 中,移动语义成为了新的特征之一。它让我们可以快速而安全地转移(重用)在已经移动的对象上。它正在改变我们的 C++ 代码和开发习惯。现在,在C++11 中,你可以通过使用 std::vector,去优化 DECLARE_MESSAGE_MAP 实现。
typedef 的完美转移
假设我们要在这个 vector 容器中填充所有的消息:
```
std::vector
entries.reserve(N); // 申请 N 个消息映射入口的空间
for (int i = 0; i < N; i++) {
MSGMAP_ENTRY-entry = { ... };
entries.push_back(std::move(entry));
}
```
你可能会问为什么使用std::move?因为在这里,我们将使用移动语义来“向量条目”转移消息映射条目。这样做就有一个好处,可以避免就地构建条目,这将在容器添加时会导致复制多次构建。
Begin_Message_Map的优化
由于我们的目的是智能化 Declare_Message_Map,我们需要重写 BEGIN_MESSAGE_MAP 的实现方法,以支持 std::vector。如下所示:
struct ProtocolEntry {
UINT nMessage;
UINT nID;
UINT nSig;
AFX_PMSG pfn;
};
struct Protocol {
using const_iterator = const ProtocolEntry*;
using iterator = ProtocolEntry*;
Protocol() = default;
constexpr Protocol(size_t size) noexcept { _entries.reserve(size); }
Protocol(const Protocol&) = delete;
Protocol& operator= (const Protocol&) = delete;
Protocol(Protocol&&) = default;
Protocol& operator= (Protocol&&) = default;
bool empty() const noexcept { return _entries.empty(); }
void push_back(ProtocolEntry&& entry) noexcept {
_entries.push_back(std::move(entry));
}
auto begin() noexcept -> iterator { return _entries.begin(); }
auto end() noexcept -> iterator { return _entries.end(); }
auto cbegin() const noexcept -> const_iterator { return _entries.cbegin(); }
auto cend() const noexcept -> const_iterator { return _entries.cend(); }
size_t size() const noexcept { return _entries.size(); }
private:
std::vector
};
using MsgHandlerFunc = LRESULT(__thiscall CWnd::*)(WPARAM, LPARAM);
template
class TMessageMap : public CAtlMap
public:
using CMessageMapBase = CAtlMap
TMessageMap() = default;
~TMessageMap() = default;
MsgHandlerFunc Lookup(UINT message) const noexcept {
MsgHandlerFunc pf = nullptr;
if (CMessageMapBase::Lookup(message, pf)) {
return pf;
}
if (auto pMsg = (AFX_MSGMAP_ENTRY*)_msgMapEntries.cbegin();
!_msgMapEntries.empty()) {
pMsg += _msgMapEntries.size() - 1;
while (pMsg >= _msgMapEntries.cbegin()) {
if (pMsg->nMessage == message) {
return (ThisClass::pfnSig_t)(pMsg->nSig) -
(ThisClass::pfnSig_t)0x8000;
}
pMsg--;
}
}
return nullptr;
}
template
void Init(const AFX_MSGMAP_ENTRY(&entryArray)[Count], ThisClass* pThis) {
//for-loop: using structured binding
for (const auto&[nMessage, nID, nSig, pfn] : entryArray) {
auto msghandler = (MsgHandlerFunc)(pfn ? pfn :
GetThisMessageMap()->Lookup(nMessage));
ATLASSERT(nullptr != msghandler);
CMessageMapBase::Add(nMessage, msghandler);
if (nullptr == pfn) {
ProtocolEntry entry;
entry.nMessage = nMessage;
entry.nSig = nSig - (ThisClass::pfnSig_t)0x8000;
entry.nID = nID;
entry.pfn = nullptr;
_msgMapEntries.push_back(std::move(entry));
}
}
}
// Forwarding calls to our CAtlMap object
POSITION GetStartPosition() const noexcept { return CMessageMapBase::GetStartPosition(); ; }
void GetNextAssoc(POSITION& rPosition, UINT& rKey, MsgHandlerFunc& rValue) const noexcept { CMessageMapBase::GetNextAssoc(rPosition, rKey, rValue); }
void RemoveAll() noexcept { CMessageMapBase::RemoveAll(); }
};
// 注意:真正的 TMessageMap 派生类必须使用 DECLARE_DYNAMIC_MESSAGE_MAP。
template
constexpr bool CheckIfBaseIsCWindow() noexcept {
using BaseType = std::remove_const_t decltype(std::addressof(std::declval return std::is_same_v } template class TMessageMapTraits : public CWindowImplBaseT template class TMessageMapTraits public: ATL_NOINLINE TMessageMap return &_msgMap; } ATL_NOINLINE static const AFX_MSGMAP_ENTRY* GetThisMessageMap() noexcept { return T::template GetMessageEntries } private: TMessageMap }; template class TMessageMapBase : public Base { public: static const AFX_MSGMAP* AFXAPI GetMessageMap() noexcept { static const AFX_MSGMAP messageMap = { &Base::GetMessageMap, T::template GetMessageEntries T::nFirstMsg - 1, sizeof(AFX_MSGMAP_ENTRY), &afxMsgReg, T::template GetMessageEntries T::nLastMsg - T::nFirstMsg }; return &messageMap; } public: static const AFX_MSGMAP_ENTRY* WINAPI GetThisMessageMap() noexcept { return T::template GetMessageEntries } protected: static constexpr const UINT nFirstMsg = T::msgFirst; static constexpr const UINT nLastMsg = T::msgLast + 1; static constexpr const TMessageMapBase *_pThis = (const T*)nullptr; }; ``` END_MESSAGE_MAP 的优化实现 ``` #define DECLARE_MESSAGE_MAP() \ public: \ struct _PROTOCOL_ENTRY_DEF1 { UINT nMessage; UINT nID; \ UINT nSig; AFX_PMSG pfn; }; \ struct _PROTOCOL_DEF1 { typedef _PROTOCOL_ENTRY_DEF1 _tElement;\ typedef _tElement value_type;\ typedef _tElement& reference;\ typedef const _tElement& const_reference;\ typedef _tElement* pointer;\ typedef const _tElement* const_pointer;\ typedef ptrdiff_t difference_type;\ typedef size_t size_type;\ typedef _tElement* iterator;\ typedef const _tElement* const_iterator;\ _PROTOCOL_DEF1( ) noexcept \ : _entries((size_t)(0)) { }\ ~ _PROTOCOL_DEF1( ) noexcept \ { }\ size_type size( ) const noexcept \ { return static_cast bool empty( ) const noexcept \ { return _entries.empty( ) ; }\ iterator begin( ) noexcept \ { return _entries.begin( ) ; }\ iterator end( ) noexcept \ { return _entries.end( ) ; }\ const_iterator begin( ) const noexcept \ { return _entries.begin( ) ; }\ const_iterator end( ) const noexcept \ { return _entries.end( ) ; }\ reference front( ) noexcept \ { return _entries.front( ) ; }\ const_reference front( ) const noexcept \ { return _entries.front( ) ; }\ reference back( ) noexcept \ { return _entries.back( ) ; }\ const_reference back( ) const noexcept \ { return _entries.back( ) ; }\ void clear( ) noexcept \ { _entries.clear( ) ; }\ iterator erase( iterator pos ) noexcept \ { return _entries. erase( pos ) ; }\ iterator erase( iterator first, iterator last ) noexcept \ { return _entries. erase( first, last ) ; }\ void resize( size_type nCount ) noexcept \ { _entries. resize( nCount ) ; }\ reference at( size_type nIndex ) \ { return _entries.at( nIndex ) ; }\ const_reference at( size_type nIndex ) const \ { return _entries.at( nIndex ) ; }\ reference operator[]( size_type nIndex ) noexcept \ { return _entries[ nIndex ] ; }\ const_reference operator[]( size_type nIndex ) const noexcept \ { return _entries[ nIndex ] ; }\ void reserve( size_type nNewCapacity ) noexcept \ { _entries. reserve( nNewCapacity ) ; }\ void push_back( const value_type& value ) noexcept \ { _entries. push_back( value ) ; }\ void push_back( value_type&& value ) noexcept \ { _entries. push_back( std::forward void append( const _PROTOCOL_DEF1 & proto ) noexcept \ { _entries. insert( end(), proto. begin( ), proto. end( ) ) ; }\ private: \ std::vector< _PROTOCOL_ENTRY_DEF1 > _entries; \ private: \ static const AFX_MSGMAP_ENTRY * WINAPI _GetThisMessageMap( ) noexcept \ { typedef CTMessageMap< CThisClass, CBaseClass > ThisMap; \ static const ThisMap::value_type _messageEntries[ ] = \ { #define END_MESSAGE_MAP() \ { AFX_MSGMAP_ENDMESSAGE } \ }; \ static TMessageMap< CWnd, CWindow > _staticMsgMap; \ static const ThisMap _messageMap( ) noexcept \ { ThisMap map( ); \ const ThisMap::value_type * pEntry = _messageEntries; \ while ( pEntry → nSig != AfxSig_end ) \ { ThisMap::pfnSig_t pfn = ( ThisMap::pfnSig_t ) \ pEntry -> nSig; \ if ( pEntry -> nIDeet ) \ map.Add( pEntry -> nID, pfn ) ; \ else \ { _staticMsgMap. push_back( ( CWnd * ) 0, \ pEntry → nMessage, pEntry → nSig, ( AFX_PMSG ) pfn ) ; \ } \ pEntry ++; } \ return map ; \ } \ static const AFX_MSGMAP messageMap; \ virtual ThisMap *PASCAL GetMessageMap( ) const noexcept { \ return ( ThisMap * ) &_messageMap ; } \ static const AFX_MSGMAP * PASCAL _GetBaseMessageMap( ) noexcept \ { return &CBaseClass::messageMap ; } \ static const AFX_MSGMAP * PASCAL GetThisMessageMap( ) noexcept \ { return &_staticMsgMap ; } \ }; \ const AFX_MSGMAP CThisClass::messageMap = \ { _GetBaseMessageMap, \ &_messageEntries[ 0 ], \ ( UINT * ) &_messageEntries[ \ sizeof( _messageEntries ) / sizeof( AFX_MSGMAP_ENTRY ) - 1 \ ].nMessage, \ sizeof( AFX_MSGMAP_ENTRY ), \ &afxMsgReg, \ &_messageEntries[ \ sizeof( _messageEntries ) / sizeof( AFX_MSGMAP_ENTRY ) - 1 \ ] \ } ; \ const AFX_MSGMAP * CThisClass::GetMessageMap( ) const noexcept \ { return &messageMap ; } \ friend class TMessageMapTraits< CThisClass, \ CheckIfBaseIsCWindow< CThisClass >( ) > ; ``` DECLARE_MESSAGE_MAP 宏 我们通过DECLARE_MESSAGE_MAP 宏,实现了一个可读性高的消息映射表。 这个声明使用vector容器在