Plugging a pair of headphones into a jack is a nice analog to passing an interface pointer on a sink object to a connection point. The connection point will call AddRef on that pointer in order to maintain the connection as long as it needs it. Unplugging the headphones means asking the connection point to release its hold on that pointer.1 These two operations are the purpose of two of the IConnectionPoint member functions, as described in Table 4-2.
interface IConnectionPoint : IUnknown
{
HRESULT GetConnectionInterface(IID *);
HRESULT GetConnectionPointContainer(IConnectionPointContainer **);
HRESULT Advise(IUnknown *, DWORD *);
HRESULT Unadvise(DWORD);
HRESULT EnumConnections(IEnumConnections **);
}
Member Function | Description |
Advise | Establishes a connection with an outgoing interface and returns a key (or cookie) to identify the connection. A connection point can support any number of connections, whatever is appropriate for the design of the connectable object. |
Unadvise | Terminates a connection given a key or cookie from Advise. |
EnumConnections | Creates and returns an enumerator with the IEnumConnections interface, through which the caller can iterate over every connection established through Advise. |
GetConnectionPointContainer | Returns the IConnectionPointContainer pointer for the connectable object that manages this connection point. This is highly useful for clients that have an IConnectionPoint pointer and want to navigate up to the full object. |
GetConnectionInterface | Returns the IID of the single outgoing interface that this connection point supports. This is needed when a client calls IConnectionPointContainer::EnumConnectionPoints and needs to know the IID of each connection point therein. |
Table 4-2.
Member functions for IConnectionPoint.
GetConnectionPointContainer and GetConnectionInterface exist because of the relationship between the connectable object and its contained connection points. Given one or the other, you can find all the information you need about which outgoing interfaces an object supports.
With both the IConnectionPoint and IConnectionPointContainer interfaces spelled out, we can see the exact sequence of steps taken by a client in order to establish a connection. For the sake of discussion, let's assume that there's an event interface named IDuckEvents with the members Quack, Flap, and Paddle and that a client would like to connect a sink with this interface to some connectable object for which it has an IUnknown pointer. Here are the steps the client would follow:
Call IUnknown::QueryInterface(IID_IConnectionPointContainer, &pCPC). If this fails, no connections are possible.
Call pCPC->FindConnectionPoint(IID_IDuckEvents, &pConnPt). If this fails, the object doesn't support this outgoing interface. Regardless of the success or failure of this call, the client should always call pCPC->Release: if the call succeeds, the connectable object will still be alive because one of its contained connection points is alive.
Call pConnPt->Advise(pIDuckEvents, &dwCookie) to establish the connection to the instance of the sink identified by the pIDuckEvents pointer. The connection key is returned in dwCookie. Now, whenever appropriate events occur in the source, or the source has reason to make a request to the client, it will call some member function in pIDuckEvents.
When the client wants to terminate the connection, it calls pConnPt-> Unadvise(dwCookie), passing the same connection key that was returned from Advise. This is followed by pConnPt->Release.
The last three steps of this process are illustrated in Figure 4-4. The complete process is repeated for every connection that a client wants to make with whatever sources it's dealing with. For example, an OLE Control container will connect to the custom event sets for each control placed in a document or form.
Figure 4-4.
The process of connecting a sink through a connection point.
You might be thinking that this connection mechanism seems unreasonably complicated for a reasonably simple operation. Wouldn't it be simpler to get rid of the connection point business altogether? Couldn't we simply put Advise, Unadvise, and EnumConnections into IConnectionPointContainer and add an IID argument to Advise? That would let us eliminate all the other junk, making everything quite a bit simpler. The primary reason for this mechanism is extensibility: the design as it stands allows the nature of the connectable object to change, perhaps with new interfaces, independently of the connection points, and allows the connection points to change without bothering the connectable object. This is intended to keep the design free of constraints in the future when this technology serves larger purposes. Such extensibility is a key part of working with OLE because evolution over time is so fundamental. A little added complication here, which isn't tremendous, saves a lot of complication years down the road. The Connectable Object technology is meant to be generic and flexible.
1 Can you imagine rather temperamental headphone jacks that you must politely ask to let go of your headphones? Fortunately objects aren't moody and they comply with any Release call. |