previous page
next page

A Review of Connection Points

An object implements one or more interfaces to expose its functionality. The term connection points refers to a logically inverse mechanism that allows an object to expose its capability to call one or more specific interfaces.

Another perspective is that QueryInterface allows a client to retrieve from an object a pointer to an interface that the object implements. Connection points allow a client to give an object a pointer to an interface that the client implements. In the first case, the client uses the retrieved interface pointer to call methods provided by the object. In the second case, the object uses the provided interface pointer to call methods provided by the client.

A slightly closer inspection of the two mechanisms reveals that QueryInterface allows a client to retrieve from an object only interfaces that the client knows how to call. Connection points allow a client to provide to an object only interfaces that the object knows how to call.

A connection has two parts: the object making calls to the methods of a specific interface, called the source or, alternatively, the connection point; and the object implementing the interface (receiving the calls), called the sink object (see Figure 9.1) Using my terminology from earlier paragraphs, the object is the source and makes calls to the sink interface methods. The client is the sink and implements the sink interface. One additional complexity is that a particular source object can have connections to multiple sink objects.

Figure 9.1. A connection to two sinks


The IConnectionPoint Interface

A client uses the source object's implementation of the IConnectionPoint interface to establish a connection. Here is the definition of the IConnectionPoint interface:

interface IConnectionPoint : IUnknown {                       
    HRESULT GetConnectionInterface ([out] IID* pIID);         
    HRESULT GetConnectionPointContainer (                     
        [out] IConnectionPointContainer** ppCPC);             
    HRESULT Advise ([in] IUnknown* pUnkSink,                  
        [out] DWORD* pdwCookie);                              
    HRESULT Unadvise ([in] DWORD dwCookie);                   
    HRESULT EnumConnections ([out] IEnumConnections** ppEnum);
}                                                             

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 Figure 9.2), act as a container of these source subobjects. A client uses the source object's GetConnectionPointContainer method to retrieve a pointer to the connectable object. A connectable object implements the IConnectionPointContainer interface.

Figure 9.2. A connectable object


Implementing IConnectionPointContainer indicates that a COM object supports connection points and, more specifically, that it can provide a connection point (a source subobject) for each sink interface the connectable object knows how to call. Clients then use the connection point as described previously to establish the connection.

The IConnectionPointContainer Interface

Here is the definition of the IConnectionPointContainer interface:

interface IConnectionPointContainer : IUnknown {
  HRESULT EnumConnectionPoints (                
    [out] IEnumConnectionPoints ** ppEnum);     
  HRESULT FindConnectionPoint ([in] REFIID riid,
    [out] IConnectionPoint** ppCP);             
}                                               

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):

CComPtr<IUnknown>
  spSource = /* Set to the source of the events */ ;
CComPtr<_ISpeakerEvents>
  spSink = /* Sink to receive the events */ ;
DWORD dwCookie ;

CComPtr<IConnectionPointContainer> spcpc;
HRESULT hr = spSource.QueryInterface (&spcpc);

CComPtr<IConnectionPoint> spcp ;
hr = spcpc->FindConnectionPoint(__uuidof(_ISpeakerEvents),
  &spcp);
hr = spcp->Advise (spSink, &dwCookie) ;// Establish connection
// Time goes by, callbacks occur...
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.

ATLAPI AtlAdvise(IUnknown* pUnkCP, IUnknown* pUnk,             
                 const IID& iid, LPDWORD pdw);                 
ATLAPI AtlUnadvise(IUnknown* pUnkCP, const IID& iid, DWORD dw);

You use these functions like this:

DWORD dwCookie;
// Make a connection
hr = AtlAdvise(spSource, spSink, __uuidof(_ISpeakerEvents),
  &dwCookie);
// ... Receive callbacks ...
// Break the connection
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 as method 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.

[1] For ActiveX controls and other in-process objects, connection points are acceptable, but when round-trips are a concern, they are horrid. Read Effective COM (Addison-Wesley, 1998), by Don Box, Keith Brown, Tim Ewald, and Chris Sells, for an in-depth discussion of these issues.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)