使用 C++向 COM 公开托管事件
Exposing managed events to COM using C++
可以公开用 C# 编写的托管事件,以便在使用 C++ 编写的 COM 对象中公开和使用。 对 COM 和 ATL 不太熟悉。您能否展示一下 MSDN 文章中显示的示例C++方面会是什么样
子http://msdn.microsoft.com/en-us/library/dd8bf0x3.aspx
显示的VB6代码证明它是可行的。
C++最简单的方法是 IMO 借助 ATL 的IDispEventImpl
和IDispEventSimpleImpl
模板实现事件接收器。可以在此处找到示例项目的解释。
有许多关于如何执行此操作的在线资源,例如这个或这个,但以下是所需步骤的列表:
首先,让我们看一下托管端。
为了提供事件,我们必须执行以下操作:
- 声明事件接口(基于
IDispatch
) - 使用
ComSourceInterfaces
属性标记 coclass 以将事件接口绑定到 coclass - 在 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 或"手动"执行此操作。
需要执行以下步骤:
- 接收器将继承
IDispEventSimpleImpl
(或IDispEventImpl
) - 声明了包含所有所需方法的接收器映射
- 为每个事件编写处理程序方法
- 接收器已注册到事件源
- 最终,当不再需要时,接收器将断开连接
下面是 ATL C++客户端中的代码:
// 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
实例):
_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
)。 需要声明计划在接收器映射中使用的每个事件 - 如果不需要所有事件,请不要映射它们。
现在,需要将接收器连接到事件源:
// 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
会导致代码少一点,因为您不需要提供可能是最丑陋部分的类型 info 对象。但是,它有两个缺点:
- 访问 typelib(因为它需要读取接口元数据才能提供类型信息本身)
- 有点慢(但我猜不是很明显)
如果你可以使用C++/CLI,你可以只做(来源):
// class that defines methods that will called when event occurs
ref class EventReceiver {
public:
void OnMyClick(int i, double d) {
Console::WriteLine("OnClick: {0}, {1}", i, d);
}
void OnMyDblClick(String^ str) {
Console::WriteLine("OnDblClick: {0}", str);
}
};
int main() {
EventSource ^ MyEventSource = gcnew EventSource();
EventReceiver^ MyEventReceiver = gcnew EventReceiver();
// hook handler to event
MyEventSource->OnClick += gcnew ClickEventHandler(MyEventReceiver, &EventReceiver::OnMyClick);
}
Zdeslav Vojkovic 提出的解决方案对我来说几乎是完整的答案,但是在 Outlook 加载项中实现接收器时,我遇到了稳定性问题。在第一次建议后,系统变得不稳定,并可能在下一次建议时崩溃。对我来说,解决方案是将接收器作为COM对象。
为了简洁起见,我只添加了解决方案的 ATL 端,因为托管代码可以完全按照 Zdeslav Vojkovic 的建议保留。
我在Visual Studio 2017中执行了以下步骤:
创建 ATL 简单对象
- 右键单击"项目",然后选择"添加新项>...
- 选择 ATL 简单对象,然后单击添加按钮
- 在"其他"选项卡中,为"线程模型"选择"单个">
- 单击完成
填写接收器接口详细信息
在生成的 idl 中添加用于初始化和关闭的处理程序:
[
object,
uuid(a5211fba-...),
dual,
nonextensible,
pointer_default(unique)
]
interface IMyClient : IDispatch
{
[id(1), helpstring("method InitHandler"), local] HRESULT InitHandler(IUnknown* myserver);
[id(2), helpstring("method ShutdownHandler"), local] HRESULT ShutdownHandler(void);
};
填写头文件 (MyClient.h)
const UINT SINK_ID = 234231341;
extern _ATL_FUNC_INFO SomethingHappenedInfo;
// LIBID_MyATLComLib should point to the LIBID of the type library
class ATL_NO_VTABLE CMyClient :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMyClient, &CLSID_MyClient>,
public IDispatchImpl<IMyClient, &IID_IMyClient, &LIBID_MyATLComLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispEventSimpleImpl<SINK_ID, CMyClient, &MyServer::DIID_IMyEvents>
{
public:
typedef IDispEventSimpleImpl</*nID =*/ SINK_ID, CMyClient, &MyServer::DIID_IMyEvents> SomethingHappenedEvent;
...
BEGIN_SINK_MAP(CMyClient)
SINK_ENTRY_INFO(SINK_ID, MyServer::DIID_IMyEvents, 0x1, OnSomethingHappened, &SomethingHappenedInfo)
END_SINK_MAP()
...
private:
CComQIPtr<MyServer::IMyComServer> mMyComServer;
public:
STDMETHOD(InitHandler)(IUnknown* myserver);
STDMETHOD(ShutdownHandler)(void);
void __stdcall OnSomethingHappened(DateTime timestamp, string message);
};
一些生成的代码被保留为"...">
填写C++代码(MyClient.cpp)
_ATL_FUNC_INFO SomethingHappenedInfo = { CC_STDCALL, VT_EMPTY, 2 , {VT_DECIMAL, VT_BSTR} };
STDMETHODIMP CMyClient::InitHandler(IUnknown* myserver)
{
this->mMyComServer = myserver;
SomethingHappenedEvent::DispEventAdvise((IDispatch*)this->mMyComServer);
return S_OK;
}
STDMETHODIMP CMyClient::ShutdownHandler(void)
{
SomethingHappenedEvent::DispEventUnadvise(this->mMyComServer);
return S_OK;
}
void __stdcall CMyClient::OnSomethingHappened(DateTime timestamp, string message)
{
...
}
请注意,此处的"建议/取消建议"调用的执行方式不同。
- Android NDK传感器向事件队列报告奇怪的间隔
- 从文本文件中读取时钟时间和事件时间并进行处理
- WMI检测进程创建事件-c++
- EvtExportLogneneneba API正在将远程计算机的事件日志保存到远程PC本身.如何将其保存到主机
- 处理闪烁窗口事件
- C++Builder中的OnClick事件签名存在问题
- 跟踪滚动条上的鼠标事件
- 什么是事件表 (wxWidgets)?
- 如何在 MFCaptureEngine 中获取"Camera removed"事件
- 给定顺序中的事件处理
- 当服务中的事件被触发时,如何将响应从服务发送回客户端?
- 在 C++/CLI 中将 .NET 事件从一个 DLL 引发到另一个 DLL
- 如何创建事件驱动的 SDL2 应用程序
- Windows 进程间同步类似事件?
- 如何从C++端挂接到 QML 项的 onClick 事件
- 如何通过多类"Union variable" (sfml) 使用轮询事件
- 如何在Qt 4.8中阻止/忽略/丢弃早于特定超时的用户输入事件
- C++ 信号和插槽不工作:插槽不响应事件
- 在虚幻引擎中触发C++ dll的事件
- 具有Qt事件循环的可移植通用共享库设置