标题也许不恰当,不必在意。本文主要是记录一种 消息(事件)==》消息响应(事件处理)的映射方式,避免使用大量的消息宏定义。
对于传统的win32窗口、或者mfc窗口、或者duilib窗口等,常见的使用场景比如:
工作线程执行某个函数处理=》处理完毕通知主线程(UI线程)=》主线程收到通知做出对应的处理
在这个过程中,通知主线程通常使用PostMessage或者SendMessage等函数来发送消息的方式。我们需要定义很多的消息宏定义来对消息做区分,以便于主线程收到消息后知道调用哪些消息响应函数。这样我们就需要写很多宏定义,还要给宏定义加注释,还要写对应的映射关系代码,比较累,后续分析代码时也需要跳来跳去有些费劲。
因此本文提供一种方法示例,能够避免写这些消息宏定义,更直观的知道接下来需要调用哪些函数处理。先看代码:
/*这个宏定义可以写到统一公共的头文件里面,避免值重复了
#define WM_MY_MSG_DISPATCHER1 WM_USER + 1001 //自定义消息分发1,多路分发,防止一个阻塞后后续消息无法处理
#define WM_MY_MSG_DISPATCHER2 WM_USER + 1002 //自定义消息分发2
#define WM_MY_MSG_DISPATCHER3 WM_USER + 1003 //自定义消息分发3
#define WM_MY_MSG_DISPATCHER4 WM_USER + 1004 //自定义消息分发4
*/
/***一个自动锁类,单独一个.h文件即可***/
//CKCritSec.h
//
#ifndef __KCRITSEC_H__
#define __KCRITSEC_H__
#include <windows.h>
#pragma warning(disable:4800)
class CKCritSec
{
public:
CKCritSec()
{
InitializeCriticalSection(&m_CritSec);
};
~CKCritSec()
{
DeleteCriticalSection(&m_CritSec);
};
void Lock()
{
EnterCriticalSection(&m_CritSec);
};
void Unlock()
{
LeaveCriticalSection(&m_CritSec);
};
protected:
CRITICAL_SECTION m_CritSec;
};
class CKAutoLock
{
public:
CKAutoLock(CKCritSec * plock)
{
m_pLock = plock;
if (m_pLock)
{
m_pLock->Lock();
}
};
~CKAutoLock() {
if (m_pLock)
{
m_pLock->Unlock();
}
};
protected:
CKCritSec * m_pLock;
};
class CKEvent
{
public:
CKEvent(bool bManualReset = false)
{
m_hEvent = CreateEvent(NULL, bManualReset, false, NULL);
};
virtual ~CKEvent()
{
CloseHandle(m_hEvent);
};
public:
bool Set(void)
{
bool bRet = false;
bRet = ::SetEvent(m_hEvent);
return bRet;
};
bool Wait(unsigned long ulTimeout)
{
if (ulTimeout == 0)
ulTimeout = 0xffffffff;
bool bRet = false;
if(WAIT_OBJECT_0 == WaitForSingleObject(m_hEvent, ulTimeout))
bRet = true;
return bRet;
};
bool Reset(void)
{
bool bRet = false;
bRet = ::ResetEvent(m_hEvent);
return bRet;
};
protected:
HANDLE m_hEvent;
};
#endif
/****消息分发处理,一个MsgDispatch.h文件即可********/
#pragma once
template<class T>
class CMsgDispatch
{
public:
CMsgDispatch()
{
};
virtual ~CMsgDispatch()
{
CKAutoLock theLock(&m_lockMsg);
m_dequeMsg.clear();
};
typedef void(T::*POnMsgFunc)(WPARAM wParam, LPARAM lParam);
typedef struct tagMsg
{
tagMsg()
{
pFunc = NULL;
wParam = 0;
lParam = 0;
};
tagMsg& operator=(const tagMsg& value)
{
pFunc = value.pFunc;
wParam = value.wParam;
lParam = value.lParam;
return *this;
};
POnMsgFunc pFunc;
WPARAM wParam;
LPARAM lParam;
}Msg;
public:
void OnDispatch()
{
Msg theMsg;
while (GetPostMsg(theMsg))
{
POnMsgFunc pFunc = theMsg.pFunc;
(((T*)this)->*pFunc)(theMsg.wParam, theMsg.lParam);
}
};
void PostMsg(HWND hWnd, POnMsgFunc pMsgFunc, WPARAM wParam = 0, LPARAM lParam = 0)
{
AddPostMsg(pMsgFunc, wParam, lParam);
::PostMessage(hWnd, WM_MY_MSG_DISPATCHER1, 0, 0);
::PostMessage(hWnd, WM_MY_MSG_DISPATCHER2, 0, 0);
::PostMessage(hWnd, WM_MY_MSG_DISPATCHER3, 0, 0);
::PostMessage(hWnd, WM_MY_MSG_DISPATCHER4, 0, 0);
};
void AddPostMsg(POnMsgFunc pMsgFunc, WPARAM wParam, LPARAM lParam)
{
CKAutoLock theLock(&m_lockMsg);
Msg theMsg;
theMsg.pFunc = pMsgFunc;
theMsg.wParam = wParam;
theMsg.lParam = lParam;
m_dequeMsg.push_back(theMsg);
};
bool GetPostMsg(Msg& theMsg)
{
CKAutoLock theLock(&m_lockMsg);
if (m_dequeMsg.size() > 0)
{
theMsg = m_dequeMsg.front();
m_dequeMsg.pop_front();
return true;
}
return false;
}
CKCritSec m_lockMsg;
deque<Msg> m_dequeMsg;
};
上面是两个.h文件的内容,我写到一起了,一个是封装的互斥锁,一个是消息分派的模版类。这就完成了定义,使用的时候这样(以duilib中的窗口作为示例,其他win32或mfc的窗口原理类似):
class CMainWnd : public CWindowWnd, public INotifyUI, public CMsgDispatch<CMainWnd>
{
public:
CMainWnd();
~CMainWnd();
virtual LPCTSTR GetWindowClassName() const { return _T("CMainWnd"); }
virtual void Notify(TNotifyUI& msg);
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
virtual LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
UINT GetClassStyle() const { return CS_DBLCLKS; }; //参考360DEMO
public:
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
void CloseDlg(WPARAM wParam, LPARAM lParam);
void OnInitHostSuccess(WPARAM wParam, LPARAM lParam);
void OnInitHostFailed(WPARAM wParam, LPARAM lParam);
protected:
CPaintManagerUI m_PM;
};
LRESULT CMainWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
BOOL bHandled = TRUE;
switch (uMsg)
{
case WM_CREATE: lRes = OnCreate(uMsg, wParam, lParam); break;
case WM_NCCALCSIZE: break;
case WM_NCACTIVATE: lRes = 1; break;
case WM_NCPAINT: break;
case WM_NCHITTEST: lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break;
case WM_SIZE: lRes = OnSize(uMsg, wParam, lParam, bHandled); break;
case WM_NCLBUTTONDBLCLK: bHandled = TRUE; break;
case WM_MY_MSG_DISPATCHER1: OnDispatch(); break;
case WM_MY_MSG_DISPATCHER2: OnDispatch(); break;
case WM_MY_MSG_DISPATCHER3: OnDispatch(); break;
case WM_MY_MSG_DISPATCHER4: OnDispatch(); break;
default:
bHandled =FALSE;
}
if( bHandled ) return lRes;
if( m_PM.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes;
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
//其他线程函数中
void InitHostThread(ULONG_PTR lParam)
{
if (true)
{
g_pMainWnd->PostMsg(g_pMainWnd->GetHWND(),&CMainWnd::OnInitHostSuccess);
return;
}
g_pMainWnd->PostMsg(g_pMainWnd->GetHWND(), &CMainWnd::OnInitHostFailed);
}
上述使用主要有两点:1.窗口类要继承这个模板类,模版参数要改为当前的窗口类。2.消息响应函数必须是定义的成员函数指针那样的形式(void返回值,一个WPARAM参数,一个LPARAM参数)。
每一个想要自己添加一些消息映射的窗口,都可以上述方法使用,这样在其他线程想让UI线程来执行某些函数时,直接指定对应的函数即可,不用再做消息宏定义,消息映射等。
对于MFC或者其他的win32窗口等,略微修改即可同样使用。
个人水平有限,欢迎讨论指正,也欢迎提出更好的思路。