techvanguards.com
Last updated on 1/25/2016

Implementing an Event Framework
by Binh Ly

Download the source code for this article

One of the complexities of developing COM-based distributed applications is implementing a stable and consistent event infrastructure. A lot of professionals often ask me:

  1. I can implement connection points easily, but how do I trigger events from outside of the object that contains the connection point? Or better yet, how do I trigger events from a second thread or an external application?
  2. How do I combine threads and connection points to produce some sort of an asynchronous event infrastructure?

Event Classes

In a nutshell, the answer to these questions is to develop a loosely coupled event system. A loosely coupled event system introduces the idea of separating an event into an abstraction called an event class. An event class is simply an entity that represents any custom event that we define in our system together with all of that event's interested parties. For instance, we define a trading stock update event class as the trading stock update event plus all clients/subscribers that are interested in this particular event:

Stock Update event class = Stock Update event + Subscribers to Stock Update event

In this context, an event subscriber is simply a party that's interested in a particular event. To reiterate, an event class is the event itself combined with all known subscribers of that event. An interesting aspect of the event class is that we can instantiate an event class just like any other COM object. Furthermore, the event class exposes the interface of the event it defines. When we call a method on an instantiated event class, we are actually triggering the event method to all subscribers of that event.

The introduction of an event class enables us to build sophisticated event mechanisms than possible when simply using the normal tightly coupled event system such as the COM connection point technology. When using connection points, a client that wishes to receive events must first find an instance of a server object that it can connect to. Once it finds and connects to a server object, it will normally receive events that originate from that server object. This means that in order to trigger the event from outside of the server object, such as from a separate thread, object, or application, the source of the event needs to somehow acquire a pointer to the connected server object and then trigger the event from there. Although it is possible to programmatically do this kind of thing, it is very cumbersome and error-prone. This is why we call it a tightly coupled event system - the event source and the event receiver have a coupling that can be hard to break to satisfy some event requirements.  

You'll notice that with the event class, it doesn't matter anymore how events are triggered. Whether it's in a separate thread, object, or application, the event source simply creates an instance of the event class and then calls a method on the event class to trigger the event. Unlike the connection points mechanism, the source of an event doesn't care and doesn't need to know how to get to the list of subscribers of a given event - this is now the responsibility of the event class. An interesting aspect of this mechanism is that an event class can then do some interesting things such as instantiating subscribers on the fly (for subscribers that may want to be instantiated and notified only when the event arrives), filtering events, and enabling administrative event configuration and maintenance. In fact, the COM+ event services, which is based on event classes, allows us to configure event classes and subscriptions easily within the Component Services administrative tool.

EventService - A Simple Event Framework

So the question you're probably anxiously waiting to ask is "How do I implement event classes? Do I wait for COM+ and Windows 2000 to be able to do this?" Fortunately, the answer is no! As I was doing research in this area, I found out that it is relatively easy to produce an event framework using the concepts of event classes that will work today (on NT & 9x) and on Windows 2000, when it arrives. We'll call this framework "EventService". Note that we're not going to build an event system that's similar to what COM+ provides - that's way too complicated. What we'll do is build a framework that enables us to easily develop applications that can make use of event classes without forcing you to learn much about the nitty-gritty details of how to properly implement events. I believe this framework will be very valuable for beginners who know what they need but don't have much experience in the intricacies of COM events, as well as experts who know the details but would rather not have to duplicate code from project to project to reproduce similar event requirements.

An Overview of EventService

Before going into the details of implementing EventService, I'd like to show you first how you, as a developer, would develop your COM applications that  make use of events. What I'm going to show you is a bit of a shift in the model of how you used to implement events using the old COM connection points technology. So please read carefully to make sure you understand what I'm talking about. 

First, before starting to implement events, we should always design the event interfaces and methods up front. Once we come up with the design, we create a "dummy" COM server and define the events in this server. The purpose of this server is to serve as a repository for definitions of all events that we'll use in our applications. Therefore this server must only contain event definitions, and only event definitions. To further illustrate, let's assume we have a need to create an event interface named IChatEvent for a chat room event notification:

IChatEvent = interface
  procedure OnMessage (Sender, Message)
end;

To create an event definition for IChatEvent, we simply create a new ActiveX DLL server. Then we create a new automation object named ChatEvent, after which we come up with something like this:

Figure: Defining IChatEvent

Looking at the above definition, ChatEvent defines our event class. In other words, anyone who wants to trigger an IChatEvent event method will instantiate ChatEvent asking for IChatEvent, and then call the desired IChatEvent method: 

var Event : IChatEvent
//create ChatEvent event class
Event = CreateEventClass ("ChatEvents.ChatEvent")  
//trigger OnMessage event method
Event.OnMessage ("From Me", "Hello World")  

Note that the above 3 lines is all that's required for us to be able to trigger events, regardless of whether or not we're in a separate thread, object, or application. Imagine how simple this is for beginners, as well as hard-core experts. Of course, EventService will have to somehow kick in to be able to handle the details involved in realizing ChatEvent as an event class but we'll get to that later. 

Now that we've seen how to trigger events, let's look at how we can easily act as event subscribers to be able to receive events. Using EventService, we'll need to create something called a subscription object every time we want to receive a particular event. In simple terms, the subscription object represents our connection as a subscriber to a particular event. As long as the subscription object is alive, we'll be able to receive events from a particular event class. Then when we're not interested in the event anymore, we simply destroy the subscription object. 

Note that the subscription object does not receive the events, per se. We'll need a separate event handler object for that. This object is the one that actually implements the event interface. What the subscription does is bind this event handler object to the event class so that it can start receiving events from the event class. Once we destroy the subscription, the subscription object will automatically disconnect our event handler object from the event class. We'll talk more about the event handler object later in this article.

With that said, creating an event subscription is as simple as instantiating a subscription object and passing in the desired parameters necessary to establish an event connection:

var Subscription : IUnknown
//create event subscription object connecting 
//EventHandlerObject to ChatEvents.ChatEvent
Subscription = CreateEventSubscription (
  "ChatEvents.ChatEvent", //event class name
  EventHandlerObject      //event handler object
)
//at this point, any events triggered on the ChatEvents.ChatEvent 
//event class will be received by EventHandlerObject
...
//destroy subscription object to disconnect EventHandlerObject
Subscription = NIL

The above lines will be the standard mechanism in establishing an application that wants to receive events using EventService. If you've noticed, there as absolutely no mention of what's going on under-the-hood which is how it should be. Over the past few months, a lot of developers ask me things like how to handle threads, how to trigger events from separate objects, how to perform marshaling, etc. so hopefully, this framework will answer most, if not all, of your questions.

Inside EventService

This section is for those of you who are interested to know how I built EventService. If you have no inclination to learn the details, you can simply skip to the next section where you can learn how to use EventService immediately.

I built EventService using Delphi. In the interest of readership of both Delphi and C++ Builder developers, I will demonstrate the details using pseudocode instead of Delphi code. I believe there is no reason for me to duplicate EventService in C++ Builder because the Delphi-built framework also works for C++ Builder applications. 

EventService is implemented as a COM server named EventService. EventService can hold any number of event classes from any number of applications. In other words, if we have 10 COM applications wanting to use EventService for 10 different event classes, we'll only need 1 EventService application running on our system. Inside EventService is a global event catalog that stores:

  1. All event classes that are currently available
  2. A list of subscribers for each event class

The event class list is stored in a class named TEvents. TEvents allows us to do 2 things, among others:

  1. Obtain a list of subscribers for any given event class. This is used to trigger events to subscribers of a specific event class
  2. Add a new event class to the list

In simple terms:

TEvents = class
  //adds a new event class whose name is specified by EventName
  function Add (const EventName : string) : ISubscribers;
  //obtains the subscribers list for the event class whose name is specified by EventName
  property EventSubscribers [const EventName : string] : ISubscribers;
end;

ISubscribers represents an internal list of subscribers per event class and is implemented in a class named TSubscribers. It is defined as follows:

ISubscribers = interface
  //add a new subscriber/event handler and return subscriber ID into Cookie
  procedure Add (const Subscriber: IUnknown; out Cookie: Integer); safecall;
  //delete a subscriber specified by the ID in Cookie
  procedure Delete (Cookie: Integer); safecall;
  //returns a subscriber at specified index
  property Item [Index: Integer]: ISubscriber;
  //returns the count of subscribers in this list
  property Count: Integer;
end;

Given TEvents and TSubscribers, it should be easy to see how an event class is implemented. We simply create a separate object that references the list of subscribers for a given event class extracted from TEvents, and at the same time, have this object implement the event interface for the particular event. In order to quickly produce such an object, I rely on the COM dispinterface (IDispatch.Invoke) mechanism which requires that our event interfaces support IDispatch. Note that this is not late binding - it uses the dispinterface binding technique that we all know from the vast majority of connection point implementations today. 

The event class construction technique I mentioned above is implemented in a class named TSimpleEventClass. The most important part of TSimpleEventClass is it's implementation of IDispatch.Invoke and how it triggers an event method for each subscriber:

procedure TSimpleEventClass.Invoke;
begin
  i = 0;
  //iterate subscribers list
  while (i < Subscribers.Count) do
  begin
    //get a single subscriber
    Subscriber := Subscribers.Item [i];

    //invoke subscriber as IDispatch
    //note: this implicitly assumes that the subscriber/sink is
    //handling dispinterface events!
    if Succeeded (Subscriber.QueryInterface (IDispatch)) then
      Subscriber.Invoke (...);

    //move to next subscriber
    i = i + 1;
  end;
end;

That's basically how the event class works. When the client creates an event class, it will eventually get a pointer to an IDispatch interface implemented by TSimpleEventClass. You can inspect the source code to trace into the details of this.

Using TEvents and TSubscribers, it is also very easy to implement the subscription object. We simply ask the TEvents instance to either add a new subscriber (using the TEvents.EventSubscribers [] property and then the ISubscribers.Add method) or create a new event class subscriber list and add a new subscriber to it (using the TEvents.Add and ISubscribers.Add methods). The subscription object will then represent a live connection from a subscriber to an event class. Once the client destroys the subscription object, it simply calls ISubscribers.Delete to remove the live connection from the associated TSubscribers instance. I won't go into the details of this but you look at the source code and inspect the TSubscription class together with the TSimpleEventCatalog SubscribeEvent and UnsubscribeEvent methods to see how this is done.

So what about COM+? 

EventService can be instantiated to use either the event mechanism described above or using the native COM+ event services. We do this through the InitializeEventService function that we have to call in every application that wishes to use EventService. Once we specify that we are using COM+ events, our event class simply becomes the native event class instance that COM+ fabricates, and our subscription object simply becomes a COM+ transient event subscription. Note that this is all transparent to you as a developer. I won't go into the details of COM+ events because that's not my goal here. My goal is to not force you to learn the COM+ event services while still being able to implement COM+ events in your applications. Regardless of COM or COM+ deployment, all we'll ever see are the concepts of an event class and a subscription object, nothing more, nothing less.

Using EventService

For the sake of simplicity, let's create a simple multi-user chat application. Readers who are familiar with my COM events article from last year will notice that this application is similar to the sample application in that article. However, this application is probably 10x much simpler in terms of implementation than the one in that tutorial. This is exactly the goal I had in mind for this article - provide a mechanism/framework to implement COM events in the simplest manner possible!

We'll start by designing our simple event interface, IChatEvent:

IChatEvent = interface
  procedure OnMessage (Sender, Message)
end;

IChatEvent is an event interface implemented by clients who are interested in receiving messages from a conceptual online chat room. In simple terms, if we have a group of online chatters talking to each other, a chatter who wishes to broadcast a message triggers the IChatEvent.OnMessage event method. Correspondingly, a chatter who wishes to receive the IChatEvent.OnMessage event will subscribe to the ChatEvent event class using the subscription mechanism described above.

Implementation

First we define IChatEvent in a separate COM server that serves as our event repository. We do this by creating a new ActiveX DLL using the using the File | New | ActiveX | Active X Library menu from the IDE. Let's save this project as "ChatEvents.dpr". Then we add a new automation object to it using the File | New | ActiveX | Automation Object menu from the IDE. Let's name this object ChatEvent so that Delphi will create a coclass named ChatEvent with an IChatEvent interface. Then we go into the type library editor (TLE) and define the IChatEvent interface based on it's definition:

Figure: Defining IChatEvent

This will be our ChatEvent event class definition. Strictly speaking, this event class is named "ChatEvents.ChatEvent", which is simply the ProgID of this coclass. That's it for our ChatEvent event class. Note that this server is simply a repository and does not need to be deployed nor registered on your system. The sole purpose of this server is for development and for ease of configuration of COM+ events as we shall see later. 

Let's now look at the chat client application:

Figure: Chat client application main form

You'll find the definition of the above form in ChatFrm.pas. Whenever a chatter wants to broadcast a chat message, he first types in a message into the message edit box (ChatMessage) and then hits the Send button. The Send button simply creates a ChatEvent event class and triggers the IChatEvent.OnMessage method. Here's what the send code actually looks like:

uses 
  EventUtils, ChatEvents_TLB;

procedure TChatForm.SendClick(Sender: TObject);
var
 
//note we can only use dispinterface events as
  //discussed in the article
  Events : IChatEventDisp; 
begin
  //check if Chat Message edit box has text in it
  if ChatMessage.Text <> '' then
  begin
    //create ChatEvent event class
    CreateEventClass (
      'ChatEvents.ChatEvent', //event class name
      Events //output event class
    );
    //trigger OnMessage event method
    Events.OnMessage (FUserID, ChatMessage.Text);
    //clear message for next one
    ChatMessage.Text := '';
  end
  else
    //beep if no message to send
    Beep;
  //restore focus to edit
  ActiveControl := ChatMessage;
end;

I've highlighted the relevant lines above where the ChatEvent event class is instantiated and the OnMessage method is called. Also note that Events is defined as type IChatEventsDisp because we can only use dispinterface calls as discussed above [this is not a limitation, however, when we use the native COM+ event services]. 

And to prove that we can trigger events as easily in a separate thread, I've also included a Send from Thread button on the form whose code is surprisingly similar to the above:

uses 
  EventUtils, ChatEvents_TLB;

procedure TChatForm.SendFromThreadClick(Sender: TObject);
var
  SendThread : TSendThread;
begin
  //check if Chat Message edit box has text in it
  if ChatMessage.Text <> '' then
  begin
    SendThread := TSendThread.Create (FUserID, ChatMessage.Text);
    SendThread.Resume;
    //clear message for next one
    ChatMessage.Text := '';
  end
  else
    //beep if no message to send
    Beep;
  //restore focus to edit
  ActiveControl := ChatMessage;
end;

procedure TSendThread.Execute;
var
  //note we can only use dispinterface events as
  //discussed in the article
  Events : IChatEventDisp;
begin
  //initialize COM
  CoInitialize (NIL);
  //create ChatEvent event class
  CreateEventClass (
    'ChatEvents.ChatEvent', //event class name
    Events //output event class
  );
  //trigger OnMessage event method
  Events.OnMessage (FUserID, FMessage);
  //free all COM pointers before CoUninit or else we're in trouble
  Events := NIL;
  //uninitialize COM
  CoUninitialize;
end;

As you can see, there is absolutely no difference in triggering an event between a secondary thread and the normal way. No marshaling, no synchronization, nothing extra!

Since we've seen how to trigger events, let's now look at how we subscribe to events. The subscription process is actually very simple: we create a subscription object to establish our connection as an event handler to a particular event class, and we destroy the subscription object when we're no longer interested in receiving events. In our chat client, the Login button establishes a subscription and the Logout button terminates a subscription. This is what the Login button code looks like:

procedure TChatForm.LoginClick(Sender: TObject);
begin
  //create event subscription
  CreateEventSubscription (
    'ChatEvents.ChatEvent', //event class name
    ChatEventHandler, //event handler object
    IChatEvent, //event IID
    FSubscription //output event subscription
  );
  //notify user the we're ready to receive events
  ShowMessage ('Now ready to accept incoming chat messages');
end;

In the highlighted code, ChatEventHandler is our event handler object, IChatEvent is the interface ID of the event that we want to handle, and FSubscription is an output IUnknown pointer returned from CreateEventSubscription. FSubscription represents our subscription object. What you're probably wondering right now is what exactly is the ChatEventHandler object. If you're a fan of my EventSinkImp utility, it's simply a component generated by EventSinkImp. This means that we don't have to worry about writing code for this object, we'll let EventSinkImp do the hard work for us.

EventSinkImp is now on version 1.7. This new version has an enhancement that enables you to produce event handler objects mentioned in this article. If you haven't downloaded the latest copy, you can visit the EventSinkImp site by navigating to the Products section on this site. If you're unsure what your EventSinkImp version is, run it and click the About button.

ChatEventHandler is simply the TIChatEvent class generated by EventSinkImp. I was able to do this by running EventSinkImp, manually loading our ChatEvents.tlb file (use the "..." button in EventSinkImp's main form to browse to ChatEvents.tlb), checking the IChatEvent interface, and then performing an import on this type library. 

That does it for creating subscriptions. To terminate a subscription, we simply set the FSubscription variable to NIL at the appropriate time:

procedure TChatForm.LogoutClick(Sender: TObject);
begin
  //destroy subscription
  FSubscription := NIL;
end;

One last thing before I forget. Before your application can make use of EventService, you'll need to initialize it and then uninitialize it when you're done. In our chat application, the following code in ChatClient.dpr shows you how to do this:

uses 
  EventUtils;

begin
  Application.Initialize;
  //initialize event service
  InitializeEventService;
  Application.CreateForm(TChatForm, ChatForm);
  Application.Run;
  //uninitialize event service
  UninitializeEventService;
end.

If you want to make use of the COM+ event services, simply call InitializeService and pass in True as a parameter:

procedure InitializeEventService (UseCOMPlusEvents : boolean = False);

A Note on COM+

If you are using the COM+ event services as mentioned above, you'll need to build and install the event repository project that we created earlier. First build ChatEvents.dpr into ChatEvents.dll. Then go into the Component Services administrative tool and install ChatEvents.dll as an event class into a COM+ application (you can create a new COM+ application, perhaps named ChatEvents,  if you need to). To do this, when in the COM Component Install Wizard dialog, select the Install new event class(es) option and pick ChatEvents.dll from there.

Deploying EventService

EventService is is normally deployed and registered as an EXE COM server. To register EventService, simply run EventService.exe /regserver once.

If you want to deploy EventService in Microsoft Transaction Server (MTS), simply create a separate package for it (make sure the package Activation is set up as a Server package instead of Library package) and install EventService.dll into the package.

If you want to deploy EventService as a COM+ application in Windows 2000, simply create a separate COM+ application for it (make sure the application Activation is set up as Server application instead of Library application) and install EventService.dll into the COM+ application.

When deploying EventService as a COM+ application or in an MTS package, make sure that you  do not also deploy and register EventService.exe as a standard EXE on the same machine. Also, my tests on Windows 2000 Beta RC2 indicate that EventService does not work when deployed as a COM+ application and you are not using the COM+ event services. Whatever reason is causing this remains to be seen once Windows 2000 ships. In other words, if you deploy EventService into a COM+ application, make sure all your client applications initialize it as follows:

InitializeEventService (True);  //True means use COM+ event services

When using EventService as a standalone EXE in a NT or 9x environment, make sure you have an NT4 SP3 equivalent installation of DCOM. For NT, this is Service Pack 3. For 9x, this is at least DCOM 9x 1.2 and above.

Conclusion

In this tutorial, I've shown you a very easy-to-use yet very effective COM event framework. This framework does not replace COM connection points or any hand-coded callback mechanisms you've come to learn and love for the past few years. However, I can say for sure that this framework can tremendously help professionals who want a simple, quick, and effective way to build COM event mechanisms into their applications. Good luck and have fun!

A Word of Caution

All source code presented here is not necessarily production quality code. In particular, tasks like error handling, multithreading, optimizations, etc. were not taken into consideration because they are not relevant to the tutorial topic. Therefore, I do not make any guarantees that the code will work for you, and you cannot hold me liable for any damages resulting from the use (or misuse) of this material. In other words, don't blame me if you get ****ed!

Copyright (c) 1999-2011 Binh Ly. All Rights Reserved.