3

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

对于线程 2 中收到的某些消息,我想通过发送 COM 事件来通知 COM 客户端。我已经阅读了许多线程(从另一个线程触发 COM 事件就是其中之一)并且提到了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 中定义的所有方法都可以向线程 1 发布消息(使用 PostMessage()),线程 1 将使用 p_CTestCOM 访问和发送 COM 事件。这会起作用,但我确信必须有一个更好(更安全)的解决方案,更准确地遵循 COM 设计原则。

我有人可以提供一些启示,我将不胜感激!

4

2 回答 2

3

Roman R. 提供了一些不错的选择,但还有一个更好的选择,IMO:您可以将侦听器编组到触发事件的线程。由于建议侦听器通常IConnectionPointImpl在 ATL 项目的类中完成,因此您“只需”修改默认值IConnectionPointImpl即可为您进行编组(例如,通过比编组 API 更简单的GIT )。

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

该实现在 Microsoft 知识库文章 KB280512 中进行了讨论,现在似乎已被删除,但PJ Naughter 提供了一个改进的实现,您可以使用它来替换默认实现。

这是我使用的版本,基于缺少的知识库文章。用法很简单,只需在生成的 CP.h 文件中重命名类并修改m_vec.GetAt部分,如我链接的 gist 中所述。

于 2013-05-15T08:13:23.953 回答
0

开始的重要事情是您的 COM 对象的线程模型。它是 MTA,那么您可能只想使用 COM 分别初始化您的工作线程,CoInitializeEx(NULL, COINIT_MULTITHREADED)并从那里触发事件。

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

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

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

于 2013-05-15T08:01:25.027 回答