The game of connectable objects has three players. First, we have the client of the connectable object, which communicates with that object as usual through the object's incoming interfaces. Second, we have the object. In order to have the object communicate with the client in the other direction, the object must somehow obtain a pointer to an outgoing interface implemented somewhere in the client. Through this pointer, the object sends events, notifications, or requests to the client.
The "somewhere" in this picture is the third player—the sink—which is an object itself but a very simple object that doesn't need anything fancy like a CLSID or type information. The client "connects" this sink to the object by passing the sink's interface pointer to the object through some other interface method. The object keeps a copy of this pointer (after calling AddRef) and calls the sink's member functions when necessary. The basic connection process is illustrated in Figure 4-1. Keep in mind that although the sink is conceptually a separate object (which is how its QueryInterface behaves in relation to the rest of the container), it is strongly tied to the rest of the client's code because that client will want to perform some action in response to the event or request. This is useful to know because sometimes you can make some other object act like a sink without making two separate objects. The separation is merely a question of the QueryInterface implementations.
An object doesn't necessarily have a one-to-one relationship with a sink. In fact, a single instance of an object usually supports any number of connections to sinks in any number of separate clients. This is called multicasting.
Figure 4-1.
The process of connecting a client sink object to a connectable source object. This allows the connectable object to make outgoing function calls to the sink.
In addition, any sink can be connected to any number of objects. These situations, illustrated in Figure 4-2 below and Figure 4-3 on the following page, both have a myriad of uses because the notion of outgoing interfaces is quite generic.
Figure 4-2.
A one-to-many relationship between an object and sinks.
Figure 4-3.
A many-to-one relationship between objects and a sink.
Like an object, a sink can support as many outgoing interfaces as you want. That is, you can easily write a single sink that accepts a wide range of events and requests from any number of external objects, centralizing the code to handle all these calls. This sink could be connected to any number of objects, and each source could call functions in any combination of that sink's interfaces. An object, however, will never query a sink for an interface pointer unless that object is told specifically—by the container—to connect to that sink. In other words, an object considers each sink interface pointer it receives to belong to a sink that implements only that one interface and no others (except IUnknown, of course).
But what is the mechanism through which the client hands its sink pointers to objects? For each outgoing interface, a connectable object will manage another small object called a connection point, so named because these objects implement the IConnectionPoint interface. Through this interface, the client passes the interface pointers to its sinks. Each connection point is contained within the connectable object itself, and the connection points usually share whatever information they have from clients. In addition, a connection point's reference count is included in the object's reference count so that as long as any connection points remain, so does the containing object.
Regardless of the implementation details, the object must expose these individual connection points to clients. It does this by implementing the IConnectionPointContainer interface itself. Through this interface, the client asks the object about its outgoing interfaces; when it wants to connect a sink, the client asks for a connection point for one outgoing interface and then hands the sink's interface pointer to that connection point. Let's look at this mechanism in detail to see how it all works.