Chapter 9. Connection Points
A Review of Connection Points
An object implements one or more interfaces to expose its functionality. The term connection points refers to a logically inverse mechanism that allows an object to expose its capability to call one or more specific interfaces.
Another perspective is that QueryInterface
allows a client to retrieve from an object a pointer to an
interface that the object implements. Connection points allow a
client to give an object a pointer
to an interface that the client implements. In the first case, the
client uses the retrieved interface pointer to call methods
provided by the object. In the second case, the object uses the
provided interface pointer to call methods provided by the
client.
A slightly closer inspection of the two
mechanisms reveals that QueryInterface
allows a client to
retrieve from an object only interfaces that the client knows how
to call. Connection points allow a client to provide to an object
only interfaces that the object knows how to call.
A connection has two parts: the object making calls to the methods of a specific interface, called the source or, alternatively, the connection point; and the object implementing the interface (receiving the calls), called the sink object (see Figure 9.1) Using my terminology from earlier paragraphs, the object is the source and makes calls to the sink interface methods. The client is the sink and implements the sink interface. One additional complexity is that a particular source object can have connections to multiple sink objects.
Figure 9.1. A connection to two sinks

The IConnectionPoint Interface
A client uses the source
object’s implementation of the IConnectionPoint
interface
to establish a connection. Here is the definition of the IConnectionPoint
interface:
1 interface IConnectionPoint : IUnknown {
2 HRESULT GetConnectionInterface ([out] IID* pIID);
3 HRESULT GetConnectionPointContainer (
4 [out] IConnectionPointContainer** ppCPC);
5 HRESULT Advise ([in] IUnknown* pUnkSink,
6 [out] DWORD* pdwCookie);
7 HRESULT Unadvise ([in] DWORD dwCookie);
8 HRESULT EnumConnections ([out] IEnumConnections** ppEnum);
9 }
The GetConnectionInterface
method
returns the interface identifier (IID) of the sink interface for
which a connection point makes calls. Using the previous example,
calling GetConnectionInterface
returns IID_ISomeSink
. A client calls the Advise
method
to establish a connection. The client provides the appropriate sink
interface pointer for the connection point and receives a magic
cookie (token) that represents the connection. A client can later
call the Unadvise
method, specifying the magic cookie to
break the connection. The EnumConnections
method returns a
standard COM enumeration object that a client uses to enumerate all
the current connections that a connection point holds. The last
method is GetConnectionPointContainer
, which introduces a
new complexity.
So far, this design allows a source object to make calls on only one specific interface. The source object maintains a list of clients that want to receive calls on that specific interface. When the source object determines that it should call one of the methods of its sink interface, the source iterates through its list of sink objects, calling that method for each sink object. What the design (again, as described so far) doesn’t include is the capability for an object to originate calls on multiple different interfaces using this mechanism. Alternatively, to present the question directly, we have a design in which an object can support multiple connections to a single connection point, but how can an object support multiple different connection points?
The solution is to demote the source object to
subobject status and have an encapsulating object, called the
connectable object (see Figure9.2), act as a container of these source subobjects. A client
uses the source object’s GetConnectionPointContainer
method to retrieve a pointer to the connectable object. A
connectable object implements the IConnectionPointContainer
interface.
Figure 9.2. A connectable object

Implementing IConnectionPointContainer
indicates that a COM object
supports connection points and, more specifically, that it can
provide a connection point (a source subobject) for each sink
interface the connectable object knows how to call. Clients then
use the connection point as described previously to establish the
connection.
The IConnectionPointContainer Interface
Here is the definition of the IConnectionPointContainer
interface:
1 interface IConnectionPointContainer : IUnknown {
2 HRESULT EnumConnectionPoints (
3 [out] IEnumConnectionPoints ** ppEnum);
4 HRESULT FindConnectionPoint ([in] REFIID riid,
5 [out] IConnectionPoint** ppCP);
6 }
You call the FindConnectionPoint
method
to retrieve an IConnectionPoint
interface pointer to a
source subobject that originates calls on the interface specified
by the riid
parameter. The EnumConnectionPoints
method returns a standard COM enumerator subobject that implements
the IEnumConnectionPoints
interface. You can use this
enumerator interface to retrieve an IConnectionPoint
interface pointer for each connection point that the connectable
object supports.
Most often, a client that wants to establish a connection to a connectable object does the following (with error checking removed for clarity):
1 CComPtr<IUnknown>
2 spSource = /* Set to the source of the events */ ;
3 CComPtr<_ISpeakerEvents>
4 spSink = /* Sink to receive the events */ ;
5 DWORD dwCookie ;
6
7 CComPtr<IConnectionPointContainer> spcpc;
8 HRESULT hr = spSource.QueryInterface (&spcpc);
9
10 CComPtr<IConnectionPoint> spcp ;
11 hr = spcpc->FindConnectionPoint(__uuidof(_ISpeakerEvents),
12 &spcp);
13 hr = spcp->Advise (spSink, &dwCookie) ;// Establish connection
14 // Time goes by, callbacks occur...
15 hr = spcp->Unadvise (dwCookie) ; // Break connection
In fact, ATL provides two useful functions that
make and break the connection between a source and a sink object.
The AtlAdvise
function makes the connection between a
connectable object’s connection point (specified by the pUnkCP
and iid
parameters) and a sink interface
implementation (specified by the pUnk
parameter), and
returns a registration code for the connection in the pdw
location. The AtlUnadvise
function requests the
connectable object’s connection point to break the connection
identified by the dw
parameter.
1 ATLAPI AtlAdvise(IUnknown* pUnkCP, IUnknown* pUnk,
2 const IID& iid, LPDWORD pdw);
3 ATLAPI AtlUnadvise(IUnknown* pUnkCP, const IID& iid, DWORD dw);
You use these functions like this:
1 DWORD dwCookie;
2 // Make a connection
3 hr = AtlAdvise(spSource, spSink, __uuidof(_ISpeakerEvents),
4 &dwCookie);
5 // ... Receive callbacks ...
6 // Break the connection
7 hr = AtlUnadvise(spSource, __uuidof(_ISpeakerEvents), dwCookie);
In summary, to establish a connection, you need an interface pointer for the connectable object, an interface pointer to an object that implements the sink interface, and the sink interface ID.
You commonly find connection points used by ActiveX controls. An ActiveX control is often a source of events. An event source implements event callbacks asmethod calls on a specified event sink interface. Typically, an ActiveX control container implements an event sink interface so that it can receive specific events from its contained controls. Using the connection points protocol, the control container establishes a connection from the source of events in the control (the connection point) to the event sink in the container. When an event occurs, the connection point calls the appropriate method of the sink interface for each sink connected to the connection point.
I should point out that the connection points design is deliberately a very general mechanism, which means that using connection points isn’t terribly efficient in some cases. [1] Connection points are most useful when an unknown number of clients might want to establish callbacks to a variety of different sink interfaces. In addition, the connection points protocol is well known; therefore, objects with no custom knowledge of each other can use it to establish connections. If you are writing both the source and sink objects, you might want to invent a custom protocol that is easier to use than the connection point protocol, to trade interface pointers.
Creating an ATL-Based Connectable Object
The Brilliant Example Problem That Produces Blinding Insight
Let’s create a Demagogue COM object that
represents a public speaker. The ATL-based CDemagogue
class implements the ISpeaker
interface. When asked to Speak
, a speaker can whisper, talk, or yell his speech,
depending on the value of Volume
.
1interface ISpeaker : IDispatch {
2 [propget, id(1)] HRESULT Volume([out, retval] long *pVal);
3 [propput, id(1)] HRESULT Volume([in] long newVal);
4 [propget, id(2)] HRESULT Speech([out, retval] BSTR *pVal);
5 [propput, id(2)] HRESULT Speech([in] BSTR newVal);
6 [id(3)] HRESULT Speak();
7};
Whispering, talking, and yelling generate event
notifications on the _ISpeakerEvents
source dispatch
interface, and the recipients of the events hear the speech. Many
client components can receive event notifications only when the
source interface is a dispatch interface.
1dispinterface _ISpeakerEvents {
2properties:
3methods:
4 [id(1)] void OnWhisper(BSTR bstrSpeach);
5 [id(2)] void OnTalk(BSTR bstrSpeach);
6 [id(3)] void OnYell(BSTR bstrSpeach);
7};
The underscore prefix is a naming convention that causes many type library browsers to not display the interface name. Because an event interface is an implementation detail, typically you don’t want such interfaces displayed to the authors of scripting languages when they use your component.
Note that the Microsoft Interface Definition
Language (MIDL) compiler prefixes DIID_
to the name of a dispinterface
when it generates its named globally unique
identifier (GUID). So DIID__ISpeakerEvents
is the named
GUID for this interface.
Therefore, the following coclass
definition describes a Demagogue. I’ve added an interface that lets
me name a particular Demagogue if I don’t like the default (which
is Demosthenes).
1coclass Demagogue {
2 [default] interface IUnknown;
3 interface ISpeaker;
4 interface INamedObject;
5 [default, source] dispinterface _ISpeakerEvents;
6};
I start with the CDemagogue
class: an
ATL-based, single-threaded-apartment-resident, COM-createable
object to represent a Demagogue.
1class ATL_NO_VTABLE CDemagogue :
2 public CComObjectRootEx<CComSingleThreadModel>,
3 public CComCoClass<CDemagogue, &CLSID_Demagogue>,
4 public ISupportErrorInfo,
5 public IDispatchImpl<ISpeaker, &IID_ISpeaker,
6 &LIBID_ATLINTERNALSLib>,
7 ...
8{ ... };
Seven Steps to a Connectable Object
There are seven steps to creating a connectable object using ATL:
Implement the
IConnectionPointContainer
interface.Implement
QueryInterface
so that it responds to requests forIID_IConnectionPointContainer
.Implement the
IConnectionPoint
interface for each source interface that the connectable object supports.Provide a connection map that is a table that associates an IID with a connection point implementation.
Update the
coclass
definition for the connectable object class in your IDL file to specify each source interface. Each source interface must have the[source]
attribute. The primary source interface should have the[default, source]
attributes.Typically, you implement helper methods that call the sink methods for all connected sinks.
You must call the helper methods at the appropriate times.
Adding Required Base Classes to Your Connectable Object
For an object to fire events using the
connection points protocol, the object must be a connectable
object. This means that the object must implement the IConnectionPointContainer
interface. You can use an
ATL-provided implementation of IConnectionPointContainer
and IConnectionPoint
by deriving the connectable object
class from the appropriate base classes.
Step 1. Derive the CDemagogue
connectable
object class from the ATL template class IConnectionPointContainerImpl
. This template class
requires one parameter, the name of your derived class. This
derivation provides the connectable object with an implementation
of the IConnectionPointContainer
interface.
1class ATL_NO_VTABLE CDemagogue :
2 ...
3 public IConnectionPointContainerImpl<CDemagogue>,
4 ...
Changes to the COM_MAP for a Connectable Object
Step 2. Any time you add a new interface
implementation to a class, you should immediately add support for
the interface to your QueryInterface
method. ATL
implements QueryInterface
for an object by searching the
object’s COM_MAP
for an entry matching the requested IID.
To indicate that the object supports the IConnectionPointContainer
interface, add a COM_INTERFACE_ENTRY
macro for the interface:
1BEGIN_COM_MAP(CDemagogue)
2...
3 COM_INTERFACE_ENTRY(IConnectionPointContainer)
4...
5END_COM_MAP ()
Adding Each Connection Point
A
connection point container needs a collection of connection points
to contain (otherwise, the container is somewhat boring as well as
misleading). For each source interface that the connectable object
supports, you need a connection point subobject. A connection point
subobject is logically a separate object (that is, its COM object
identity is unique) that implements the IConnectionPoint
interface.
Step 3. To create connection point subobjects, you
derive your connectable object class from the template class IConnectionPointImpl
one or more timesonce for each source
interface supported by the connectable object. This derivation
provides the connectable object with one or more implementations of
the IConnectionPoint
interface on separately
reference-counted subobjects. The IConnectionPointImpl
class requires three template parameters: the name of your
connectable object class, the IID of the connection point’s source
interface, and, optionally, the name of a class that manages the
connections.
1class ATL_NO_VTABLE CDemagogue :
2 ...
3 public IConnectionPointContainerImpl<CDemagogue>,
4 public IConnectionPointImpl<CDemagogue, &DIID__ISpeakerEvents>
5 ...
Where, O Where Are the Connection Points? Where, O Where Can They Be?
Any implementation of IConnectionPointContainer
needs some fundamental
information: a list of connection point objects and the IID that
each connection point object supports. The ATL implementation uses
a table called a connection point
map in which you provide the required information. You
define a connection point map in your connectable object’s class
declaration using three ATL macros.
The BEGIN_CONNECTION_POINT_MAP
macro
specifies the beginning of the table. The only parameter is the
class name of the connectable object. Each CONNECTION_POINT_ENTRY
macro places an entry in the table
and represents one connection point. The macro’s only parameter is
the IID of the interface that the connection point supports.
Note that the CONNECTION_POINT_ENTRY
macro requires you to specify an IID, whereas the COM_INTERFACE_ENTRY
macro needs an interface class name.
Historically, you could always prepend an IID_
prefix to
an interface class name toproduce the name of the GUID for the interface.
Earlier versions of ATL’s COM_INTERFACE_ENTRY
macro
actually did this to produce the appropriate IID.
However, source interfaces have no such regular
naming convention. Various versions of MFC, MKTYPLIB, and MIDL have
generated different prefixes to a dispinterface
. The CONNECTION_POINT_ENTRY
macro couldn’t assume a prefix, so
you had to specify the IID explicitly. By default, ATL uses the __uuidof
keyword to obtain the IID for a class.
The END_CONNECTION_MAP
macro generates
an end-of-table marker and code that returns the address of the
connection map as well as its size.
Step 4. Here’s the connection map for the CDemagogue
class:
1BEGIN_CONNECTION_POINT_MAP(CDemagogue)
2 CONNECTION_POINT_ENTRY(__uuidof(_ISpeakerEvents))
3END_CONNECTION_POINT_MAP()
Update the coclass to Support the Source Interface
Step 5. Clients often read the type library, which
describes an object that is a source of events, to determine
certain implementation details, such as the object’s source
interface(s). You need to ensure that the source object’s coclass
description is up-to-date by adding an entry to
describe the source interface.
1coclass Demagogue
2{
3 [default] interface IUnknown;
4 interface ISpeaker;
5 interface INamedObject;
6 [default, source] dispinterface _ISpeakerEvents;
7};
Where There Are Events, There Must Be Fire
So far, we have a Demagogue connectable object that is a container of connection points and one connection point. The implementation, as presented up to now, permits a client to register a callback interface with a connection point. All the enumerators will work. The client can even disconnect. However, the connectable object never issues any callbacks. This isn’t terribly useful and has been a bit of work for no significant gain, so we’d better finish things. A connectable object needs to call the sink interface methods, otherwise known as firing the events.
To fire an event, you call the appropriate event method of the sink interface for each sink interface pointer registered with a connection point. This task is complex enough that you’ll generally find it useful to add event-firing helper methods to yourconnectable object class. You have one helper method in your connectable object class for each method in each of your connection points’ supported interfaces.
You fire an event by calling the associated event method of a particular sink interface. You do this for each sink interface registered with the connection point. This means you need to iterate through a connection point’s list of sink interfaces and call the event method for each interface pointer. “How and where does a connection point maintain this list?” you ask. Good timing, I was about to get to that.
Each IConnectionPointImpl
base class
object (which means each connection point) contains a member
variable m_vec
that ATL declares as a vector of IUnknown
pointers. However, you don’t need to call QueryInterface
to get the appropriate sink interfaces out
of this collection; ATL’s implementation of IConnectionPointImpl::Advise
has already performed this
query for you. For example, the vector in the connection point
associated with DIID_ISpeakerEvents
actually contains _ISpeakerEvents
pointers.
By default, m_vec
is a CComDynamicUnkArray
object, which is a dynamically
allocated array of IUnknown
pointers, each a client sink
interface pointer for the connection point. The CComDynamicUnkArray
class grows the vector as required, so
the default implementation provides an unlimited number of
connections.
Alternatively, when you declare the IConnectionPointImpl
base class, you can specify that m_vec
is a CComUnkArray
object that holds a fixed
number of sink interface pointers. Use the CComUnkArray
class when you want to support a fixed maximum number of
connections. ATL also provides an explicit template, CComUnkArray<1>
, that is specialized for a single
connection
Step 6. To fire an event, you iterate through the
array and, for each
non- NULL
entry,
call the sink interface method associated with the event you want
to fire. Here’s a simple helper method that fires the OnTalk
event of the _ISpeakerEvents
interface.Note that m_vec
is unambiguous only when you have a single
connection point.
1HRESULT Fire_OnTalk(BSTR bstrSpeech)
2{
3 CComVariant arg, varResult;
4 int nIndex, nConnections = m_vec.GetSize();
5
6 for (nIndex = 0; nIndex < nConnections; nIndex++) {
7 CComPtr<IUnknown> sp = m_vec.GetAt(nIndex);
8 IDispatch* pDispatch =
9 reinterpret_cast<IDispatch*>(sp.p);
10 if (pDispatch != NULL) {
11 VariantClear(&varResult);
12 arg = bstrSpeech;
13 DISPPARAMS disp = { &arg, NULL, 1, 0 };
14 pDispatch->Invoke(0x2, IID_NULL, LOCALE_USER_DEFAULT,
15 DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
16 }
17 }
18 return varResult.scode;
19}
The ATL Connection Point Proxy Generator
Writing the helper methods
that call a connection point interface method is tedious and prone
to errors. An additional complexity is that a sink interface can be
a custom COM interface or a dispinterface
. Considerably
more work is involved in making a dispinterface
callback
(that is, using IDispatch::Invoke
) than making a vtable
callback. Unfortunately, the dispinterface
callback is the most frequent case because it’s the only event
mechanism that scripting languages, Internet Explorer, and most
ActiveX control containers support.
The Visual Studio 2005 IDE, however, provides a source codegeneration tool that generates a connection point class that contains all the necessary helper methods for making callbacks on a specific connection point interface. In the Visual Studio 2005 Class View pane, right-click on the C++ class that you want to be a source of events. Select the Add Connection Point menu item from the context menu. The Implement Connection Point Wizard appears (see Figure 9.3).
Figure 9.3. The Implement Connection Point dialog box

The
Implement Connection Point Wizard creates one or more classes
(declared and defined in the specified header file) that represent
the specified interface(s) and their methods. To use the code
generator, you must have a type library that describes the desired
event interface. The code generator reads the type library
description of an interface and generates a class, derived from IConnectionPointImpl
, that contains an event-firing helper
function for each interface method. You specify the generated class
name as one of your connectable object’s base classes. This base
class implements a specific connection point and contains all
necessary event-firing helper methods.
The Implement Connection Point Proxy-Generated Code
The proxy generator produces a template class
with a name in the form CProxy_<SinkInterfaceName>
.
This proxy class requires one parameter: your connectable object’s
class name. The proxy class derives from an IConnectionPointImpl
template instantiation that specifies
your source interface.
Here is the code that the Implement Connection
Point Wizard generates for the previously described _ISpeakerEvents
interface:
1#pragma once
2
3template<class T>
4class CProxy_ISpeakerEvents :
5 public IConnectionPointImpl<T, &__uuidof(_ISpeakerEvents)>
6{
7public:
8 HRESULT Fire_OnWhisper(BSTR bstrSpeech) {
9 HRESULT hr = S_OK;
10 T * pThis = static_cast<T*>(this);
11 int cConnections = m_vec.GetSize();
12
13 for (int iConnection = 0;
14 iConnection < cConnections;
15 iConnection++)
16 {
17 pThis->Lock();
18 CComPtr<IUnknown> punkConnection =
19 m_vec.GetAt(iConnection);
20 pThis->Unlock();
21
22 IDispatch * pConnection =
23 static_cast<IDispatch*>(punkConnection.p);
24
25 if (pConnection) {
26 CComVariant avarParams[1];
27 avarParams[0] = bstrSpeech;
28 DISPPARAMS params = { avarParams, NULL, 1, 0 };
29 hr = pConnection->Invoke(DISPID_ONWHISPER,
30 IID_NULL,
31 LOCALE_USER_DEFAULT,
32 DISPATCH_METHOD,
33 ¶ms, NULL, NULL, NULL);
34 }
35 }
36 return hr;
37 }
38
39// Other methods similar, deleted for clarity
40
41};
Using the Connection Point Proxy Code
Putting everything together
so far, here are the pertinent parts of the CDemagogue
connectable object declaration. The only change from previous
examples is the use of the generated connection point proxy class, CProxy_ISpeakerEvents<CDemagogue>
, as a base class
for the connection point instead of the more generic IConnectionPointImpl
class.
1class ATL_NO_VTABLE CDemagogue :
2...
3 public IConnectionPointContainerImpl<CDemagogue>,
4 public CProxy_ISpeakerEvents<CDemagogue>, ... {
5
6BEGIN_COM_MAP(CDemagogue)
7...
8 COM_INTERFACE_ENTRY(IConnectionPointContainer)
9...
10END_COM_MAP()
11
12BEGIN_CONNECTION_POINT_MAP(CDemagogue)
13 CONNECTION_POINT_ENTRY(__uuidof(_ISpeakerEvents))
14END_CONNECTION_POINT_MAP()
15...
16};
Firing the Events
Step 7. The final step to make everything work is to fire each event at the appropriate time. When to do this is application-specific, but here is one example.
The CDemagogue
object makes its speech when a client calls the Speak
method. The Speak
method, based on the current volume
property, either whispers, talks, or yells. It does this by calling
the OnWhisper
, OnTalk
, or OnYell
event
methods, as appropriate, of all clients listening to the
demagogue’s _ISpeakerEvents
interface.
1STDMETHODIMP CDemagogue::Speak() {
2 if (m_nVolume <= -100)
3 return Fire_OnWhisper(m_bstrSpeech);
4
5 if (m_nVolume >= 100)
6 return Fire_OnYell(m_bstrSpeech);
7
8 return Fire_OnTalk(m_bstrSpeech);
9}
Going the Last Meter/Mile, Adding One Last Bell
The changes described so far provide a complete implementation of the connection point protocol. However, one last change makes your connectable object easier for clients to use. A connectable object should provide convenient client access to information about the interfaces that the object supports.
More specifically, many clients that want to
receive events from a connectable object can ask the object for its IProvideClassInfo2
interface. Microsoft Internet Explorer,
Visual Basic, and ATL-based ActiveX control containers do this,
for example. The client calls the GetGUID
method of this interface with the parameter GUIDKIND_DEFAULT_SOURCE_DISP_IID
to retrieve the IID of
the primary event disp-interface
that the connectable
object supports. This is the IID of the dispinterface
listed in the connectable object’s coclass
description
with the [default, source]
attributes.
Supporting IProvideClassInfo2
gives
arbitrary clients a convenient mechanism for determining the
primary event source IID and then using the IID to establish a
connection. Note that the IID returned by this call to GetGUID
must be a dispinterface
; it cannot be a
standard IUnknown
-derived (vtable
) interface.
When a connectable object fails the query for IProvideClassInfo2
, some clients ask for IProvideClassInfo
. A client can use this interface to
retrieve an ITypeInfo
pointer about the connectable
object’s class. With a considerable bit of effort, a client can use
this ITypeInfo
pointer and determine the default source
interface that the connectable object supports. The IProvideClassInfo2
interface derives from the IProvideClassInfo
interface, so by implementing the first
interface, you’ve already implemented the second one.
Because most connectable objects should
implement the IProvideClassInfo2
interface, ATL provides a
template class for the implementation, IProvideClassInfo2Impl
, which provides a default
implementation of all the IProvideClassInfo
and IProvideClassInfo2
methods. The declaration of the class
looks like this:
1template <const CLSID* pcoclsid, const IID* psrcid,
2 const GUID* plibid = &CAtlModule::m_libid,
3 WORD wMajor = 1, WORD wMinor = 0,
4 class tihclass = CComTypeInfoHolder>
5class ATL_NO_VTABLE IProvideClassInfo2Impl
6 : public IProvideClassInfo2
7{ ... }
To use this implementation in your connectable
object, you must derive the connectable object class from the IProvideClassInfo2Impl
class. The last two template
parameters in the following example are the major and minor version
numbers of the component’s type library. They default to 1 and 0,
respectively, so I didn’t need to list them. However, when you
change the type library’s version number, you also need to change
the numbers in the template invocation. You won’t get a compile
error if you fail to make the change, but things won’t work
correctly.
By always listing the version number explicitly, I remember to make this change more often:
1#define LIBRARY_MAJOR 1
2#define LIBRARY_MINOR 0
3
4class ATL_NO_VTABLE CDemagogue :
5...
6 public IConnectionPointContainerImpl<CDemagogue>,
7 public CProxy_ISpeakerEvents<CDemagogue>,
8 public IProvideClassInfo2Impl<&CLSID_Demagogue,
9 &__uuidof(_ISpeakerEvents),
10 &LIBID_ATLINTERNALSLib, LIBRARY_MAJOR, LIBRARY_MINOR>,
11...
12};
You also need to update the interface map so
that QueryInterface
responds to IProvideClassInfo
and IProvideClassInfo2
.
1BEGIN_COM_MAP(CDemagogue)
2...
3 COM_INTERFACE_ENTRY(IProvideClassInfo2)
4 COM_INTERFACE_ENTRY(IProvideClassInfo)
5END_COM_MAP()
Finally, here are all connectable-object-related changes in one place:
1#define LIBRARY_MAJOR 1
2#define LIBRARY_MINOR 0
3
4// Event dispinterface
5dispinterface _ISpeakerEvents {
6properties:
7methods:
8 [id(1)] void OnWhisper(BSTR bstrSpeech);
9 [id(2)] void OnTalk(BSTR bstrSpeech);
10 [id(3)] void OnYell(BSTR bstrSpeech);
11};
12
13// Connectable object class
14coclass Demagogue {
15 [default] interface IUnknown;
16 interface ISpeaker;
17 interface INamedObject;
18 [default, source] dispinterface _ISpeakerEvents;
19};
20
21// Implementation class for coclass Demagogue
22class ATL_NO_VTABLE CDemagogue :
23...
24 public IConnectionPointContainerImpl<CDemagogue>,
25 public CProxy_ISpeakerEvents<CDemagogue>,
26 public IProvideClassInfo2Impl<&CLSID_Demagogue,
27 &__uuidof(_ISpeakerEvents),
28 &LIBID_ATLINTERNALSLib, LIBRARY_MAJOR, LIBRARY_MINOR>,
29 ... {
30public:
31BEGIN_COM_MAP(CDemagogue)
32 COM_INTERFACE_ENTRY(IConnectionPointContainer)
33 COM_INTERFACE_ENTRY(IProvideClassInfo2)
34 COM_INTERFACE_ENTRY(IProvideClassInfo)
35...
36END_COM_MAP()
37
38BEGIN_CONNECTION_POINT_MAP(CDemagogue)
39 CONNECTION_POINT_ENTRY(__uuidof(_ISpeakerEvents))
40END_CONNECTION_POINT_MAP()
41...
42};
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
.
1coclass EarPolitic {
2 [default] dispinterface _ISpeakerEvents;
3};
Now, implement the class using ATL as the CEarPolitic
class.
1class ATL_NO_VTABLE CEarPolitic :
2 ...
3 public _ISpeakerEvents,
4 ... {
5public:
6 ...
7BEGIN_COM_MAP(CEarPolitic)
8 COM_INTERFACE_ENTRY(IDispatch)
9 COM_INTERFACE_ENTRY(_ISpeakerEvents)
10...
11END_COM_MAP()
12
13// _ISpeakerEvents
14 STDMETHOD(GetTypeInfoCount)(UINT* pctinfo);
15 STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid,
16 ITypeInfo** pptinfo);
17 STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames,
18 UINT cNames, LCID lcid, DISPID* rgdispid);
19 STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid,
20 WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
21 EXCEPINFO* pexcepinfo, UINT* puArgErr);
22};
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:
1MIDL_INTERFACE("A924C9DE-797F-430d-913D-93158AD2D801")
2_ISpeakerEvents : public IDispatch
3{
4};
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.
1template <UINT nID, class T, const IID* pdiid = &IID_NULL,
2 const GUID* plibid = &GUID_NULL,
3 WORD wMajor = 0, WORD wMinor = 0,
4 class tihclass = CComTypeInfoHolder>
5class ATL_NO_VTABLE IDispEventImpl :
6 public IDispEventSimpleImpl<nID, T, pdiid>
7{ ... }
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.
1template <UINT nID, class T, const IID* pdiid>
2class ATL_NO_VTABLE IDispEventSimpleImpl :
3 public _IDispEventLocator<nID, pdiid>
4{ ... }
When using the IDispEventSimpleImpl
class, you must provide an _ATL_FUNC_INFO
structure
containing information that describes the expected parameters for
the event handler.
1struct _ATL_FUNC_INFO {
2 CALLCONV cc; // Calling convention
3 VARTYPE vtReturn; // VARIANT type for return value
4 SHORT nParams; // Number of parameters
5 VARTYPE pVarTypes[_ATL_MAX_VARTYPES]; // Array of parameter
6 // VARIANT type
7};
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 appropriatestructure 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:
1//Helper for finding the function index for a DISPID
2virtual HRESULT GetFuncInfoFromId(const IID& iid,
3 DISPID dispidMember,
4 LCID lcid,
5 _ATL_FUNC_INFO& info) {
6 ATLTRACE(_T("TODO: Classes using IDispEventSimpleImpl "
7 "should override this method\n"));
8 ATLASSERT(0);
9 ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetFuncInfoFromId"));
10}
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 IDispEventImpl
once for each unique event interface coming
from each unique event source. Here’s the template class
specification once more:
1template <UINT nID, class T, const IID* pdiid = &IID_NULL,
2 const GUID* plibid = &GUID_NULL,
3 WORD wMajor = 0, WORD wMinor = 0,
4 class tihclass = CComTypeInfoHolder>
5class ATL_NO_VTABLE IDispEventImpl :
6 public IDispEventSimpleImpl<nID, T, pdiid>
7{ ... }
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
:
1template <UINT nID, class T, const IID* pdiid>
2class ATL_NO_VTABLE IDispEventSimpleImpl :
3 public _IDispEventLocator<nID, pdiid>
4{ ... }
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.
1static const int DEFENDANT_SOURCE_ID = 0 ;
2static const int PLAINTIFF_SOURCE_ID = 1 ;
3
4class CEarPolitic;
5
6typedef IDispEventImpl<DEFENDANT_SOURCE_ID,
7 CEarPolitic,
8 &__uuidof(_ISpeakerEvents),
9 &LIBID_ATLINTERNALSLib,
10 LIBRARY_MAJOR, LIBRARY_MINOR> DefendantEventImpl;
11
12typedef IDispEventSimpleImpl<PLAINTIFF_SOURCE_ID,
13 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:
1class ATL_NO_VTABLE CEarPolitic :
2 ...
3 public DefendantEventImpl,
4 public PlaintiffEventImpl {
5
6// Event sink map required in here
7
8};
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 ofIDispEventImpl
and assumes that you call theAtlAdviseSinkMap
function to establish the connection. TheAtlAdviseSinkMap
function assumes that your class is derived fromCWindow
. All in all, theSINK_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 theSINK_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 theIDispEventSimpleImpl
class.
You end the event sink map using the END_SINK_MAP
macro.
The general form of the table is as follows:
1BEGIN_SINK_MAP(CEarPolitic)
2 SINK_ENTRY_EX(SOURCE, DIID, DISPID, EventHandlerFunc)
3 SINK_ENTRY_INFO(SOURCE, DIID, DISPID, EventHandlerFunc, &info)
4...
5END_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 value – that 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
.
1void __stdcall OnHearPlaintiffWhisper(BSTR bstrText);
2void __stdcall OnHearPlaintiffTalk(BSTR bstrText);
3void __stdcall OnHearPlaintiffYell(BSTR bstrText);
4
5static const int DISPID_WHISPER = 1 ;
6static const int DISPID_TALK = 2 ;
7static const int DISPID_YELL = 3 ;
8
9_ATL_FUNC_INFO OnHearWhisperInfo = {
10 CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
11_ATL_FUNC_INFO OnHearTalkInfo = {
12 CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
13_ATL_FUNC_INFO OnHearYellInfo = {
14 CC_STDCALL, VT_EMPTY, 1, { VT_BSTR }};
Here’s the event sink map for the CEarPolitic
object:
1BEGIN_SINK_MAP(CEarPolitic)
2 SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
3 DISPID_WHISPER, OnHearDefendantWhisper)
4 SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
5 DISPID_TALK, OnHearDefendantTalk)
6 SINK_ENTRY_EX(DEFENDANT_SOURCE_ID, __uuidof(_ISpeakerEvents),
7 DISPID_YELL, OnHearDefendantYell)
8 SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
9 DISPID_WHISPER, OnHearPlaintiffWhisper, &OnHearWhisperInfo)
10 SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
11 DISPID_TALK, OnHearPlaintiffTalk, &OnHearTalkInfo)
12 SINK_ENTRY_INFO(PLAINTIFF_SOURCE_ID, __uuidof(_ISpeakerEvents),
13 DISPID_YELL, OnHearPlaintiffYell, &OnHearYellInfo)
14END_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 DISPID
s 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 DISPID
s 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:
1void __stdcall OnHearDefendantWhisper(BSTR bstrText);
2void __stdcall OnHearDefendantTalk(BSTR bstrText);
3void __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:
1void __stdcall CEarPolitic::OnHearDefendantTalk(BSTR bstrText) {
2 CComBSTR title ;
3 CreateText(title, OLESTR("defendant"),
4 OLESTR("talking"), m_defendant);
5
6 MessageBox(NULL, COLE2CT(bstrText), COLE2CT(title), MB_OK);
7}
8
9void CEarPolitic::CreateText(CComBSTR& bstrText,
10 LPCOLESTR strRole,
11 LPCOLESTR strAction,
12 LPUNKNOWN punk) {
13 bstrText = m_bstrName;
14 bstrText += OLESTR(" hears the ");
15 bstrText += strRole;
16 bstrText += OLESTR(" (");
17
18 CComQIPtr<INamedObject> spno = punk;
19 CComBSTR bstrName;
20 HRESULT hr = spno->get_Name (&bstrName) ;
21
22 bstrText.AppendBSTR(bstrName);
23 bstrText += OLESTR(") ");
24
25 bstrText += strAction;
26}
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.
1template <class T>
2inline 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.
1template <UINT nID, class T, const IID* pdiid>
2class ATL_NO_VTABLE IDispEventSimpleImpl : ... {
3...
4 HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid);
5 HRESULT DispEventUnadvise(IUnknown* pUnk, const IID* piid);
6...
7}
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.
1interface IListener : IDispatch {
2 typedef enum SpeakerRole { Defendant, Plaintiff } SpeakerRole;
3
4 [id(1)] HRESULT ListenTo(SpeakerRole role, IUnknown* pSpeaker);
5 [id(2)] HRESULT StopListening(SpeakerRole role);
6};
The implementation of these methods is
straightforward. ListenTo
calls the DispEventAdvise
method on the appropriate event sink to
establish the connection:
1STDMETHODIMP CEarPolitic::ListenTo(SpeakerRole role,
2 IUnknown *pSpeaker) {
3 HRESULT hr = StopListening(role) ; // Validates role
4 if (FAILED(hr)) return hr ;
5
6 switch (role) {
7 case Defendant:
8 hr = DefendantEventImpl::DispEventAdvise (pSpeaker,
9 &DIID__ISpeakerEvents);
10 if (SUCCEEDED(hr))
11 m_defendant = pSpeaker;
12 else
13 Error(OLESTR("The defendant does not support listening"),
14 __uuidof(IListener), hr);
15 break;
16
17 case Plaintiff:
18 hr = PlaintiffEventImpl::DispEventAdvise (pSpeaker,
19 &DIID__ISpeakerEvents);
20 if (SUCCEEDED(hr))
21 m_plaintiff = pSpeaker;
22 else
23 Error(OLESTR("The Plaintiff does not support listening"),
24 __uuidof(IListener), hr);
25 break;
26 }
27 return hr;
28}
The StopListening
method calls DispEventUnadvise
to
break the connection:
1STDMETHODIMP CEarPolitic::StopListening(SpeakerRole role) {
2 HRESULT hr = S_OK;
3 switch (role) {
4 case Defendant:
5 if (m_defendant)
6 hr = DefendantEventImpl::DispEventUnadvise (m_defendant,
7 &DIID__ISpeakerEvents);
8
9 if (FAILED(hr))
10 Error (OLESTR("Unexpected error trying to stop listening "
11 "to the defendant"), __uuidof(IListener), hr);
12
13 m_defendant = NULL;
14 break;
15
16 case Plaintiff:
17 if (m_plaintiff)
18 hr = PlaintiffEventImpl::DispEventUnadvise(m_plaintiff,
19 &DIID__ISpeakerEvents);
20 if (FAILED(hr))
21 Error(OLESTR("Unexpected error trying to stop listening "
22 "to the Plaintiff"), __uuidof(IListener), hr);
23
24 m_plaintiff = NULL;
25 break;
26
27 default:
28 hr = E_INVALIDARG;
29 break;
30 }
31
32 return hr;
33}
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.
How It All Works: The Messy Implementation Details
Classes Used by an Event Source
The IConnectionPointContainerImpl Class
Let’s start by examining the IConnectionPointContainerImpl
template class
implementation of the IConnectionPointContainer
interface.
First, the class needs to provide a vtable
that is compatible with the IConnectionPointContainer
interface. This vtable
must contain five methods: the three IUnknown
methods and
the two IConnectionPointContainer
methods.
1template <class T>
2class ATL_NO_VTABLE IConnectionPointContainerImpl
3 : public IconnectionPointContainer {
4 typedef CComEnum<IEnumConnectionPoints,
5 &__uuidof(IEnumConnectionPoints), IConnectionPoint*,
6 _CopyInterface<IConnectionPoint> >
7 CComEnumConnectionPoints;
8public:
9 STDMETHOD(EnumConnectionPoints)(
10 IEnumConnectionPoints** ppEnum) {
11 if (ppEnum == NULL) return E_POINTER;
12
13 *ppEnum = NULL;
14 CComEnumConnectionPoints* pEnum = NULL;
15 ATLTRY(pEnum = new CComObject<CComEnumConnectionPoints>)
16 if (pEnum == NULL) return E_OUTOFMEMORY;
17
18 int nCPCount;
19 const _ATL_CONNMAP_ENTRY* pEntry = T::GetConnMap(&nCPCount);
20
21 // allocate an initialize a vector of connection point
22 // object pointers
23 USES_ATL_SAFE_ALLOCA;
24 if ((nCPCount < 0) || (nCPCount >
25 (INT_MAX / sizeof(IConnectionPoint*))))
26 return E_OUTOFMEMORY;
27 size_t nBytes=0;
28 HRESULT hr=S_OK;
29 if( FAILED(hr=::ATL::AtlMultiply(&nBytes,
30 sizeof(IConnectionPoint*),
31 static_cast<size_t>(nCPCount)))) {
32 return hr;
33 }
34 IConnectionPoint** ppCP =
35 (IConnectionPoint**)_ATL_SAFE_ALLOCA(
36 nBytes, _ATL_SAFE_ALLOCA_DEF_THRESHOLD);
37 if (ppCP == NULL) {
38 delete pEnum;
39 return E_OUTOFMEMORY;
40 }
41
42 int i = 0;
43 while (pEntry->dwOffset != (DWORD_PTR)-1) {
44 if (pEntry->dwOffset == (DWORD_PTR)-2) {
45 pEntry++;
46 const _ATL_CONNMAP_ENTRY* (*pFunc)(int*) =
47 (const _ATL_CONNMAP_ENTRY* (*)(int*))(
48 pEntry->dwOffset);
49 pEntry = pFunc(NULL);
50 continue;
51 }
52 ppCP[i++] = (IConnectionPoint*)(
53 (INT_PTR)this+pEntry->dwOffset);
54 pEntry++;
55 }
56
57 // copy the pointers: they will AddRef this object
58 HRESULT hRes = pEnum->Init((IConnectionPoint**)&ppCP[0],
59 (IConnectionPoint**)&ppCP[nCPCount],
60 reinterpret_cast<IConnectionPointContainer*>(this),
61 AtlFlagCopy);
62 if (FAILED(hRes)) {
63 delete pEnum;
64 return hRes;
65 }
66 hRes = pEnum->QueryInterface(
67 __uuidof(IEnumConnectionPoints),
68 (void**)ppEnum);
69 if (FAILED(hRes)) delete pEnum;
70 return hRes;
71 }
72
73 STDMETHOD(FindConnectionPoint)(REFIID riid,
74 IConnectionPoint** ppCP) {
75 if (ppCP == NULL) return E_POINTER;
76 *ppCP = NULL;
77 HRESULT hRes = CONNECT_E_NOCONNECTION;
78 const _ATL_CONNMAP_ENTRY* pEntry = T::GetConnMap(NULL);
79 IID iid;
80 while (pEntry->dwOffset != (DWORD_PTR)-1) {
81 if (pEntry->dwOffset == (DWORD_PTR)-2) {
82 pEntry++;
83 const _ATL_CONNMAP_ENTRY* (*pFunc)(int*) =
84 (const _ATL_CONNMAP_ENTRY* (*)(int*))(
85 pEntry->dwOffset);
86 pEntry = pFunc(NULL);
87 continue;
88 }
89 IConnectionPoint* pCP =
90 (IConnectionPoint*)((INT_PTR)this+pEntry->dwOffset);
91 if (SUCCEEDED(pCP->GetConnectionInterface(&iid)) &&
92 InlineIsEqualGUID(riid, iid)) {
93 *ppCP = pCP;
94 pCP->AddRef();
95 hRes = S_OK;
96 break;
97 }
98 pEntry++;
99 }
100 return hRes;
101 }
102};
The IUnknown
methods are easy: The class doesn’t implement them. You bring in
the proper implementation of these three methods when you define a CComObject
class parameterized on your connectable object
class – for example, CCom-Object<CConnectableObject>
.
The CComEnumConnectionPoints
typedef
declares a class for a standard COM enumerator that implements the IEnumConnectionPoints
interface. You use this class of
enumerator to enumerate IConnectionPoint
interface
pointers. A template expansion of the ATL CComEnum
class
provides the implementation. The implementation of the EnumConnectionPoints
method creates and returns an
instance of this enumerator.
EnumConnectionPoints
begins with some
basic error checking and then creates a new instance of a CComEnumConnectionPoints
enumerator on the heap. The ATL
enumerator implementation requires an enumerator to be initialized
after instantiation. ATL enumerators are rather inflexible: To
initialize an enumerator, you must pass it an array of the items
the enumerator enumerates. In this particular case, the enumerator
provides IConnectionPoint
pointers, so the initialization
array must be an array of IConnectionPoint
pointers.
A connectable object’s connection map contains
the information needed to produce the array of IConnectionPoint
pointers. Each connection map entry
contains the offset in the connectable object from the base of the IConnectionPointContainerImpl
instance (that is, the
current this
pointer value) to the base of an IConnectionPointImpl
instance.
EnumConnectionPoints
allocates space
for the initialization array on the stack, using _ATL_SAFE_ALLOCA
. It iterates through each entry of the
connection map, calculates the IConnectionPoint
interface
pointer to each IConnectionPointImpl
object, and stores
the pointer in the array. Note that these pointers are not
reference-counted because the lifetime of the pointers in a
stack-based array is limited to the method lifetime.
The call to the enumerator Init
method
initializes the instance. It’s critical here to use the AtlFlagCopy
argument. This informs the enumerator to make
a proper copy of the items in the initialization array. For
interface pointers, this means to AddRef
the pointers when
making the copy. Refer to Chapter 8, “Collections and Enumerators,” for
details on COM enumerator initialization.
The pEnum
pointer is a CComEnumConnectionPoints
pointer, although it would be a
bit better if it were declared as a CComObject<CComEnumConnectionPoints>
pointerbecause that’s what it actually is. Regardless, EnumConnectionPoints
must return an IEnumConnectionPoints
pointer, not pEnum
itself,
so it queries the enumerator (via pEnum
) for the
appropriate interface pointer and returns the pointer.
The FindConnectionPoint
method is quite
straightforward. After the usual initial error checking, FindConnectionPoint
uses the connection map to calculate
an IConnectionPoint
interface pointer to each connection
point in the connectable object. Using the interface pointer, it
asks each connection point for the IID of its supported interface
and compares it with the IID it’s trying to find. A match causes it
to return the appropriate AddRef
’ed interface pointer with
status S_OK
; otherwise, failure returns the CONNECT_E_NOCONNECTION
status code.
One minor oddity is the check to see if the
offset is -2
when walking through the connection point
map. This is a value that appears only when using attributed ATL
(discussed in AppendixD, “Attributed ATL”). Attributed ATL inserts a second
connection point map into your class. The check for an offset of -2
is used to signal that it’s time to jump from the
standard connection point map into the attributed map. This is done
by calling the function pointer in that slot to get the new
starting address of the next map to walk. This has the potential to
be a general-purpose chaining mechanism for connection point maps;
however, the connection-point macros don’t support it, so it would
take a good deal of hacking.
Most of the real work is left to the connection point implementation, so let’s look at it next.
The IConnectionPointImpl Class
The IConnectionPointImpl
template class
implements the IConnectionPoint
interface. To do that, the
class needs to provide a vtable
that is compatible with
the IConnectionPoint
interface. This vtable
must
contain eight methods: the three IUnknown
methods and the
five IConnectionPoint
methods.
The first item of note is that the IConnectionPointImpl
class derives from the _ICPLocator
class:
1template <class T, const IID* piid,
2 class CDV = CComDynamicUnkArray >
3class ATL_NO_VTABLE IConnectionPointImpl
4 : public _ICPLocator<piid>
5{ ... }
The _ICPLocator Class
1template <const IID* piid>
2class ATL_NO_VTABLE _ICPLocator {
3public:
4 //this method needs a different name than QueryInterface
5 STDMETHOD(_LocCPQueryInterface)(REFIID riid,
6 void ** ppvObject) = 0;
7 virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
8 virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
9};
The _ICPLocator
class contains the declaration of the virtual
method, _LocCPQueryInterface
. A virtual method occupies a
slot in the vtable
, so this declaration states that calls
through the first entry in the vtable
, the entry callers
use to invoke QueryInterface
, will be sent to the method _LocCPQueryInterface
. The declaration is purely virtual,
so a derived class needs to provide the implementation. This is
important because each connection point needs to provide a unique
implementation of _LocCPQueryInterface
.
A connection point must maintain a COM object
identity that is separate from its connection point container.
Therefore, a connection point needs its own implementation of QueryInterface
. If you named the first virtual method in
the _ICPLocator
class QueryInterface
, C++
multiple inheritance rules would see the name as just another
reference to a single implementation of QueryInterface
for
the connectable object. Normally, that’s exactly what you want. For
example, imagine that you have a class derived from three
interfaces. All three interfaces mention the virtual method QueryInterface
, but you want a single implementation of
the method that all base classes share. Similarly, you want a
shared implementation of AddRef
and Release
as
well. But you don’t want this for a connection point in a base
class.
The idea here is that we want to expose two
different COM identities (the connectable object and the connection
point), which requires two separate implementations of QueryInterface
. But we merge the remaining IUnknown
implementation (AddRef
and Release
) because we don’t want to keep a separate
reference count for each connection point. ATL uses this “unique”
approach to avoid having to delegate AddRef
and Release
calls from the connection point object to the
connectable object.
The IConnectionPointImpl Class’s Methods
The IUnknown
methods are more
complicated in IConnectionPointImpl
than was the case in IConnectionPointContainerImpl
so that a connection point
can implement its own unique QueryInterface
method. For a
connection point, this is the _LocCPQueryInterface
virtual
method.
1template <class T, const IID* piid,
2 class CDV = CComDynamicUnkArray >
3class ATL_NO_VTABLE IConnectionPointImpl
4 : public _ICPLocator<piid> {
5 typedef CComEnum<IEnumConnections,
6 &__uuidof(IEnumConnections), CONNECTDATA,
7 _Copy<CONNECTDATA> > CComEnumConnections;
8 typedef CDV _CDV;
9public:
10 ~IConnectionPointImpl();
11 STDMETHOD(_LocCPQueryInterface)(REFIID riid, void ** ppvObject) {
12#ifndef _ATL_OLEDB_CONFORMANCE_TESTS
13 ATLASSERT(ppvObject != NULL);
14#endif
15 if (ppvObject == NULL)
16 return E_POINTER;
17 *ppvObject = NULL;
18
19 if (InlineIsEqualGUID(riid, __uuidof(IConnectionPoint)) ||
20 InlineIsEqualUnknown(riid)) {
21 *ppvObject = this;
22 AddRef();
23#ifdef _ATL_DEBUG_INTERFACES
24 _AtlDebugInterfacesModule.AddThunk((IUnknown**)ppvObject,
25 _T("IConnectionPointImpl"), riid);
26#endif // _ATL_DEBUG_INTERFACES
27 return S_OK;
28 }
29 else
30 return E_NOINTERFACE;
31 }
32
33 STDMETHOD(GetConnectionInterface)(IID* piid2) {
34 if (piid2 == NULL)
35 return E_POINTER;
36 *piid2 = *piid;
37 return S_OK;
38 }
39 STDMETHOD(GetConnectionPointContainer)(
40 IConnectionPointContainer** ppCPC) {
41 T* pT = static_cast<T*>(this);
42 // No need to check ppCPC for NULL since
43 // QI will do that for us
44 return pT->QueryInterface(
45 __uuidof(IConnectionPointContainer), (void**)ppCPC);
46 }
47
48 STDMETHOD(Advise)(IUnknown* pUnkSink, DWORD* pdwCookie);
49 STDMETHOD(Unadvise)(DWORD dwCookie);
50 STDMETHOD(EnumConnections)(IEnumConnections** ppEnum);
51 CDV m_vec;
52};
The _LocCPQueryInterface Method
The _LocCPQueryInterface
method has the same function
signature as the COM QueryInterface
method, but it
responds to only requests for IID_IUnknown
and IID_IConnectionPoint
by producing an AddRef
’ed
pointer to itself. This gives each base class instance of an IConnectionPointImpl
object a unique COM identity.
The AddRef and Release Methods
As usual, you bring in the proper implementation
of these two methods when you define a CComObject
class
parameterized on your connectable object class – for example, CComObject<CConnectableObject>
.
The GetConnectionInterface and GetConnectionPointContainer Methods
The GetConnectionInterface
interface
method simply returns the source interface IID for the connection
point, so the implementation is trivial. The GetConnectionPointContainer
interface method is also
simple, but some involved typecasting is required to request the
correct interface pointer.
The issue is that the current class, this
particular IConnectionPointImpl
expansion, doesn’t support
the IConnectionPointContainer
interface. But the design of
the template classes requires your connectable object class,
represented by class T
in the template, to implement the IConnectionPointContainer
interface:
1T* pT = static_cast<T*>(this);
2return pT->QueryInterface(IID_IConnectionPointContainer,
3 (void**)ppCPC);
The typecast goes from the connection point
subobject down the class hierarchy to the (deriving) connectable
object class and calls that class’s QueryInterface
implementation to obtain the required IConnectionPointContainer
interface pointer.
The Advise, Unadvise, and EnumConnections Methods
The Advise
, Unadvise
, and EnumConnections
methods all need a list of active
connections. Advise
adds new entries to the list, Unadvise
removes entries from the list, and EnumConnections
returns an object that enumerates over the
list.
This list is of template parameter type CDV
. By default, this is type CComDynamic-UnkArray
, which provides a dynamically
growable array implementation of the list. As I described
previously, ATL provides a fixed-size list implementation and a
specialized single-entry list implementation. However, it is
relatively easy to provide a custom list implementation because the Advise
, Unadvise
, and EnumConnections
implementations always access the list through its well-defined
methods: Add
, Remove
, begin
,
end
, GetCookie
, and GetUnknown
.
The CComEnumConnections
typedef
declares a class for a standard COM enumerator that implements the IEnumConnections
interface. You use this class of
enumerator to enumerate CONNECTDATA
structures, which
contain a client sink interface pointer and its associated magic
cookie-registration token. A template expansion of the ATL CComEnum
class provides the implementation. The
implementation of the EnumConnections
interface method
creates and returns an instance of this enumerator.
The Advise Method
1template <class T, const IID* piid, class CDV>
2STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise(
3 IUnknown* pUnkSink,
4 DWORD* pdwCookie) {
5 T* pT = static_cast<T*>(this);
6 IUnknown* p;
7 HRESULT hRes = S_OK;
8 if (pdwCookie != NULL)
9 *pdwCookie = 0;
10 if (pUnkSink == NULL || pdwCookie == NULL)
11 return E_POINTER;
12 IID iid;
13 GetConnectionInterface(&iid);
14 hRes = pUnkSink->QueryInterface(iid, (void**)&p);
15 if (SUCCEEDED(hRes)) {
16 pT->Lock();
17 *pdwCookie = m_vec.Add(p);
18 hRes = (*pdwCookie != NULL) ? S_OK :
19 CONNECT_E_ADVISELIMIT;
20 pT->Unlock();
21 if (hRes != S_OK)
22 p->Release();
23 }
24 else if (hRes == E_NOINTERFACE)
25 hRes = CONNECT_E_CANNOTCONNECT;
26 if (FAILED(hRes))
27 *pdwCookie = 0;
28 return hRes;
29}
The Advise
method retrieves the sink
interface IID for this connection point and queries the IUnknown
pointer that the client provides for the sink
interface. This ensures that the client passes an interface pointer
to an object that actually implements the expected sink interface.
Failure to provide the correct interface pointer produces a CONNECT_E_CANNOTCONNECT
error. You don’t want to keep a
connection to something that can’t receive the callback. Plus, by
obtaining the correct interface pointer here, the connection point
doesn’t have to query for it during each callback.
When the query succeeds, the connection point needs to add the connection to the list. So, it acquires a lock on the entire connectable object, adds the connection to the list if there’s room, and releases the lock.
The Unadvise Method
1template <class T, const IID* piid, class CDV>
2STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Unadvise(
3 DWORD dwCookie) {
4 T* pT = static_cast<T*>(this);
5 pT->Lock();
6 IUnknown* p = m_vec.GetUnknown(dwCookie);
7 HRESULT hRes = m_vec.Remove(dwCookie) ? S_OK :
8 CONNECT_E_NOCONNECTION;
9 pT->Unlock();
10 if (hRes == S_OK && p != NULL)
11 p->Release();
12 return hRes;
13}
The Unadvise
method is relatively
simple. It locks the connectable object, asks the list class to
translate the provided magic cookie value into the corresponding IUnknown
pointer, removes the connection identified by the
cookie, unlocks the connectable object, and releases the held sink
interface pointer.
The EnumConnections Method
EnumConnection
begins with some basic
error checking and then creates a new instance of a CComObject<CComEnumConnections>
enumerator on the
heap. As before, the ATL enumerator implementation requires that an
enumerator be initialized from an array after instantiationin this
particular case, a contiguous array of CONNECTDATA
structures.
1template <class T, const IID* piid, class CDV>
2STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::EnumConnections(
3 IEnumConnections** ppEnum) {
4 if (ppEnum == NULL)
5 return E_POINTER;
6 *ppEnum = NULL;
7 CComObject<CComEnumConnections>* pEnum = NULL;
8 ATLTRY(pEnum = new CComObject<CComEnumConnections>)
9 if (pEnum == NULL)
10 return E_OUTOFMEMORY;
11 T* pT = static_cast<T*>(this);
12 pT->Lock();
13 CONNECTDATA* pcd = NULL;
14 ATLTRY(pcd = new CONNECTDATA[m_vec.end()-m_vec.begin()])
15 if (pcd == NULL) {
16 delete pEnum;
17 pT->Unlock();
18 return E_OUTOFMEMORY;
19 }
20 CONNECTDATA* pend = pcd;
21 // Copy the valid CONNECTDATA's
22 for (IUnknown** pp = m_vec.begin();pp<m_vec.end();pp++) {
23 if (*pp != NULL)
24 {
25 (*pp)->AddRef();
26 pend->pUnk = *pp;
27 pend->dwCookie = m_vec.GetCookie(pp);
28 pend++;
29 }
30 }
31 // don't copy the data, but transfer ownership to it
32 pEnum->Init(pcd, pend, NULL, AtlFlagTakeOwnership);
33 pT->Unlock();
34 HRESULT hRes = pEnum->_InternalQueryInterface(
35 __uuidof(IEnumConnections), (void**)ppEnum);
36 if (FAILED(hRes))
37 delete pEnum;
38 return hRes;
39}
The connection list stores IUnknown
pointers. The CONNECTDATA
structure contains the interface
pointer and its associated magic cookie. EnumConnections
allocates space for the initialization array from the heap. It
iterates through the connection list entries, copying the interface
pointer and its associated cookie to the dynamically allocated CONNECTDATA
array. It’s important to note that any
non-NULL
interface pointers are AddRef
’ed. This
copy of the interface pointers has a lifetime greater than the EnumConnections
method.
The
call to the enumerator Init
method initializes the
instance. It’s critical here to use the AtlFlagTakeOwnership
argument. This informs the enumerator
to use the provided array directly instead of making yet another
copy of it. This also means that the enumerator is responsible for
correctly releasing the elements in the array as well as the array
itself.
The EnumConnections
method now uses _InternalQueryInterface
to return an IEnumConnections
interface pointer on the enumerator
object, which, at this point, is the only outstanding reference to
the enumerator. [2]
EnumConnections
uses _InternalQueryInterface
instead of IUnknown::QueryInterface
because the latter
results in a virtual function call, whereas the former is a more
efficient direct call.
Classes Used by an Event Sink
First, you need to understand the big picture
about event sinks. Your object class might want to implement
multiple event sink interfaces or the same event sink interface
multiple times. All event sink interfaces need nearly identical
functionality: IUnknown
, IDispatch
,
Invoke
, and the capability to look up the DISPID
in a sink map and delegate the event method call to the appropriate
event handler. But each implementation also needs some custom
functionalityspecifically, each implementation must be a unique COM
identity.
ATL defines a class named _IDispEvent
that implements the common functionality and, through template
parameters and interface coloring, allows each derivation from this
one C++ class to maintain a unique COM identity. This means that
ATL implements all specialized event sink implementations using a
single C++ class, _IDispEvent
.
The _IDispEvent Class
Let’s examine the _IDispEvent
class.
The first interesting aspect is that it is intended to be used as
an abstract base class. The first three virtual methods are
declared using the COM standard calling convention and are all
purely virtual. The first method is _LocDEQueryInterface
,
and the following two are the AddRef
and Release
methods. This gives the _IDispEvent
class the vtable
of a COM object that supports the IUnknown
interface. The _IDispEvent
class cannot simply derive from IUnknown
because it needs to provide a specialized version
of QueryInterface
. A derived class needs to supply the _LocDEQueryInterface
, AddRef
, and Release
methods.
1class ATL_NO_VTABLE _IDispEvent {
2...
3public:
4 //this method needs a different name than QueryInterface
5 STDMETHOD(_LocDEQueryInterface)(REFIID riid,
6 void ** ppvObject) = 0;
7 virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
8 virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
9...
10};
The class maintains five member variables, only
one of which is always used, m_dwEventCookie
. Each member
variable is initialized by the constructor:
1_IDispEvent() :
2 m_libid(GUID_NULL),
3 m_iid(IID_NULL),
4 m_wMajorVerNum(0),
5 m_wMinorVerNum(0),
6 m_dwEventCookie(0xFEFEFEFE)
7{ }
The m_dwEventCookie
variable holds the
connection point registration value returned from the source
object’s IConnectionPoint::Advise
method until it’s needed
to break the connection. The class assumes that no event source
will ever use the value 0xFEFEFEFE
as the connection
cookie because it uses that value as a flag to indicate that no
connection is established. [3]
Note
that this places a constraint on a connection list implementation
(that is, the CDV
template parameter class)namely, that it
never provides the value 0xFEFEFEFE
as a connection
cookie.
The m_libid
, m_iid
,
m_wMajorVerNum
, and m_wMinorVerNum
variables hold
the type library GUID, the source interface IID, and the type
library major and minor version number, respectively.
1GUID m_libid;
2IID m_iid;
3unsigned short m_wMajorVerNum;
4unsigned short m_wMinorVerNum;
5DWORD m_dwEventCookie;
This _IDispEvent
class provides the DispEventAdvise
and DispEventUnadvise
methods, which establish and break,
respectively, a connection between the specified source object’s
(pUnk
) source interface (piid
) and the _DispEvent
sink object.
1HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid) {
2 ATLENSURE(m_dwEventCookie == 0xFEFEFEFE);
3 return AtlAdvise(pUnk, (IUnknown*)this, *piid, &m_dwEventCookie);
4}
5
6HRESULT DispEventUnadvise(IUnknown* pUnk, const IID* piid) {
7 HRESULT hr = AtlUnadvise(pUnk, *piid, m_dwEventCookie);
8 m_dwEventCookie = 0xFEFEFEFE;
9 return hr;
10}
You can implement multiple event sinks in a single ATL COM object. In the most general case, this means that you need a unique event sink for each different source of events (source identifier). Furthermore, you need a unique event sink object for each separate connection (source interface) to a source of events.
The _IDispEventLocator Class
Implementing multiple event sinks requires the
sink to derive indirectly from _IDispEvent
multiple times.
But we need to do so in a way that allows us to find a particular _IDispEvent
base class instance, given a source object
identifier and the source interface on that object.
ATL uses the template class _IDispEventLocator
to do this. Each unique _IDisp-EventLocator
template invocation produces a
different, addressable _IDispEvent
event sink
instance. [4]
Keith
Brown pointed out that it would have been more appropriate to call
these Locator classes COM-Identity classes – for example, _IDispEventCOMIdentity
and IConnectionPointCOMIdentity
. Their fundamental purpose is
to provide a unique base class instance for each required COM
identity. Yes, you need to locate the appropriate base class when
needed, but the sole purpose in renaming the QueryInterface
method is to implement a separate
identity.
1template <UINT nID, const IID* piid>
2class ATL_NO_VTABLE _IDispEventLocator : public _IDispEvent {
3public:
4};
The IDispEventSimpleImpl Class
The IDispEventSimpleImpl
class implements
the IDispatch
interface. It derives from the _IDispEventLocator<nID, pdiid>
class to inherit the IUnknown vtable
, the member variables, and the
connection-point Advise
and Unadvise
support that
the _IDispEvent
base class provides.
1template <UINT nID, class T, const IID* pdiid>
2class ATL_NO_VTABLE IDispEventSimpleImpl :
3 public _IDispEventLocator<nID, pdiid> {
4// Abbreviated for clarity
5 STDMETHOD(_LocDEQueryInterface)(REFIID riid,
6 void ** ppvObject);
7 virtual ULONG STDMETHODCALLTYPE AddRef();
8 virtual ULONG STDMETHODCALLTYPE Release();
9 STDMETHOD(GetTypeInfoCount)(UINT* pctinfo);
10 STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid,
11 ITypeInfo** pptinfo);
12 STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames,
13 UINT cNames, LCID lcid, DISPID* rgdispid);
14 STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid,
15 LCID lcid, WORD wFlags, DISPPARAMS* pdispparams,
16 VARIANT* pvarResult, EXCEPINFO* pexcepinfo,
17 UINT* puArgErr);
18};
Notice that the IDispEventSimpleImpl
class provides an implementation of the _LocDEQueryInterface
, AddRef
, and Release
methods that it inherited from its _IDispEvent
base class. Notice also that the next four
virtual methods are the standard IDispatch
interface
methods. The IDispEventSimpleImpl
class now has the proper vtable
to support IDispatch
. It cannot simply
derive from IDispatch
to obtain the vtable
because the class needs to provide a specialized version of QueryInterface
.
The IDispEventSimpleImpl
class
implements the _LocDEQueryInterface
method so that each
event sink is a separate COM identity from that of the deriving
class. The event sink object is supposed to respond positively to
requests for its source dispatch interface ID, the IUnknown
interface, the IDispatch
interface, and
the GUID contained in the m_iid
member variable.
1 STDMETHOD(_LocDEQueryInterface)(REFIID riid,
2 void ** ppvObject) {
3 ATLASSERT(ppvObject != NULL);
4 if (ppvObject == NULL)
5 return E_POINTER;
6 *ppvObject = NULL;
7
8 if (InlineIsEqualGUID(riid, IID_NULL))
9 return E_NOINTERFACE;
10
11 if (InlineIsEqualGUID(riid, *pdiid) ||
12 InlineIsEqualUnknown(riid) ||
13 InlineIsEqualGUID(riid, __uuidof(IDispatch)) ||
14 InlineIsEqualGUID(riid, m_iid)) {
15
16 *ppvObject = this;
17 AddRef();
18#ifdef _ATL_DEBUG_INTERFACES
19 _AtlDebugInterfacesModule.AddThunk(
20 (IUnknown**)ppvObject, _T("IDispEventImpl"),
21 riid);
22#endif // _ATL_DEBUG_INTERFACES
23 return S_OK;
24 }
25 else
26 return E_NOINTERFACE;
27 }
The IDispEventSimpleImpl
class also
provides a simple implementation of the AddRef
and Release
methods. This permits the class to be used
directly as a COM object.
1template <UINT nID, class T, const IID* pdiid>
2class ATL_NO_VTABLE IDispEventSimpleImpl : ... {
3...
4 virtual ULONG STDMETHODCALLTYPE AddRef() { return 1; }
5 virtual ULONG STDMETHODCALLTYPE Release() { return 1; }
6...
7};
However, when you compose the class into a more
complex ATL-based COM object, the AddRef
and Release
methods in the deriving class are used. In other
words, an AddRef
to the event sink of a typical ATL COM
object calls the deriving object’s CComObject::AddRef
method (or whatever your most-derived class is). Watch out for
reference-counting cycles due to this. A client holds a reference
to the event source, which holds a reference to (nominally) the
event sink but is actually to the client itself.
The IDispEventSimpleImpl
class implements the GetTypeInfoCount
, GetTypeInfo
, and GetIDsOfNames
methods by returning the error E_NOTIMPL
. An event-dispatch interface is required only to
support the IUnknown
methods and the Invoke
method.
1STDMETHOD(GetTypeInfoCount)(UINT*)
2{ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetTypeInfoCount"));}
3
4STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**)
5{ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetTypeInfo"));}
6
7STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*)
8{ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetIDsOfNames"));}
9
10STDMETHOD(Invoke)(DISPID dispidMember, REFIID, LCID lcid, WORD /*wFlags*/,
11 DISPPARAMS* pdispparams, VARIANT* pvarResult,
12 EXCEPINFO* /*pexcepinfo*/, UINT* /*puArgErr*/);
The Invoke
method searches the deriving
class’s event sink map for the appropriate event handler for the
current event. It finds the appropriate sink map by calling _GetSinkMap
, which is a static member function defined in
the deriving class by the BEGIN_SINK_MAP
macro (described
later in this section). The proper event handler is the entry that
has the matching event source ID (nID
) as the template
invocation, the same source interface IID (pdiid
) as the
template invocation, and the same DISPID
as the argument
to Invoke
.
When the matching event sink entry specifies an _ATL_FUNC_INFO
structure (meaning that the event sink
entry was defined using the SINK_ENTRY_INFO
macro), Invoke
uses the structure to call the handler. Otherwise, Invoke
calls the GetFuncInfoFromId
virtual
function to obtain the required structure. When the GetFuncInfoFromId
function fails, Invoke
silently
returns S_OK
. This is as it should be because an event
handler must respond with S_OK
to events the handler
doesn’t recognize.
You must override the GetFuncInfoFromId
method when using the SINK_ENTRY_EX
macro with the IDispEventSimpleImpl
class. The default implementation
silently fails:
1virtual HRESULT GetFuncInfoFromId(const IID&, DISPID, LCID,
2 _ATL_FUNC_INFO&) {
3 ATLTRACE(_T("TODO: Classes using IDispEventSimpleImpl should "
4 "override this method\n"));
5 ATLASSERT(0);
6 ATLTRACENOTIMPL(_T("IDispEventSimpleImpl::GetFuncInfoFromId"));
7}
This means that if you use the IDispEventSimpleImpl
class directly, and you specify an
event hander using the SINK_ENTRY_EX
macro, and you forget
to override the GetFuncInfoFromId
method or you implement
it incorrectly, everything compiles cleanly but your event handler will never be called.
The IDispEventSimpleImpl
class provides
some overloaded helper methods for establishing and breaking a
connection to an event source. The following two methods establish
and break a connection between the current sink and the specified
event interface (piid
) on the specified event source
(pUnk
):
1// Helpers for sinking events on random IUnknown*
2 HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid) {
3 ATLENSURE(m_dwEventCookie == 0xFEFEFEFE);
4 return AtlAdvise(pUnk, (IUnknown*)this, *piid, &m_dwEventCookie);
5 }
6 HRESULT DispEventUnadvise(IUnknown* pUnk, const IID* piid) {
7 HRESULT hr = AtlUnadvise(pUnk, *piid, m_dwEventCookie);
8 m_dwEventCookie = 0xFEFEFEFE;
9 return hr;
10 }
The next two methods establish and break a connection between the current sink and the specified event source using the sink’s dispatch interface:
1HRESULT DispEventAdvise(IUnknown* pUnk) {
2 return _IDispEvent::DispEventAdvise(pUnk, pdiid);
3}
4HRESULT DispEventUnadvise(IUnknown* pUnk) {
5 return _IDispEvent::DispEventUnadvise(pUnk, pdiid);
6}
The Sink Map: Associated Structure, Macros, and the _GetSinkMap Method
The sink map is an array of _ATL_EVENT_ENTRY
structures. The structure contains the
following fields:
|
The event source identifier; control ID for contained controls |
|
The source dispatch interface IID |
|
The offset of the event sink implementation from the deriving class |
|
The event callback dispatch ID |
|
The member function pointer of the event handler to invoke [5] |
|
The |
If
you’ve been wondering why your event-handler functions have to use
the __stdcall
calling convention; the declaration of this
pointer specifies it.
1template <class T>
2struct _ATL_EVENT_ENTRY {
3 UINT nControlID; // ID identifying object instance
4 const IID* piid; // dispinterface IID
5 int nOffset; // offset of dispinterface from this pointer
6 DISPID dispid; // DISPID of method/property
7 void (__stdcall T::*pfn)(); // method to invoke
8 _ATL_FUNC_INFO* pInfo; // pointer to info structure
9};
When you use the BEGIN_SINK_MAP
macro, you
define a static member function in your class called _GetSinkMap
. It returns the address of the array of _ATL_EVENT_ENTRY
structures.
1#define BEGIN_SINK_MAP(_class)\
2 typedef _class _GetSinkMapFinder;\
3 static const ATL::_ATL_EVENT_ENTRY<_class>* _GetSinkMap() {\
4 PTM_WARNING_DISABLE \
5 typedef _class _atl_event_classtype;\
6 static const ATL::_ATL_EVENT_ENTRY<_class> map[] = {
Each SINK_ENTRY_INFO
macro adds one _ATL_EVENT_ENTRY
structure to the array.
1#define SINK_ENTRY_INFO(id, iid, dispid, fn, info) {id, &iid,
2 (int)(INT_PTR)(static_cast<ATL::_IDispEventLocator<
3 id, &iid>*>((_atl_event_classtype*)8))-8,
4 dispid, (void (__stdcall _atl_event_classtype::*)())fn, info},
Two aspects of the macro are a little unusual.
The following expression computes the offset of the _IDispEventLocator<id, &iid>
base class with
respect to your deriving class (the class containing the sink map).
This enables us to find the appropriate event sink referenced by
the sink map entry.
1(int)(INT_PTR)(static_cast<_IDispEventLocator<
2 id, &iid>*>((_atl_event_classtype*)8))-8
The following cast saves the event-handler function address as a pointer to a member function:
1(void (__stdcall _atl_event_classtype::*)()) fn
The SINK_ENTRY_EX
macro is the same as
the SINK_ENTRY_INFO
macro with a NULL
pointer for
the function information structure. The SINK_ENTRY
macro
is thesame as the SINK_ENTRY_INFO
macro with IID_NULL
for the dispatch interface and a NULL
pointer for the function information structure.
1#define SINK_ENTRY_EX(id, iid, dispid, fn) \
2 SINK_ENTRY_INFO(id, iid, dispid, fn, NULL)
3#define SINK_ENTRY(id, dispid, fn) \
4 SINK_ENTRY_EX(id, IID_NULL, dispid, fn)
The END_SINK_MAP
macro ends the array
and completes the _GetSinkMap
function implementation:
1#define END_SINK_MAP() {0, NULL, 0, 0, NULL, NULL} }; \
2 return map;\
The IDispEventImpl Class
Finally, we come to the IDispEventImpl
class. This is the class that the code-generation wizards use. It
derives from the IDispEventSimpleImpl
class and,
therefore, inherits all the functionality previously discussed. The
additional template parameters specify a type library that
describes the source dispatch interface for the event sink.
1template <UINT nID, class T, const IID* pdiid = &IID_NULL,
2 const GUID* plibid = &GUID_NULL,
3 WORD wMajor = 0, WORD wMinor = 0,
4 class tihclass = CComTypeInfoHolder>
5class ATL_NO_VTABLE IDispEventImpl :
6 public IDispEventSimpleImpl<nID, T, pdiid>
7{ ... }
The main feature of the IDispEventImpl
class is that it uses the type information to implement
functionality that is missing in the base class. The class
implements the GetTypeInfoCount
, GetTypeInfo
, and GetIDsOfNames
methods using the type library via a CComTypeInfoHolder
object:
1STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) {
2 *pctinfo = 1; return S_OK; }
3STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
4{ return _tih.GetTypeInfo(itinfo, lcid, pptinfo); }
5STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames,
6 UINT cNames, LCID lcid, DISPID* rgdispid) {
7 return _tih.GetIDsOfNames(riid, rgszNames, cNames,
8 lcid, rgdispid);
9}
It also overrides the GetFuncInfoFromId
method and initializes an _ATL_FUNC_INFO
structure using the information provided in
the type library:
1HRESULT GetFuncInfoFromId(const IID& iid,
2 DISPID dispidMember, LCID lcid,
3 ATL_FUNC_INFO& info) {
4 CComPtr<ITypeInfo> spTypeInfo;
5
6 if (InlineIsEqualGUID(*_tih.m_plibid, GUID_NULL))
7 {
8 m_InnerLibid = m_libid;
9 m_InnerIid = m_iid;
10 _tih.m_plibid = &m_InnerLibid;
11 _tih.m_pguid = &m_InnerIid;
12 _tih.m_wMajor = m_wMajorVerNum;
13 _tih.m_wMinor = m_wMinorVerNum;
14 }
15 HRESULT hr = _tih.GetTI(lcid, &spTypeInfo);
16 if (FAILED(hr))
17 return hr;
18 return AtlGetFuncInfoFromId(spTypeInfo, iid,
19 dispidMember, lcid, info);
20}
Summary
The connection point protocol defines a mechanism for a client interested in receiving event callbacks to pass its event sink interface pointer to an event source. Neither the client nor the event source needs to be written with knowledge of each other. Objects hosted on a web page or, more generally, objects used by scripting languages must use the connection points protocol to fire events to the scripting engine. Also, ActiveX controls fire their events using the connection points protocol. Although this protocol is acceptable for intra-apartment use, it is inefficient (when considering round-trips) for use across an apartment boundary.
ATL provides the IDispEvent
and IDispEventSimple
classes for a client object to use to
receive event callbacks. Additionally, ATL provides the Implement
Connection Point Wizard so you can easily generate a class that
manages a connection point and contains helper methods to fire the
events to all connected clients.