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