Чем объясняется это странное поведение PeekMessage (попытка справиться с полной очередью сообщений, фильтрация определенных сообщений)? - PullRequest
6 голосов
/ 22 ноября 2011

Наше приложение действует как COM-сервер, где вся автоматизация происходит в пределах одной квартиры STA (в главном потоке приложения), а некоторые VBS-скрипты, которые выполняют длинные (> 10 минут) вызовы, не работают с ошибкой «Системный вызов не выполнен ( 80010100)». Некоторые исследования ( one , two , three ) показывают, что это, вероятно, вызвано заполнением очереди сообщений, поэтому, когда COM пытается вызвать следующий метод, он не может.

Если это важно, приложение разработано с Embarcadero RAD Studio 2010 (в основном C ++ , smatterings Delphi для некоторых классов COM.)

Я думал, что проверю очередь сообщений потока в конце длинного вызова метода 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. Мой вывод состоит в том, что переполненная очередь сообщений действительно является причиной проблемы. В моей системе предел очереди сообщений по умолчанию составляет 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 << 16 должна обрабатывать только сообщения таймера, но она генерирует значительно больше сообщений, многие из которых вообще не являются таймерами. </p>

Наконец, если я назову третий вариант вместо этого:

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

, который фильтрует непосредственно для пользовательского сообщения, которое первая строка кода возвращает тысячи, я получаю семнадцать сообщений удалены.

Я воспроизвел все это несколько раз - ни один из них не является разовым поведением.

Итак:

  • Почему первый вызов PeekMessage удаляет больше, чем просто таймеры (по сравнению со вторым вызовом)? Только любопытство.
  • Почему первый вызов PeekMessage удаляет несколько тысяч сообщений WM_APP + 202 (одно мы определяем и используем и не отправляем столько), но если я вместо вызываю третий вариант, который Фильтры непосредственно для этого конкретного сообщения, я получаю 17?
  • Если я получу такие разные результаты для одного и того же сообщения, как мне выяснить, что заполнило очередь и как лучше ее избежать?
  • Или чтобы избежать всего вышеперечисленного: могу ли я спокойно проигнорировать все это, и тогда как мне обращаться с полной очередью сообщений, когда COM собирается попытаться вызвать метод?

Я озадачен и, возможно, допускаю элементарную ошибку - дошло до того, что вы смотрите на что-то загадочное, когда вы это делаете. Будем весьма благодарны за любую помощь в связи с проблемой COM или объяснения поведения сообщения, в том числе «Вы совершили элементарную ошибку X, черт возьми, которая была глупой с вашей стороны»:)

1 Ответ

1 голос
/ 23 ноября 2011

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 константы.

Я подозреваю, что QS_TIMER << 16 дает некоторый фильтр, который делает больше, чем просто фильтрует WM_TIMER сообщения (ясно, что я могуне могу с уверенностью сказать, какой фильтр он выдает).

Насколько я знаю, WM_TIMER является единственным сообщением, связанным с таймером, поэтому нет необходимости иметь более широкий фильтр для большего набора супер таймеров сообщений.- такого суперсета нет.Если вы хотите фильтровать сообщения таймера, просто отфильтруйте WM_TIMER.

...