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)] public interface
IPaint
{
[DispId( 1 )]
void Draw( 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)]
public interface
IEvent
{
[DispId( 20 )]
void DrawEvent( 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 )]
public class
Paint : IPaint
{
public delegate
void DrawDelegate( int
count);
//注意事件的名稱必須和IEvent定義的名字一致,而且必須public
public event DrawDelegate DrawEvent;
#region IPaint 成員
public void
Draw( int count)
{
Console.WriteLine( "Trigger Event" );
OnDraw(count);
}
public void
OnDraw( int count)
{
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 :
public IDispEventImpl< 0 ,
EventReceiver,
&(__uuidof(ComEvent::IEvent)),
&(__uuidof(ComEvent::__ComEvent)), 1 ,
0 >
{
public :
STDMETHOD(DrawEventResponse)( int count);
BEGIN_SINK_MAP(EventReceiver)
SINK_ENTRY_EX( 0 , (__uuidof(ComEvent::IEvent)), 20 ,
DrawEventResponse)
END_SINK_MAP()
};
STDMETHODIMP EventReceiver::DrawEventResponse( int count)
{
printf( "Event Reponse : %d\n" , count);
return S_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();
return 0 ;
}
|
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)。