Gestione di eventi in COM
Nella gestione degli eventi COM si configura un'origine evento e un ricevitore di eventi usando rispettivamente gli attributi e specificando type
com
=.event_receiver
event_source
Questi attributi inseriscono il codice appropriato per interfacce personalizzate, dispatch e duali. Il codice inserito consente alle classi con attributi di generare eventi e gestire eventi tramite punti di connessione COM.
Nota
Gli attributi di evento in C++ nativi non sono compatibili con C++Standard. Non vengono compilati quando si specifica la /permissive-
modalità di conformità.
Dichiarazione di eventi
In una classe di origine evento usare la __event
parola chiave in una dichiarazione di interfaccia per dichiarare i metodi dell'interfaccia come eventi. Gli eventi di tale interfaccia vengono generati quando vengono chiamati come metodi di interfaccia. I metodi sulle interfacce di evento possono avere zero o più parametri (che devono essere tutti inclusi nei parametri). Il tipo restituito può essere void o qualsiasi tipo integrale.
Definizione dei gestori eventi
I gestori eventi vengono definiti in una classe ricevitore di eventi. I gestori eventi sono metodi con firme (tipi restituiti, convenzioni di chiamata e argomenti) che corrispondono all'evento che gestiranno. Per gli eventi COM, le convenzioni di chiamata non devono corrispondere. Per altre informazioni, vedere Eventi COM dipendenti dal layout di seguito.
Associazione di gestori eventi agli eventi
In una classe ricevitore di eventi si usa anche la funzione __hook
intrinseca per associare gli eventi ai gestori eventi e __unhook
per annullare l'associazione di eventi dai gestori eventi. È possibile eseguire l'hook di più eventi a un singolo gestore eventi o di diversi gestori eventi a un singolo evento.
Nota
In genere, sono disponibili due tecniche per consentire a un ricevitore di eventi COM di accedere alle definizioni dell'interfaccia dell'origine evento. La prima, come mostrata di seguito, consiste nel condividere un file di intestazione comune. Il secondo consiste nell'usare #import con il embedded_idl
qualificatore di importazione, in modo che la libreria dei tipi di origine eventi venga scritta nel file con estensione tlh con il codice generato dall'attributo mantenuto.
Generazione di eventi
Per generare un evento, chiamare un metodo nell'interfaccia dichiarata con la __event
parola chiave nella classe di origine dell'evento. Se per l'evento è stato eseguito l'hook ai gestori, questi verranno chiamati.
Codice evento COM
Nell'esempio seguente viene illustrato come generare un evento in una classe COM. Per compilare ed eseguire l'esempio, fare riferimento ai commenti presenti nel codice.
// 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;
Quindi il server:
// 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;
}
};
Quindi il client:
// 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;
}
Output
MyHandler1 was called with value 123.
MyHandler2 was called with value 123.
Eventi COM dipendenti dal layout
La dipendenza del layout è solo un problema per la programmazione COM. Nella gestione degli eventi nativi e gestiti, le firme (tipo restituito, convenzione di chiamata e argomenti) dei gestori devono corrispondere ai relativi eventi, ma i nomi dei gestori non devono corrispondere ai relativi eventi.
Tuttavia, nella gestione degli eventi COM, quando si imposta il parametro di event_receiver
su true
, viene applicato il layout_dependent
nome e la corrispondenza della firma. I nomi e le firme dei gestori nel ricevitore di eventi e negli eventi associati devono corrispondere esattamente.
Quando layout_dependent
è impostato su false
, la convenzione di chiamata e la classe di archiviazione (virtuale, statica e così via) possono essere miste e corrispondenti tra il metodo di attivazione dell'evento e i metodi di hook (relativi delegati). È leggermente più efficiente avere layout_dependent
=true
.
Ad esempio, si supponga che IEventSource
sia definito con i metodi seguenti:
[id(1)] HRESULT MyEvent1([in] int value);
[id(2)] HRESULT MyEvent2([in] int value);
Si supponga che l'origine evento abbia il formato seguente:
[coclass, event_source(com)]
class CSource : public IEventSource {
public:
__event __interface IEvents;
HRESULT FireEvent() {
MyEvent1(123);
MyEvent2(123);
return S_OK;
}
};
Quindi, nel ricevitore, qualsiasi gestore di cui è stato eseguito l'hook a un metodo in IEventSource
deve corrispondere al nome e alla firma, nel modo seguente:
[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
}
};