1. 程式人生 > >C#的COM事件在C++中觸發和響應的實現

C#的COM事件在C++中觸發和響應的實現

在C++中呼叫C#開發COM元件時,一般的介面呼叫都比較容易實現,但是對於COM元件中的事件,C++中要去響應卻不好實現。因為C#中事件是採用委託機制,而C++中卻沒有委託的機制,這樣就無法實現對應。那要怎麼辦呢?

在C++中雖然沒有委託的型別來對應,不過C++卻可以開發ATL元件,同時裡面有用到事件的對映,那麼我們是不是可以應用這種機制去實現呢?進過不斷的查詢資料和一番努力,總算是達成了目標,請看效果圖。

Trigger Event是由C#封裝的COM元件內部輸出的,而Event Reponse : 10000是由COM元件觸發C++的事件後輸出的。那麼這個具體要如何實現呢?我們先看C#的COM元件程式碼:

IPaint介面

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace ComEvent { [Guid("7EEDF2D8-836C-4294-90A0-7A144ADC93F9")] [InterfaceType(ComInterfaceType.InterfaceIsDual)]
publicinterface IPaint { [DispId(1)] voidDraw(int count); } }

IEvent介面

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace ComEvent { [Guid("7FE32A1D-F239-45ad-8188-89738C6EDB6F"
)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] publicinterface IEvent { [DispId(20)] voidDrawEvent(int count); } }


Paint實現

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace ComEvent { [Guid("76BBA445-7554-4308-8487-322BAE955527"), ClassInterface(ClassInterfaceType.None), ComDefaultInterface(typeof(IPaint)), ComSourceInterfaces(typeof(IEvent)), ComVisible(true)] publicclass Paint : IPaint { publicdelegate void DrawDelegate(int count); //注意事件的名稱必須和IEvent定義的名字一致,而且必須public publicevent DrawDelegate DrawEvent; #region IPaint 成員 publicvoid Draw(intcount) { Console.WriteLine("Trigger Event"); OnDraw(count); } publicvoid OnDraw(intcount) { try { if(DrawEvent == null) { Console.WriteLine("Event is NULL!"); } else { DrawEvent(count); } } catch(Exception ex) { Console.WriteLine(ex.Message); } } #endregion } }

說明

1.程式碼中的甩有GUID都必須不一樣,可以使用GUID生成器來生成。其中要特別注意的是IEvent介面中的DsidpId的值,我在實現時就是這裡吃了很大的虧。

2.事件介面IEvent的介面型別一般是InterfaceIsIDispatch。

3.在實現的類中Paint需要新增對所要暴露的COM介面加以引用,即 ComDefaultInterface(typeof(IPaint))和ComSourceInterfaces(typeof(IEvent)),注意typeof後面的是我們自定義的介面。

4.由於Paint中沒有繼承IEvent介面,但在Paint卻要有相應的DrawEvent事件觸發,所以我們需要在Paint中定義一個相同於IEvent中DrawEvent的委託來對應,即public delegate void DrawDelegate(int count)和public event DrawDelegate DrawEvent;

5.為了使用COM元件能夠使用,需要對專案的屬性作一些配置。見下面的圖

\載入中...


\載入中...

編譯COM元件,這時Output會生成ComEvent.dll、ComEvent.pdb、ComEvent.tlb三個檔案,其中ComEvent.tlb是要在C++呼叫COM元件時使用的。

下面是C++呼叫的實現程式碼

ComCall_CPlusPlus.cpp

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // ComCall_CPlusPlus.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #import"..\Output\ComEvent.tlb" using namespace ComEvent; ATL::CComModule _Module; class EventReceiver : publicIDispEventImpl<0, EventReceiver, &(__uuidof(ComEvent::IEvent)), &(__uuidof(ComEvent::__ComEvent)),1, 0> { public: STDMETHOD(DrawEventResponse)(intcount); BEGIN_SINK_MAP(EventReceiver) SINK_ENTRY_EX(0, (__uuidof(ComEvent::IEvent)),20, DrawEventResponse)          END_SINK_MAP() }; STDMETHODIMP EventReceiver::DrawEventResponse(intcount) { printf("Event Reponse : %d\n", count); returnS_OK; } int _tmain(int argc, _TCHAR* argv[]) {     CoInitialize(NULL); ComEvent::IPaintPtr  pPaint(__uuidof(ComEvent::Paint)); _Module.Init(NULL, (HINSTANCE)GetModuleHandle(NULL)); EventReceiver * pReceiver =new EventReceiver; HRESULT hresult=pReceiver->DispEventAdvise(pPaint);    pPaint->Draw(10000); pReceiver->DispEventUnadvise(pPaint); _Module.Term(); CoUninitialize(); return0; }


stdafx.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // stdafx.h : 標準系統包含檔案的包含檔案, // 或是經常使用但不常更改的 // 特定於專案的包含檔案 // #pragma once #include "targetver.h" #include <stdio.h> #include <tchar.h> #include  //增加 extern CComModule _Module;//增加 #include //增加 // TODO: 在此處引用程式需要的其他標頭檔案 </atlcom.h></atlbase.h></tchar.h></stdio.h>

說明

1.ATL模組引用:需要在stdafx.h中增加atlbase.h、extern CComModule _Module和atlcom.h,在ComCall_CPlusPlus.CPP中增加ATL::CComModule _Module;

2.定義繼承自ATL模板介面IDispEventImpl的事件接收類EventReceiver。IDispEventImpl的模板引數第一個是0,第二個是事件接收類的名字EventReceiver,第三個引數是事件介面ID的指標(使用 &(__uuidof(ComEvent::IEvent))來計算),第四個引數是類ID的指標(使用 &(__uuidof(ComEvent::__ComEvent))來計算),第五個引數是1,第六個引數是0.

3.要注意新增對ComEvent名稱空間的引用

4.事件對映DrawEventResponse。BEGIN_SINK_MAP、SINK_ENTRY_EX、END_SINK_MAP三個必須要一組,才能實現對事件的對映。

BEGIN_SINK_MAP的引數是EventReceiver

SINK_ENTRY_EX第一個引數是0,第二個引數是事件介面的ID(使用__uuidof(ComEvent::IEvent)來計算,該值必須與IDispEventImpl使用的事件ID一致),第四個引數是事件的DispId(就是在COM元件定義時定義的值20,一定要是這個值,不然會出錯),第五個引數是DrawEventResponse的具體實現。

5.初始化COM和釋放例項:CoInitialize(NULL)和  CoUninitialize()必須要配對

6.ATL實始化_Module.Init

7.ATL掛接和取消事件pReceiver->DispEventAdvise(pPaint)和pReceiver->DispEventUnadvise(pPaint)。