COM 中的事件處理

在 COM 事件處理中,您會分別使用 event_source 和 屬性來設定事件來源和事件接收器,並指定 comtype= 。 event_receiver 這些屬性會為自訂、分派和雙重介面插入適當的程式碼。 插入的程式碼可讓屬性類別引發事件,並透過 COM 連接點處理事件。

注意

原生 C++ 中的事件屬性與標準 C++ 不相容。 當您指定 /permissive- 一致性模式時,它們不會編譯。

宣告事件

在事件來源類別中,使用 __event 介面宣告上的 關鍵字,將該介面的方法宣告為事件。 當您以介面方法呼叫它們時,會引發該介面的事件。 事件介面上的方法可以有零個或多個參數(這應該全部在 參數中 )。 傳回型別可為 void 或任何整數類型。

定義事件處理常式

您可以在事件接收器類別中定義事件處理常式。 事件處理常式是具有簽章的方法(傳回型別、呼叫慣例和引數),符合它們將處理的事件。 針對 COM 事件,呼叫慣例不需要相符。 如需詳細資訊,請參閱 下方的配置相依 COM 事件

將事件處理常式連結至事件

此外,在事件接收器類別中,您也可以使用內建函式 __hook 將事件與事件處理常式產生關聯,以及 __unhook 將事件與事件處理常式解除關聯。 您可以在事件處理常式中攔截多個事件,或在事件中攔截多個事件處理常式。

注意

通常,有兩種方法可以讓 COM 事件接收器存取事件來源介面定義。 第一種是共用常見的標頭檔,如下所示。 第二個是使用 #import embedded_idl 匯入限定詞,讓事件來源型別程式庫寫入 .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_s("MyHandler1 was called with value %d.\n", nValue);
      return S_OK;
   }

   HRESULT MyHandler2(int nValue) {
      printf_s("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 程式設計才有的問題。 在原生和 Managed 事件處理中,處理常式的簽章(傳回型別、呼叫慣例和引數)必須符合其事件,但處理常式名稱不需要符合其事件。

不過,在 COM 事件處理中,當您將 的 event_receiver 參數設定 layout_dependenttrue 時,會強制執行名稱和簽章比對。 事件接收器和連結事件中處理常式的名稱和簽章必須完全相符。

當 設定為 falselayout_dependent ,呼叫慣例和儲存類別(虛擬、靜態等)可以在引發事件方法和攔截方法(其委派)之間進行混合和比對。 擁有 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
   }
};

另請參閱

事件處理