Creating an Object
That Is an Event Recipient
It is quite easy, in
theory, to implement an object that receives events on a single
interface. You define a class that implements the interface and
connect the object to the event source. We have a Demagogue class
that generates events on the _ISpeakerEvents dispatch
interface. Let's define a CEarPolitic class (clearly one
ear of the body politic) that implements
_ISpeakerEvents.
coclass EarPolitic {
[default] dispinterface _ISpeakerEvents;
};
Now, implement the class using ATL as the
CEarPolitic class.
class ATL_NO_VTABLE CEarPolitic :
...
public _ISpeakerEvents,
... {
public:
...
BEGIN_COM_MAP(CEarPolitic)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(_ISpeakerEvents)
...
END_COM_MAP()
// _ISpeakerEvents
STDMETHOD(GetTypeInfoCount)(UINT* pctinfo);
STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid,
ITypeInfo** pptinfo);
STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames,
UINT cNames, LCID lcid, DISPID* rgdispid);
STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
EXCEPINFO* pexcepinfo, UINT* puArgErr);
};
Unfortunately, an event interface is typically a
dispinterface, so the normal interface-implementation
techniques don't work. When you run the MIDL compiler, all you get
for the generated _ISpeakerEvents interface is this:
MIDL_INTERFACE("A924C9DE-797F-430d-913D-93158AD2D801")
_ISpeakerEvents : public IDispatch
{
};
Instead of being able to simply implement the
methods of the interface as regular C++ member functions, we must
implement the IDispatch interface and support, at a
minimum, the Invoke method. Invoke is a tedious
method to write for any nontrivial event interface.
Another alternative is to use the
IDispatch interface implementation that the
IDispatchImpl template class provides. Unfortunately, the
template class requires parameters describing a dual interface, not
a dispinterface. To use IDispatchImpl, you need
to define a dummy dual interface that has the same dispatch
methods, dispatch identifiers, and function signatures as the event
dispinterface.
This has more implications than are usually
apparent. A dispinterface is not immutable, unlike a
regular COM interface. If you don't control the definition of the
dispinterface, it might change from release to release.
(It's not that likely to change, but it is possible.) This means
your dual interface needs to change as well. This implies that you
cannot document the dual interface because, after it is published,
it is immutable because some client may implement it. Because you
should not describe the dual interface in a type library (because
that documents it), you cannot use the universal type
library-driven-marshaler and need a remoting proxy/stub for the
dual interface. These are all theoretical issues because the dual
interface, in this case, is an implementation detail that is
specific to the implementation class, but the issues give us
motivation enough to look for another solution.
On a slightly different note, what if you want
to receive the same events from more than one event source and you
want to know which source fired the event? For example, let's say
you want to implement an EarPolitic class that acts as a
judge listening to _ISpeakerEvents from both a
Defendant object and a Plaintiff object. Each
object is a source of OnWhisper, OnTalk, and
OnYell events, but the judge needs to keep track of who is
saying what.
This requires you to implement the
_ISpeakerEvents interface multiple times: once for each
event source. Providing separate implementations of any interface
multiple times in a class requires each implementation to be in a
separate COM identity. Two typical solutions to this problem are
member-wise composition (in which each implementation is in a
nested class) and something similar to tear-off interfaces (in
which each implementation is in a separate class).
The IDispEventImpl
and IDispEventSimpleImpl Classes
To avoid these issues, ATL provides two template
classes, IDispEventImpl and IDispEventSimpleImpl,
that provide an implementation of the IDispatch interface
for an ATL COM object. Typically, you use one of these classes in
an object that wants to receive event callbacks. Both classes
implement the dispatch interface on a nested class object that
maintains a separate COM identity from the deriving class. This
means that you can derive from these classes multiple times when
you need to implement multiple source-dispatch interfaces.
The IDispEventImpl class requires a
type library that describes the dispatch interface. The class uses
the typeinfo at runtime to map the VARIANT
parameters received in the Invoke method call to the
appropriate types and stack frame layout necessary for calling the
event-handler member function.
template <UINT nID, class T, const IID* pdiid = &IID_NULL,
const GUID* plibid = &GUID_NULL,
WORD wMajor = 0, WORD wMinor = 0,
class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE IDispEventImpl :
public IDispEventSimpleImpl<nID, T, pdiid>
{ ... }
The IDispEventSimpleImpl class doesn't
use a type library, so it's a more lightweight class. You use it
when you don't have a type library or when you want to be more
efficient at runtime.
template <UINT nID, class T, const IID* pdiid>
class ATL_NO_VTABLE IDispEventSimpleImpl :
public _IDispEventLocator<nID, pdiid>
{ ... }
When using the IDispEventSimpleImpl
class, you must provide an _ATL_FUNC_INFO structure
containing information that describes the expected parameters for
the event handler.
struct _ATL_FUNC_INFO {
CALLCONV cc; // Calling convention
VARTYPE vtReturn; // VARIANT type for return value
SHORT nParams; // Number of parameters
VARTYPE pVarTypes[_ATL_MAX_VARTYPES]; // Array of parameter
// VARIANT type
};
Notice that IDispEventImpl derives from
the IDispEventSimpleImpl class. The
IDispEventSimpleImpl class calls the event handler based
on the information in an _ATL_FUNC_INFO structure. You can
provide the structure statically (at compile time) by referencing
the structure in the sink map (described later in this
chapter).
When you provide no structure reference, the
IDispEventSimpleImpl class calls the virtual method
GetFuncInfoFromId to get an _ATL_FUNC_INFO
structure for the event handler associated with the specified
DISPID. You provide the structure dynamically by
overriding GetFuncInfoFromId and returning the appropriate
structure when called. You
must use GetFuncInfoFromId when you want to call different
event methods based on the locale provided by the event source.
Here's the default implementation provided by
the IDispEventSimpleImpl class:
//Helper for finding the function index for a DISPID
virtual HRESULT GetFuncInfoFromId(const IID& iid,
DISPID dispidMember,
LCID lcid,
_ATL_FUNC_INFO& info) {
ATLTRACE(_T("TODO: Classes using IDispEventSimpleImpl "
"should override this method\n"));
ATLASSERT(0);
ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetFuncInfoFromId"));
}
The IDispEventImpl class overrides this
virtual method to create the structure from the typeinfo
in the specified type library.
Implementing an
Event Sink
The easiest way
to implement one or more event sinks in a nonattributed ATL object
is to derive the object one or more times from
IDispEventImplonce for each unique event interface coming
from each unique event source. Here's the template class
specification once more:
template <UINT nID, class T, const IID* pdiid = &IID_NULL,
const GUID* plibid = &GUID_NULL,
WORD wMajor = 0, WORD wMinor = 0,
class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE IDispEventImpl :
public IDispEventSimpleImpl<nID, T, pdiid>
{ ... }
The nID parameter specifies an
identifier for the event source that is unique to the deriving
class T. You'll see in Chapter 11, "ActiveX Controls," that when the
event source is a contained control and the event recipient is a
composite control, the identifier is the contained control's child
window identifier.
A composite control can default all other
IDispEventImpl template parameters, but an arbitrary COM
object must specify all parameters except the last. The
pdiid parameter specifies the GUID for the event
dispinterface that this class implements. The
dispinterface must be described in the type library
specified by the plibid parameter and the type library
major and minor version numbers, wMajor and
wMinor. The tihclass parameter specifies the
class to manage the type information for the deriving class
T. The default CComTypeInfoHolder class is
generally acceptable.
The more efficient way to implement one or more event
sinks in an ATL object uses the IDispEventSimpleImpl class
and needs no type library at runtime. However, you must provide the
necessary _ATL_FUNC_INFO structure, as described
previously. When using the IDispEventSimpleImpl class, you
need to specify only the nID event source identifier, the
deriving class T, and the pdiid GUID for the
event dispinterface:
template <UINT nID, class T, const IID* pdiid>
class ATL_NO_VTABLE IDispEventSimpleImpl :
public _IDispEventLocator<nID, pdiid>
{ ... }
Using the easier technique, let's redefine the
CEarPolitic class to implement the
_ISpeakerEvents dispatch interface twice: once for a
Demagogue acting as a Defendant and once for a different Demagogue
acting as a Plaintiff. We have a type library, so we use the
IDispEventImpl class to implement the sink for the
Defendant object's _ISpeakerEvents callbacks. We use the
IDispEventSimpleImpl class for the Plaintiff object's
_ISpeakerEvents callbacks to demonstrate the alternative
implementation. We typically introduce a typedef for each event
interface implementation, to minimize typing and mistakes.
static const int DEFENDANT_SOURCE_ID = 0 ;
static const int PLAINTIFF_SOURCE_ID = 1 ;
class CEarPolitic;
typedef IDispEventImpl<DEFENDANT_SOURCE_ID,
CEarPolitic,
&__uuidof(_ISpeakerEvents),
&LIBID_ATLINTERNALSLib,
LIBRARY_MAJOR, LIBRARY_MINOR> DefendantEventImpl;
typedef IDispEventSimpleImpl<PLAINTIFF_SOURCE_ID,
CEarPolitic, &__uuidof(_ISpeakerEvents)> PlaintiffEventImpl;
In this example, we arbitrarily chose 0 and 1
for the event source identifiers. The identifiers could have been
any numbers. Now, we need to derive the CEarPolitic class
from the two event-implementation classes:
class ATL_NO_VTABLE CEarPolitic :
...
public DefendantEventImpl,
public PlaintiffEventImpl {
// Event sink map required in here
};
The Event Sink
Map
The
IDispEventSimpleImpl class's implementation of the
Invoke method receives the event callbacks. When the event
source calls the Invoke method, it specifies the event
that has occurred using the DISPID parameter. The
IDispEventSimpleImpl implementation searches an event sink
table for the function to call when event DISPID occurs on
dispatch interface DIID from event source identifier
SOURCE.
You specify the beginning of the event sink map
using the BEGIN_SINK_MAP macro within the declaration of
the class that derives from IDispEventImpl or
IDispEventSimpleImpl. You map each unique
SOURCE/DIID/DISPID TRiple to the proper event-handling
method using the SINK_ENTRY, SINK_ENTRY_EX, and
SINK_ENTRY_INFO macros.
-
SINK_ENTRY(SOURCE, DISPID, func).
Use this macro in a composite control to name the handler for the
specified event in the specified contained control's default source
interface. This macro assumes the use of IDispEventImpl
and assumes that you call the AtlAdviseSinkMap function to
establish the connection. The AtlAdviseSinkMap function
assumes that your class is derived from CWindow. All in
all, the SINK_ENTRY macro isn't very useful for
nonuser-interface (UI) objects that want to receive events.
-
SINK_ENTRY_EX(SOURCE, DIID, DISPID,
func). Use this macro to indicate the handler for the
specified event in the specified object's specified source
interface. This macro is most useful for non-UI objects that want
to receive events and for composite controls that want to receive
events from a contained control's nondefault source interface.
-
SINK_ENTRY_INFO(SOURCE, DIID, DISPID, func,
info). This macro is similar to the
SINK_ENTRY_EX macro, with the addition that you can
specify the _ATL_FUNC_INFO structure to be used when
calling the event handler. You typically use this macro when using
the IDispEventSimpleImpl class.
You end the event sink map using the
END_SINK_MAP macro.
The general form of the table is as follows:
BEGIN_SINK_MAP(CEarPolitic)
SINK_ENTRY_EX(SOURCE, DIID, DISPID, EventHandlerFunc)
SINK_ENTRY_INFO(SOURCE, DIID, DISPID, EventHandlerFunc, &info)
...
END_SINK_MAP()
In the CEarPolitic example, there are
three events, all from the same dispatch interface but coming from
two different event sources. Therefore, we need six event sink map
entries. We can use the SINK_ENTRY_EX macro to identify
the event handlers for the Defendant event source. We
don't need to specify the _ATL_FUNC_INFO structure because
the IDispEventImpl base class uses the type library to
provide the appropriate structure at runtime. We need to use the
SINK_ENTRY_INFO macro for the Plaintiff object.
Because we used the IDisp-EventSimpleImpl base class, we
need to provide the function information structure describing each
event method.
Each function information structure describes
one event method. The structure contains the calling convention,
the VARIANT type of the return value of the method, the
number of parameters, and the VARIANT types of each of the
parameters. The calling convention should be set to
CC_STDCALL because that's what the
IDispEventSimpleImpl class expects the event handlers to
use.
Here are the function prototypes for the
Plaintiff's three event methods and their information
structures (all identical in this example). Event handlers
typically do not return a valuethat is, they are void
functions. Note: The proper
vtreturn value in the _ATL_FUNC_INFO structure to
represent a void function return is VT_EMPTY, not
VT_VOID.
void __stdcall OnHearPlaintiffWhisper(BSTR bstrText);
void __stdcall OnHearPlaintiffTalk(BSTR bstrText);
void __stdcall OnHearPlaintiffYell(BSTR bstrText);
static const int DISPID_WHISPER = 1 ;
static const int DISPID_TALK = 2 ;
static const int DISPID_YELL = 3 ;
_ATL_FUNC_INFO OnHearWhisperInfo = {
CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
_ATL_FUNC_INFO OnHearTalkInfo = {
CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
_ATL_FUNC_INFO OnHearYellInfo = {
CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
Here's the event sink map for the
CEarPolitic object:
BEGIN_SINK_MAP(CEarPolitic)
SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
DISPID_WHISPER, OnHearDefendantWhisper)
SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
DISPID_TALK, OnHearDefendantTalk)
SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
DISPID_YELL, OnHearDefendantYell)
SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
DISPID_WHISPER, OnHearPlaintiffWhisper, &OnHearWhisperInfo)
SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
DISPID_TALK, OnHearPlaintiffTalk, &OnHearTalkInfo)
SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
DISPID_YELL, OnHearPlaintiffYell, &OnHearYellInfo)
END_SINK_MAP()
One caution:
The sink map contains a hard-coded DISPID for each event.
This means that the sink map technically is specific to a
particular dispinterface version. COM allows the
DISPIDs in a dispinterface to change from version
to version. Now, this isn't something that often happensand a
control vendor that does such a thing is just asking for tech
support calls and angry customers. But it is allowed.
The only absolutely correct way to obtain a
DISPID is to ask the object implementing the dispinterface
at startup for the DISPID corresponding to an event. For a
source interface, this is impossible because when you build the
sink, you're implementing the source interface, so there's no
object to query. The only realistic option is to read the object's
type library at runtime. ATL, Visual Basic, and MFC don't support
this. They all assume that the DISPIDs in a
dispinterface will never change, as a questionable
performance optimization.
The Callback
Methods
The callback method specified by the sink entry
macros must use the __stdcall calling convention. The
parameters for each callback method specified in the sink map must
agree in type and number with the corresponding event method as
described in the type library. Here are the Defendant's
event methods; they are identical to the Plaintiff's, as
expected:
void __stdcall OnHearDefendantWhisper(BSTR bstrText);
void __stdcall OnHearDefendantTalk(BSTR bstrText);
void __stdcall OnHearDefendantYell(BSTR bstrText);
After two remaining steps, the
CEarPolitic class will be complete: Implement the callback
methods and establish the connection between a CEarPolitic
instance and a Demagogue (which invokes the
_ISpeakerEvents methods).
We
use the following simple implementation for each of the event
handlers:
void __stdcall CEarPolitic::OnHearDefendantTalk(BSTR bstrText) {
CComBSTR title ;
CreateText(title, OLESTR("defendant"),
OLESTR("talking"), m_defendant);
MessageBox(NULL, COLE2CT(bstrText), COLE2CT(title), MB_OK);
}
void CEarPolitic::CreateText(CComBSTR& bstrText,
LPCOLESTR strRole,
LPCOLESTR strAction,
LPUNKNOWN punk) {
bstrText = m_bstrName;
bstrText += OLESTR(" hears the ");
bstrText += strRole;
bstrText += OLESTR(" (");
CComQIPtr<INamedObject> spno = punk;
CComBSTR bstrName;
HRESULT hr = spno->get_Name (&bstrName) ;
bstrText.AppendBSTR(bstrName);
bstrText += OLESTR(") ");
bstrText += strAction;
}
Connecting the
Event Sink to an Event Source
When your class is a composite control, you
should use the AtlAdviseSinkMap function to establish and
remove the connections between all the source interfaces of the
contained controls listed in the sink map and your
IDispEventImpl implementation(s). This method uses the
event source identifier as a child window identifier. Using the
CWindow::GetDlgItem method, AtlAdviseSinkMap
navigates to a child window handle and, from there, to the
contained control's IUnknown interface. From the
IUnknown interface, it gets the
IConnectionPointContainer interface and the appropriate
connection point, and calls its Advise method.
template <class T>
inline HRESULT AtlAdviseSinkMap(T* pT, bool bAdvise);
You must use the
AtlAdviseSinkMap function to establish the connections any
time you use the IDispEventImpl class and you specify only
the first two template parameters, using default values for the
rest. Not specifying the source interface implies using the default
source interface for the event source. The
AtlAdvise-SinkMap method determines the default source
interface, if unspecified, for each event source and establishes
the connection point to that interface.
When your class isn't a composite control, as in
the ongoing example, you must explicitly call the
DispEventAdvise method of each of your
IDispEventSimpleImpl (or derived) base classes to connect
each event source to each event sink implementation. The
pUnk parameter to the DispEventAdvise method is
any interface on the event source; the piid parameter is
the desired source dispatch interface GUID. The
DispEventUnadvise method breaks the connection.
template <UINT nID, class T, const IID* pdiid>
class ATL_NO_VTABLE IDispEventSimpleImpl : ... {
...
HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid);
HRESULT DispEventUnadvise(IUnknown* pUnk, const IID* piid);
...
}
Here is the IListener interface. We
added it to the EarPolitic coclass to provide a means of
determining whether a COM object can listen to a Defendant
and a Plaintiff. It also provides the ListenTo
and StopListening methods to establish and break the
connection point between a Speaker event source and the
Defendant or Plaintiff event sink.
interface IListener : IDispatch {
typedef enum SpeakerRole { Defendant, Plaintiff } SpeakerRole;
[id(1)] HRESULT ListenTo(SpeakerRole role, IUnknown* pSpeaker);
[id(2)] HRESULT StopListening(SpeakerRole role);
};
The implementation of these methods is
straightforward. ListenTo calls the
DispEventAdvise method on the appropriate event sink to
establish the connection:
STDMETHODIMP CEarPolitic::ListenTo(SpeakerRole role,
IUnknown *pSpeaker) {
HRESULT hr = StopListening(role) ; // Validates role
if (FAILED(hr)) return hr ;
switch (role) {
case Defendant:
hr = DefendantEventImpl::DispEventAdvise (pSpeaker,
&DIID__ISpeakerEvents);
if (SUCCEEDED(hr))
m_defendant = pSpeaker;
else
Error(OLESTR("The defendant does not support listening"),
__uuidof(IListener), hr);
break;
case Plaintiff:
hr = PlaintiffEventImpl::DispEventAdvise (pSpeaker,
&DIID__ISpeakerEvents);
if (SUCCEEDED(hr))
m_plaintiff = pSpeaker;
else
Error(OLESTR("The Plaintiff does not support listening"),
__uuidof(IListener), hr);
break;
}
return hr;
}
The
StopListening method calls DispEventUnadvise to
break the connection:
STDMETHODIMP CEarPolitic::StopListening(SpeakerRole role) {
HRESULT hr = S_OK;
switch (role) {
case Defendant:
if (m_defendant)
hr = DefendantEventImpl::DispEventUnadvise (m_defendant,
&DIID__ISpeakerEvents);
if (FAILED(hr))
Error (OLESTR("Unexpected error trying to stop listening "
"to the defendant"), __uuidof(IListener), hr);
m_defendant = NULL;
break;
case Plaintiff:
if (m_plaintiff)
hr = PlaintiffEventImpl::DispEventUnadvise(m_plaintiff,
&DIID__ISpeakerEvents);
if (FAILED(hr))
Error(OLESTR("Unexpected error trying to stop listening "
"to the Plaintiff"), __uuidof(IListener), hr);
m_plaintiff = NULL;
break;
default:
hr = E_INVALIDARG;
break;
}
return hr;
}
In
summary, use the IDispEventImpl and
IDispEventSimpleImpl classes to implement an event sink
for a dispatch interface. Call the DispEventAdvise and
DispEventUnadvise methods of each class to establish and
break the connection.
Derive your class directly from the source
interface when the source is a simple COM interface. Call the
AtlAdvise and AtlUnadvise global functions to
establish and break the connection. When you need to implement the
same source interface multiple times, use one of the various
standard techniques (nested composition, method coloring, separate
classes, or intermediate base classes) to avoid name
collisions.
|