如何解释这种奇怪的 PeekMessage 行为(尝试处理完整的消​​息队列,过滤特定消息)?

发布于 2024-12-17 19:39:40 字数 3981 浏览 0 评论 0原文

我们的应用程序充当 COM 服务器,其中所有自动化都发生在单个 STA 单元(在应用程序的主线程中)内,并且一些进行长时间(> 10 分钟)调用的 VBS 脚本失败,并出现错误“系统调用失败 (80010100)” ”。一些研究(一个,< a href="https://stackoverflow.com/questions/123323/how-deep-is-the-win32-message-queue">两个,)表示这可能是由消息引起的队列已满,因此当 COM 尝试调用下一个方法时,它无法调用。

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

我想我应该在末尾检查线程的消息队列冗长的 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。我的结论是消息队列已满确实是问题的原因。我的系统的消息队列限制是默认 10000(请参阅备注部分。)

Application->ProcessMessages() 的调用(调用应用程序的消息循环,直到它为空,对于那些不是 Delphi / C++Builder 用户的人来说 - 这是一个相当正常的“获取/翻译/调度,直到没有更多消息”方法)解决了问题,COM 脚本可以成功调用next方法。虽然在这种特定情况下可能没问题,但在有效的随机点调用 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 << 16 应该只处理计时器消息,但它会产生更多的消息,其中许多根本不是计时器。

最后,如果我调用第三种变体:

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

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

我已经将这一切重复了好几次——没有一个是一次性的行为。

那么:

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

我很困惑,而且很可能犯了一个低级错误——在你这样做的时候,你会看到一些令人费解的东西。对于 COM 问题的任何帮助或消息行为的解释,包括“你犯了基本错误 X,天哪,你太愚蠢了”,我们将不胜感激:)

Our application acts as a COM server where all automation occurs within a single STA apartment (in the application's main thread), and some VBS scripts which make lengthy (>10 minute) calls are failing with the error "System call failed (80010100)". Some research (one, two, three) indicates this is probably caused by the message queue filling up, so that when COM tries to invoke the next method it is unable to.

In case it's important, the app is developed with Embarcadero RAD Studio 2010 (mostly C++, smatterings of Delphi for some of the COM classes.)

I thought I would examine the thread's message queue at the end of the lengthy COM method call (i.e., just before it returns) to see what it contains, by using GetQueueStatus and PeekMessage. While it appears that the queue is full, I am seeing some odd behaviour and I'm having trouble figuring out both why PeekMessage is behaving the way it is, and exactly why the queue is full - i.e., what it's full with.

Slightly lengthy explanation ahead:

Testing the thread's message queue is full

Code like this:

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
}

when run at the end of a short COM-invoked method can post thousands (say, 9996) messages; at the end of the lengthy method call that is causing the script to fail, it can post 0. My conclusion is that the message queue being full really is the cause of the problem. My system's message queue limit is the default 10000 (see the Remarks section.)

A call to Application->ProcessMessages() (invokes the app's message loop until it's empty, for those of you who aren't Delphi / C++Builder users - it's a fairly normal "get/translate/dispatch until no more messages" method) solves the problem and the COM script can invoke the next method successfully. Although probably okay in this specific situation, calling ProcessMessages() in effectively random spots is something to avoid - it can lead to re-entrancy. I'd like to find out what is causing the queue to be full if possible.

Examining the contents of the message queue

Using GetQueueStatus to determine what sort of messages are in the queue reveals that there are timer (QS_TIMER), posted messages (QS_POSTMESSAGE), 'all posted messages' (i.e. other posted ones, QS_ALLPOSTMESSAGE), and paint messages (QS_PAINT).

Here's where it gets weird. I'm trying to remove select messages or types of messages using PeekMessage with PM_REMOVE to both partially empty the queue and to count the number of each type of message.

If I call:

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

I get just over ten thousand messages, usually 10006 or so. Not all of them are WM_TIMER: several thousand are WM_APP+202, a message we use internally, which does not appear to be being posted (by us) in anywhere near such huge quantities. I've checked this: it's only sent a few times. There are also a few thousand of another WM_APP+something message we use; this one probably genuinely is being sent too often.

If I call this instead :

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

I get about ten messages, all of which are truly WM_TIMERs. Why? The PeekMessage documentation indicates that passing QS_TIMER << 16 should process only timer messages, but it produces vastly more messages, many of which aren't timers at all.

Finally, if I call a third variation instead:

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

which is filtering directly for the custom message that the first line of code returns thousands of, I get seventeen messages removed.

I've reproduced all this several times - none of it is once-off behaviour.

So:

  • Why does the first call to PeekMessage remove more than just timers (compared to the second call)? Curiosity only, really.
  • Why does the first call to PeekMessage remove several thousand of the WM_APP+202 messages (one we define and use and don't send that many of) but if I instead call the third variation, which filters directly for that specific message, I get 17?
  • If I get such differing results for the same message, how do I figure out what's filled up the queue and how best to avoid it?
  • Or to avoid all the above: can I safely ignore all this, and so then how should I deal with a full message queue when COM is about to try invoking a method?

I'm puzzled, and may well be making an elementary mistake - it's got to the stage of looking at something puzzling where you do that. Any help for the COM problem or explanations of the message behaviour, including 'You made elementary mistake X, golly that was stupid of you', will be greatly appreciated :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

趁微风不噪 2024-12-24 19:39:40

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 常量 < < 16,但没有任何地方表明情况始终如此,并且可以推断到所有 QS_xxxx 常量。

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

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

GetQueueStatus() accepts QS_xxx parameters but PeekMessage() accepts only PM_QS_xxx constants.

This explains the discrepancy between the number of WM_TIMER messages indicated by QueueStatus and subsequently removed by PeekMessage(). Your PeekMessage(PM_REMOVE) call is not removing WM_TIMER messages but something else entirely.

I think you have misunderstood the documentation of PeekMessage(). PM_QS_POSTMESSAGE is documented as being of equivalent value as:

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

And other PM_QS_xxx constants are documented as being equal to the corresponding QS_xxx constant << 16, but nowhere does it say that this is consistently the case and can be extrapolated to ALL QS_xxxx constants.

I suspect that QS_TIMER << 16 is yielding some filter which is doing more than just filtering WM_TIMER messages (clearly it is, I just can't say with certainty what filter it yields).

As far as I know, WM_TIMER is the only timer related message so there is no need to have a wider filter for a larger super set of timer messages - there is no such super set. If you want to filter timer messages just filter WM_TIMER.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文