消息队列相关知识总结

消息
消息概念:
消息就是在事件驱动模式下,事件发布函数和具体功能执行函数(或者代码段)之间的调用协议,调用协议的执行表现为窗口事件发布函数跟具体功能执行函数(或者代码段)之间的选择关系。
在Windows系统下,对计算机外设的操作,例如当用户敲击键盘键、点击鼠标、热插拔USB盘等,系统都认为外设发生了事件,于是系统调用专职函数就把这些事件进行收集,形成现场信息,赋值给一个叫做“消息结构体MSG”的对象,那么这个被赋予特定意义的消息结构体对象就可以称之为“消息”;然后,专职函数再把这个“消息”投递到(Post)到系统中一个专职消息管理的机构中去,这个过程就完成了消息形成和投递。
Windows中的消息种类大体上有系统定义消息(System-Defined Messages)和用户自定义的消息。其中系统定义消息三种:窗口消息、命令消息、通知消息,这是按消息目的、行为进行划分的。
窗口消息:跟窗体变化有关的消息,与窗口的内部运作有关,在Windows中,当窗体发生变化时,窗体与操作系统、窗体与窗体之间都会有消息传递,从而对窗体起到控制作用。
传递路径一般为:窗体<—>操作系统、窗体<-->窗体
常见窗口消息:面向Dialog的:WM_CREATE、WM_PAINT、WM_SIZE、WM_DESTROY、WM_CHAR、WM_TIMER、WM_KEYDOWN、WM_MOVE、WM_SETFOCUS
命令消息:跟处理用户请求有关,或者说是跟用户操作有关,属于跟用户相交互的消息。
传递路径一般为:用户—>窗体—>窗体。其本质也是一种窗口消息,一种特殊的窗口消息,只不过其起因来源于用户而已。
常见的命令消息有:面向控件的:例如ListCtrl的NM_CLICK、NM_DBCLICKED、HDN_ITEMCLICKED等、按钮控件的BN_CLICKED、BN_DBCLICKED等。菜单消息就属于命令消息。
通知消息:控制参数到达某个量级时、满足某个条件时或者接到某个参数值时,系统为进行控制行动而形成的消息。
传递路径:系统-->窗体
常见的通知消息有:Notify类消息,其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针,NMHDR包含控件通知的内容,可以任意扩展。
这三类消息之间相互贯通,并没有严格的界限,有时候命令消息、通知消息也是为了窗体变化而来的,窗口消息也可以被视为命令和通知。比如说在一个Dialog上放置了一个Edit控件,当用户在Edit中编辑文本时,产生消息纯为命令消息,当文字编辑超越可视区域时,编辑框产生滚动条,此时属于通知消息,通知Edit窗口要调整了。当调整窗口时,又要产生窗口更新消息,此时就属于窗口消息。
用户自定义的消息:其范围有如下规定:WM_USER: 0x0400-0x7FFF、0x8000-0xBFFF。
系统保留消息ID的值在0X0000在0X03FF范围,这些值为系统所保留定义,用户自定义消息ID不能够与其相冲突。用户自定义消息ID范围为从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF。
消息管理机制:
1、系统每时每刻都有可能发生事件,所发事件都是针对某个或者某些具体应用程序的,所以计算机需要先对这些由事件形成的消息进行集中收集,并完成存操作(即消息投递),一般来说新来的消息都存放在靠后位置,也就是严格按照先来后到的时间顺序进行存操作的;
2、再由系统专职函数对这些消息进行读取,完成取操作。取操作跟存操作刚好相反,是从头开始的,就是先取存放时间最早的那个消息。然后,这些专职函数根据所取的消息的参数的含义,把所取的消息再投递到指定的应用程序的“消息管理机构”中去。

我们上面做说的系统“消息管理机构”就叫做系统消息队列,系统消息队列就是起到集中收集、集中管理外设消息的。
系统消息队列
系统消息队列对消息的指派过程跟它自己对消息收集的那个过程有些区别:首先,收集消息过程是个被动过程,系统消息队列不会主动去收集消息,而是由系统的专职函数投递到系统消息队列的,也就是说这个操作由外部调用其接口来完成的;其次,收集消息时,是不管消息来源的。而系统消息队列对消息的指派则是需要关注消息的去向的,一般来说,消息的去向是由消息对象成员hwnd所决定的,请看消息结构体代码如下所示:
typedef struct tagMSG {
HWND hwnd; // 接受该消息的窗口句柄
UINT message; // 事件标识符,也就是消息ID
WPARAM wParam; // 事件的特定附加信息,依赖于事件
LPARAM lParam; // 事件的特定附加信息,依赖于事件
DWORD time; // 消息被Post时的时间,以毫秒形式给出
POINT pt; // 消息创建时的鼠标/光标在屏幕坐标系中的位置
} MSG;
Hwnd 窗口句柄,窗口可以是任何类型的屏幕对象,例如:窗口、对话框、按钮、编辑框、列表等,它们是可视对象或者隐藏对象等的句柄。
Message
1、是区别其它事件的常量,这些常量可以是Windows预定义的常量,也可以是用户自定义的常量。
2、事件标识符以常量命名的方式指出事件的本质,其前缀指明系统消息属于的类别。例如:WM_LBUTTONDOWN表示发生了鼠标左键按下事件,WM_SIZE表示发生了窗口布局调整事件,都是窗口消息。
3、当窗口事件发布函数接收到消息之后,就会根据消息ID来决定如何处理消息。例如:WM_PAINT告诉窗口事件发布函数 窗体UI需要重绘。
例如以下一部分是关于MFC的消息:
常见系统消息的分类:
|
WM General window(一般的窗口) ABM Application desktop toolbar (应用程序桌面工具条) BM Button control (按钮控件) CB Combo box control (组合框控件) CBEM Extended combo box control(扩展的组合框控件) CDM Common dialog box (普通的对话框) DBT Device (设备) DL Drag list box (下拉列表) DM Default push button control (默认按钮控件) DTM Date and time picker control(日期和时间选择控件) EM Edit control (编辑控件) HDM Header control (表头控件) HKM Hot key control (热键控件) IPM IP address control (IP地址控件) LB List box control (列表框控件) LVM List view control (列表视图控件) MCM Month calendar control (数学日历控件) PBM Progress bar (进度条控件) PGM Pager control () PSM Property sheet (属性页) RB Rebar control (分隔条控件) SB Status bar window (状态条控件) SBM Scroll bar control (滚动条控件) STM Static control (静态控件) TB Toolbar (工具条) TBM Trackbar (跟踪栏) TCM Tab control (选项卡控件) TTM Tooltip control () TVM Tree-view control () UDM Up-down control ()
|
进程消息队列
跟系统对消息的管理一样,应用程序也有一个叫做“消息管理机构”的机构,这个就被称为“进程消息队列“(每一个进程都会有一个主线程去管理进程,因此也称为“线程消息队列”)。
并不是所有的进程都有消息队列,在Windows下,只有那些具备窗口(GUI用户接口界面)的进程,才会有消息队列,那些不具备GUI的进程是没有消息队列的。也就是说:操作系统在开启一个新的进程时,并没有为其创建消息队列,而是当进程第一次调用GDI函数后,才创建,并且进程持有窗口时,才持有消息队列。
进程消息队列有两个过程,即“存“和”取“,存放跟本应用有关的消息,也是按照先来后到的顺序进行的,并也是按照先来后到的顺序读取已经存放的消息,然后根据消息对象成员hWnd把消息投递到具体窗口去。
系统消息队列是由系统的事件回调函数操作,进程消息队列是由进程的事件回调函数进程处理,一般情况下,队列化消息基本上是用户输入的结果,队列消息的操作都是通过消息循环来完成的。对于消息队列而言,所谓的消息循环就是这样一个循环,不断地访问消息队列,检测队列中有没有消息存在,如果有,就取出。
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
消息到达具体窗口后,那么对消息的管理就由窗口回调函数来负责管理了。
每个窗口在被创建后,都有一个CALLBACK成员函数,
一般函数名称为:WndProc,
它有四个参数:HWND(窗口句柄),UINT(消息ID),和两个消息参数(wParam, lParam);当窗口接到消息时,就由该函数来执行对消息的处理。它对消息的处理,是利用对消息ID的选择来进行的。下面就是一个Win32进程的WndProc函数的示意:
// 函数主要功能: 处理被指派到进程主窗口的消息.
// 主要消息说明:
// WM_COMMAND: 应用程序的菜单消息(命令消息);
// WM_PAINT: 窗口绘制消息(窗口消息)
// WM_DESTROY: 窗口销毁并使应用程序退出的消息(通知消息)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND: // 命令消息
wmId = LOWORD(wParam); wmEvent = HIWORD(wParam);
// 解析具体命令
switch (wmId)
{
case IDM_ABOUT: // 打开关于对话框
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT: // 发出退出命令
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT: // 窗口消息
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: // 通知消息(也包含了窗口消息成分)
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
上面这个WndProc函数就是窗口回调函数。窗口回调函数就是窗体消息的处理函数,所有与窗体消息有关的功能执行协议都是由它来处理的。
窗口回调函数有以下特点:
存在依赖于窗体,一个窗体对应一个事件回调函数;
是窗体的消息处理中心,所有与窗体消息有关的处理都在该函数进行;
调用是由进程完成的。
非队列消息
队列消息是指那些通过投递方式进入消息队列的消息。
非队列消息是没有被投递到消息队列,而直接发送给窗口事件回调函数的消息。
非队列消息一经发送到窗口,窗口事件回调函数就会对其进行分析处理,按照其Message内容进行选择执行对应的代码段。
如上面的代码所示:当消息是命令消息(WM_COMMAND)时,执行进入到命令消息代码段,首先解析命令参数内容,根据命令来源执行再进一步相应代码,
例如,如果是打开关于对话框命令,就执行case IDM_ABOUT段代码,
如果是退出命令,就执行case IDM_EXIT段代码,销毁窗体,退出应用程序。
非队列消息一般来自特定的Windows函数,例如:
当WinMain呼叫CreateWindow时,Windows将建立窗口并在处理中给窗口事件发布函数传递一个WM_CREATE消息;
当WinMain呼叫ShowWindow时,Windows将给窗口事件发布函数传递WM_SIZE和WM_SHOWWINDOW消息;
当WinMain呼叫UpdateWindow时,Windows将给窗口事件发布函数传递WM_PAINT消息。
也就是说非队列消息将会绕过系统消息队列,直接发送到窗口过程。

浙公网安备 33010602011771号