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.
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:
- 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
Here are is the managed code:
[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;
...
}
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'.
The following steps are required:
- the sink will inherit
IDispEventSimpleImpl
(or IDispEventImpl
)
- sink map with all the needed methods is declared
- handler methods are written for each event
- the sink is registered with event source
- eventually, when not needed anymore, the sink is disconnected
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
}
...
}
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 -----------------------------------------+
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);
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:
- requires access to the typelib (as it needs to read the interface metadata in order to provide the type info itself)
- is a bit slower (but I would guess not significantly)