是什么解释了这个奇怪的PeekMessage行为(尝试处理完整的消​​息队列,过滤特定的消息)?

时间:2011-11-22 15:56:23

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

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

如果它很重要,应用程序的开发时间为Embarcadero RAD Studio 2010(大部分为C++,某些COM类的Delphi字节为。{/ p>

我以为我会在漫长的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用户的人来说 - 这是一个相当正常的“获取/翻译/调度直到不再消息“方法”解决了问题,COM脚本可以成功调用下一个方法。虽然在这种特定情况下可能还行,但在有效的随机位置调用ProcessMessages()是可以避免的 - 它可以导致重新入侵。我想找出导致队列充满的原因。

检查消息队列的内容

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

这是奇怪的地方。我正在尝试使用带有PM_REMOVE的{​​{3}}删除选择的消息或消息类型,以便部分清空队列并计算每种消息类型的数量。

如果我打电话:

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

我收到的信息超过一万条,通常是10006条左右。并非所有这些都是WM_TIMER:数千是WM_APP + 202,我们在内部使用的消息, not 似乎是在我们这样大量的任何地方发布(由我们)。我已经检查了这个:它只发送了几次。我们还使用了几千条WM_APP+something消息;这个可能真的经常发送。

如果我这样称呼:

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

我收到大约十条消息,所有消息都是真正的WM_TIMER。为什么? PeekMessage文档表明传递QS_TIMER&lt;&lt; 16应该只处理定时器消息,但它会产生更多的消息,其中许多消息根本不是定时器。

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

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,对你来说是愚蠢的',将不胜感激:)

1 个答案:

答案 0 :(得分:1)

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

这解释了由WM_TIMER指示并随后由QueueStatus()删除的PeekMessage消息数之间的差异。您的PeekMessage(PM_REMOVE)来电不会删除WM_TIMER个消息,而是完全删除其他内容。

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

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

并且其他PM_QS_xxx常量被记录为等于相应的QS_xxx常量<< 16,但是没有任何地方说这一直是这种情况并且可以外推到所有QS_xxxx 1}}常数。

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

据我所知,WM_TIMER是唯一与定时器相关的消息,因此不需要为更大的超级定时器消息集提供更宽的过滤器 - 没有这样的超级集。如果您想过滤计时器消息,只需过滤WM_TIMER