消息与消息队列
消息与消息队列
About Messages and Message Queues
基于windows的应用程序是事件驱动的。它们不进行明确的函数调用(比如调用C运行库)来获得输入。取而代之的是他们等待操作系统将输入传递给他们。
操作系统传递所有的输入给各种应用程序。每个window有一个函数叫做窗口过程,只要有输入操作系统就调用他们。窗口过程处理输入后,将控制返回给操作系统。详细资料请看窗口过程。
Microsoft? Windows? XP:如果最顶层(top-level)的窗口停止响应几秒钟,系统将认为窗口被挂起。这种情况下,系统将隐藏这个窗口并用一个ghost window在相同z坐标上,相同的大小,相同的可视属性来取代他。以允许用户移动窗口,改变窗口大小,关闭窗口。然而,仅有这些行为是可操作的因为应用程序已挂起。在Debugger模式下,系统不产生ghost窗口。
This section discusses the following topics:
? Windows Messages
? Message Types
? Message Routing
? Message Handling
? Message Filtering
? Posting and Sending Messages
? Message Deadlocks
? Broadcasting Messages
? Query Messages
Windows Messages
系统以messages的形式将输入传递给窗口过程。消息可以被系统和应用程序产生。系统产生消息当每一次输入事件发生,如当用户敲键盘,移鼠标,或者单击滚动条。系统也用消息来响应由于应用程序引起的系统变化。如应用程序改变系统字体资源或者改变它的一个窗口。应用程序产生消息来让他的窗口执行一些任务或者与其他应用程序的窗口通讯。
系统发送消息给窗口过程并传递4个参数,windows句柄, 消息标识符, 2个消息参数。系统使用窗体句柄来决定那个窗口过程来接受消息。
消息标识符以常量命名指出消息的含义。当窗口过程接收到消息,使用消息标识符决定如何处理消息。例如、WM_PAINT告诉窗口过程窗体客户区被改变了需要重绘。
消息参数指定被窗口过程使用的数据和数据的位置。其含义和值取决于消息类型。消息参数可以包含一个整数, 标志位,一个指针等。 当消息不使用消息参数时,他们被设置位NULL。一个Window窗口过程必须根据消息标识符来决定如何解释消息参数。
Message Types
This section describes the two types of messages:
? System-Defined Messages
? Application-Defined Messages
System-Defined Messages
当系统和应用程序通讯时,系统post和send系统定义消息。他使用消息来控制应用程序的操作,提供输入和其他信息让应用程序处理。应用程序也可以post或者send系统定义消息。
每一个系统定义消息由一个唯一的标识符与一致的常量以声明消息的含义。例如 WM_PAINT 要求窗口绘制它的内容。
符号常量指定系统定义消息属于的类别,常量的前缀指定处理解释消息的窗体的类型。以下使一些前缀和他们相关的消息类别。
Prefix Message category
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
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
WM General window
通用窗体消息覆盖了很大一个信息和请求的范围, 包括鼠标键盘消息, 菜单对话框的输入, 窗体产生与管理, 动态数据交换 (DDE).
Application-Defined Messages
应用程序可以产生自己用的消息或者与其他进程中窗体通讯。如果应用程序产生自己的消息,窗口过程接受并且必须提供合适的处理。
系统保留消息标识符的值在0x0000在0x03ff(WM_USER-1)范围。这些值被系统定义消息使用。 应用程序不能使用这些值给自己的消息。
private window classes用0x0400(WM_USER)到0x7fff消息标识符
? If your application is marked version 4.0, you can use message-identifier values in the range 0x8000 (WM_APP) through 0xBFFF for private messages.
系统使用RegisterWindowMessage来注册消息,返回一个消息标识符范围在0XC000到0XFFFF,使用这个函数来保证整个系统范围内是唯一的
Message Routing
系统有两个方法将消息传递到窗口过程。Post一个消息到先进先出的消息队列。系统定义的临时内存对象。和直接 send消息到窗口过程。
被发送到消息队列的消息称做入队消息,主要是由鼠标键盘输入,例如 WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR messages。还包括定时器, 刷新, 退出: WM_TIMER, WM_PAINT, and WM_QUIT。其他直接发送到窗口过程的消息被称为 非入队消息。
Queued Messages
系统可以显示任何数量的窗体同一时间。为了传递鼠标键盘消息到合适的窗口,系统使用消息队列。
系统维护一个系统消息队列和每一个GUI线程的消息队列,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列。仅当线程第一次调用GDI函数时,系统给线程创建一个消息队列。只要用户移动鼠标,点击鼠标,敲键,驱动程序将其转换为消息将他们放在系统消息队列中。系统将他们从系统消息队列中移走,检查他们的目标窗口,然后将他们发送到创建目标窗口的线程的消息队列。线程消息队列接收由这个线程创建的所有窗口的鼠标键盘消息。线程删除消息系统调用窗口过程进行处理。
WM_PAINT是一个例外,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而, 仅当没有其他消息时WM_PAINT才被传递窗口过程。同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN减少了刷新窗口的次数。
系统通过填充MSG结构并将它复制到消息队列来发送消息到线程队列。MSG结构包括:窗口句柄,消息标识符,两个消息参数。 消息被posted的时间, 和鼠标的位置。线程可以使用PostMessage和PostThreadMessage来给发送消息到自己消息队列或者另一个线成的消息队列。
应用程序可以使用GetMessage从消息队列删除消息。可以使用 PeekMessage来检查一个消息而不删除它。 这个函数将消息队列的消息填充到MSG结构。
在从消息队列删除了一个消息,应用程序可以使用DispatchMessage使系统将消息发送到窗口过程来处理。DispatchMessage拥有一个指向由GetMessage或者PeekMessage填充的MSG结构的指针,传递窗口句柄,消息标识符,消息参数给窗口过程。但它并不传递消息发送的时间和鼠标的位置,应用程序可以通过GetMessageTime和GetMessagePos来得到这些信息。
线程可以使用WaitMessage将控制交给其他线程当消息队列中没有他的消息队烈时,这个函数挂起线程,并不返回,直到新的消息放置于消息队列中。
你可以调用SetMessageExtraInfo函数来关联一个值到当前线程的消息队列。调用GetMessageExtraInfo来得到这个与最后一次通过GetMessage或者PeekMessaage获得的消息关联得值。
Nonqueued Messages
非入队消息即直接发送到窗口过程的消息,绕过系统队列和线程消息队列。系统发送非入队消息通知一个窗口事件,例如,当用户激活一个新的应用程序窗口,系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了,键盘输入被直接传递到窗口,鼠标在当前窗口内移动。 非入队消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。
一些函数也发送非入队消息如 BroadcastSystemMessage, BroadcastSystemMessageEx, SendMessage, SendMessageTimeout, and SendNotifyMessage.
Message Handling
应用程序必须移除和处理被post到消息队列的消息。单线程应用程序通常在WinMain使用消息循环来移除和分发消息到何时窗口过程来处理。多线程应用程序可以在每一个创建窗口的线程中中包含消息循环。
Message Loop
A simple message loop consists of one function call to each of these three functions: GetMessage, TranslateMessage, and DispatchMessage. Note that if there is an error, GetMessage returns -1 -- thus the need for the special testing.
Show Example
MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
GetMessage 函数从消息队列获得一个消息并将它复制到MSG结构。他返回一个非0值,除非遇到WM_QUIT消息。否则它返回0然后结束循环。In a single-threaded application, ending the message loop is often the first step in closing the application. 应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的WM_DESTROY消息中调用。
如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。详细见消息过滤。 一个线程循环必须包括TranslateMessage如果线程接受键盘的字符输入。每一次用户按键系统产生虚拟键消息,一个虚拟键消息包含虚拟键用来标志那个键被按下,并不是他的字符值,要获得这个值消息循环必须调用TranslateMessage,用于将虚拟键转换为字符消息WM_CHAR然后将它放回应用程序消息队列。通过将它转发到窗口过程,字符消息被删除。
DispatchMessage 函数分发消息到MSG结构中的窗口句柄关联的窗口过程。如果窗口句柄是HWND_TOPMOST,DispatchMessage分发消息到系统中的所有的top-level窗口的窗口过程。如果句柄是NULL,DispatchMessage不做任何事。
应用程序得主线程在初始化,创建至少一个窗口后启动它的消息循环,一旦启动消息循环持续从线程队列中获得消息,然后分发他们到合适的窗口。消息循环在通过GetMessage得到WM_QUIT并将他从队列中删除后结束。
一个消息都列仅需要一个消息循环, 即使程序包含很多窗口。DispatchMessage 总是分发消息到合适的窗口,这是因为MSG结构包含消息所属的窗口的句柄。
你可以用各种方式修改消息循环。例如,从消息队列获得消息消息但不把他们分发到窗口中去。当应用程序发送一个消息但不指定窗口时是有用的。你可以用GetMessage获得一个特定的消息,而保留其他消息在消息队列。当你需要改变先进先出的顺序时是有用的。
应用程序使用加速键必须将键盘消息转换为WM_COMMAND消息。所以消息循环必须包括TranslateAccelerator函数。详细信息参见加速键。
如果线程使用非模态对话框,消息循环必须包括 IsDialogMessage 以使非模态对话框获得键盘输入。
Window Procedure
窗口过程是一个用于处理所有发送到这个窗口的消息的函数。任何一个窗口类都有一个窗口过程。同一个类的窗口使用同样的窗口过程来响应消息。
系统发送消息给窗口过程将消息数据作为参数传递给他,窗口过程使用参数产生合适行为。
一个窗口过程通常不忽略消息,如果他不处理,它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个处理。窗口过程必须return一个值作为它的消息处理结果。大多数窗口只处理小部分消息和将其他的通过DefWindowProc传递给系统做默认的处理。
因为窗口过程是同一个类的窗口共享的,它可以为不同的窗口处理消息。通过检查消息中的窗口句柄来找到被消息影响的窗口。详细信息请看Window Procedures。
Message Filtering
应用程序可以从消息队列选择特定的消息。使用GetMessage或者PeekMessage并指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。GetMessage和PeekMessage使用过滤器来选择从消息队列中获得那些消息。当应用程序要查找一个后入消息队列的消息是很有用。
一个应用程序过滤消息时必须保证符合过滤条件的消息能被发送。例如应用程序在窗口中过滤WM_CHAR并不能得到键盘输入,GetMessage并不返回。这将挂起应用程序。
Posting and Sending Messages
应用程序可以post和send消息,通过将消息复制到消息队列即post消息,send消息将消息数据作为参数直接传递到窗口过程。
可以使用PostMessage来post消息,SendMessage,BroadcastSystemMessage, SendMessageCallback, SendMessageTimeout, SendNotifyMessage, or SendDlgItemMessage来send消息。
Posting Messages
应用程序post消息通知指定窗体执行任务。PostMessage可以创建MSG结构并将它Copy到消息队列。消息循环最终捕获消息并分发到合适的窗口过程。
给PostMessage传递一个NULL句柄不指定哪一个窗口,这个消息就被发送到当前线程消息队列,应用程序必须在消息处理中处理这个消息。这是为整个应用程序发送消息的一个方法。
偶尔你可以使用HWND_TOPMOST 这个参数作为句柄参数向所有的top-level窗口发送消息。
当消息队列满的时候PostMessage并不发送消息,应用程序需要检查PostMessage函数的返回值来确定消息是否被发送,或者没有需要重发。
Sending Messages
通过Send消息来通知窗口过程立即执行任务。SendMessage将消息发送给指定窗口的窗口过程。函数将等待窗口过程处理完才返回一个消息结果。父窗口和子窗口通常使用Send消息来互相通讯。例如,一个父窗口拥有以一个文本框作为它的子窗口,它可以通过发送消息到子窗口来给文本框设置文字。子窗口也可将文字被用户改变的消息发送给父窗口。
SendMessageCallback也将消息发送给指定窗口的窗口过程,但是他立即返回。在窗口过程处理完消息后,系统调用指定的回调函数,回调函数的详细资料参见SendAsyncProc
偶尔,你可以发送消息系统中到所有的top-level窗口,例如,应用程序改变了系统时间。它必须以HWND_TOPMOST作为句柄参数发送一个WM_TIMECHANGE 消息通知所有的top-level窗口,你也可以将lpdwRecipients 指定为BSM_APPLICATIONS 用BroadcastSystemMessage函数向所有应用程序广播。
可以使用InSendMessage或者InSendMessageEx函数,窗口过程可以判断它处理的消息是否是由其他线程调用SendMessage发送过来的。This capability is useful when message processing depends on the origin of the message.
Message Deadlocks
一个线程可以调用SendMessage想其他线程发送消息,这个线程不能继续执行直到获得消息得窗口过程返回。如果接受消息的线程处理消息时yields控制,发送线程的消息将永远得不到执行,因为他在等待SendMessage返回。如果接受线程和发送线成语同一个消息队列联系起来,它可能导致消息死锁。
Note that the receiving thread need not yield control explicitly; calling any of the following functions can cause a thread to yield control implicitly.
接受线程不需要明确yield控制,下面任何一个函数回导致一个线程明确yield控制。
? DialogBox
? DialogBoxIndirect
? DialogBoxIndirectParam
? DialogBoxParam
? GetMessage
? MessageBox
? PeekMessage
? SendMessage
为了避免潜在的死锁,可以使用SendNotifyMessage 或者 SendMessageTimeout,否则窗口过程,将用InSendMessage或者InSendMessageEx判断消息是否由另一个线程发送过来。当调用前面任何一个函数窗口过程将首先调用InSendMessage或者InSendMessagEx,如果函数返回true窗口过程必须在引起线程yeild控制前调用ReplyMessage 。
Broadcasting Messages
每一个消息包括消息标识符和两个参数,wParam和lParam,消息标识符是唯一的代表这个消息的含义。参数提供与消息相关的额外的信息,但是wParam参数通常是一个类型值提供更多的消息信息。
消息广播是简单的将消息发送到系统中的多个接收者。使用BroadcastSystemMessage函数来广播消息,你必须指定一个或者多个接收者类型,这些类型可以是applications, installable drivers, network drivers, and system-level device drivers。系统将消息发送给指定类型的所有成员。
系统广播消息来响应系统设备驱动程序或者组件的变化。驱动程序或相关组件广播消息给应用程序和其他组件以通知他们这些变化。例如,负责磁盘响应的组件广播消息只要软盘驱动程序发现媒体的变化,如当用户将磁盘插入驱动器。
系统按一下顺序广播消息给接收者:系统级的设备驱动程序,网络驱动程序,installable drivers,和应用程序。意味着如果系统级设备驱动程序作为接收者总是第一个有机会来响应消息。在接受者类型中,没有一个驱动程序能保证在其他驱动程序前接受一个消息。即一个给特定驱动程序的消息必须有一个全局唯一的标志使其他不关心这个消息的驱动程序不处理它。
你也可以广播消息给所有顶层窗口通过在SendMessage等函数中指定HWND_BROADCAST。
应用程序通过顶层窗口的窗口过程来接收消息。消息不发送到子窗口。服务可以接收消息通过窗口过程或者他们的服务控制函数。
Query Messages
你可以创建自定义的消息使用它们来调整你的应用程序和其他组件之间的行为。这个非常有用如果你已创建你自己的installable drivers和系统机设备驱动程序。你的驱动程序和使用这个驱动程序的应用程序可以通过自定义消息互相传递信息。
To poll recipients for permission to carry out a given action, use a query message.你可以通过在dwFlags参数中设置BSF_QUERY调用BroadcastSystemMessage。每一个query message的接收者必须返回TRUE来将消息发送到下一个接收者。如果任何一个接收者返回BROADCAST_QUERY_DENY,广播立即停止函数返回0。
Windows 95/98/Me: 你可以创建广播和处理消息的installable drivers。一个installable drivers是一个导出DriverProc函数的dll。驱动程序通过它的DriverProc来接收消息。Installable drivers典型的用来支持多媒体设备,例如sound boards,也可以用于其他设备和目的。
Windows 95/98/Me: 网络驱动程序是给应用程序提供以下支持的dlls。系统级设备驱动程序是系统特定的提供直接访问和管理计算机硬件的可执行组件。这些组件如何处理系统消息超出了这篇文章的范围。