【问题标题】:What explains this strange PeekMessage behaviour (trying to deal with a full message queue, filtering for specific messages)?什么解释了这种奇怪的 PeekMessage 行为(尝试处理完整的消​​息队列,过滤特定消息)?
【发布时间】:2012-01-04 00:12:09
【问题描述】:

我们的应用程序充当 COM 服务器,其中所有自动化都发生在单个 STA 单元内(在应用程序的主线程中),并且一些进行长时间(>10 分钟)调用的 VBS 脚本失败并出现错误“系统调用失败( 80010100)”。一些研究(onetwothree)表明这可能是由于消息队列已满,因此当 COM 尝试调用下一个方法时它无法调用。

如果它很重要,应用程序是使用 Embarcadero RAD Studio 2010 开发的(主要是 C++,对于某些 COM 类,还有少量 Delphi。)

我想我会在冗长的 COM 方法调用结束时(即,在它返回之前)检查线程的消息队列,以查看它包含的内容,方法是使用 GetQueueStatusPeekMessage。虽然看起来队列已满,但我看到了一些奇怪的行为,而且我无法弄清楚为什么PeekMessage 的行为方式是这样,以及队列为什么是满的——即,它是什么。

前面有点冗长的解释:

测试线程的消息队列已满

这样的代码:

int iMessages = 0;
DWORD dwThreadId = GetCurrentThreadId();
while (::PostThreadMessage(dwThreadId, WM_USER, 0, 0)) {
  iMessages++;
}
if (GetLastError() == ERROR_NOT_ENOUGH_QUOTA) {
  String strError = L"Not enough quota, posted " + IntToStr(iMessages) + L" messages";
  // Do something with strError
}

当在一个简短的 COM 调用方法结束时运行时,可能会发布数千条(比如 9996 条)消息;在导致脚本失败的冗长方法调用结束时,它可以发布 0。我的结论是消息队列已满确实是问题的原因。我系统的message queue limit is the default 10000(见备注部分)

Application->ProcessMessages() 的调用(调用应用程序的消息循环直到它为空,对于那些不是 Delphi / C++Builder 用户的人来说——这是一个相当正常的“获取/翻译/发送直到没有更多消息” method) 解决了问题,COM 脚本可以成功调用下一个方法。尽管在这种特定情况下可能没问题,但在有效的随机位置调用ProcessMessages() 是需要避免的——它可能导致重新进入。如果可能的话,我想找出导致队列已满的原因。

检查消息队列的内容

使用GetQueueStatus 确定队列中的消息类型显示有计时器(QS_TIMER)、已发布消息(QS_POSTMESSAGE)、“所有已发布消息”(即其他已发布消息,QS_ALLPOSTMESSAGE ),并绘制消息 (QS_PAINT)。

这就是奇怪的地方。我正在尝试使用PeekMessagePM_REMOVE 删除选择的消息或消息类型,以部分清空队列并计算每种类型消息的数量。

如果我打电话:

while (::PeekMessage(&oMsg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD | (QS_TIMER << 16)) != 0) {...

我收到了一万多条消息,通常是 10006 条左右。并非所有这些都是 WM_TIMER:数千个是 WM_APP+202,这是我们内部使用的一条消息,没有似乎正在(由我们)以如此大量的数量发布。我已经检查过了:它只发送了几次。我们还使用了几千条 WM_APP+something 消息;这个可能真的发送得太频繁了。

如果我改为这样:

while (::PeekMessage(&oMsg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE | PM_NOYIELD) != 0) {...

我收到大约十条消息,所有这些都是真正的 WM_TIMER。为什么? PeekMessage 文档表明传递 QS_TIMER

最后,如果我改为调用第三个变体:

while (::PeekMessage(&oMsg, NULL, WM_APP+202, WM_APP+202, PM_REMOVE | PM_NOYIELD) != 0) {...

直接过滤第一行代码返回数千条的自定义消息,我删除了17条条消息。

我已经多次复制了这一切 - 这些都不是一次性行为。

所以:

  • 为什么第一次调用 PeekMessage 时删除的不仅仅是计时器(与第二次调用相比)?只是好奇,真的。
  • 为什么第一次调用 PeekMessage 会删除数千条 WM_APP+202 消息(我们定义和使用但不发送那么多消息),但如果我改为调用第三个变体,哪个过滤器直接针对该特定消息,我得到 17?
  • 如果我在同一条消息中得到如此不同的结果,我如何确定队列中填满了什么以及如何最好地避免它?
  • 或者为了避免上述所有情况:我可以安全地忽略这一切,那么当 COM 即将尝试调用方法时,我应该如何处理完整的消​​息队列?

我很困惑,很可能犯了一个基本的错误——它已经到了看你做这件事的地方令人费解的阶段。任何对 COM 问题的帮助或对消息行为的解释,包括“你犯了基本的错误 X,天哪,你太愚蠢了”,将不胜感激:)

【问题讨论】:

  • 你能不能冻结所有其他线程,这样它们就不会在你忙着从另一端计算东西时从一端抽出东西。报告可能有些混乱
  • @DavidHeffernan:好主意 - 有趣的结果!当我冻结其他人时,PeekMessage几乎没有提取任何消息,并且10000多个WM_APP + 202从未出现过。当我解冻所有其他线程时,下一个 COM 调用成功完成(我猜其中一个线程是内部 COM 线程,必须运行才能调用下一个方法?)但是我们所有的线程(进程中有一些未识别的线程)坐在 WaitForSingleObject 或类似设备上,不做任何工作,绝对不发送消息。令人困惑。
  • Raymond Chen 在devblogs.microsoft.com/oldnewthing/20160624-00/?p=93745 中讨论了类似的问题。

标签: delphi winapi com message-queue c++builder


【解决方案1】:

GetQueueStatus() 接受 QS_xxx 参数,但 PeekMessage() 只接受 PM_QS_xxx 常量。

这解释了由QueueStatus 指示的WM_TIMER 消息数量与随后由PeekMessage() 删除的消息数量之间的差异。您的 PeekMessage(PM_REMOVE) 呼叫并未删除 WM_TIMER 消息,而是完全删除了其他内容。

我想你误解了PeekMessage() 的文档。 PM_QS_POSTMESSAGE 被记录为具有与以下相同的价值:

((QS_POSTMESSAGE | QS_HOTKEY | QS_TIMER) << 16)

其他PM_QS_xxx 常量被记录为等于相应的QS_xxx 常量&lt;&lt; 16,但没有任何地方说这始终是这种情况并且可以外推到所有QS_xxxx 常量。

我怀疑QS_TIMER &lt;&lt; 16 正在产生一些过滤器,它不仅仅是过滤WM_TIMER 消息(显然是这样,我只是不能肯定地说它会产生什么过滤器)。

据我所知,WM_TIMER 是唯一与计时器相关的消息,因此无需为更大的计时器消息超集设置更广泛的过滤器 - 没有这样的超集。如果您想过滤计时器消息,只需过滤 WM_TIMER

【讨论】:

  • 感谢@Deltics!这就解释了为什么我收到非计时器消息。你必须仔细阅读MSDN,不是吗!对其他三个问题有任何想法吗?它们是最重要的……首先是真正的好奇心,要了解它在做什么。我仍然需要找出解决原始问题的好方法:/
  • 我认为您的其他 3 个问题中的第一个由过滤问题回答。 QS_TIMER some 过滤器,它匹配许多消息(甚至可能是 all 消息?)。但正如我所说,我不知道过滤器 QS_TIMER
  • (cont...) 至于更广泛的问题,在我看来,您的问题的根本原因是消息队列已满,这表明它可能没有被“泵送”一个消息循环。而不是试图找出一种方法来处理症状(一个完整的消息队列),我会首先调查为什么队列被填满并处理它(防止问题发生)。
  • 这是主线程中的 STA 方法调用,因此主应用程序消息循环没有运行,并且队列会随着时间的推移而填满。除非(这是我希望弄清楚的)某些东西正在向消息队列发送垃圾邮件,因此它比正常情况更快地填满。但是我得到的奇怪结果(即使在你帮助理解了第一项之后)让这有点令人费解:/
猜你喜欢
  • 2020-06-06
  • 1970-01-01
  • 2015-12-16
  • 2019-12-12
  • 2012-10-17
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
  • 1970-01-01
相关资源
最近更新 更多