从另一个线程发射COM事件

时间:2009-10-29 15:37:20

标签: c++ com atl

我使用ATL创建了一个进程内COM对象(DLL)。请注意,这是一个对象,而不是一个控件(所以没有窗口或用户界面。)我的问题是我试图从第二个线程触发一个事件,我得到一个'灾难性的失败'(0x8000FFFF)。如果我从主线程中触发事件,那么我不会收到错误。第二个线程正在调用CoInitializeEx,但这没有任何区别。我正在使用Apartment线程模型,但切换到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 个答案:

答案 0 :(得分:4)

COM基础知识

在STA中,您的对象位于单个线程(线程)上。这个线程就是它创建的线程,它的方法被执行,它的事件就开始了。 STA确保不会同时执行对象的两个方法(因为它们必须在线程上执行,因此这是一个很好的结果)。

这并不意味着无法从其他线程访问您的对象。这是通过为除Thread之外的每个线程创建对象的代理来完成的。在线程中,您使用CoMarshalInterThreadInterfaceInStream打包IUnknown,在另一个线程上使用CoGetInterfaceAndReleaseStream解压缩,这实际上在另一个线程上创建了代理。此代理使用消息泵来同步对您的对象的调用,仍然在线程上执行的调用,因此线程必须是空闲的(不忙)才能执行来自另一个线程的调用。

在您的情况下,您希望您的对象能够在一个线程上执行方法并在另一个线程上引发事件。所以这个在MTA中发生,所以你的对象必须存在于MTA中,所以你的类必须是自由线程的。线程只属于一个公寓,因此线程不能同时在MTA和STA中。如果您的对象在STA对象尝试使用它时都存在于MTA中,则必须创建代理。所以你得到一点点开销。

我猜你正在考虑一些非常聪明的“技术”来卸载你的主线程,并做一些“异步”事件,最终不会飞:-))如果你想到它有在第二个[工人]线程上听一听......

顺便说一句,这一行

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

只能在MTA中完成。

答案 1 :(得分:0)

我认为真正的问题不是您的组件配置的内容,而是主机进程。许多主机,如Office对象模型的主机,都住在一个单独的线程公寓中,在这种情况下,不允许从主线程以外的任何东西调用它们。
如果是这种情况,您可以让COM通过使用单线程单元模型并将实际调用移动到CoClass中的函数并从您的线程调用该函数来完成工作。
这只会在幕后传递窗口消息,但是你不必自己实现它。

Threading in COM(维基百科):
单线程公寓(STA)模型是一种非常常用的模型。这里,COM对象位于类似于桌面应用程序的用户界面的位置。在STA模型中,单个线程专用于驱动对象的方法,即始终使用单个线程来执行对象的方法。在这样的安排中,来自公寓外部的线程的方法调用被编组并由系统自动排队(通过标准Windows消息队列)。因此,不必担心竞争条件或缺乏同步性,因为对象的每个方法调用总是在调用另一个对象之前执行完毕。

另请参阅同一篇文章中的Message Pumping