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

Dav*_*vid 6 delphi com winapi c++builder message-queue

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

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

我想我会在漫长的COM方法调用结束时检查线程的消息队列(即,在它返回之前),通过使用GetQueueStatus和查看它包含的内容PeekMessage.虽然看起来队列已经满了,但我看到了一些奇怪的行为,我无法弄清楚为什么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
}
Run Code Online (Sandbox Code Playgroud)

当在一个简短的COM调用方法结束时运行可以发布数千(例如,9996)消息; 在导致脚本失败的冗长方法调用结束时,它可以发布0.我的结论是消息队列已满是真正导致问题的原因.我的系统的消息队列限制是默认值10000(请参阅"备注"部分.)

调用Application->ProcessMessages()(调用应用程序的消息循环,直到它为空,对于那些不是Delphi/C++ Builder用户的人来说 - 这是一个相当正常的"get/translate/dispatch,直到没有更多的消息"方法)解决了问题, COM脚本可以成功调用下一个方法.虽然在这种特定情况下可能还可以,但ProcessMessages()有效地随意调用随机点是可以避免的 - 它可以导致重新进入.如果可能的话,我想找出导致队列满的原因.

检查消息队列的内容

使用GetQueueStatus以确定什么样的消息在队列中发现,有计时器(QS_TIMER),发布的消息(QS_POSTMESSAGE),"所有发布的消息"(即其他发布者,QS_ALLPOSTMESSAGE)和绘制消息(QS_PAINT).

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

如果我打电话:

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

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

如果我这样称呼:

while (::PeekMessage(&oMsg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE | PM_NOYIELD) != 0) {...
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

我已经多次复制了这一切 - 没有一次是一次性行为.

所以:

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

我很困惑,很可能犯了一个基本的错误 - 它已经进入了一个令人费解的事情的阶段.任何有关COM问题的帮助或消息行为的解释,包括'你犯了基本错误X,很愚蠢的你',将非常感谢:)

Del*_*ics 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)
Run Code Online (Sandbox Code Playgroud)

其他PM_QS_xxx常量被记录为等于相应的QS_xxx常量<< 16,但没有任何地方表明情况始终如此并且可以外推到所有QS_xxxx常量。

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

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