User32 SendMessage зависает, когда насос сообщений бездействует

У меня есть многопоточная dll для стороннего приложения. Моя dll вызывает сообщения в основной поток пользовательского интерфейса, вызывая SendMessage с настраиваемым типом сообщения:

typedef void (*CallbackFunctionType)();
DWORD _wm;
HANDLE _hwnd;
DWORD threadId;

Initialize()
{
    _wm = RegisterWindowMessage("MyInvokeMessage");
    WNDCLASS wndclass = {0};
    wndclass.hInstance = (HINSTANCE)&__ImageBase;
    wndclass.lpfnWndProc = wndProcedure;
    wndclass.lpszClassName = "MessageOnlyWindow";
    RegisterClass(&wndclass);
    _hwnd = CreateWindow(
         "MessageOnlyWindow",
         NULL,
         NULL,
         CW_USEDEFAULT,
         CW_USEDEFAULT,
         CW_USEDEFAULT,
         CW_USEDEFAULT,
         NULL,
         NULL,
         (HINSTANCE)&__ImageBase,
         NULL);
    threadId = GetCurrentThreadId();
}

void InvokeSync(CallbackFunctionType funcPtr)
{
    if (_hwnd != NULL && threadId != GetCurrentThreadId())
        SendMessage(_hwnd, _wm, 0, (LPARAM)funcPtr);
    else
        funcPtr();
}
static LRESULT CALLBACK wndProcedure(
    HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    if (Msg == _wm)
    {
        CallbackFunctionType funcPtr = (CallbackFunctionType)lParam;
        (*funcPtr)();
    }
    return DefWindowProc(hWnd, Msg, wParam, lParam);
}

Приложение представляет собой MDI, и я выполняю открытие документа/извлечение содержимого/процесс в фоновом режиме/сохранение набора документов, поэтому оно постоянно переключает активные документы и открывает и закрывает новые.

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

Когда я приостанавливаю его в отладчике, я вижу, что основной поток имеет этот стек вызовов:

user32.dll!_NtUserGetMessage@16() + 0x15 bytes
user32.dll!_NtUserGetMessage@16() + 0x15 bytes
mfc42.dll!CWinThread::PumpMessage() + 0x16 bytes
// the rest is normal application stuff

И фоновый поток, который заблокирован, имеет такой стек вызовов:

user32.dll!_NtUserMessageCall@28() + 0x15 bytes
user32.dll!_NtUserMessageCall@28() + 0x15 bytes
mydll!InvokeSync(funcPtr)
// the rest is expected dll stuff

Таким образом, кажется, что он застревает на вызове «SendMessage()», но, насколько я вижу, насос сообщений в основном потоке сидит без дела.

Однако, если я вручную нажму на неактивный документ (чтобы сделать его активным), каким-то образом это все разбудит, и событие SendMessage(), наконец, пройдет, и оно возобновит обработку.

Основное приложение использует Microsoft Fibers, 1 волокно на документ. Может ли мой SendMessage застрять в фоновом волокне, которое отключается или что-то в этом роде? на волокне прямо перед тем, как оно станет неактивным или что-то в этом роде, и только при принудительном переключении контекста это волокно когда-либо сможет обрабатывать свои сообщения? Я действительно не понимаю, как нити и волокна взаимодействуют друг с другом, поэтому я как бы хватаюсь за соломинку.

Что может привести к тому, что сообщения останутся без обработки? Что еще более важно, есть ли способ предотвратить возникновение этой ситуации? Или, по крайней мере, как мне отладить такую ​​ситуацию?


person Bryce Wagner    schedule 23.06.2015    source источник
comment
Скорее всего, вы отправляете сообщение в окно, принадлежащее потоку без перекачки сообщений.   -  person David Heffernan    schedule 23.06.2015
comment
Нет, я разместил стек вызовов потока, которому принадлежит HWND, он сидит там без дела, ожидая _NtUserGetMessage(). Вероятно, 9999/10000 сообщений обрабатываются правильно, просто иногда одно из них висит и забывается, ожидая обработки.   -  person Bryce Wagner    schedule 23.06.2015
comment
Судя по вашему описанию, сообщение никогда не попадает в очередь. Иногда это может быть вводящим в заблуждение симптомом (имеется в виду, что происходит что-то еще, о чем не сообщалось должным образом). Отображает ли ваше окно вывода какие-либо примечательные элементы?   -  person Jeff    schedule 23.06.2015
comment
Просто куча сообщений Поток 'Win32 Thread' (0x???) завершился с кодом 0 (0x0).   -  person Bryce Wagner    schedule 23.06.2015
comment
SendMessage() опасна и может привести к взаимоблокировке. Вместо этого используйте PostMessage().   -  person Hans Passant    schedule 23.06.2015
comment
Я не понимаю первые предложения описания. Вы внедряете DLL в сторонний код? В каком смысле ваша DLL многопоточная? Если это стороннее приложение, как узнать, что оно использует волокна? Вы создаете свое окно только для сообщений в основном потоке пользовательского интерфейса или в случайном потоке?   -  person Adrian McCarthy    schedule 23.06.2015
comment
Помните, что SendMessage не проходит через конвейер сообщений. Два сообщения, отправленные одновременно (через SendMessage) из разных потоков, должны быть сериализованы. Вы, наверное, зашли в тупик.   -  person Adrian McCarthy    schedule 23.06.2015
comment
dll загружается как плагин сторонним приложением, и создатели этого приложения хорошо задокументировали тот факт, что оно использует волокна внутри, с одним волокном на документ, но не так много информации о последствиях этого. DLL является многопоточной, потому что она отключается и выполняет кучу обработки в нескольких потоках, но все взаимодействия с основным приложением необходимо вызывать обратно в основной поток приложения.   -  person Bryce Wagner    schedule 23.06.2015
comment
@AdrianMcCarthy Во время зависания существует только один отправляющий поток, а принимающий (основной) поток бездействует в _NtUserGetMessage(), и этот основной поток все еще обрабатывает другие сообщения (клавиатура, мышь и т. д.), но не SendMessage() из фонового потока.   -  person Bryce Wagner    schedule 23.06.2015
comment
@HansPassant Основная причина, по которой я использовал SendMessage, заключалась в том, что это гарантированная доставка, в отличие от PostMessage, где сообщения могут быть отброшены, если они переполняют очередь. Так что это означает, что я должен обрабатывать всю свою собственную синхронизацию. Но на данный момент PostMessage с повторной попыткой и тайм-аутом может быть единственным шансом на его работу. Просто очень грязно.   -  person Bryce Wagner    schedule 23.06.2015
comment
Очередь 10000. Как быстро вы отправляете/отправляете сообщения?   -  person Martin James    schedule 23.06.2015
comment
Кроме того, задолго до того, как очередь заполнится, вы обнаружите, что ваш графический интерфейс довольно хорошо мертв для взаимодействия с пользователем - не будет двигаться, изменять размер, таймеры не срабатывают, ввод КБ/мыши не обрабатывается и т. д. Тем не менее, почти любой схема лучше, чем SendMessage().   -  person Martin James    schedule 23.06.2015
comment
@MartinJames У меня, вероятно, никогда не будет больше дюжины или около того моих собственных сообщений в самом крайнем случае. Но тот факт, что PostMessage не гарантирует доставку, означает, что я должен реализовать механизм повторной попытки/тайм-аута, иначе существует вероятность того, что все это заблокируется по моей вине.   -  person Bryce Wagner    schedule 23.06.2015
comment
Вы так и не прояснили проблему. Вы внедряете DLL в сторонний код? В каком смысле ваша DLL многопоточная? Если это стороннее приложение, как узнать, что оно использует волокна?   -  person Adrian McCarthy    schedule 24.06.2015
comment
@BryceWagner, если «дюжина или около того» - это все, вы можете предположить, что PostMessage гарантирует доставку. После успешной постановки в очередь PostMessage никогда не теряет сообщения. Если бы это произошло, это было бы катастрофой для Windows.   -  person Martin James    schedule 24.06.2015
comment
@AdrianMcCarthy Никаких инъекций, у приложения есть подключаемый API, оно загружает мою DLL и вызывает точку входа. В этой точке входа я создаю окно для вызова сообщений и регистрирую некоторые обратные вызовы. Когда он вызывает один из этих обратных вызовов, я создаю новый поток для своего кода и немедленно возвращаю управление приложению. Затем, когда мой код хочет поговорить с приложением, он использует очередь сообщений Windows для синхронного вызова обратных вызовов в основной поток.   -  person Bryce Wagner    schedule 24.06.2015
comment
@MartinJames SendMessage тоже не должен терять такие сообщения ... Но использование PostMessage и моя собственная синхронизация, похоже, решили мою проблему.   -  person Bryce Wagner    schedule 24.06.2015


Ответы (2)


Проверьте аргументы GetMessage. Третий и четвертый — это диапазон идентификаторов сообщений. Ваше сообщение будет счастливо стоять в очереди, если его идентификатор находится за пределами этого диапазона.

person MSalters    schedule 23.06.2015
comment
Хороший совет, но GetMessage находится внутри mfc42.dll PumpMessage() и доставляется после переключения контекста волокна, поэтому я думаю, что фильтрация диапазона вряд ли является причиной. - person Bryce Wagner; 24.06.2015

Я пошел дальше и реализовал свою собственную очередь сообщений и формат сообщения, который использует семафор для уведомления о том, что сообщение было получено, и другое, когда оно было завершено, а затем повторять PostMessage каждую 1 секунду, пока «сообщение получено» не будет сигнализировано , затем дождитесь завершения сообщения с бесконечным тайм-аутом.

Любые дополнительные сообщения PostMessages игнорируются, потому что они больше не содержат полезной нагрузки для выполнения, они просто сообщают основному потоку о необходимости проверить очередь на наличие входящих событий.

С тех пор, как я внес эти изменения, я больше не сталкивался с ситуацией. Насколько я могу судить, отправленное сообщение должно оказаться в очереди отключенного волокна и быть забытым до тех пор, пока это волокно не будет снова включено. Репостируя сообщение, он может просто повторять попытки, пока активное волокно не заметит находящееся там сообщение.

person Bryce Wagner    schedule 23.06.2015