CoInitializeEx在COM对象内部调用时返回S_OK

时间:2013-09-20 08:49:03

标签: c++ com com-interop atl apartments

前一段时间,我不得不修改旧的COM DLL(Visual C ++ 2010,ATL),将其从“Apartment”线程模型迁移到“Both”,即现在可以从STA和MTA线程调用它而无需序列化调用(当然,我必须为共享数据添加内部同步)。当从.NET应用程序通过Interop调用我的DLL时,这会在将COM事件(连接点)转换为.NET事件时导致问题(即使在.NET应用程序中我也必须支持STA和MTA)。 为了解决这些问题,我改变了触发事件的方式。

1)如果在STA上下文中调用DLL,它就像以前一样工作,即它创建一个不可见的窗口,然后,当必须引发事件时,它调用PostMessage到该窗口,然后主STA线程调用实际的事件触发代码,即CProxy_IMyEventFiringInterface成员函数(CProxy_IMyEventFiringInterface派生自IConnectionPointImpl)。

2)如果在MTA上下文中调用DLL,我没有主COM线程,我不能做PostMessage,所以我使用我创建的自定义线程,让该线程调用IConnectionPointImpl函数。

但是AFAIK没有Windows API可以检测调用线程是STA还是MTA。许多网站都建议像这样使用CoInitializeEx:

HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
switch(hr)
{
    case S_FALSE:  // we are in a Multi-Threaded Apartment (MTA)
    CoUninitialize(); // each successful call to CoInitialize or CoInitializeEx, including any call that returns S_FALSE, must be balanced by a corresponding call to CoUninitialize
    break;
    case RPC_E_CHANGED_MODE:  // we are in a Single-Threaded Apartment (STA)
    break;
    default:  // IMPOSSIBLE!!!!
}

我决定将这个调用放在CMyComComponent :: FinalConstruct中的CoInitializeEx中。 一切都很好......直到今天。在客户场景中,我从我的跟踪工具中看到,对于某个.NET EXE应用程序(我没有源代码),上面的代码最终会出现在默认分支中,因为CoInitializeEx返回了S_OK。 这怎么可能? Microsoft文档说S_OK意味着“COM库已在此线程上成功初始化”,但我是INSIDE COM对象,COM库必须已经初始化! 顺便说一句,默认分支不会关闭应用程序,但是,由于返回了S_OK,它会调用CoUninitialize(每次成功调用CoInitialize或CoInitializeEx,包括任何返回S_FALSE的调用,都必须通过对CoUninitialize的相应调用进行平衡)然后DLL继续假设STA(后见之明的错误移动)。但它不是STA:事实上,后来的Pos​​tMessage返回FALSE。

如果CoInitializeEx(NULL,COINIT_MULTITHREADED)返回S_OK,我可以简单地更改代码以使用MTA作为默认值,我应该从一开始就做到了。 但我也希望确保这是正确的做法,以避免将来出现进一步的问题。 非常感谢你 德梅特里奥

1 个答案:

答案 0 :(得分:5)

当您使用implicit MTA帖子时,这是可能的。正确的功能是这样的:

BOOL IsMultiThreadedApartment() throw()
{
    HRESULT nResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if(SUCCEEDED(nResult))
        CoUninitialize();
    return SUCCEEDED(nResult);
}

您正在遇到这种不寻常的情况,因为调用者应用程序无法在其实例化您的类的线程上初始化COM。但请注意,如果稍后调用者将线程初始化为STA,则可能会遇到棘手的情况,即您的类在STA线程上以MTA模式运行。另一方面,自己做CoInitializeEx可能会导致调用者无法错误地进行COM初始化。