使用 C++ 向 COM 公开托管事件 [英] Exposing managed events to COM using C++
问题描述
可以公开用 C# 编写的托管事件,以便在用 C++ 编写的 COM 对象中公开和使用.对 com 和 atl 不太熟悉.您能否说明 MSDN 文章中显示的示例的 C++ 方面会是什么样子
It is possible to exposed managed events written in C# to be exposed and used in a COM object written using c++. not being that familiar with com and atl. Can you please show what would the C++ side of things would look like for the example shown in the MSDN article
http://msdn.microsoft.com/en-us/library/dd8bf0x3.aspx
显示的 VB6 代码证明它是可行的.
The VB6 code shown proves that it is doable.
推荐答案
IMO 中 C++ 中最简单的方法是在 ATL 的 IDispEventImpl
和 IDispEventSimpleImpl<的帮助下实现事件接收器/code> 模板.示例项目的说明可以在此处找到.
The easiest way in C++ would be, IMO, to implement an event sink with help of ATL's IDispEventImpl
and IDispEventSimpleImpl
templates. An explanation with sample project can be found here.
有许多关于如何执行此操作的在线资源,例如this 或 这个,但这里是所需步骤的列表:
There are many online resources about how to do this, e.g. this or this, but here is the list of required steps:
首先让我们看看托管端.
First let's take a look at managed side.
为了提供事件,我们必须做到以下几点:
In order to provide events, we must do the following:
- 声明一个事件接口(基于
IDispatch
) - 用
ComSourceInterfaces
属性标记coclass,将事件接口绑定到coclass - 在 coclass 中实现匹配事件
- declare an event interface (
IDispatch
-based) - mark the coclass with
ComSourceInterfaces
attribute to bind the event interface to coclass - implement matching events in the coclass
这里是托管代码:
[ComVisible(true),
Guid("D6D3565F-..."),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] //! must be IDispatch
public interface IMyEvents
{
[DispId(1)] // the dispid is used to correctly map the events
void SomethingHappened(DateTime timestamp, string message);
}
[ComVisible(true)]
[Guid("E22E64F7-...")]
[ProgId("...")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IMyEvents))] // binding the event interface
public class MyComServer : IMyComServer
{
// here we declare the delegate for the event
[ComVisible(false)]
public delegate void MyEventHandler(DateTime timestamp, string message);
// and a public event which matches the method in IMyEvents
// your code will raise this event when needed
public event MyEventHandler SomethingHappened;
...
}
现在,回到不受管理的一面.我将使用 ATL,因为我发现它是编写 COM 客户端的最有效方法,但您可以尝试 MFC 或手动"进行.
Now, back to unmanaged side. I will use ATL, as I find it the most effective way to write COM clients, but you can try MFC or do it 'manually'.
需要以下步骤:
- 接收器将继承
IDispEventSimpleImpl
(或IDispEventImpl
) - 声明了所有需要的方法的接收器映射
- 为每个事件编写处理程序方法
- 接收器已注册到事件源
- 最终,当不再需要时,接收器断开连接
这是 ATL C++ 客户端中的代码:
Here's the code in an ATL C++ client:
// import the typelib of your COM server
// 'named_guids' ensures friendly ID of event interface
#import "myserver.tlb" named_guids
const UINT SINK_ID = 234231341; // we need some sink id
class MyClient : public IDispEventSimpleImpl<SINK_ID, MyClient, &MyServer::DIID_IMyEvents >
{
public:
// now you need to declare a sink map - a map of methods handling the events
BEGIN_SINK_MAP(MyClient)
SINK_ENTRY_INFO(SINK_ID, MyServer::DIID_IMyEvents, 0x1, OnSomethingHappened, &someEvent)
^ ^ ^ ^
// event interface id (can be more than 1)---+ | | |
// must match dispid of your event -----------------+ | |
// method which handles the event ------------------------+ |
// type information for event, see below --------------------------------------+
END_SINK_MAP()
// declare the type info object. You will need one for each method with different signature.
// it will be defined in the .cpp file, as it is a static member
static _ATL_FUNC_INFO someEvent; // 'placeholder' object to carry event information (see below)
// method which handles the event
STDMETHOD (OnSomethingHappened)(DATE timestamp, BSTR message)
{
// usually it is defined it in the .cpp file
}
...
}
现在,我们需要在 cpp 文件中定义类型信息成员(即上面示例中的 someEvent
实例):
Now, we need to define the type info members in the cpp file (i.e. the someEvent
instance from example above):
_ATL_FUNC_INFO MyClient::traceEvent = { CC_STDCALL, VT_EMPTY, 2 , {VT_DECIMAL, VT_BSTR} }; // dispid = 1
^ ^ ^ ^
// calling convention (always stdcall) --------+ | | |
// type of return value (only VT_EMPTY makes sense) ----+ | |
// number of parameters to the event -------------------------+ |
// Variant types of event arguments -----------------------------------------+
这可能很棘手,因为类型映射并不总是显而易见的(例如,托管 int
映射到 VT_I4
可能很明显,但 >DateTime
映射到 VT_DECIMAL
).您需要声明您计划在接收器映射中使用的每个事件 - 如果您不需要所有事件,请不要映射它们.
This can be tricky as type mappings are not always obvious (e.g. a it might be clear that managed int
maps to VT_I4
, but it is less obvious that DateTime
maps to VT_DECIMAL
).
You need to declare each event you plan to use in the sink map - if you don't need all of them, don't map them.
现在您需要将接收器连接到事件源:
Now you need to connect your sink to the event source:
// IUnknown* pUnk = interface to you COM server instance
pMyClient->DispEventAdvise(pUnk);
// .. from this point, events will be caught by the client
// when you are done, disconnect:
pMyClient->DispEventUnadvise(pUnk);
差不多就是这样.使用 IDispEventImpl
而不是 IDispEventSimpleImpl
会减少一些代码,因为您不需要提供可能是最丑陋部分的类型信息对象.但是,它有两个缺点:
This is it, more or less. Using IDispEventImpl
instead of IDispEventSimpleImpl
results in a bit less code, as you don't need to supply the type info objects which might be the ugliest part. However, it has two drawbacks:
- 需要访问 typelib(因为它需要读取接口元数据以提供类型信息本身)
- 有点慢(但我猜不会太明显)
这篇关于使用 C++ 向 COM 公开托管事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!