消息和消息队列(二)

消息处理Message Handling

应用程序必须移走并处理自己线程消息队列中的消息。单线程应用程序通常使用消息循环在自己的WinMain函数中移走并send消息给正确的窗口过程进行处理。多线程应用程序可以在每个创建窗口的线程中建立一个消息循环。下面的内容描述了消息循环如何工作以及窗口过程所充当的角色。

消息循环Message Loop

一个简单的消息循环包括一个函数,其中调用了下面每个函数:GetMessage, TranslateMessage, 和 DispatchMessage。需要注意,如果发生了错误,GetMessage函数返回-1,因此在程序中需要对此进行检验。

示例代码:

GetMessage函数从消息队列中取得消息并将其复制到MSG结构中。它返回非零值,但当取到了WM_QUIT消息的时候,GetMessage返回FALSE并结束消息循环。在单线程应用程序中,结束消息循环通常是关闭应用程序的第一个步骤。应用程序可以通过PostQuitMessage函数来结束自己的消息循环,通常主窗口的窗口过程在响应WM_DESTROY消息的时候就是这样做的。

如果你指定了窗口句柄作为GetMessage函数的第二个参数,则仅是指定窗口的消息才会被从消息队列中取到。GetMessage也可以过滤队列中的消息,仅取到指定范围内的消息。关于消息过滤的更多内容,请参考Message Filtering

如果线程要取得键盘输入的字符,则线程的消息循环必须要包含TranslageMessage函数。系统在用户每次按键的时候生成虚拟键盘消息(WM_KEYDOWN 和 WM_KEYUP)。一个虚拟键盘消息包括标识哪个键被按下的虚拟键码,但不是它的字符值。要得到字符值,消息循环必须包括TranslageMessage函数,TranslageMessage将虚拟键盘消息转换成字符消息(WM_CHAR),并将消息放到应用程序的消息队列中。这个字符消息会在消息循环的下一次迭代中被取走并被分发到窗口过程。

DispatchMessage函数send消息给与MSG结构中指定的窗口句柄关联的窗口过程。如果窗口句柄是HWND_TOPMOST,则DispatchMessage发送消息给系统中所有顶级窗口的窗口过程。如果窗口句柄是NULL,DispatchMessage对此消息不做任何处理。

应用程序的主线程在初始化应用程序后启动自己的消息循环并创建至少一个窗口。消息循环在启动之后,会持续不断的从线程消息队列中取到消息并分发给正确的窗口处理。当GetMessage函数取到WM_QUIT消息的时候,消息循环就会结束。

一个消息队列只需要一个消息循环,即使应用程序包含了许多窗口。DispatchMessage总是分发消息给正确的窗口,这是因为每个队列中的消息都是MSG结构的,其中包含了拥有消息的窗口的句柄。

你可以以多种方式来改变消息循环的工作方式,例如,你可以从队列中取得消息但并不分派给窗口,这样做对于发送没有指定接收窗口的消息的应用程序是有用的。你也可以使用GetMessage函数寻找特定的消息,而将其它消息留在消息队列中。这对于必须临时绕过消息队列通常的FIFO工作方式是有用的。

使用快捷键的应用程序必须能够将键盘消息转换成命令消息。为达到此目的,应用程序的消息队列必须包括一个对TranslageAccelerator函数的调用。关于快捷键的更多信息,请参考Keyboard Accelerators。

如果线程使用非模态的对话框,消息循环必须包括IsDialogMessage函数以使对话框可以取得键盘输入。

窗口过程Window Procedure

窗口过程是一个接受并处理所有发送给窗口的消息的函数。每个窗口类有一个窗口过程,并且每个用同一个窗口类创建的窗口使用同一个窗口过程来响应消息。

系统通过传递消息数据作为参数的方式send消息给窗口过程。窗口过程随后针对消息执行一个相应的动作,当处理消息的时候,它检查消息标识符,使用消息参数所包含的信息。

窗口过程通常不会忽略一个消息,如果它没有处理一个消息,那么它必须将这个消息send回系统以进行缺省的处理。窗口过程通过调用DefWindowProc函数来做到这一点,DefWindowProc函数执行一个缺省的操作并返回消息结果。之后窗口过程必须将DefWindowProc的结果当成自己的返回值进行返回。多数窗口过程仅处理少数的信息,并通过调用DefWindowProc函数传递其它消息给系统。

由于窗口过程为同一窗口类的所有窗口所共享,所以它可以为不同的窗口处理消息。要标识消息影响的某个具体窗口,窗口过程可以检验窗口句柄值。关于窗口过程的更多内容,参考Window Procedures。

消息过滤Message Filtering

应用程序能够通过为GetMessage或PeekMessage函数指定一个筛选器而从消息队列中筛选特定的消息(忽略其它消息)。筛选器是一个消息标识符的范围(通过指定一个消息标识符的开始和结束标识符),或一个窗口句柄,或二者兼有。GetMessage和PeekMessage使用一个消息过滤器来选择从消息队列中取出哪些消息。消息过滤对于应用程序必须先取到在队列中位置靠后的消息的情况是有用的。对于应用程序必须在处理posted消息前先处理硬件输入消息的情况,消息过滤也是有用的。

WM_KEYFIRST和WM_KEYLAST常量能够用来作为过滤值以取得所有的键盘消息。WM_MOUSEFIRST和WM_MOUSELAST常量可以用来取得所有的鼠标消息。

任何过滤消息的应用程序必须确保符合过滤器的消息会被处理。例如,如果应用程序某个窗口的WM_CHAR消息过滤器不能取得键盘输入,则GetMessage函数就不会返回。这种情况下,应用程序将会“挂起”。

Posting and Sending Messages

应用程序可以post或send消息。与系统类似,应用程序通过将消息复制到消息队列的方式post消息,也可以通过将消息数据作为参数来调用窗口过程的方式来send消息。应用程序使用PostMessage函数来post消息,若要send消息,应用程序可以调用SendMessage, BroadcastSystemMessage, SendMessageCallback, SendMessageTimeout, SendNotifyMessage, 或 SendDlgItemMessage 函数。

Posting Messages

应用程序通常post一个消息来通知指定窗口执行某个任务。PostMessage为消息创建一个MSG结构,并将其复制到消息队列。应用程序的消息循环最终会取得消息并将其分派给窗口过程。

应用程序可以post一个不指定窗口的消息。如果应用程序调用PostMessage的时候提供的窗口句柄是NULL,这个消息被post到当前线程的消息队列,由于窗口句柄没有指定,应用程序必须在消息循环中处理这个消息。这是一个创建对整个应用程序起作用的消息的办法,而不是针对某个特定的窗口。

有时候,你会希望post消息给系统中的所有顶级窗口。应用程序可以通过将HWND_TOPMOST作为句柄参数来调用PostMessage以发送信息给所有的顶级窗口。

一个常见的误解是认为PostMessage函数总是会post一个消息。当消息队列满的时候,这个判断是错误的。应用程序应当检查PostMessage的返回值以判断消息是否被成功post,如果没有,重新post消息。

Sending Messages

应用程序通常send一个消息以通知窗口过程立即执行某个任务。SendMessage函数给特定窗口的窗口过程send消息。这个函数会等待窗口过程处理结束并返回结果值。父窗口和子窗口经常通过send消息来相互通讯。例如,有一个编辑框控件子窗口的父窗口可以通过send一个消息给子窗口来设置编辑框中的文本。控件可以向父窗口send消息来通知自己的文本发生了改变。

SendMessageCallback函数也可以向某个窗口的窗口过程send消息,但这个函数会立即返回。当窗口过程处理消息后,系统会调用指定的回调函数。关于回调函数的更多信息,参考SendAsyncProc函数。

有时候,你希望send消息给系统中的所有顶级窗口。例如,如果应用程序改变了系统时间,它必须通过send WM_TIMECHANGE消息给所有的顶级窗口来通知。应用程序可以通过调用窗口句柄是HWND_TOPMOST的SendMessage函数来通知所有的顶级窗口。你也可以使用BroadcastSystemMessage函数,并指定lpdwRecipients参数为BSM_APPLICATIONS,这样可以向所有的应用程序广播一个消息。

通过使用InSendMessage或InSendMessageEx函数,窗口过程可以判断自己正在处理的一个消息是否由另一个线程send的。

消息死锁Message Deadlocks

调用SendMessage函数的线程在向其它线程send消息的时候将停止执行,直到窗口过程返回消息的处理结果。如果接收消息的线程在处理消息的时候放弃了的控制权,那么send消息的线程就不能继续执行下去,因为它在等待SendMessage函数返回。如果接受者线程和发送者线程使用同一个消息队列,这可能会引起应用程序死锁的发生。(注意,journal hooks附着在相同消息队列的同一个线程。)

注意,接收线程可能不是显示的放弃控制,调用下面任意函数会引起线程隐式的放弃控制。

  • DialogBox
  • DialogBoxIndirect
  • DialogBoxIndirectParam
  • DialogBoxParam
  • GetMessage
  • MessageBox
  • PeekMessage
  • SendMessage

要避免应用程序中潜在的死锁,可以考虑使用 SendNotifyMessageSendMessageTimeout函数。另外,窗口过程可以使用InSendMessageInSendMessageEx函数来判断自己取得的这个消息是否是由另一个线程发送的。在调用上述列表中的函数的时候,窗口过程首先应该调用InSendMessageInSendMessageEx函数,如果函数返回TRUE,窗口过程在调用将会引起失去控制的函数之前必须调用ReplyMessage函数。

广播消息Broadcasting Messages

每个消息包含一个消息标识符和两个参数,wParam和lParam。消息标识符是一个标识消息用途的唯一值。两个参数提供了和消息相关的其它消息,但wParam参数通常是一个提供关于消息更多信息的type value。

广播消息send消息给系统中的多个接收者。使用BroadcastSystemMessage函数自应用程序中广播一个消息,指定消息的接收者。与指定单独的接收者不同,你必须指定一个或多个类型的接收者。这些类型有应用程序,可安装的驱动程序(installable drivers),网络驱动程序,系统级设备驱动程序(system-level device drivers)。系统发送广播消息给指定类型的所有成员。

系统通常用广播消息来对发生的系统级设备驱动程序或相关组件的事件作出响应。驱动程序或相关组件向应用程序和其它组件广播消息以通知自身的变化。例如,负责监控磁盘的组件在软盘驱动器检测到介质变化如用户插入一个软盘到驱动器中时,就会广播一个消息。

系统按下面的接收者顺序广播消息:系统级设备驱动,网络驱动,可安装的驱动,应用程序。这意味着,如果选择系统级设备驱动作为接收者,则它具有最先处理消息的机会。在同一种接收者类型里面,没有哪个驱动程序可以保证比其它的驱动程序可以更早接收到消息。这意味着欲发送给某个驱动的消息必须具有全局唯一的消息标识符以确保不会有其它的驱动会无意中对消息进行了处理。

你也可以通过指定HWND_BROADCAST参数给SendMessageSendMessageCallback, SendMessageTimeout, 或 SendNotifyMessage函数来广播消息给所有的顶级窗口。

应用程序通过它们的顶级窗口来接收消息。消息不会send给子窗口。服务程序可以通过窗口过程或服务控制句柄来接收消息。

注意:系统级设备驱动程序使用一个相关的,系统级函数来广播系统消息。

查询消息Query Messages

你可以创建自己的消息来协调你的应用程序和系统中其它组件的行为。这在你创建了自己的可安装驱动程序或系统级设备驱动程序时特别有用。你的自定义消息可以在你的驱动和应用程序之间流动,而消息中带着一些你需要的信息。

使用消息查询来挑选出允许执行某一操作的接收者。你可以通过设置在调用BroadcastSystemMessage的时候设置dwFlags参数为BSF_QUERY来产生自己的查询消息。每一个查询消息的接收者必须返回TRUE以使消息send给下一个接收者。如果任何一个接收者返回BROADCAST_QUERY_DENY,则广播立即终止,并且函数返回0。

标签: WINDOWS
[@more@]

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/777154/viewspace-927531/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/777154/viewspace-927531/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值