2009-10-29 57 views
3

我使用ATL创建了一个进程内COM对象(DLL)。请注意,这是一个对象,而不是控件(所以没有窗口或用户界面。)我的问题是,我试图从第二个线程触发一个事件,并且出现'灾难性故障'(0x8000FFFF)。如果我从主线程中触发事件,那么我不会收到错误。第二个线程调用CoInitializeEx,但这没有什么区别。我正在使用Apartment线程模型,但切换到Free Threaded并没有帮助。从另一个线程触发COM事件

事实上,我试图从第二个线程做到这一点显然至关重要。有没有简单的方法来做到这一点,或者我将不得不实施一些隐藏窗口的消息传递?

例如,在我的主要对象的源文件:

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); 
} 

回答

4

COM基础

在STA你的对象住在一个单独的线程(线程)。这个线程是它创建的线程,它的方法被执行,并且事件被触发。 STA确保你的对象没有两个方法被同时执行(因为它们必须在The Thread上执行,所以这是一个很好的结果)。

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

在你的情况下,你希望你的对象能够在一个线程上执行方法并且在另一个线程上产生事件。所以这发生在MTA中,所以你的对象必须住在MTA中,所以你的类必须是自由线程的。线程只属于一个单元,所以一个线程不能同时在MTA和STA中。如果你的对象住在MTA中,只要STA对象试图使用它,它就必须创建一个代理。所以你会有轻微的开销。

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

顺便说一句,这条线

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

可以在MTA只能做。

+0

非常好的解释。非常感谢你。 – Rob 2009-10-30 07:43:51

0

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

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

另请参阅Message Pumping在同一篇文章。

+0

你有更多关于这里涉及什么的细节? – Rob 2009-10-29 19:49:34

+0

用更多的信息更新了答案 - 现在没有时间通过​​msdn抓取简要的细节:) – 2009-10-29 20:38:56

+0

但要缩短它:如果你的主机应用程序是一个STA,你需要从拥有的线程调用或调用呼叫在通过COM(编组)的右侧线程上。否则,你必须自己照顾正确的线程。 – 2009-10-29 20:50:36