Invoke () в стиле WinForm в неуправляемом C ++ - PullRequest
3 голосов
/ 13 апреля 2010

Я играл с дизайном типа DataBus для хобби-проекта, и столкнулся с проблемой. Внутренние компоненты должны уведомлять пользовательский интерфейс о том, что что-то произошло. Моя реализация шины доставляет сообщения синхронно по отношению к отправителю. Другими словами, когда вы вызываете Send(), метод блокируется, пока не будут вызваны все обработчики. (Это позволяет вызывающим сторонам использовать управление стековой памятью для объектов событий.)

Однако рассмотрим случай, когда обработчик событий обновляет графический интерфейс в ответ на событие. Если вызывается обработчик, а отправитель сообщения живет в другом потоке, то обработчик не может обновить графический интерфейс из-за того, что элементы графического интерфейса Win32 имеют привязку к потоку. Более динамичные платформы, такие как .NET, позволяют вам обрабатывать это, вызывая специальный метод Invoke () для перемещения вызова метода (и аргументов) в поток пользовательского интерфейса. Я предполагаю, что они используют парковочное окно .NET или тому подобное для такого рода вещей.

Родилось болезненное любопытство: можем ли мы сделать это на C ++, даже если мы ограничим масштаб проблемы? Можем ли мы сделать это лучше, чем существующие решения? Я знаю, что Qt делает нечто подобное с функцией moveToThread().

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

if(! this->IsUIThread())
{
    Invoke(MainWindowPresenter::OnTracksAdded, e);
    return;
}

на вершине каждого метода пользовательского интерфейса. Этот танец был обычным явлением в WinForms при решении этой проблемы. Я думаю, что такого рода проблемы должны быть изолированы от предметно-ориентированного кода и объекта-обертки, созданного для его решения.

Моя реализация состоит из:

  • DeferredFunction - функтор, который сохраняет целевой метод в FastDelegate и копирует один аргумент события. Это объект, который отправляется через границы потока.

  • UIEventHandler - отвечает за отправку одного события из шины. Когда вызывается метод Execute(), он проверяет идентификатор потока. Если он не соответствует идентификатору потока пользовательского интерфейса (установленному во время создания), в куче выделяется функция DeferredFunction с экземпляром, методом и аргументом события. Указатель на него отправляется в поток пользовательского интерфейса через PostThreadMessage().

  • Наконец, хук-функция для потока сообщений потока используется для вызова функции DeferredFunction и отмены ее выделения. В качестве альтернативы я могу использовать фильтр цикла сообщений, так как мой UI Framework (WTL) поддерживает их.

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

Ответы [ 2 ]

3 голосов
/ 14 апреля 2010

Я давно вышел из игры Win32, но для достижения этой цели мы использовали PostMessage, чтобы отправить сообщение Windows обратно в поток пользовательского интерфейса, а затем обработать вызов оттуда, передавая дополнительный информация, которая вам нужна в wParam / lParam.

На самом деле я не удивлюсь, если именно так .NET справится с этим в Control.Invoke.

Обновление: я был currios, поэтому я проверил с отражателем, и это то, что я нашел.

Control.Invoke вызывает MarshaledInvoke, который выполняет кучу проверок и т. Д., Но интересными являются вызовы RegisterWindowMessage и PostMessage. Так что все не сильно изменилось:)

1 голос
/ 15 апреля 2010

Немного последующей информации:

Есть несколько способов сделать это, каждый из которых имеет свои преимущества и недостатки:

  • Самый простой способ - это, вероятно, QueueUserAPC() вызов. APC слишком подробны для объяснения, но единственным недостатком является то, что они могут работать, когда вы не готовы к ним, если поток случайно переведен в состояние ожидания с оповещением. Из-за этого я их избегал. Для коротких приложений это, вероятно, нормально.

  • Второй способ включает использование PostThreadMessage(), как упоминалось ранее. Это лучше, чем QueueUserAPC(), поскольку ваши обратные вызовы не чувствительны к тому, что поток пользовательского интерфейса находится в состоянии ожидания с оповещением, но при использовании этого API возникает проблема, связанная с тем, что ваши обратные вызовы вообще не запускаются. См. обсуждение Раймонда Чена по этому вопросу . Чтобы обойти это, вам нужно перехватить очередь сообщений потока.

  • Третий способ заключается в создании невидимого окна только для сообщений, в котором WndProc вызывает отложенный вызов, и использовании PostMessage() для ваших данных обратного вызова. Поскольку оно направлено на конкретное окно, сообщения не будут съедены в модальных ситуациях пользовательского интерфейса. Кроме того, окна только для сообщений не защищены от широковещательной рассылки системных сообщений (что предотвращает конфликты идентификаторов сообщений). Недостатком является то, что он требует больше кода, чем другие опции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...