C#的COM事件在C++中触发和响应的实现

时间:2022-08-31 00:16:18

在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组件能够使用,需要对项目的属性作一些配置。见下面的图

C#的COM事件在C++中触发和响应的实现C#的COM事件在C++中触发和响应的实现


C#的COM事件在C++中触发和响应的实现C#的COM事件在C++中触发和响应的实现

编译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)。