Outgoing Interface Definitions and Type Information

This whole scheme of connectable objects and their relationships to clients and sinks is quite powerful, but it raises some concerns. Given an arbitrary object, a client can, by asking for IConnectionPointContainer, determine whether the object has any outgoing interfaces. If that client already knows about the outgoing interfaces it wants to connect—that is, it has compiled header files with the definitions of those interfaces—there is little complication: it calls FindConnectionPoint and goes through the steps listed earlier.

However, what if a client wants to connect to whatever outgoing interfaces there may be, regardless of whether it had compile-time knowledge about those interfaces? In this case, it has to call EnumConnectionPoints and then IConnectionPoint::GetConnectionInterface to get a list of all the outgoing interface IIDs. But what good is an IID if you don't have any information about it?

Well, an IID by itself with no other context is practically useless, which is one reason why type information is fundamentally important. Given an object and a list of its outgoing IIDs, a client can ask the source for type information by querying for IProvideClassInfo followed by a call to GetClassInfo in that interface. With the ITypeInfo pointer you are given back, you can use functions such as ITypeInfo::GetRefOfImplType and ITypeInfo::GetRefTypeInfo to find the ITypeInfo structure for the interface matching the IID you already have. Through that ITypeInfo structure, you can learn about all the attributes, member functions, and arguments for that interface, enough information to provide an implementation of it on a sink and connect that sink to the connectable object. This run-time process works much more easily with outgoing dispinterfaces than with outgoing vtable interfaces because dispinterfaces were designed for such late-binding considerations.2 We'll see how to do this when we deal with OLE Control containers in Chapter 24.

Suffice it to say that if a client encounters an unknown outgoing IID in a connectable object but cannot retrieve type information for that object, it is, to put it mildly, flat out of luck. The client simply doesn't have enough information to connect to the object through that interface.


Specific Outgoing Interfaces and Historical Trivia

As time moves on, the vast majority of connectable outgoing interfaces will be defined through type information, especially because a plethora of new OLE controls and other similar objects will be coming to market.

There are, however, a few standard interfaces defined in the OLE header files that deal with various types of object-to-client events, notifications, and requests, all of which are designed for specific purposes. These are IAdviseSink, IPropertyNotifySink, IOleClientSite, IOleInPlaceSite, IPropertyPageSite, and IOleControlSite. However, only one of them, IPropertyNotifySink, is handled through connection points as described in this chapter. Connections to the others are established through specific member functions of IDataObject, IViewObject, IViewObject2, IOleObject, IOleInPlaceObject, IPropertyPage, and IOleControl. There are two reasons for this.

The first reason applies to the interface IAdviseSink, which contains notifications of data changes, view changes, layout changes, object closure, object renaming, and object saving. This interface was designed as part of the original OLE 2 specification way back in 1991–92. It works specifically with aspects of Uniform Data Transfer, Viewable Objects, and OLE Documents, which were defined at the same time. The interfaces IDataObject, IViewObject2, and IOleObject each have advise and unadvise member functions to establish and terminate connections through IAdviseSink. This was an ad hoc solution to the specific problems of these technologies. In 1994, the authors of the OLE Controls specification were faced with creating either another ad hoc solution only for OLE Controls or an extensible generic mechanism that would be reusable in other present and future designs. So the OLE Controls specification is the original source of all the connection point business and the IConnection* interfaces. Because IAdviseSink appeared way before OLE Controls, it remains an oddity, and it will remain so unless there's some compelling reason to change it.

The other reason applies to the Site interfaces, which generally contain very rich functionality for an object to use itself. In fact, Site interfaces represent more than just simple notifications or requests; they expose services from both containers and clients to their respective objects. These interfaces were designed specifically for the rich OLE Documents and OLE Controls integration protocols. In these protocols, bidirectional communication is not optional, as it is with connectable objects. Both sides need to talk to each other. Establishing a connection between an object and the site that conceptually "contains" it is a fixed part of the protocol.

As you see new interfaces—and design your own—anything using Sink in its name implies a simpler interface that is managed with connection points. A Site as part of the interface name implies a more complex relationship between an object and a container that goes beyond the scope of sinks.


2 A dispinterface is simpler because a sink can implement an IDispatch interface at compile time whose specific methods and properties are not determined until run time using type information. This involves code that executes some sequence of actions when IDispatch::Invoke is passed an appropriate dispID. It is far more tedious to create a run-time interface implementation that requires you to construct the vtable and create entry points for each member function that will handle a proper stack frame for that function. It is possible, but it's not nearly as trivial as implementing a run-time dispinterface.