Win32事件驱动编程在底层是如何实现的?

12
在Win32 C++应用程序中,我们启动一个消息循环,该循环从队列中获取消息,将其翻译并进行分发。最终,每条消息都会到达我们的WndProc,以便处理相关事件。
我理解这部分内容。我不理解的是其间的过程,具体来说:
1. 不同类型的OS中断处理程序必须将消息放置在所述的“消息队列”中,但是该队列在进程地址空间的哪个位置?它如何向中断处理程序代码公开?
2. “翻译”消息是什么意思?调用TranslateMessage()实际上是做什么?
3. 一旦通过DispatchMessage()分派,消息在到达我的WndProc之前经过哪些地方(即,操作系统对它执行了哪些操作)?
如果有人知道以上问题的答案,请满足一下我的好奇心。谢谢。
5个回答

6
操作系统维护一个消息队列,将事件(例如来自中断或其他来源的事件)放入其中。然后根据消息(例如,不会向没有焦点的窗口发送键消息),将这些消息从该队列发送到所有窗口。

应用程序可以拥有自己的队列来处理消息。这些队列仅在需要时按请求创建。
翻译消息用于创建不是“真实”事件的消息。例如,WM_CONTEXTMENU消息从鼠标右键单击、上下文菜单键或Shift-F10“翻译”而来。WM_CHAR从WM_KEYDOWN消息“翻译”而来。当然,许多其他消息也是以这种方式“翻译”的。
每个应该接收到消息的窗口都会收到一条消息。根据消息的类型,操作系统决定窗口是否应该接收该消息。大多数消息都被系统等待处理,也就是说,直到窗口处理完该消息后,该消息才会被发布到另一个窗口。这对广播消息产生了很大影响:如果一个窗口在处理该消息时没有返回,队列将会blocked,其他窗口将不再接收该消息。

3
这完全是错误的。其中正确的部分,只有在问题涉及整个操作系统和应用程序都在单个线程上协同调度的 Win16 时才是正确的。 - Chris Becke

6
这取决于您的消息是如何发送和处理的。
当您调用SendMessage时,如果目标窗口归当前线程所有,则调用将绕过窗口的消息队列,窗口管理器直接调用目标窗口上的窗口过程。 如果目标窗口归另一个线程所有,则窗口管理器实际上调用PostMessage并在目标窗口从窗口过程返回之前抽取窗口消息。
当您调用PostMessage时,窗口管理器会封送消息参数并将相应对象插入到目标窗口的消息队列中。 下次调用GetMessage时,该消息将从消息队列中删除。
窗口管理器还会注册来自输入设备(键盘和/或鼠标)的原始输入事件,并为这些输入事件生成消息。 然后根据需要将这些消息插入队列中(输入事件的处理很复杂,因为它取决于窗口消息队列中已有哪些消息)。
正如Stefan所指出的那样,TranslateMessage只翻译加速键 - 例如,它将键序列转换为WM_COMMAND消息。

4
不同类型的操作系统中断处理程序必须将消息放置在所谓的“消息队列”中,但是这个队列在进程地址空间的哪里?它如何暴露给中断处理程序代码?
Windows与线程相关联。每个具有窗口的线程都有一个进程地址空间中的线程队列。操作系统在其自己的地址空间中为硬件生成的事件维护一个内部队列。使用事件的详细信息和其他状态信息(例如,哪个窗口具有焦点),操作系统将硬件事件转换为消息,然后将其放置在适当的线程队列中。
发布的消息直接放置在目标窗口的线程队列中。
发送的消息通常直接处理(绕过队列)。
详情变得复杂。例如,线程队列不仅仅是消息列表,它们还维护一些状态信息。一些消息(如WM_PAINT)实际上并没有排队,而是在查询队列且队列为空时从附加状态信息中合成的。发送到其他线程拥有的窗口的消息实际上会被发布到接收者的队列中,而不是直接处理,但是系统使其看起来像是来自调用者的常规阻塞发送。如果这可能导致死锁(由于循环发送回原始线程),则会发生有趣的事情。
Jeffrey Richter的书有很多(全部?)细节。我的版本比较旧(Advanced Windows)。当前版本似乎叫做Windows via C/C++
操作系统会做很多工作,使消息流对调用者显得合理(和相对简单)。
“翻译”消息是什么意思?调用TranslateMessage()到底做了什么?
它会监视虚拟键消息,并在识别到按下/松开键组合时添加字符消息。如果您不调用TranslateMessage,则不会收到像WM_CHAR这样的字符消息。
我怀疑它会在返回之前直接发送字符消息(而不是发布它们)。我从未检查过,但我似乎记得WM_CHAR消息在WM_KEYUP之前到达。
一旦被DispatchMessage()分派,消息在到达我的WndProc之前经过哪些地方?(即操作系统对其执行了什么操作)?

DispatchMessage将消息传递给目标窗口的WndProc。在此过程中,一些钩子可能有机会查看该消息(并可能干扰它)。


1

我不是完全确定,但我的最佳猜测是:

  1. 队列是一个系统对象,您可以通过Win32 API调用访问它。它根本不在您的进程地址空间中。因此,中断处理程序可以访问它(可能是通过内核的HAL(硬件抽象层))。

  2. 在Win16中,该调用将更大消息的各个子部分混合成整体。因此,当TranslateMessage找到相应的WM_KEYDOWN WM_KEYUP序列时,它会添加WM_KEYPRESS。它还会根据内部设置和消息的时间戳将各种按钮单击消息转换为双击消息。我不知道它是否仍然在Win32中这样做。

  3. DispatchMessage可能是窗口消息钩子被处理的地方。因此,如果您的窗口上有钩子,则在此处或调用GetMessage时调用它。我不确定。除此之外,DispatchMessage只查找与窗口关联的WndProc地址并调用它。它没有其他太多要做的事情。

希望能对您有所帮助。


1
为了回答最后一个子问题,分派的消息将在通过所有钩子(WH_CALLWNDPROC)之后被传送到您的WindowProc。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接