我正在为现有的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设计原则。
我有人可以发光,我会非常感激!
答案 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}},而后者则希望避免在线程交流中出现死锁。