Associated Files: EventSink.zip
In the ATL Timer article, I mentioned that you could receive timer event notification messages, in your client code, by deriving a class from MFC's CCmdTarget class. I did not give an example of this at the time since it is well documented in MSDN. However, how do you receive event notification messages if you are not using MFC?
Without going in to too much detail, to receive these messages in your client code, you need to create an event sink object. To this end, without using MFC, we need to employ the services of the ATL IDispEventImpl class. This class provides implementations of all IDispatch methods and you only need to supply methods to handle the events generated by your source object.
The rest of this article takes you through the development of a simple event sink class. To make the example more concrete, this class will be constructed to handle the ATL Timer's timeout notification message. If you have not already done so, you should read through the ATL Timer article before proceeding further.
So, to begin, create a new class derived from IDispEventImpl. The basic structure of the class can be seen below.
class CTmrEventSink
: public IDispEventImpl<1, CTmrEventSink,
&__uuidof(APPTMR::_ITimerEvents), &APPTMR::LIBID_APPTMR, 1, 0>
{
public:
CTmrEventSink();
~CTmrEventSink();
...
};
The most important parts of the above class declaration are the source and type library IDs
which are given by &__uuidof(APPTMR::_ITimerEvents) and &APPTMR::LIBID_APPTMR
respectively. The other required parameters are a unique source object ID, 1 in the case above,
the class name and the major and minor version numbers of the type library, which are 1 and 0
respectively.
Next, you need to add a sink map to the new class. The sink map takes the form given below.
BEGIN_SINK_MAP(CTmrEventSink)
SINK_ENTRY_EX(1, __uuidof(APPTMR::_ITimerEvents), 1, OnTimerExpire)
END_SINK_MAP()
Again, in the sink map entry, you need to specify the unique source object ID, the interface ID, event ID you want to handle (refer to the timer idl file for this) and the name of the function which will handle the event. I have called this function OnTimerExpire and it is declared with the __stdcall calling convention, as detailed below.
void __stdcall OnTimerExpire();
The class should now look the same as that detailed below.
class CTmrEventSink
: public IDispEventImpl<1, CTmrEventSink,
&__uuidof(APPTMR::_ITimerEvents), &APPTMR::LIBID_APPTMR, 1, 0>
{
public:
CTmrEventSink();
~CTmrEventSink();
BEGIN_SINK_MAP(CTmrEventSink)
SINK_ENTRY_EX(1, __uuidof(APPTMR::_ITimerEvents), 1, OnTimerExpire)
END_SINK_MAP()
void __stdcall OnTimerExpire();
};
In your client code, once you have created an instance of the ATL Timer, create a new timer event sink object and indicate that your client would like to receive timer timeout notifications by calling DispEventAdvise, passing a pointer to the event source, as follows.
m_pTmrEventSink = new CTmrEventSink();
hResult = m_pTmrEventSink->DispEventAdvise(m_pTimer);
When you don't want to receive any more events, notify the timer object by calling DispEventUnadvise, again providing a pointer to the event source, as below.
m_pTmrEventSink->DispEventUnadvise(m_pTimer);
delete m_pTmrEventSink;
Note, if you are using the timer in continuous mode, you should stop the timer before deleting the timer event sink object. Failure to do this could result in an exception being thrown and your application terminating prematurely. It may even be appropriate to wait one or two time periods, after stopping the timer, to ensure that all timer events have been handled, before deleting the sink object.
The Timer Event Sink class associated with this article can be downloaded via the ayrware downloads page.