Ken Bergmann
MSDN Content Development Group
April 1997
It can be hard to understand when to use an event (or connection point) interface and when to use a specific callback interface. The two, while similar, can vary greatly in a number of ways. The following will outline some fundamental issues that should be considered when attempting to decide how to proceed with your components.
The key conceptual difference between an event interface and a callback interface is that an event is designed to be more like an anonymous broadcast, while a callback functions more like a handshake. While other important issues will need to be addressed, most decisions about which technique to use will pivot around this anonymity.
Any object that has a reference to an event source can handle the events the source raises by simply placing that reference into a WithEvents variable. In C++, handling connection points is a little more involved, but the premise is the same. In either case, the event source has no idea what objects may be handling its events. There may be a hundred objects paying close attention or there may be none. The key point is that the event source blindly notifies each connected client through its event (connection point) interface, without any knowledge of whether the client is handling the event or what the interface to the client actually looks like.
In contrast, a server designed to perform callbacks must have an explicit reference to every object that needs notification, it must connect and manage those references, and, finally, it must perform the notification. Basically, the server must know exactly how many clients there are and how to connect and interact with each of them.
In Microsoft® Visual Basic® event interfaces, an event source has no control over the order in which clients receive their events. This is conversely true as well. Clients cannot be assured of receiving their notifications in any particular order compared with other clients. The later is true even if you were to roll your own connection points in C++.
On the other hand, a callback server must control the order in which it calls clients back. Of course, there can be benefits to doing all this work in a callback server. For example, a source server might be designed to give certain clients higher priority for notifications, or it might perform specific actions based on the results of the callback. This flexibility is even more significant in the next point.
When an event server raises an event, all of its clients get the opportunity to handle that event before the event server can regain control. So, if many clients are listening for events, the notification process could take an unforeseeable length of time. On the other hand, a callback server, because it is responsible for implementing the notification process, regains control after each call it makes to a client. Because of the level of control available here, callbacks can employ much more intelligent techniques for client notification than events.
Now, let's consider what happens to the arguments of an event. Because the event server doesn't regain control until after all clients have handled a specific event, the changes to a ByRef argument by a specific client are lost. Only the changes to an argument made by the last client are seen by the event server. This becomes really inconvenient when considered with the fact that the order in which clients are notified cannot be assured. Of course, with a callback server, the server is responsible for this process, so each client's feedback can be analyzed independently. In fact, a callback server might wish to pass fresh values when notifying each client.
The final compelling difference between the two approaches is the way in which errors are handled. If an error occurs in a client's event handler, the event source is not notified. The client handling the event might even be brought down very horribly, and the event source would never know. Of course, the client's fatal error is just as likely to bring the event server down, too (if it's an in-process component). In this case, the event server will have no clue why it was brought down, or even that an error occurred.
However, a callback server will receive notification of errors that occur in the callback methods of its clients, and, as such, must be prepared to handle them. Of course, this expectation is true of any COM interface.
When performance is at stake, it will usually be worthwhile to do the extra work required to create custom callback interfaces. By employing early binding in the callback interface connection code, significant improvements can be made in high-volume or in-process components. By design, event interfaces are not vtable bound and are, therefore, slower in most scenarios than comparable early-bound callback interfaces.
Of course, in any performance issue, the only way to gauge your own needs is to experiment, benchmark, and test. Try out the various permutations yourself and do the math. If you really care about getting the absolute last spurt of speed, then doing the tests is the only way to prove what works, no matter what promises are made.