Developer

Add events to your application with COM connection points

COM connection points provide greater flexibility than typical callback functions. Find out how connection points work and digest a generous helping of code.


Component Object Model (COM) connection points are interfaces that handle event callback functions. If you work with the Win32 API, you are probably familiar with callback functions. They show up frequently as parameters to functions, such as EnumWindows().

COM connection points, although more complicated than standard callback functions, provide greater flexibility because all parameters and calls are executed using the COM subsystem. The COM subsystem allows calls to execute correctly across process boundaries, which is not possible with simple callback functions.

Interfaces and events
Before I dive into how you add COM connection points to your applications, you need to be familiar with a little terminology—namely, interfaces and event types. Related methods are grouped into interfaces. Often, interfaces on a COM object will be similar in scope to the public methods in a C++ class. So a connection point groups a set of related events into a COM interface.

For example, consider the C++ timer class in Listing A. The class includes the functions SetExpiredProc(), SetTime(), Start(), and Stop(), as well as the constructor CExampleTimer() and a protected member function RunTimer(). The timer also fires an event when it expires. A COM interface describing this class would comprise only the public functions related to being a timer, such as SetTime(), Start(), and Stop(). You could construct a COM connection point interface containing a single method named TimerExpired().

Client server vs. sink source
In a typical COM application, a COM object acts as a server with a specific interface. Clients connect to it and execute methods on the interface. When dealing with connection points, however, the COM server advertises that it has events that it will fire off to any clients that want to receive them. A client can receive a set of events if it exposes the event COM interface. When referring to connection points, the server is called the source and the client is called the sink.

So the main difference between normal programming with COM in a standard client-server system and using connection points is that in the standard client-server case, the server exposes a list of methods that the client employs, and in the connection point case, the client exposes a set of methods that the server uses.

Connecting a sink to a source
With the terminology under your belt, you can consider how connection points work. First, the sink attaches itself to the source by implementing the IConnectionPointContainer interface on the event source. The IConnectionPointContainer interface provides two methods:
  • ·        FindConnectionPoint() allows a sink to ask the source if it fires off a specific set of events.
  • ·        EnumConnectionPoints() allows a sink to ask the source for all of its event types.

To keep things simple, assume that the event sink knows which set of events it wants and that it will ask for just that set of events. In this case, the event sink will take advantage of only the IConnectionPointContainer::FindConnectionPoint() method, passing in a unique identifier for the event type COM interface. If everything succeeds, and the source supports the requested event type, the sink receives a pointer to an IConnectionPoint interface.

The IConnectionPoint interface
The IConnectionPoint interface manages a connection’s lifetime and provides the following methods:
  • ·        Advise() attaches a sink to the connection point source.
  • ·        Unadvise() detaches a sink from the connection point source.
  • ·        GetConnectionPointContainer() returns an IConnectionPointContainer interface pointer to the object that contains the connection point.
  • ·        GetConnectionInterface() returns the unique identifier to its event type COM interface.
  • ·        EnumConnections() allows the caller to get a list of all the sinks that are connected to the connection point.

For this article, only the Advise() method and the Unadvise() methods are necessary. You complete the attachment of the sink to the connection point source by employing the IConnectionPoint::Advise() method of the interface returned from IConnectionPointContainer::FindConnectionPoint(). The call to IConnectionPoint::Advise() needs two parameters: the IUnknown interface pointer to the sink and a pointer to a DWORD datatype (frequently used to store 4-byte values) for the source to initialize.

The sink depends on the DWORD initialized by the source in IConnectionPoint::Advise() as an identifier. This identifier is used to detach the sink from the source when it no longer needs to receive events. The sink detaches from the source simply by calling IConnectionPoint::Unadvise() and passing in the DWORD. See Listing B for the code.

Firing events from the source to the sink
Now that you've seen how the event sink informs the source that it wants to receive events, you can look at how that event source fires those events. The process begins with the implementation of the IConnectionPoint::Advise() method. Remember that this method will call and pass in a pointer to its IUnknown interface, as well as a pointer to a DWORD.  The source simply needs to store that IUnknown pointer in some data structure, possibly an array, and initialize the DWORD to a value that identifies the IUnknown interface pointer, possibly its index in the array.

The last thing necessary for an event source to fire events to any listening sinks is a helper method called Fire_EventX(), which is the name of a method in the COM event interface. This method abstracts the EventX() calls on the sink objects that were stored during the execution of the IConnectionPoint::Advise() method. After abstracting the objects, the event source fires off events to all event sinks that care by calling Fire_EventX(), as shown in Listing C.

Conclusion
COM connection points offer a great deal of flexibility because the subsystem makes all function calls so they will execute across processes. Once you get the hang of interfaces and event types, you should be well on your way to taking advantage of this approach.

 

Editor's Picks