Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > September 1999
September 1999

Microsoft Systems Journal Homepage

The COM+ Event Service Eases the Pain of Publishing and Subscribing to Data

David Platt

A publisher is any program that makes the COM calls that initiate events, and a sub­scriber is a COM+ component that receives the COM calls representing events from a publisher. The subscriber implements an interface as a COM server; the publisher makes calls on it as a COM client.

This article assumes you're familiar with COM and C++

Code for this article: COM+ Events.exe (92KB)
David Platt is president and founder of Rolling Thunder Computing Inc. (http://www.rollthunder.com), and teaches COM and COM+. This article is based on a chapter from his book Understanding COM+ (Microsoft Press, 1999).

Connecting programs that provide information that changes over time (publishers) with programs that want to receive notifications of those changes (subscribers) has long been a challenge in distributed computing. The COM+ event service provides an infrastructure that makes publishing and subscribing to data easy.
In this article I'll describe the background and motivation for using the COM+ event service by walking you through a sample stock ticker program. The sample program will demonstrate the ease with which extremely simple COM-based programs can use the event notification infrastructure provided by COM+. As I cover the accompanying sample code, I'll also show you when and how to take advantage of this technology.
Because of the thorough reworking of the COM+ event system based on developer feedback from the Windows® 2000 beta 2 release, many of the UI and API features shown and described in this article were not finished in time for the beta 3 release. This article was written using a post-beta 3 build of COM+, not available to the public, that was considered UI-complete. Some of the details mentioned here may change prior to the final release of Windows 2000.

Publishing and Subscribing

Notifying interested parties of changes to data is a classic problem of distributed computing. One program detects a change in the world that it thinks other programs want to know about: a stock ticker program notes a change in price, a weather monitoring program notes changes in barometric readings from remote sensors, or a medical monitoring program notes that a patient's blood pressure has exceeded the acceptable range. Somewhere else in the world are other programs that would like to hear about these changes: a portfolio program that buys a stock when it hits a certain price, an alarm program that tells fishing boats to return to port, a patient monitoring program that signals the nurses' station that a patient requires medication.
The meanings of client and server become murky in discussions of scenarios like these, so I will introduce a new nomenclature. Programs that provide notifications to other programs, such as the stock ticker program, I will call "publishers." Applications such as the portfolio program, which receive data from publishers and act on it, I will call "subscribers." A publisher detects a change that the subscriber cares about, but how does the subscriber find out from the publisher when a change takes place? The simplest approach is for the subscriber to poll the publisher every so often, analogous to you telephoning your stockbroker periodically to ask for the latest price of a stock. In the terminology of COM+, the publisher would provide the subscriber with an interface, and the subscriber would periodically call a method on that interface to see if any changes had taken place, as shown in Figure 1.
Figure 1  Subscriber Polling
Figure 1 Subscriber Polling

This strategy is simple to code, but it's a terrible idea for several reasons. First, the subscriber wastes an enormous amount of time and energy asking, "Are there any changes?" The publisher also wastes time and effort replying, "No, there aren't." You might get away with polling in a desktop application that spends most of its time idling, waiting for user input, but it's deadly in an enterprise application where many clients connect to a single server. Mutual fund giant Fidelity just announced restrictions on their account holders who did this too often. And anyone who has ever screamed at their child's tenth repetition of "Are we almost there yet?" in as many minutes will understand the fundamental wrongness of this approach.
Second, polling involves some inevitable amount of latency between the time the change occurs and the time the subscriber polls for it. On average, this latency is equal to half the polling interval. As you lengthen the polling interval to waste fewer CPU cycles, the latency increases. Not only is this latency bad in and of itself, but the fact that it is nondeterministic (it varies from one call to another) is also a problem in designing systems. Polling is evil in an enterprise application, so don't do it.
You really want the publisher to initiate the notification process when it detects interesting changes in the world. Instead of you having to call your stockbroker periodically to find out the latest prices, you would save yourselves a lot of headaches if you gave the broker your phone number and asked him to call when something changed. In COM terms, the subscriber provides the publisher with an interface, and the publisher calls a method on it when something interesting happens. This is the approach that ActiveX® controls use to fire events to their containers, as shown in Figure 2. Here, the control is the publisher and the container is the subscriber.
Figure 2  ActiveX Callbacks
Figure 2 ActiveX Callbacks

This is called a tightly coupled event. The subscriber knows exactly which publisher to request notifications from (the container knows the CLSID, or class identifier, of the control) and the mechanism for connecting to it (the IConnectionPointContainer and IConnectionPoint interfaces exposed by the control). A tightly coupled event works reasonably well on a single desktop, but it has a number of drawbacks when used at the scale of an enterprise system.
For the event mechanism to function, a tightly coupled event requires both the publisher and the subscriber to be running at all times. Both sides have to be running when the subscriber (the container) provides the publisher (the control) with its callback interface, and both sides have to be running when the publisher calls the method on the subscriber's interface. ActiveX controls are mostly used for desktop user interface elements, which have no reason not to match the lifetimes of their containers, so this restriction is not usually a problem. Requiring lifetimes to overlap can be a big problem in enter­prise applications, as I demonstrated in my previous article on Queued Components, "Building a Store-and-Forward Mech­anism with Windows 2000 Queued Components" (MSJ, June 1999). You'd like the subscriber to be able to make subscription requests while the publisher isn't running, and the publisher to be able either to launch the subscriber just in time to fire an event or to use Queued Components instead of direct connections for sending event notifications.
Another problem with tightly coupled events is that the subscriber needs to know the exact mechanism a particular publisher requires for establishing subscriptions, and this mechanism can vary radically from one publisher to another. For example, ActiveX controls use the IConnec­tionPoint mechanism for hooking up the callback circuit to deliver notifications of their events. An OLE server uses the method Advise on the IOleObject interface to hook up a callback circuit to deliver notifications of embedding-related events. It would be great to standardize on one connection mechanism for publishers and subscribers, and to be able to use this standard connection mechanism administratively instead of having to write code to access it.
The third problem of the classic event mechanism is that it contains no mechanism for filtering or interception. When I tell my broker that I care about changes to stock prices, he doesn't call me with the change of any stock price anywhere in the world. Instead, I tell him which stocks I care about, usually those that I own or am thinking about buying. I might even tell him that I care only about certain movements—for example, if the price of my favorite stock tops $150. You'd like to have some mechanism whereby a subscriber could specify that it wanted to receive calls only if their parameters had certain values—for example, if the parameter designating the stock symbol matched one that you cared about, or if the parameter designating the price was greater than a certain value. And ideally, you'd like to be able to specify this information administratively as well.
One solution to these problems would be to store the information about matching publishers with subscribers externally instead of inside the programs themselves. The publisher would maintain an external database containing a list of the different events for which it knows how to send notifications. Subscribers could read this list and pick the events that they want to hear about. The publisher would also maintain some sort of subscription database, conceptually similar to a mailing list, of the CLSIDs of subscribers that want to hear about each event. Administrative tools or the subscriber programs themselves would know how to make entries in this subscription database. When the publisher wants to fire an event, it looks in this database, finds the CLSIDs of all the interested subscribers, creates a new object of each interested class, and calls a method on that object. I would call such a system loosely coupled rather than tightly coupled because the information about which subscriber wanted to hear from which publisher would be maintained in a central database instead of being bound to the programs themselves.
This promising design approach has two snags. First, you'd have to develop and maintain the events and subscriptions database and write all the code for the publisher-side event firing mechanism and the administrative tools. You'd have to sell a lot of units before this approach became cost-effective, and enterprise applications generally have relatively low unit volume compared to desktop applications. Your time and money would be much better spent on your business logic instead of an event infrastructure—customers aren't paying you for infrastructure.
Second, even if you did develop all this infrastructure, your subscription process would still be different from every other vendor's. Subscribers would have to know not only the specific techniques required to subscribe to your events, but also the different mechanisms required for every other publisher they ever want to hear from. What you'd really like is to inherit such a mechanism from the operating system. This way you would have to write very little code, and every vendor's subscription process would be the same.

Event Services

The COM+ event service is the operating system service that deals with matching and connecting publishers and subscribers. Its architecture is shown in Figure 3.
Figure 3  COM+ Event Service Architecture
Figure 3 COM+ Event Service Architecture

Using this terminology, an event represents a single call to a method on a COM interface, originated by a publisher and delivered by the event service to the correct subscriber or subscribers. A publisher is any program that makes the COM calls that initiate events, and a subscriber is a COM+ component that receives the COM calls representing events from a publisher. The subscriber implements an interface as a COM server; the publisher makes calls on it as a COM client. The only change from classic COM is the event service in the middle, which keeps track of which subscribers want to receive the calls and directs the calls to those subscribers without requiring any specific knowledge of the publisher.
Making a single event call is known as firing the event. You may also see the term "publishing" used in the documentation as a synonym for firing. This term suffers from ambiguity between its general meaning—to make information available—and its specific meaning—in this sense, to initiate a single event. Since the term "firing" is unambiguous, it is used in the names of the interfaces and methods relating to the event system, and is the term that the many users of ActiveX controls are accustomed to. I will continue using it here.
The connection between a publisher and a subscriber is represented by an event class. An event class is a COM+ component, synthesized by the event system, that contains the interfaces and methods a publisher will call to fire events and that a subscriber needs to implement if it wants to receive events. The interfaces and methods provided by an event class are called event interfaces and event methods. You tell COM+ which interfaces and methods you want an event class to contain by providing COM+ with a type library. Event classes are stored in the COM+ catalog, placed there either by the publishers themselves or by administrative tools.
A subscriber indicates its desire to receive events from a publisher by registering a subscription with the COM+ event service. A subscription is a data structure that provides the event service with information about the recipient of an event. It specifies which event class, and which interface or method within that event class, the subscriber wants to receive calls from. Subscriptions are stored in the COM+ catalog, placed there either by the subscribers themselves or by administrative tools. Persistent subscriptions survive a restart of the operating system; transient subscriptions do not.
When a publisher wants to fire an event, it uses the standard object creation functions, such as CoCreate­Instance or CreateObject, to create an object of the desired event class. This object, known as an event object, contains the event system's implementation of the requested interface. The publisher then calls the event method that it wants to fire to the subscribers. Inside the event system's synthesized implementation of the interface, the event system looks in the COM+ catalog and finds all the subscribers that have registered subscriptions to that interface and method. Once that task is completed, the event system then connects to each subscriber (using any combination of direct creation, monikers, or queued com­po­nents) and calls the specified method.
Because frequently more than one subscriber wants notification for each event, event methods may not use any type of output parameters and must return only success or failure HRESULTs—the same restrictions as for Queued Component interface methods. In this way, essentially any COM client can become a publisher, and essentially any COM+ component can become a subscriber. Neither has to know anything about the intervening gyrations performed by the event service to make the connection.
The current implementation of the event system has some limitations. First, the subscription mechanism is not itself distributed. There is currently no enterprise-wide repository of all the publishers and subscribers. You could certainly construct such a thing yourself by placing all the event classes and subscriptions on a central machine. Publishers would then create event objects on the central machine, and subscribers would receive their notifications from the event system on that central machine. This approach would be easy to set up, but it would cost you one extra network hop per event fired from the publisher (for the call to travel from the publisher's machine to the central machine). The central machine would also represent a potential single point of failure, so you'd have to make sure you backed it up with a hot spare using Microsoft Clustering Services.
Second, delivery of events in the current system occurs either by DCOM or Queued Components, which are one-to-one communication mechanisms. This means that the delivery time and effort increase linearly with the number of subscribers, which in turn means that the current system is not well suited to firing events to many subscribers. The exact number of simultaneous subscribers that can be supported will obviously vary widely depending on system workload and hardware capacity, and in any event will have to await the final performance tuning of the product. For ballpark numbers at the time of this writing, figure that dozens of subscribers are probably fine (unless you call them every second and pass them the Encyclopedia Britannica), and thousands of subscribers are probably not fine (unless you only call them once or twice a day, pass zero information, and don't care when they process it).
The current event system does not support broadcast datagrams, which would be the best way of reaching very large numbers of subscribers. If you need to do that today, you can write one subscriber that receives events via DCOM or Queued Components and then resends the incoming data to many users via a datagram.

The Simplest Event Example

The workings of the COM+ event system are shown in the Publisher Demo sample, a simulated stock ticker program included in this month's code download. The Publisher Demo sample application, shown in Figure 4, allows a human user to fire two events, one signaling a change in a stock price, and the other signaling the listing of a new stock on the exchange. A sample subscriber is provided to demonstrate reception of calls from the event object. The combination shows the ease with which relatively simple programs that don't know anything about the COM+ event system can nonetheless tap into its power by making simple administrative entries in the COM+ catalog.
Figure 4 Publisher Sample
Figure 4 Publisher Sample

The first thing you need to do is register the event class. To do this, you create a new COM+ application and start installing a component by using the Component Services snap-in—clicking the "Install new event class(es)" button, as shown in Figure 5.
Figure 5 Installing a Component
Figure 5 Installing a Component

You need to provide COM+ with a type library describing the event interfaces and methods so that it will know how to synthesize the event class on your behalf. To work with the event system, the type library needs to reside in or be accompanied by a self-registering DLL. The user interface used to make these settings is shown in Figure 6.
Figure 6 Installing an Event Class
Figure 6 Installing an Event Class

I've registered the event class in the sample using the ATL COM AppWizard to create a dummy component called StockEventCls (the name of the component and project can't be the same in ATL), which you will find in the StockEventClass folder. It contains the definition of the interface that the subscriber will implement and that the publisher will call. In this case, the interface is named IStockEventCls and contains the methods StockPrice­Changed and NewStockListed. The event system uses the type library and the self-registration code from this component. Even though I had to add implementations of the methods to the event class component because of the internal structure of the ATL COM AppWizard, the methods on this component will never be called by the event system. It was simply the fastest way to produce the type library and self-registration that the event system requires.
Entering the path to the event class component DLL and clicking OK tells the Component Services snap-in to synthesize an event class and install it in the COM+ catalog. The snap-in does this internally by going to the COM+ catalog and calling the method ICOM­AdminCatalog::InstallEventClass. The component will look very much like any other component in the snap-in. The only discernible difference at this level is the entries on the Advanced tab of the component's property sheet, as shown in Figure 7. In this example, I add a Publisher ID string that will show up in the snap-in when I add the subscriptions to this event class later. This is all I need to do for a subscriber to find the event class and subscribe to it, and for a publisher to create an object of the event class and fire it.
Figure 7 Event Class Properties
Figure 7 Event Class Properties

Next, add a subscriber, which must be a configured component in a COM+ application. You will find the subscriber component in the StockSubscriber folder. You can install them both in the same COM+ application for convenience if you want. After you create an application and install the component as usual, you'll notice that each component shown in the snap-in contains a Subscriptions folder just beneath its Interfaces folder, as shown in Figure 8. Right-clicking on the Subscriptions folder will bring up a wizard allowing you to enter a new subscription. The wizard will offer you a choice of all the interfaces that have been added as event classes, as shown in Figure 9.
Figure 8 Snap-in Subscriptions
Figure 8 Snap-in Subscriptions

Figure 9 Creating a New Subscription
Figure 9 Creating a New Subscription

You can subscribe to a single method or to all methods on the entire interface. If you want to receive calls to more than one method, but not every method, you must add a subscription for each desired method. The wizard searches the COM+ catalog for registered event classes that support the specified interface and offers you the choice to subscribe, as shown in Figure 10. Choose the publisher that you want your subscriber to hear from.
Figure 10 Subscribing to a Method
Figure 10 Subscribing to a Method

One additional step in the wizard allows you to enter a name for the subscription and, more importantly, enable the subscription (see Figure 11). Subscriptions may be enabled or disabled. The latter receive no event notifications. That's all you have to do.
Figure 11 Subscription Options
Figure 11 Subscription Options

When the Publisher Demo application wants to fire an event, it simply creates an object of the event class and calls the method on it. The code to do this is identical to what the publisher would do if it was making calls on a single object in classic COM. The only difference is that the publisher creates an object of the COM+ synthesized event class instead of the subscriber class. When the publisher calls the method on the event class, the event system looks through the COM+ catalog to find all the relevant subscribers. It creates the subscriber object in the manner specified by the subscriber—either directly, queued, or with a moniker—and passes on to each of them the method call that the publisher originally made.
A sample listing demonstrating this process is shown in Figure 12. The subscriber code is also quite similar to what would be used to call directly by the publisher instead of the event mechanism, as shown in Figure 13. That's all you need to tap into the COM+ event system.

More on Subscriptions

A subscription is a data structure that resides in the COM+ catalog. A subscription's properties are available through the IEventSubscription interface, shown in Figure 14. Many of the properties can be modified through the Component Services snap-in, but others are not available through this user interface. You must write your own administrative utility program to access these properties.
Subscriptions come in two flavors, persistent and transient. A persistent subscription is the type used in the Publisher Demo sample. Persistent subscriptions live in the COM+ catalog and survive a system restart. They exist independently of the lifetime of the subscriber object. A subscriber program often creates a persistent subscription when the program is installed, and removes the subscription when the program is uninstalled.
When a publisher makes a call on an event object, the event object looks for all the persistent subscriptions in the catalog and creates a new instance of each subscriber class. The creation process can happen either directly or through a moniker (the latter is how queued components are used for event subscribers). You specify which subscriber object to create by setting the SubscriberCLSID or Subscriber­Moniker property of the subscription. The subscriber object created by a persistent subscription is always released after each event call, regardless of success or failure and whether the publisher releases the event object. Any reader wanting to verify this can put a message box in the destructor (in Microsoft Visual C++®) or Class_ Terminate function (in Visual Basic®) of the subscriber sample objects.
A transient subscription requests that event calls be made to a specific existing subscriber object. Transient subscriptions are also stored in the COM+ catalog, but they do not survive a system shutdown. The Component Services snap-in has no provision for setting up transient subscriptions; it is up to the subscriber program to do that itself by using the COM+ administrative interfaces.
You set up a transient subscription by adding a new subscription to the event system and setting its Sub­scri­ber­Interface property to the IUnknown interface of the subscriber object. In this case, the event mechanism will not create a new instance of the subscriber object when firing an event, but it will use the one it has been handed. The event system will hold a reference count on the subscriber object until either an administrative program or the subscriber object itself removes it from the event system.
Because they do not involve repeated object creations and destructions, transient subscriptions are more efficient than persistent subscriptions, although they raise all the lifetime issues that persistent subscriptions abstract away. A transient subscription would be a good choice when a subscriber cares only about receiving updates during its own externally controlled lifetime. For example, an application that displays the latest foreign exchange rates to a trader would care only about receiving updates when the trader is logged in. A persistent subscription would be a good choice when an application cares about any update, but doesn't want to hang out waiting for one to happen. For example, a financial arbitrage application might want to be launched only when spreads on exchange rates make it profitable.
Any subscription can be disabled. To do that, set the Enabled property of the subscription to FALSE. A disabled subscription is never called by the event system.

More on Firing Events

Firing an event is always synchronous with respect to the event object. When an event method returns, everything that is going to happen on the publisher machine has already happened. All non-Queued Component subscriber objects with transient subscriptions have been called and returned. All non-Queued Component subscribers with persistent subscriptions have been created, called, returned, and released. Calls to all Queued Component subscribers have been recorded, and the persistent ones enqueued for transmission. Since transient subscriber objects are not released until their subscriptions are removed, no transient Queued Component subscriptions will be enqueued until this happens.
The return code of an event method tells the publisher the result of the event system's operation. Some of the possible return codes are listed in Figure 15. They tell the publisher that either all the subscribers returned success code S_OK, that some of them did, that none of them did, or that there weren't any subscribers. Currently, there is no way at this level to determine which of the subscribers failed or for what reason. The design philosophy of loosely coupled events is that the publisher doesn't know or care about subscribers' identities. A publisher that can't live without this information can obtain it by implementing a publisher filter, as I'll describe later.
The event system does not currently provide a simple mechanism for specifying the order in which an event is delivered to multiple subscribers. The default firing protocol is to fire events one at a time, but in no deterministic or repeatable order. A publisher that needs to control the order in which subscribers receive an event can do this by implementing a publisher filter. A publisher can mark an event for firing in parallel by selecting the "Fire in parallel" checkbox shown previously in Figure 7. This allows the event system to use multiple threads to deliver an event to more than one subscriber at a time, and can greatly decrease the average delivery time of event notifications.
For example, suppose the first subscriber to an event took a long time to process an event notification, perhaps having to block while waiting for something else in the system to complete processing. Without firing in parallel, all the other subscribers to the event would have to wait for the first subscriber to finish before receiving their event notifications. If firing in parallel is enabled, other threads could be firing the events to these other subscribers, bypassing the blocked one. Note that selecting this checkbox allows parallel delivery, but doesn't guarantee it.

Subscriber Parameter Filtering of Incoming Calls

You saw how easy it is to hook up a subscriber to be notified when a publisher fires an event. At times, it's too easy. A subscriber might not care about everything a publisher has to say. For example, most investors are probably interested in changes in the prices of only a small subset of stocks traded worldwide, those that an investor owns or is considering buying. You could certainly write code on the subscriber side to check the incoming stock symbol and ignore it if it isn't one of the ones you care about, but doing this is inefficient on two counts.
First, it wastes a lot of CPU time by rejecting an incoming event call late in the event process—the event system would have already created the subscriber object and made the call into it. It's better to have that check and possible rejection occur as far upstream as possible so that you don't waste time on a useless operation. Second, it requires writing code on the subscriber to filter the event, and you'd really like to avoid that if at all possible by handling the task administratively.
The COM+ event system provides this capability by allowing a subscriber to specify a filter criteria string as part of its subscription. The user interface for doing this is shown in Figure 16. You specify the criteria to be met by using the variable names specified in the type library. The event system then evaluates the variables at call time and allows the call to proceed only if the criteria in the string evaluates to true. Figure 16 shows a filter string that will result in the specified event being fired only for changes to stocks in which the variable named Symbol has the string value MSFT.
Figure 16 Subscriber Filtering
Figure 16 Subscriber Filtering

Publisher Filtering of Outgoing Calls

The example shown earlier demonstrated the simplest way of getting the job done. Using this approach makes it extremely easy to write and deploy your applications, but it may not always be as flexible as you need. In particular, the system as demonstrated offers no possibility of controlling the order in which subscribers receive their events, nor does it offer any chance for the publisher to refuse to deliver an event to a specific subscriber. These types of decisions are often part of a publisher's business logic. For example, a publisher might want to check its current financial records to see if a subscriber has paid the requisite fees before actually firing an event to that subscriber. Or the publisher might want to fire events to subscribers in a particular order; perhaps some subscribers have paid extra for priority notification. The default functionality of the event system does not handle these situations. For maximum flexibility, you need a way for the publisher to inject itself into the event system's logic at event firing time.
The publisher can exercise fine-grained control over the event firing process by means of the IEventControl interface, whose methods are listed in Figure 17. Of particular interest is the method GetSubscriptions, which returns an enumerator object that the publisher can use to examine the subscribers for the event, determine whether to fire it, and determine the order in which subscribers should receive notification. The collection is self-updating; every time you walk it you get the current list of subscribers, with additions and removals being taken into account.
A publisher can access the IEventControl interface by querying the event object. However, accessing it at this point raises several problems. First, it won't work if the event class itself is a Queued Component. This is an entirely reasonable design, as I'll explain later. The interface isn't queueable because it contains output parameters. Second, this strategy works only if it is compiled into the publisher program. It won't help you control a publisher for which you don't have the source code. If you use this approach, you won't be able to change your filter behavior without rebuilding your entire publisher program. The publisher control mechanism should work with Queued Components and without requiring modification of the publisher code.
Fortunately, the COM+ event system provides a publisher filter mechanism that allows you to handle these cases. A publisher filter is a COM+ component that is installed downstream from the event object. Putting publisher modification code in a publisher filter means that it will work with Queued Components and third-party software. You tell COM+ which publisher filter to use for an event class by setting the event class's PublisherFilterCLSID property in the COM+ catalog. The Component Services snap-in contains no user interface for this; you will have to write your own administrative application to do it.
When the publisher creates the event object, the event system reads the CLSID of the filter specified for that event class and creates an object of the filter class. If the filter object cannot be created, the creation of the event object fails. The filter class must implement the IPublisherFilter interface, whose methods are shown in Figure 18. If the event class supports more than one interface, the publisher filter must support the IMultiInterfacePublisherFilter interface instead. The IMultiInterfaceEventControl and IMulti­Inter­facePublisherFilter interfaces supersede IEventControl and IPub­lisherFilter and really should be used instead since they are no more complex. You are encouraged to implement IMulti­Interface­EventControl and IMultiInterface­Pub­lish­erFilter for composition with Queued Components and handling multiple interfaces on an event class. Here, however, I will discuss only the IPublisherFilter interface for simplicity.
After creating the filter object, the event system will call the IPublisherFilter::Initialize method, passing the filter's IDispatch interface (which you query for the IEventControl interface or IMultiInterfaceEventControl) if filtering more than one interface. This interface is only passed once, in the Initialize method. Use this interface to obtain needed information (such as Enum interface on the collection of subscriptions), then release it before returning from the Initialize method. This is to avoid a circular reference between the filter object and the synthesized event system "agent" object, thus preventing the destructor of each from executing. When the publisher calls a method on the event interface exposed by the event object, the event system will call the filter's IPublisherFilter::PrepareToFire method. The first parameter specifies the name of the method to be fired, and the second is an interface pointer of type IFiring­Control. This interface has one method named FireSub­scription. You use it to tell the event system to deliver events to one subscriber. Calling this method invokes all of the event system's standard delivery mechanisms, which include the parameter filtering described earlier.
At this point, you have two choices. If all your filter wants to do is use its business logic to swallow calls to certain recipients, your filter need not support the actual event interface that the publisher is calling; it only has to support IPublisherFilter. Your filter will include some mechanism for checking which subscribers are supposed to receive the event and which are not. Within the PrepareToFire method, call IFiringControl::FireSubscription for every subscription in the list that you want to deliver.
You might, however, want your filter to perform more sophisticated logic based not just on the subscriber list, but also on the parameters passed by the publisher to this individual method. For example, you might want to deliver a stock price change event only if the new stock price is at least $5 above the cost of current holdings of that stock, which the publisher knows as part of its business logic. To perform filtering at this level, your filter class must support the event interface that it is filtering, in addition to IPub­lisherFilter. After calling PrepareToFire, the event system will query for this interface. If it finds the interface, the event system will then call the actual event method on your filter object. Within this method, you will look at the parameters, decide which subscribers you want to receive the event, and call FireSubscription for each subscriber.

Using Events with Queued Components

Events and Queued Components solve different problems. Events exist to handle the question of which publisher talks to which subscriber. The COM+ event mechanism tracks the desired delivery paths administratively, which saves you a lot of development time. Queued Components, on the other hand, exist to asynchronously transfer COM calls from clients to objects. The Queued Components mechanism keeps you from having to match the lifetimes of client and server, which again saves you a lot of development time. Events would be much less useful if they required all parties to an event delivery to be running at the same time.
Fortunately, events and Queued Components were designed to work well together, and the combination can be quite powerful. Queued Components and events can be combined in two different ways, both of which can work simultaneously if you want. Consider the following problem. A nurse carries her Handheld PC (H/PC) on her evening rounds of patient rooms and discovers that a patient has died in bed. She makes an entry to that effect in the patient chart program running on her H/PC. There are a number of other programs in the hospital enterprise that might want to know about the patient's death—the finance department to prepare the final bill, the food service to check what they had last fed the poor devil, and the undertaker to come collect the body.
You do not want the knowledge of which other programs need to hear about the patient's death to reside in the patient chart program; you do not want the patient death event to be tightly coupled to its subscribers. What would happen, for example, when a new application came online that also wanted to hear about patients' deaths, say PC Chaplain. You would have to rewrite the patient chart program and every other publisher in the enterprise. Not a good idea.
Loosely coupled events in COM+ are a natural for this situation. Every program that wants to hear about a patient death would make a subscription entry in the COM+ catalog. The publisher would then create a single event object and call a method on it. Keeping track of, locating, and delivering the event to all the registered subscribers is then the problem of the COM+ event system. You don't have to write any code for it; you just inherit the functionality from the operating system.
That's great, except that the different lifetimes and connection times of the different programs in the enterprise mean that synchronous DCOM just will not work to connect them. The odds that all the machines containing interested programs are running and connected to the network at the same time are vanishingly small. Using Queued Components abstracts the lifetime problem quite nicely—that's what it's for. But somehow you have to tie it to the event system. The nurse's application for the H/PC needs to be able to fire an event while disconnected, recording it for playback later when she reconnects to the main network. Subscribers also need to be able to receive their event notifications when they come online.
Both of these situations are quite easy to handle with Queued Components. You handle the first case, firing the event while the nurse is disconnected, by placing the event class on a central server machine and making the event class itself a Queued Component. It's a trivial matter: simply select the Queued checkbox shown previously in Figure 16, just as you do for any other component. The nurse's patient chart program will create the event object using a queue moniker, which means it will be connected to the Queued Components recorder instead of to the actual event object, as described in my June 1999 article. The calls that it makes to the event interfaces will be recorded for later playback. When the nurse reconnects to the network at the end of her rounds, the recorded calls to the event class will be forwarded and replayed on the main event machine, with the event fired to its subscribers at that time.
Why did I put the event class on a central server machine instead of directly on the nurse's H/PC? Because in version 1.0 of the COM+ event system, subscriptions are machine-specific. A subscriber can't just say that it wants to hear news about patient deaths from anyone on the network who sends it. By putting the event class on a central server machine, subscribers can subscribe to it there and know that all publishers will be using it to fire the events. Since this machine would then become the hub of your entire enterprise notification system, you would definitely want to provide a hot spare or two via Clustering Services to ensure you don't have a single point of failure.
What happens if any of the subscribers aren't running when the event class does get fired? It's simple: use Queued Components for them, too. When you mark the subscriber interface as queued, simply set up a subscription to an event class as I did earlier. When the publisher creates the event object and calls the event method, the event object will create a queued connection to the subscriber object and deliver its event notification that way.
What hazards or pitfalls are there to using Queued Components in an event situation? The main one involves the order in which events are delivered. Remember that Queued Components records and plays back all of the calls within the lifetime of a single object in a single message for Microsoft Message Queue Services (MSMQ), part of Windows 2000. All calls made to a single Queued Components object are guaranteed to be replayed in the order in which they were made. However, the results of more than one Queued Components session with more than one object are not guaranteed to be replayed in the order in which the sessions originally occurred on the client. This can cause trouble if the two sessions are related in some manner.
For example, suppose a publisher creates queued event object A, makes a few calls on it, and releases it. A single MSMQ message gets sent to the server containing all the calls made to object A. The same publisher now creates queued event object B in the same COM+ application, makes calls on it, and releases it. A single MSMQ message gets sent to the server containing all the calls made to object B. MSMQ works on a first-in-first-out (FIFO) basis, so you know that the message carrying object A's calls will get dequeued before the message carrying object B's calls. However, the Queued Components listener uses multiple threads to increase its throughput. The message carrying object B's calls can easily be dequeued on a thread different from the message carrying object A's calls, and essentially anything in the operating system can affect the scheduling of different threads.
Perhaps the calls to object A need to wait on some external object during playback, but the calls to object B don't. You have no way of knowing whether the calls made on one queued event object will be played back before the calls made on a different queued object. Any sort of time dependence is anathema to Queued Components, and except for the case of calls to a single object, so is any kind of order dependence. If it is important that one event necessarily take place before another, you have to make the calls on the same specific Queued Components object instance or provide some sort of outside application logic that enforces the desired order.
In the hospital scenario, for example, suppose you have one event that announces the death of a patient and another that announces that a patient has been transferred to a certain bed. If you send out the death event first, and ten minutes later send the event that announces that another patient has been transferred to the same bed, there is no way of guaranteeing that the death event will be processed first on every subscriber. A doctor's computer might actually process the events in an order that says, "There's a new patient in bed 330," and then "The patient in bed 330 just died," leading to a scene reminiscent of a Monty Python sketch.
To avoid this kind of trouble and the resulting lawsuits, you need to design your system in such a manner that events are self-contained—that is, they carry with them all the information needed to get the job done. In this example, the events depended on each other. Remember college chemistry labs, when you didn't label your test tubes but just remembered the order in which you had placed them in the rack? A better design would be to have each event carry patient identification data so that the doctor would at least know which patient had died.

Using Events with Transactions

Transactions are a useful feature. Any service that doesn't work and play well with transactions has a very limited future in the enterprise. Accordingly, the COM+ event system is compatible with transactions. If a publisher is participating in a transaction, you can arrange for the transaction to be propagated to the subscribers so that they can vote on its outcome.
For example, consider the case of business rules. Suppose you have a publisher that wants to make some updates to a database. Suppose you also have a set of business rules to guard the integrity of your database. These business rules can easily vary from one location to another, such as when different states impose different regulatory requirements. The publisher application does not want the burden of keeping track of all the rules in place at a particular installation; it's much more cost-effective if that's an administrator's job. The publisher just needs to work correctly with whichever rules exist.
Rather than write the publisher application to keep track of all the different business rules that apply in a particular situation, it would be very convenient to create a business rule event that a publisher would fire within the context of a transaction. An administrator would install subscriptions for all the business rules that the publisher needed to apply. You could use filtering to avoid wasting time on useless calls. For example, if the patient's age is less than 65, don't check the Medicare rules. The business rule subscribers would abort the transaction if any of their strictures were violated.
You do this by simply setting the event class and the subscriber components to support transactions using the Component Services snap-in. The publisher's transaction will be propagated to the event class and then to the subscribers. The subscribers can use the IObjectContext interface to vote to commit or abort the transaction.
A problem can arise with multiple deliveries of events if some of the subscribers are transacted and some are not. Suppose a publisher, within the context of a transaction, fires an event. A transacted subscriber uses resource managers to make changes to a database, and then another transacted subscriber aborts the transaction. The publisher's changes and the first subscriber's changes will be rolled back as the transaction aborts. So far so good. But what happens if a nontransacted third subscriber has made persistent changes to a resource, perhaps to a plain old flat file. When the publisher retries the transaction, all the transacted resources will be starting from their original state, as they threw away their changes when the first transaction aborted. The nontransacted subscriber, however, will not know that the transaction aborted, so it will not have rolled back its state. Beware of mixing transacted and nontransacted subscribers to the same event.

Event System Security

The event system has the same security considerations as any other portion of COM+. The standard security mechanisms used in the other portions of COM+ apply to events as well, and function in the same way. Security for installing an event class is the same as for installing any other component. Only members of the COM+ System application's Administrators role are allowed to do it. In addition, you want to ensure that a malicious subscriber doesn't add millions of subscriptions to deliberately gum up the works, and also that no unauthorized user is allowed to read the subscription database. These, too, are restricted to members of the Administrators role.
Event subscribers may need to authenticate the identity of their publisher to ensure that the organization offering them such a good rate on their foreign exchange transactions really is the bank that it claims to be. Subscriber applications do this by using the COM+ authentication mechanism in exactly the same manner as any other application.
An event subscriber may be configured as a COM+ server application running in a separate process from the publisher or as a library application sharing the same process. Remember that components sharing the same process share the same address space and can thus read and write any portion of each other's memory. Also, components sharing the same process run with the identity of the same security principal, unless they go out of their way to change it. Any outgoing calls made by the in-proc component will have the identity of the client process. The relationship between publisher and subscriber is usually less familiar and trusting than relationships between components in other places in COM+.
Remember that the design philosophy of loosely coupled events means that the publisher often doesn't know or care who is subscribing to its events. The publisher can trust the security mechanism to ensure that no subscriber is allowed access who shouldn't receive the information the publisher is sending with its events, but it's a long way from there to feeling comfortable allowing the subscriber into the publisher's address space.
If the event class and subscriber component are both configured as library applications, the subscriber object could be created in the publisher's address space. A publisher probably doesn't want that, unless you know exactly who all the subscribers are and trust them all not to hurt you—a level of trust unusual in the real enterprise, although not unheard of. If the publisher wants to allow subscribers configured in library applications to share its address space, select the "Allow in-process subscribers" checkbox shown previously in Figure 7. If this checkbox is not selected, the subscriber object will be created in a separate process, even if it is configured to run as a library app.

Conclusion

The ability to send notifications to interested parties is extremely useful in distributed computing. Matching publishers and subscribers has long been problematic. The COM+ event service in Windows 2000 provides a prefabricated infrastructure that allows subscribers to register for notifications and publishers to locate and notify subscribers very easily and without writing much code. Event notifications may be synchronous or asynchronous, may use transactions to maintain data integrity, and may use COM+ role-based security services.


For related information see: Com+Events at: http://msdn.microsoft.com/library/sdkdoc/ cossdk/pgservices_events_2y9f.htm.

  Also check http://msdn.microsoft.com for daily updates on developer programs, resources and events.

From the September 1999 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1999 Microsoft Corporation. All rights reserved.
Terms of Use      Privacy Policy.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft