2013-05-14 45 views
2

我正在實現一個COM接口到現有的VS2010 C++ MFC應用程序。 COM接口交互的大部分工作很好,但是我很困惑如何從COM接口運行/定義的另一個線程觸發COM事件。該應用程序是多線程的,其中一個主線程運行COM接口並處理GUI更改(線程1)和一個線程以接收來自C庫(線程2)的傳入消息。ATL COM:從其他線程訪問事件方法

對於線程2中收到的某些消息,我想通過發送COM事件來通知COM客戶端。我讀過很多線索(Firing COM Event From Another Thread就是其中之一),並提到了CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream。使用谷歌我似乎無法找到任何對我有意義的方法,我只是不明白如何實現這些功能,如果他們真的會幫助我。

相關代碼部分:

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,這將使用p_CTestCOM來訪問和發送COM事件。這是行得通的,但我確定必須有更好的(更安全的)解決方案,更準確地遵循COM設計原則。

我有人可以擺脫一些光,我將不勝感激!

+0

您正在尋找的技術稱爲*編組*。我原以爲它只是在進程之間,但它似乎也可用於線程。請參閱初學者http://support.microsoft.com/kb/206076 –

+0

我成功使用GIT編組並收到指向我的ITestCOM界面的指針。但是,我無法從ITestCOM接口訪問Fire_TestEvent()事件,我不希望強制將公共方法添加到只會觸發事件的COM庫(如HRESULT TriggerTestEvent();)。有什麼方法可以訪問Fire_Event方法或在庫中創建只有應用程序可以訪問的私有方法,而不是任何客戶端? – Pettor

回答

2

羅馬R.提供了一些很好的選擇,但有一個更好的選擇,IMO:你可以當元帥的聽衆的時候觸發事件的線程。作爲勸告聽衆是內部IConnectionPointImpl類ATL項目通常這樣做,你「只是」需要修改默認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部件,如我已鏈接的要點中所述。

+0

該文章仍以其他語言提供:-)例如法語http://support.microsoft。com/kb/280512/fr(附件中的示例不可下載) –

+0

我使用提供的IConnectionPointImplMT實現,但應用程序在嘗試觸發事件時會引發異常。我更新了原始文章中的Fire_TestEvent()函數。例外情況如下: CComPtr punkConnection = m_vec.GetAt(iConnection); – Pettor

+0

哦,我看到了 - 我將在一分鐘內更新要點和答案 –

0

重要的是你的COM對象的線程模型。它是MTA,那麼你可能想簡單地用COM初始化你的工作線程,用CoInitializeEx(NULL, COINIT_MULTITHREADED)和火災事件。

如果對象是STA,很可能是這種情況,以及可能需要什麼,例如,要將對象與某些環境很好地集成在一起,那麼您需要從主線程中觸發事件,並從工作線程中獲取需要使用一些同步的事件。例如,在您的工作線程上,您發現需要觸發事件。你的代碼可能會設置一個標誌或一個內部同步對象(如事件),以便主線程代碼最終會注意到它並繼續從那裏引發外部事件。

STA COM對象的另一個常見解決方案是預先創建一個隱藏/僅消息窗口,以便工作線程可以在其上發佈消息。該消息將被髮送到窗口創建線程上的窗口過程,該窗口創建線程位於STA線程上,消息處理程序將是一個安全的地方來觸發COM事件。

或者,您可以創建一個內部COM對象並將其編組到工作線程中以創建代理/存根對,並且您的來自工作線程的調用會自動轉換爲通過marhsaling在主線程上調用您的方法。這是可行的,但幾乎在所有方面都不如窗口消息傳遞:您的界面/對象需要適合創建代理/存根對,COM呼叫在使用Windows消息時被阻止,您總是可以在SendMessagePostMessage之間選擇,它是後者你希望避免在線程間通信中出現死鎖。

+0

線程上的CoInitializeEx(NULL,COINIT_APARTMENTTHREADED)使GIT爲我工作。現在我參考ITestCOM界面。但是,我想在不使用ITestCOM接口的公共方法的情況下觸發Fire_Event函數。無論如何,我可以做到這一點?感覺像添加新方法來觸發事件功能是不必要的開銷。 – Pettor

+0

對不起,這是一種帶有'COINIT_APARTMENTTHREADED'的類型,我糾正了上面的文字。使用GIT,您不需要將公共方法添加到「ITestCOM」。相反,你把它放在GIT接口接口('IDispatch')上,但你需要更新'IConnectionPointImpl'類,這樣它才能從GIT接收sink接口,而不是從內部收集(它是Zdeslav答案中的IConnectionPointImplMT)。這也是一個很好的解決方案,請記住,它也是對原始線程的阻塞調用,並且在終止工作線程時需要注意可能存在的死鎖。 –

+0

對不起,我不完全明白。 ITestCOM的類型爲「IDispatch」,並且只包含公開的方法(見上面的.IDL聲明)。我可以添加更多的方法到該接口,但客戶端也可以觸發它們。 'CTestCOM'可以訪問所有'Fire_Event'方法,但我不允許在該對象上使用GIT(不是'IUnknown'類型)。 – Pettor