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

_images/09atl01.jpg

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

_images/09atl02.jpg

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:

  1. Implement the IConnectionPointContainer interface.

  2. Implement QueryInterface so that it responds to requests for IID_IConnectionPointContainer.

  3. Implement the IConnectionPoint interface for each source interface that the connectable object supports.

  4. Provide a connection map that is a table that associates an IID with a connection point implementation.

  5. 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.

  6. Typically, you implement helper methods that call the sink methods for all connected sinks.

  7. 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

[View full size image]

_images/09atl03.jpg

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                    &params, 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 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:

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]

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]

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]

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:

nControlID

The event source identifier; control ID for contained controls

piid

The source dispatch interface IID

nOffset

The offset of the event sink implementation from the deriving class

dispid

The event callback dispatch ID

pfn

The member function pointer of the event handler to invoke [5]

pInfo

The _ATL_FUNC_INFO structure used for the event-handler call

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.