ATL COM:从其他线程访问事件方法

时间:2013-05-14 20:38:01

标签: c++ multithreading com atl

我正在为现有的VS2010 C ++ MFC应用程序实现COM接口。 COM接口交互的大多数部分工作得很好,但我很困惑如何从另一个运行/定义COM接口的线程触发COM事件。该应用程序是多线程的,其中一个主线程运行COM接口并处理GUI更改(线程1 )和一个线程以接收来自C库的传入消息(线程2 )。

对于在线程2中收到的某些消息,我想通过发送COM事件来通知COM客户端。我读过很多帖子(Firing COM Event From Another Thread就是其中之一),并提到了CoMarshalInterThreadInterfaceInStream / CoGetInterfaceAndReleaseStream。使用Google我似乎无法找到对我有用的这些方法的任何用法;我只是不明白如何实现这些功能,如果他们真的会帮助我。

相关代码部分:

TestCOM.idl:(界面定义)

 interface ITestCOM: IDispatch
{
    [id(1), helpstring("method Test")] HRESULT Test();
};

dispinterface _ITestCOMEvents
{
    properties:
    methods:
        [id(1), helpstring("event ExecutionOver")] HRESULT TestEvent();
};

coclass TestAppCOM
{
    [default] interface ITestCOM;
    [default, source] dispinterface _ITestCOMEvents;
};

ITestCOMEvents_CP.h (VS生成的连接点/事件类)

template<class T>
class CProxy_ITestCOMEvents : 
   public ATL::IConnectionPointImpl<T, &__uuidof(_ITestCOMEvents)>
{
public:
    HRESULT Fire_TestEvent()
    {
       HRESULT hr = S_OK;
       T * pThis = static_cast<T *>(this);
       int cConnections = m_vec.GetSize();
       for (int iConnection = 0; iConnection < cConnections; iConnection++)
       {
          pThis->Lock();
          CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
          pThis->Unlock();
...

TestCOM.h (实现方法和CProxy_ITestCOMEvents类的类)

class ATL_NO_VTABLE CTestCOM :
   public CComObjectRootEx<CComMultiThreadModel>,
   public CComCoClass<CTestCOM, &CLSID_TestCOM>,
   public IConnectionPointContainerImpl<CTestCOM>,
   public CProxy_ITestCOMEvents<CTestCOM>,
   public IDispatchImpl<IMecAppCOM, &IID_ITestCOM, &LIBID_TestLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
   static CTestCOM * p_CTestCOM;

CTestCOM()
{
    p_CTestCOM = this;
}

Incoming.CPP (在线程2上运行的类应触发以下case语句中的事件)

case INCOMING_EVENT_1:
// Trigger Fire_TestEvent in thread 1
// CTestCOM::p_CTestCOM->Fire_TestEvent(); trigger event on thread 2

在上面的代码中,您可以找到此问题的当前解决方法,即创建一个指针对象p_CTestCOM,它将允许在线程1上运行的任何类触发COM事件。线程2可以访问该对象,但它会在线程2中触发它,这将无法工作。要解决此问题,Incoming.CPP中定义的所有方法都可以将消息(使用PostMessage())发布到线程1,线程1将使用p_CTestCOM来访问和发送COM事件。这可行,但我相信必须有更好(更安全)的解决方案,更准确地遵循COM设计原则。

我有人可以发光,我会非常感激!

2 个答案:

答案 0 :(得分:2)

Roman R.提供了一些不错的选择,但有一个更好的选择,IMO:你可以将监听器编组到触发事件的线程。由于建议听众通常在ATL项目的IConnectionPointImpl课程内完成,您只需要修改默认IConnectionPointImpl来为您进行编组(例如通过GIT更简单编组API)。

最大的优点是代码的其余部分几乎与以前一样,因此不需要消息传递或同步 - 只需要更新生成的* CP.h文件。

Microsoft知识库文章KB280512中讨论了该实现,该文章现在似乎已被删除,但您可以使用improved implementation by PJ Naughter来替换默认实现。

Here's the version that I use,基于缺少的知识库文章。 用法很简单,只需重命名class in the generated CP.h file并修改m_vec.GetAt部分,如我已链接的要点所述。

答案 1 :(得分:0)

首先重要的是COM对象的线程模型。它是MTA,那么你可能只想用COM分别初始化你的工作线程,CoInitializeEx(NULL, COINIT_MULTITHREADED)和火灾事件就在那里。

如果对象是STA,那很可能就是这种情况,以及可能需要的对象,例如为了将对象与某些环境很好地集成,那么你需要从主线程激发事件,并从工作线程到达那里你需要使用一些同步。例如,在您的工作线程上,您发现需要触发事件。您的代码可能会设置一个标志或一个内部同步对象(例如事件),以便主线程代码最终会注意到它并继续从那里引发外部事件。

STA COM对象的另一个常见解决方案是预先创建一个隐藏/仅消息窗口,以便工作线程可以在其上发布消息。该消息将被分派到窗口创建线程上的窗口过程,即STA线程,消息处理程序将是一个安全的地方,用于触发COM事件。

或者,您可以创建一个内部COM对象并将其编组到工作线程中以创建代理/存根对,并通过marhsaling将来自工作线程的调用自动转换为主线程上方法的调用。这是可行的,但几乎在所有方面都不如窗口消息传递:您的接口/对象需要适合创建代理/存根对,COM调用阻塞,而对于Windows消息,您始终可以在SendMessage和{之间进行选择{1}},而后者则希望避免在线程交流中出现死锁。

相关问题