COM 中的事件处理

时间:2021-12-27 05:09:44

在 COM 事件处理中,使用 event_sourceevent_receiver 属性分别建立事件源和事件接收器并指定 type=com。这些属性插入相应的自定义接口代码、调度接口代码和双重接口代码,使其应用到的类可以通过 COM 连接点激发事件并处理事件。

声明事件

在事件源类中,在接口声明中使用 __event 关键字将该接口的方法声明为事件。当您将该接口的事件作为接口方法调用时将激发它们。事件接口上的方法可以具有零个或更多参数(这些参数均应为 in 参数)。返回类型可以是 void 类型或任何整型。

定义事件处理程序

在事件接收器类中定义事件处理程序,它们是一些方法,具有与要处理的事件相匹配的签名(返回类型、调用约定和参数)。对于 COM 事件,调用约定无需与之匹配;有关详细信息,请参见下面的与布局相关的 COM 事件

将事件处理程序与事件挂钩

同样是在事件接收器类中,使用内部函数 __hook 使事件与事件处理程序相关联,使用 __unhook 使事件与事件处理程序分离。可以将几个事件与一个事件处理程序挂钩,或者将几个事件处理程序与一个事件挂钩。

注意   通常情况下,有两种技术可以使 COM 事件接收器得以访问事件源接口定义。第一种技术(如下所示)是共享公共头文件。第二种技术是使用具有 embedded_idl 导入限定符的 #import,从而在保留属性生成的代码的情况下将事件源类型库写入 .tlh 文件。

激发事件

若要激发事件,只需在接口中调用在事件源类中用 __event 关键字声明的方法。如果该事件已与一些处理程序挂钩,则将调用这些处理程序。

COM 事件代码

下例显示如何在 COM 类中激发事件。若要编译并运行该示例,请参考代码中的注释。

复制
// evh_server.h
#pragma once

[ dual, uuid("00000000-0000-0000-0000-000000000001") ]
__interface IEvents {
[id(1)] HRESULT MyEvent([in] int value);
};

[ dual, uuid("00000000-0000-0000-0000-000000000002") ]
__interface IEventSource {
[id(1)] HRESULT FireEvent();
};

class DECLSPEC_UUID("530DF3AD-6936-3214-A83B-27B63C7997C4") CSource;
复制
// evh_server.cpp
// compile with: /LD
// post-build command: Regsvr32.exe /s evh_server.dll
#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
#include "evh_server.h"

[ module(DLL, name="EventSource", uuid="6E46B59E-89C3-4c15-A6D8-B8A1CEC98830") ];

[coclass, event_source(com), uuid("530DF3AD-6936-3214-A83B-27B63C7997C4")]
class CSource : public IEventSource {
public:
__event __interface IEvents;

HRESULT FireEvent() {
__raise MyEvent(123);
return S_OK;
}
};
复制
// evh_client.cpp
// compile with: /link /OPT:NOREF
#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
#include <stdio.h>
#include "evh_server.h"

[ module(name="EventReceiver") ];

[ event_receiver(com) ]
class CReceiver {
public:
HRESULT MyHandler1(int nValue) {
printf("MyHandler1 was called with value %d./n", nValue);
return S_OK;
}

HRESULT MyHandler2(int nValue) {
printf("MyHandler2 was called with value %d./n", nValue);
return S_OK;
}

void HookEvent(IEventSource* pSource) {
__hook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler1);
__hook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler2);
}

void UnhookEvent(IEventSource* pSource) {
__unhook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler1);
__unhook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler2);
}
};

int main() {
// Create COM object
CoInitialize(NULL);
IEventSource* pSource = 0;
HRESULT hr = CoCreateInstance(__uuidof(CSource), NULL, CLSCTX_ALL,
__uuidof(IEventSource), (void **) &pSource);
if (FAILED(hr)) {
return -1;
}

// Create receiver and fire event
CReceiver receiver;
receiver.HookEvent(pSource);
pSource->FireEvent();
receiver.UnhookEvent(pSource);

CoUninitialize();
return 0;
}

输出

复制
MyHandler1 was called with value 123.
MyHandler2 was called with value 123.

与布局相关的 COM 事件

布局依赖性只是一个针对 COM 编程的问题。在本机和托管事件处理中,处理程序的签名(返回类型、调用约定和参数)必须与其事件匹配,而处理程序名称则不必如此。

但是,在 COM 事件处理中,如果将 event_receiverlayout_dependent 参数设置为 true,则名称和签名匹配具有强制性。这意味着事件接收器中处理程序的名称和���名必须与处理程序挂钩到的事件的名称和签名完全匹配。

如果将 layout_dependent 设置为 false,则调用约定和存储类(虚拟、静态等等)在激发事件方法和挂钩方法(其委托)中可以混用和匹配。如果 layout_dependent=true,则效率将会略有提高。

例如,假设将 IEventSource 定义为具有下列方法:

复制
[id(1)] HRESULT MyEvent1([in] int value);
[id(2)] HRESULT MyEvent2([in] int value);

假设事件源具有下列形式:

复制
[coclass, event_source(com)]
class CSource : public IEventSource {
public:
__event __interface IEvents;

HRESULT FireEvent() {
MyEvent1(123);
MyEvent2(123);
return S_OK;
}
};

那么,在事件接收器中,挂钩到 IEventSource 的方法的处理程序必须与其名称和签名相匹配,如下所示:

复制
[coclass, event_receiver(com, true)]
class CReceiver {
public:
HRESULT MyEvent1(int nValue) { // name and signature matches MyEvent1
...
}
HRESULT MyEvent2(E c, char* pc) { // signature doesn't match MyEvent2
...
}
HRESULT MyHandler1(int nValue) { // name doesn't match MyEvent1 (or 2)
...
}
void HookEvent(IEventSource* pSource) {
__hook(IFace, pSource); // Hooks up all name-matched events
// under layout_dependent = true
__hook(&IFace::MyEvent1, pSource, &CReceive::MyEvent1); // valid
__hook(&IFace::MyEvent2, pSource, &CSink::MyEvent2); // not valid
__hook(&IFace::MyEvent1, pSource, &CSink:: MyHandler1); // not valid
}
};