Windows消息

众所周知,Windows消息有两种:队列话消息和非队列话消息。队列话消息是被Windows操作系统放入消息队列的,程序通过主消息循环不断的从消息队列中取出消息并分发到各自的窗体调用消息处理函数处理。而非队列话消息是不传入消息队列,直接呼叫消息处理函数处理的。

WTL消息接收与分发

对于非队列话的消息,windows会直接调用对应窗体的消息处理函数进行处理,所以不需要进行任何包装,只要注册消息处理函数即可,这里不讨论。

在上一骗文章中讲到每个界面线程会对应一个CMessageLoop,在线程启动的时候加入到全局的_Module对象中(维护了一个ATL::CSimpleMap<DWORD, CMessageLoop*>类型的map)。在CMessageLoop内部维护了一个CMessageFilter和一个CIdleHandler的列表具体定义为:ATL::CSimpleArray<CMessageFilter*> m_aMsgFilter;

ATL::CSimpleArray<CIdleHandler*> m_aIdleHandler;

在任何地方都可以通过全局的_Module对象获得当前线程的CMessageLoop,通过调用AddMessageFilter方法向其中添加CMessageFilter,在主消息循环内部,会在调用TranslateMessage和DispatchMessage之前遍历CMessageFilter的列表,调用其内部唯一的成员函数PreTranslateMessage对消息进行处理。这是WTL消息处理的主要特性之一,所有实现了CMessageFilter的类都可以实现对队列话消息的处理,这大大增加了消息处理的灵活性和可封装性。

WTL的另一个特性是在消息循环内部添加了空闲处理,所有实现了CIdleHandler的类都可以通过AddIdleHandler添加到CIdleHandler列表内,在消息循环内部,当程序通过::PeekMessage检测消息队列中没有消息时,就会遍历这个列表,调用其中的OnIdle函数进行空闲处理。下面来看具体的调用过程:

 1int Run()
2 {
3 BOOL bDoIdle = TRUE;
4int nIdleCount =0;
5 BOOL bRet;
6
7for(;;)
8 {
9//如果消息队列中没有消息,则进行空闲处理
10while(bDoIdle &&!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE))
11 {
12if(!OnIdle(nIdleCount++))
13 bDoIdle = FALSE;
14 }
15
16 bRet = ::GetMessage(&m_msg, NULL, 0, 0);
17
18if(bRet ==-1)
19 {
20 ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)\n"));
21continue; // error, don't process
22 }
23elseif(!bRet)
24 {
25 ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n"));
26break; // WM_QUIT, exit message loop
27 }
28//在分发消息之前,进行消息处理
29if(!PreTranslateMessage(&m_msg))
30 {
31 ::TranslateMessage(&m_msg);
32 ::DispatchMessage(&m_msg);
33 }
34
35if(IsIdleMessage(&m_msg))
36 {
37 bDoIdle = TRUE;
38 nIdleCount =0;
39 }
40 }
41
42return (int)m_msg.wParam;
43 }

PreTranslateMessage的实现:

 1 // Override to change message filtering
2  virtual BOOL PreTranslateMessage(MSG* pMsg)
3 {
4 // loop backwards
5 for(int i = m_aMsgFilter.GetSize() -1; i >=0; i--)
6 {
7 CMessageFilter* pMessageFilter = m_aMsgFilter[i];
8 if(pMessageFilter != NULL && pMessageFilter->PreTranslateMessage(pMsg))
9 return TRUE;
10 }
11 return FALSE; // not translated
12 }

OnIdle函数的实现:

 1 // override to change idle processing
2 virtual BOOL OnIdle(int/*nIdleCount*/)
3 {
4 for(int i =0; i < m_aIdleHandler.GetSize(); i++)
5 {
6 CIdleHandler* pIdleHandler = m_aIdleHandler[i];
7 if(pIdleHandler != NULL)
8 pIdleHandler->OnIdle();
9 }
10 return FALSE; // don't continue
11 }

代码灰常简单易懂,应该不需要再解释了。

以上是WTL的主消息循环,实现了对消息的接收、分发、分发前的消息处理和空闲处理。下面来看WTL窗体的消息处理。

WTL消息处理

上面提到的在主消息循环内部分发前的消息处理和空闲处理,较为简单,此处不再讨论。

在上篇文章中提到,windows在创建窗体的时候会要求提供一个消息处理函数,这个窗体的接收到的所有消息都会调用这个消息处理函数进行处理,他的定义为:
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

这个一个全局静态函数,这大大破坏了程序的封装性,因此WTL(确切的说应该是ATL)引入了Thunk机制来解决这个问题,具体流程如下:
首先每个窗体类内部都定义了一个CWndProcThunk对象,窗体在创建时,会注册WNDCLASS,在CWindowImpl内部是通过宏DECLARE_WND_CLASS(NULL)完成的,这里注册的消息处理函数为StartWindowProc。然后,通过调用全局_Module对象的AddCreateWndData方法对CWndProcThunk对象中的_AtlCreateWndData成员变量进行赋值并将它加入到全局_Module对象的链表中,这个对象内部保存了当前窗口对象的指针和当前线程ID。完成窗体创建之后,窗体接收到第一个消息,会调用StartWindowProc进行消息处理,StartWindowProc具体定义为:

 1 template <class TBase, class TWinTraits>
2 LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3 {
4 //获得当前对象指针
5 CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_Module.ExtractCreateWndData();
6 ATLASSERT(pThis != NULL);
7 pThis->m_hWnd = hWnd;
8 //Thunk初始化
9 pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
10 WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);
11 //设置新的消息处理函数
12 WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
13 #ifdef _DEBUG
14 // check if somebody has subclassed us already since we discard it
15 if(pOldProc != StartWindowProc)
16 ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\n"));
17 #else
18 pOldProc; // avoid unused warning
19 #endif
20 //调用新的消息处理函数处理这个消息
21 return pProc(hWnd, uMsg, wParam, lParam);
22 }

首先从全局_Module对象中取出当前对象指针,并对这个指针m_hWnd赋值,m_thunk进行初始化。初始化后的m_thunk的首地址变成了初始化时传入的消息处理函数,并且此函数的第一个参数变成了pThis指针。因此在新的消息处理函数内强制转化一下,就可以使用这个pThis指针了。得到新的消息处理函数之后,调用SetWindowLong把当前窗体的消息处理函数设置为新的消息处理函数。新的消息处理函数定义如下:

 1 template <class TBase, class TWinTraits>
2 LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3 {
4 //强制转化为pThis指针
5 CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
6 // set a ptr to this message and save the old value
7 MSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };
8 const MSG* pOldMsg = pThis->m_pCurrentMsg;
9 pThis->m_pCurrentMsg =&msg;
10 // pass to the message map to process
11 LRESULT lRes;
12 //调用pThis的ProcessWindowMessage消息处理函数进行消息处理
13 BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
14 // restore saved value for the current message
15 ATLASSERT(pThis->m_pCurrentMsg ==&msg);
16 pThis->m_pCurrentMsg = pOldMsg;
17 //如果消息没有被处理,调用默认的消息处理函数
18 if(!bRet)
19 {
20 if(uMsg != WM_NCDESTROY)
21 lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
22 else
23 {
24 // unsubclass, if needed
25 LONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC);
26 lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
27 if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc)
28 ::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc);
29 // clear out window handle
30 HWND hWnd = pThis->m_hWnd;
31 pThis->m_hWnd = NULL;
32 // clean up after window is destroyed
33 pThis->OnFinalMessage(hWnd);
34 }
35 }
36 return lRes;
37 }

首先将第一个参数hwnd强制转化为CWindowImplBaseT< TBase, TWinTraits >的指针pThis,然后调用pThis的ProcessWindowMessage函数进行消息处理。所以,在窗体类的内部只需要重写此函数就可以实现对消息的处理。为了方便起见,WTL为这个方法定义了一些相关的宏。这些宏以BEGIN_MSG_MAP (theClass)开头,以END_MSG_MAP结束,中间加入对相应消息处理宏,如:COMMAND_HANDLER(id, code, func) 、NOTIFY_HANDLER(id, cd, func)等。以下是这几个宏的定义:

BEGIN_MSG_MAP(theClass)的定义

 1 #define BEGIN_MSG_MAP(theClass) \
2 public: \
3 BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID =0) \
4 { \
5 BOOL bHandled = TRUE; \
6 hWnd; \
7 uMsg; \
8 wParam; \
9 lParam; \
10 lResult; \
11 bHandled; \
12 switch(dwMsgMapID) \
13 { \
14 case0:

COMMAND_HANDLER(id, code, func)的定义

1 #define COMMAND_HANDLER(id, code, func) \
2 if(uMsg == WM_COMMAND && id == LOWORD(wParam) && code == HIWORD(wParam)) \
3 { \
4 bHandled = TRUE; \
5 lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \
6 if(bHandled) \
7 return TRUE; \
8 }

NOTIFY_HANDLER(id, cd, func) 的定义:

1 #define NOTIFY_HANDLER(id, cd, func) \
2 if(uMsg == WM_NOTIFY && id == ((LPNMHDR)lParam)->idFrom && cd == ((LPNMHDR)lParam)->code) \
3 { \
4 bHandled = TRUE; \
5 lResult = func((int)wParam, (LPNMHDR)lParam, bHandled); \
6 if(bHandled) \
7 return TRUE; \
8 }

END_MSG_MAP()的定义:

1 #define END_MSG_MAP() \
2 break; \
3 default: \
4 ATLTRACE2(atlTraceWindowing, 0, _T("Invalid message map ID (%i)\n"), dwMsgMapID); \
5 ATLASSERT(FALSE); \
6 break; \
7 } \
8 return FALSE; \
9 }

BEGIN_MSG_MAP (theClass)和END_MSG_MAP分别组成了ProcessWindowMessage函数体和switch语句开始部分和结束部分,中间的具体消息处理宏添加相应的Swithc分支,这样就组成了一个完成的ProcessWindowMessage函数。

WTL消息反射

为什么需要消息反射呢?大家都知道,Windows为我们提供了很多标准控件,如Button,Editbox等,这些所谓的标准控件,其本质还是一个windows窗体,只不过他们的WNDCLASS是系统预定义好的,他们的消息处理函数也是系统预定义好的。一个标准控件接收到一个消息之后,首先进行默认的处理和响应,然后会发送给他的父窗体,由父窗体就行消息处理。这样就很难在标准控件的内部对消息进行响应。因此一个普遍的做法就是在父窗体消息处理函数内将消息再转发给那个控件,当然这个消息必须以某种固定的规律转换成自定义消息。

WTL的做法是在父窗体里通过REFLECT_NOTIFICATIONS()宏反射给子控件,定义如下:

1 #define REFLECT_NOTIFICATIONS() \
2 { \
3 bHandled = TRUE; \
4 lResult = ReflectNotifications(uMsg, wParam, lParam, bHandled); \
5 if(bHandled) \
6 return TRUE; \
7 }

其中ReflectNotifications从各个消息中获得子控件的HWND,然后调用SendMessage方法将自定义的消息发送给子控件,具体的定义为:

 1 template <class TBase>
2 LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
3 {
4 HWND hWndChild = NULL;
5
6 switch(uMsg)
7 {
8 case WM_COMMAND:
9 if(lParam != NULL) // not from a menu
10 hWndChild = (HWND)lParam;
11 break;
12 case WM_NOTIFY:
13 hWndChild = ((LPNMHDR)lParam)->hwndFrom;
14 break;
15 case WM_PARENTNOTIFY:
16 switch(LOWORD(wParam))
17 {
18 case WM_CREATE:
19 case WM_DESTROY:
20 hWndChild = (HWND)lParam;
21 break;
22 default:
23 hWndChild = GetDlgItem(HIWORD(wParam));
24 break;
25 }
26 break;
27 case WM_DRAWITEM:
28 if(wParam) // not from a menu
29 hWndChild = ((LPDRAWITEMSTRUCT)lParam)->hwndItem;
30 break;
31 case WM_MEASUREITEM:
32 if(wParam) // not from a menu
33 hWndChild = GetDlgItem(((LPMEASUREITEMSTRUCT)lParam)->CtlID);
34 break;
35 case WM_COMPAREITEM:
36 if(wParam) // not from a menu
37 hWndChild = GetDlgItem(((LPCOMPAREITEMSTRUCT)lParam)->CtlID);
38 break;
39 case WM_DELETEITEM:
40 if(wParam) // not from a menu
41 hWndChild = GetDlgItem(((LPDELETEITEMSTRUCT)lParam)->CtlID);
42 break;
43 case WM_VKEYTOITEM:
44 case WM_CHARTOITEM:
45 case WM_HSCROLL:
46 case WM_VSCROLL:
47 hWndChild = (HWND)lParam;
48 break;
49 case WM_CTLCOLORBTN:
50 case WM_CTLCOLORDLG:
51 case WM_CTLCOLOREDIT:
52 case WM_CTLCOLORLISTBOX:
53 case WM_CTLCOLORMSGBOX:
54 case WM_CTLCOLORSCROLLBAR:
55 case WM_CTLCOLORSTATIC:
56 hWndChild = (HWND)lParam;
57 break;
58 default:
59 break;
60 }
61
62 if(hWndChild == NULL)
63 {
64 bHandled = FALSE;
65 return1;
66 }
67
68 ATLASSERT(::IsWindow(hWndChild));
69 return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
70 }

在子控件内部,可以调用与消息对应的宏进行处理,这个消息响应过程与窗体类似。

WTL的消息机制的更多相关文章

  1. iOS开发系列--通知与消息机制

    概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地 ...

  2. Android消息传递之Handler消息机制

    前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...

  3. Windows消息机制

    Windows的消息系统是由3个部分组成的: · 消息队列.Windows能够为所有的应用程序维护一个消息队列.应用程序必须从消息队列中获取消息,然后分派给某个窗口.· 消息循环.通过这个循环机制应用 ...

  4. OSG消息机制之事件处理概述

    OSG的消息机制包括好多个头文件预定义及多个类. 首先,消息接收相关的类当属osgGA::GUIEventHandler和osgGA::GUIEventAdapter这两个类了.前者处理OSG程序与用 ...

  5. [转]runtime 消息机制

    原文地址:http://www.jianshu.com/p/f6300eb3ec3d 一.关于runtime 之前在项目中有遇到过用runtime解决改变全局字体的问题,所以再一次感受到了runtim ...

  6. IOS 消息机制(NSNotificationCenter)

    消息机制 NSNotificationCenter 一直都在频繁使用,但是却对其原理不是十分了解.今天就花些时间,把消息机制原理重头到尾好好过一遍. iOS 提供了一种 "同步的" ...

  7. Android之消息机制Handler,Looper,Message解析

    PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...

  8. Objective-C总Runtime的那点事儿(一)消息机制

    最近在找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题.当然还有一些其他问题也几乎必问,例 如:RunLoop,Block,内存管理等.其他的问题 ...

  9. Windows消息机制详解

    消息是指什么?      消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉.一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用 ...

随机推荐

  1. CAS实现单点登录方案(SSO完整版)

    一.简介 1.cas是由耶鲁大学研发的单点登录服务器 2.本教材所用环境 Tomcat7.2 JDK1.7 CAS Service 版本    cas-server-3.4.8-release CAS ...

  2. 【HDOJ】1969 Pie

    二分. #include <stdio.h> #include <math.h> ; ]; int main() { int case_n, n, f, m; double r ...

  3. Default Web Site

    win7上设置默认website的物理路径

  4. bzoj2038

    网上大片的莫队算法题解,先orz一下莫队什么不会莫队?没事我来篇低端的分块大法好啊,我们知道对于区间[l,r]答案是S/P P是一下子可以算出来的,S=∑(pj-1)*pj/2 pj表示区间内颜色为j ...

  5. word在线编辑\生成图片(包含截图与合并)

    1.业务原因 word编辑后的文章复制到html编辑器(fck等)会发生排版错乱的情况,于是混沌了.需要有一个新的方法来终结,于是产生了word能不能在线编辑,后台保存,前台显示灯一系列问题. 2.首 ...

  6. access 导数据到sql server 2008

    使用sp_configure启用'Ad Hoc Distributed Queries'解决 1.启用Ad Hoc Distributed Queries:exec sp_configure 'sho ...

  7. 排序 O(nlogn)

    1. 堆排序是一种优秀的排序算法,时间复杂度O(nlogn),主要思想是用数组构造一个最大堆,满足跟节点的value>子节点的value,然后将堆顶元素(value最大)与最后一个叶子节点交换, ...

  8. Robot Framework+appium集成安装

    appium(中文教程,源码)是一款开源跨平台测试自动化工具,用于测试原生.移动web和混合应用,支持 iOS, Android和FirefoxOS平台. appium集成以下框架驱动自动化测试: i ...

  9. ASPNET5中的那些K

    ASPNET5中的那些K ASP.NET 5最大的变化是什么?首当其冲的就是多了很多K,K表示的是ASP.NET vNext的项目代号“Project K”,但很容易让人想到一个汉字——“坑”,希望K ...

  10. Ubuntu 下安装Kibana和logstash

    原文地址:http://www.cnblogs.com/saintaxl/p/3946667.html 简单来讲他具体的工作流程就是 logstash agent 监控并过滤日志,将过滤后的日志内容发 ...