Запуск COM-события из другого потока - PullRequest
3 голосов
/ 29 октября 2009

Я создал внутрипроцессный COM-объект (DLL), используя ATL. Обратите внимание, что это объект, а не элемент управления (поэтому не имеет окна или пользовательского интерфейса.) Моя проблема заключается в том, что я пытаюсь запустить событие из второго потока и получаю «катастрофический сбой» (0x8000FFFF). Если я запускаю событие из моего основного потока, я не получаю ошибку. Второй поток вызывает CoInitializeEx, но это не имеет значения. Я использую модель многопоточной обработки, но переключение на Free Threaded не помогает.

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

Например, в исходном файле моего основного объекта:

STDMETHODIMP MyObject::SomeMethod(...)
{
  CreateThread(NULL, 0, ThreadProc, this, 0, NULL);
  // Succeeds with S_OK
  FireEvent(L"Hello, world!");
  return S_OK;
}

DWORD WINAPI ThreadProc(LPVOID param)
{
  CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  MyObject* comObject = reinterpret_cast<MyObject*>(param);
  // Fails with 0x8000FFFF
  comObject->FireEvent(L"Hello, world!");
}

void MyObject::FireEvent(BSTR str)
{
  ...
  // Returns 0x8000FFFF if called from ThreadProc
  // Returns S_OK if called from SomeMethod
  pConnection->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);
}

Ответы [ 2 ]

4 голосов
/ 30 октября 2009

Основы COM

В STA ваш объект живет в одном потоке (The Thread). Этот поток является тем, на котором он создан, его методы выполняются и его события запускаются. STA гарантирует, что никакие два метода вашего объекта не выполняются одновременно (потому что они должны выполняться в потоке, поэтому это хорошее следствие).

Это не означает, что ваш объект не может быть доступен из других потоков. Это делается путем создания прокси вашего объекта для каждого потока, кроме The Thread. В потоке вы упаковываете IUnknown с CoMarshalInterThreadInterfaceInStream , а в другом потоке вы распаковываете с CoGetInterfaceAndReleaseStream , который фактически создает прокси в другом потоке. Этот прокси-сервер использует насос сообщений для синхронизации вызовов объекта, вызовов, которые все еще выполняются в потоке, поэтому поток должен быть свободен (не занят) для выполнения вызова из другого потока.

В вашем случае вы хотите, чтобы ваш объект мог выполнять методы в одном потоке и вызывать события в другом потоке. Так что это должно иметь место в MTA, поэтому ваш объект должен жить в MTA, поэтому ваш класс должен быть свободным. Нити принадлежат ровно одной квартире, поэтому нити не могут быть в MTA и STA одновременно. Если ваш объект живет в MTA всякий раз, когда объект STA пытается его использовать, ему придется создать прокси. Таким образом, вы получаете небольшие накладные расходы.

Что я предполагаю, так это то, что вы думаете о какой-то очень умной «технике» для разгрузки основного потока и выполнения некоторых «асинхронных» событий, которые в итоге не сработают :-)) Если вы думаете об этом, для слушателя во втором [рабочем] потоке ...

Кстати, эта строка

MyObject* comObject = reinterpret_cast<MyObject*>(param);

можно сделать только в MTA.

0 голосов
/ 29 октября 2009

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

Threading в COM (википедия):
Модель однопотоковой квартиры (STA) - очень распространенная модель. Здесь COM-объект находится в положении, аналогичном пользовательскому интерфейсу настольного приложения. В модели STA один поток выделяется для управления методами объекта, т. Е. Один поток всегда используется для выполнения методов объекта. При таком расположении вызовы методов из потоков за пределами квартиры распределяются и автоматически ставятся в очередь системой (через стандартную очередь сообщений Windows). Таким образом, нет никакого беспокойства по поводу состояния гонки или отсутствия синхронности, потому что каждый вызов метода объекта всегда выполняется до завершения, прежде чем будет вызван другой.

См. Также Передача сообщений в той же статье.

...