Callbacks in COM
In COM, three callback mechanisms are analogous to the .NET mechanisms:
Passing an interface instance
Passing a function pointer
Using connection points (events in Visual Basic 6)
Using callback interfaces in COM is the same idea as using callback interfaces in .NET. It tends to be a fairly common pattern, especially for C++-authored COM components, because using connection points adds complexity for both ends of the communication channel. Chapter 6, "Advanced Topics for Using COM Components," has an example of a DirectX COM object that uses a callback interface. Function pointers are a precursor to delegates. Because they aren't commonly used in COM and aren't important for this chapter, there's no need to cover them any further. Using unmanaged function pointers in managed code is covered in Chapter 19, "Deeper Into PInvoke and Useful Examples."
Connection points are the COM equivalent of .NET events. Connection points are really just a general protocol for setting up and using interface callbacks. The generic nature of the protocol enables tools like Visual Basic 6 to abstract it inside event-like syntax. Notice the difference here.NET events are based on delegates for method-level granularity whereas COM events are really based on interfaces (collections of methods).
The terminology for describing connection points in COM is notoriously confusing, so here's an attempt to sort out the terms before describing the interfaces and protocol:
The object causing an event to be raised is called the source, also known as a connectable object. This was the ChatRoomServer class in Listings 4, 5, and 6. This is clearer than calling it something like a "server" because bi-directional communication causes clients to sometimes be servers and servers to sometimes be clients!
The object handling ("listening to") events is called the sink. This was the Display class in Listings 4 and 5 because it contained the implementation that was being called from the source.
The callback interface that is implemented by the sink and called by the source is called a source interface, or sometimes called an outgoing interface. This was the IChatRoomDisplay interface in Listing 1.
For each source interface that the source calls back upon, the object doing the actual callbacks is called a connection point. Every connection point corresponds exactly to one source interface.
A sink object being attached to a connection point (from passing it a callback interface) is considered a connection.
The source interface is really the focus of the connection point protocol, and is nothing more than a callback interface. To Visual Basic 6 programs, source interfaces appear to behave like a collection of events. Through a series of initialization steps, the sink (or an object using the sink) passes the sink-implemented interface pointer to the source so it can call back on the interface to simulate events. The part that often confuses people is that the source coclass usually lists source interfaces in its coclass statement marked with the IDL [source] attribute. For example, a coclass representing the ChatRoomServer class from Listing 1 might look like the following:
[ uuid(0002DF01-0000-0000-C000-000000000046) ] coclass ChatRoomServer { [default] interface IChatRoomServer; [default, source] dispinterface IChatRoomDisplay; };
This does not mean that the ChatRoomServer class implements IChatRoomDisplay; it doesn't. Instead, it's simply expressing that it supports calling back members of the IChatRoomDisplay interface when an instance is passed to it using the connection points protocol. Listing source interfaces is the way for a COM class to advertise to type library consumers such as Visual Basic 6 that the class "has events." This is analogous to .NET classes defining public event members. In any coclass's list of source interfaces, one is always considered the default, just as with implemented interfaces. The default source interface is the only one accessible as a collection of events in Visual Basic 6 programs.
DIGGING DEEPER
The IChatRoomDisplay source interface in the previous coclass definition was marked as a dispinterface because that's the only kind of source interfaces for which Visual Basic 6 can expose as events. For this reason, source interfaces are almost always defined to be dispinterfaces. (This is also the default behavior for source interfaces generated by the ATL wizard in Visual C++.) Note that this is not a restriction of the connection points protocol, nor is it required for COM Interoperability.
At run time, the [source] markings in a type library are meaningless; COM classes advertise that they support events by implementing an interface called IConnectionPointContainer and by returning connection point objects via this interface. This interface has two methodsEnumConnectionPoints and FindConnectionPoint.
EnumConnectionPoints can be called to discover all the connection points and their source interfaces supported by the object. This method returns an IEnumConnectionPoints interface, which provides methods to enumerate over the collection of connection points, each of which is represented as an IConnectionPoint interface.
FindConnectionPoint enables a client to ask for a specific connection point identified by an IID of a source interface. This is extremely similar to QueryInterface, but in this case a client asks what source interfaces the object calls rather than what interfaces the object implements. If successful, an IConnectionPoint interface is returned.
Connection point objects (which implement IConnectionPoint) are provided by the connectable object, representing a collection of events. Just as a .NET event has add and remove accessors that enable clients to hook and unhook event handler delegates, IConnectionPoint has Advise and Unadvise methods that enable clients to hook and unhook a callback interface pointer as follows:
A client calls Advise with an IUnknown pointer to itself, then Advise passes back an integral "cookie" that uniquely identifies the connection to client. The callback object being passed to Advise must implement the appropriate source interface.
When the client is finished listening to events, it can call Unadvise with the cookie value obtained from Advise to notify the connectable object.
IConnectionPoint has a few additional methods that enable enumerating of connections, obtaining the IID of a connection point's source interface, or obtaining a connection point's container, but they aren't important for this discussion.
Figure 1 illustrates the steps of the connection points protocol in a typical scenario. The sink object is traditionally the client object that initiates the connections, waits to receive event notifications, then terminates the connections when finished. The first four steps comprise the initialization phase, similar to hooking up event handlers in .NET. Step 5 represents the period of time when any "events are raised"the source object calls back on the sink object's source interface whenever appropriate. Finally, Step 6 terminates the connection. In this illustration, the object that sets up the connection point communication is the sink object, but often this client might contain one or more sink objects, much like the source object contains one or more connection points.
Figure 1 Connection points summarized.
The connection point infrastructure doesn't prevent multicasting of events, but this needs to be managed manually by the connectable objects, just as it could be done with callback interfaces. For example, a connectable object could enumerate through its list of connections and call each sink object's method one by one.