【发布时间】:2012-01-04 00:12:09
【问题描述】:
我们的应用程序充当 COM 服务器,其中所有自动化都发生在单个 STA 单元内(在应用程序的主线程中),并且一些进行长时间(>10 分钟)调用的 VBS 脚本失败并出现错误“系统调用失败( 80010100)”。一些研究(one、two、three)表明这可能是由于消息队列已满,因此当 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
}
当在一个简短的 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)。
这就是奇怪的地方。我正在尝试使用PeekMessage 和PM_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