Вопрос

Я создал внутрипроцессный 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);
}
Это было полезно?

Решение

Основы COM

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

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

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

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

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

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

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

Другие советы

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top