C++ At Work

Event Programming

Paul DiLascia

Code download available at:CAtWork0602.exe(175 KB)

Q The Microsoft® .NET Framework lets me define events for managed classes and handle them using delegates and operator+=. Is there some way to do the same thing in native C++? It seems like this would be useful.

Q The Microsoft® .NET Framework lets me define events for managed classes and handle them using delegates and operator+=. Is there some way to do the same thing in native C++? It seems like this would be useful.

Several Readers

A Indeed it would be! Visual C++® .NET has something called the Unified Event Model that lets you implement native events the same way you would for a managed class (using the __event keyword), but alas native events have some obscure technical problems which the Redmondtonians don't plan to fix, so they've asked me to officially discourage you from using them. Does that mean C++ programmers have to live without events? Of course not! There's more than one way to skin a cat. I'll show you how to implement your own nifty event system with very little fuss.

A Indeed it would be! Visual C++® .NET has something called the Unified Event Model that lets you implement native events the same way you would for a managed class (using the __event keyword), but alas native events have some obscure technical problems which the Redmondtonians don't plan to fix, so they've asked me to officially discourage you from using them. Does that mean C++ programmers have to live without events? Of course not! There's more than one way to skin a cat. I'll show you how to implement your own nifty event system with very little fuss.

But before I do, let me write some words about events and event programming in general. It's an important topic. You can't program in the modern world without a solid understanding of events—what they are and when to use them.

Successful programming is all about managing complexity. A long time ago, when functions were called "subroutines" (I'm dating myself, I know), one of the main ways to manage complexity was top-down programming. You start with some high-level goal like "model the universe," then break it into smaller tasks like "model the galaxies" and "model the solar system" and so on until the tasks become small enough to implement in a single function. Top-down programming still works for procedural tasks, but it doesn't work well for systems that respond to real-world events that happen in a nondeterministic order. The classic example is a GUI where your program must respond when the user does something like press a key or move the mouse. Indeed, event programming was largely spurred by the advent of graphical user interfaces.

In the top-down model, high-level components at the top of the food chain bark orders to lower-level components by calling functions like DoThis and DoThat to perform various tasks. But sooner or later, the lower-level components need to talk back. In Windows®, you can call Rectangle or Ellipse to draw a rectangle or ellipse, but eventually Windows needs to call your app to paint its window. But your app doesn't even exist yet, it's still behind schedule! So how can Windows know which function to call? This is where events come in.

Figure 1 Top-Down vs. Bottom-Up

Figure 1** Top-Down vs. Bottom-Up **

At the core of every Windows-based program—whether directly coded in C, or hidden behind layers of MFC or .NET Framework classes—is a window procedure that processes messages like WM_PAINT, WM_SETFOCUS, and WM_ACTIVATE. You (or MFC or .NET) implement the window procedure and pass it to Windows. When it's time to paint, change the focus, or activate your window, Windows calls your procedure with the appropriate message code. The messages are the events. Your window procedure is the event handler.

If procedural programming is top-down, event programming is bottom-up. In a typical software system, function calls flow downward from higher-level components to lower-level ones; whereas events percolate up in the opposite direction. Figure 1 illustrates this pattern. Of course, the real world in which we romp is not always so neatly hierarchical. Many software systems look more like Figure 2.

Figure 2 A Mixed Model

Figure 2** A Mixed Model **

So what exactly is an event? Essentially, it's a callback. Instead of calling a function whose name is known at compile time, the component calls a function you provide at run time. In Windows, it's the window proc. In the .NET Framework, it's a delegate. Whatever the terminology, events provide a way for one software component to call another without knowing until runtime what function to call. The callback is called the event handler. Raising or firing an event means calling the handler. To get the ball rolling, the receiving component first gives the source component a pointer to its event handler, a process called registration.

Here are some common situations in which events are used.

To Notify Clients About Real-World Events The user pressed a key; the clock struck midnight; the fan failed and the CPU is on fire.

To Report Progress of Lengthy Operations When copying files or searching a huge database, a component might periodically raise an event to report how many files have been copied or how many records have been searched.

To Report When Something Significant or Potentially Interesting Has Happened If you use IWebBrowser2 to host Microsoft Internet Explorer in your app, the browser will notify you before and after navigating to a new page, or when it creates a new window—among other things.

To Invoke an Application-Supplied Algorithm The C runtime library function qsort sorts an array of objects, but you must supply the function that compares objects. Many STL containers let you play the same trick. Most programmers wouldn't call the qsort callback an event, but there's no reason you can't think of it that way. It's the "time to compare" event.

Some readers occasionally ask: What's the difference between an exception and an event? The main difference is that exceptions represent unexpected conditions that aren't supposed to occur. For example, your program runs out of memory or encounters divide-by-zero. Oops. These are aberrant situations you hope won't happen, and yet if they do your program has to cope. Events, on the other hand, are part of normal everyday operation and are fully expected. The user moves the mouse or presses a key. The browser just navigated to a new page. From a control-flow perspective, an event is a function call, whereas an exception is a long jump across the stack, with unwinding semantics to destroy lost objects.

A common misconception about events is that they're asynchronous. While events are often used to handle user input and other actions that happen asynchronously, the events themselves happen in synchronous fashion. Raising an event is the same thing as calling the event handler(s). In pseudocode, it looks something like the following snippet:

// raise Foo event for (/* each registered object */) { obj->FooHandler(/* args */); }

Control passes immediately to the event handler and doesn't return until it's done executing. Some systems provide a way to raise events asynchronously; for example, Windows lets you use PostMessage instead of SendMessage. Control returns immediately from PostMessage and the message gets processed later. But .NET Framework events and the events I'll discuss here are handled immediately when you raise them. Of course, you can always raise an event from code that runs in a separate thread, or you can use asynchronous delegate invocation to execute each event handler in the thread pool, in which case the event happens asynchronously relative to the main thread.

The way Windows does events, with its omniversal window proc and type-deadly WPARAM/LPARAM parameters, is downright primitive by modern programming standards. Yet every Windows program uses this mechanism, even today. Some programmers even create invisible windows just to pass events. The window proc is not a true event mechanism because Windows allows only one window proc per window, though multiple procs can be linked if each proc calls the one before it. This process is known as subclassing. In a true event system, more than one recipient can register for the same events in a non-hierarchical way.

In the .NET Framework, events are full-fledged citizens. Any object can define events, and multiple objects can listen to them. In .NET, events work using delegates, which is Framework terminology for a callback. Most important, delegates are type-safe. No more void*'s or WPARAM/LPARAM.

To define an event using the Managed Extensions, you use the __event keyword. For example, the Button class in Windows::Forms has a Click event:

// in Button class public: __event EventHandler* Click;

EventHandler is a delegate for a function that takes an Object (the sender) and EventArgs:

public __delegate void EventHandler( Object* sender, EventArgs* e );

To receive events, you implement a handler member function with the right signature and create a delegate to wrap it, then invoke the event's operator+= to register your handler/delegate. For the Click event, it looks like this:

// event handler void CMyForm::OnAbort(Object* sender, EventArgs *e) { ... } // register my handler m_abortButton->Click += new EventHandler(this, OnAbort);

Note that the handler function must have the signature defined by the delegate. All of this is Managed Extensions 101. But you didn't ask about managed events, you asked about native events—how can you implement events in native C++? C++ has no built-in event mechanism, so what can you do? You could use a typedef to define a callback and make clients supply it, something similar to qsort—but that's so old-hat. Not to mention cumbersome if you are working with several events. And it's especially ugly if you want to use member functions as the event handlers, as opposed to static extern functions.

A better way is to create an interface that defines the events. That's how COM does it. But you don't need all the overhead of COM code in C++; you can use a simple class. I wrote a class called CPrimeCalculator that shows how; it finds prime numbers. Figure 3 shows the code. CPrimeCalculator::FindPrimes(n) finds the first n prime numbers. While it's working, CPrimeCalculator can raise two kinds of events: a Progress event and a Done event. The events are defined in an interface IPrimeEvents. IPrimeEvents isn't an interface in the .NET or COM sense; it's a plain old C++ abstract base class that defines the signature (parameters and return type) for each event handler. Clients that handle CPrimeCalculator events must implement IPrimeEvents, then call CPrimeCalculator::Register to register their interface. CPrimeCalculator adds the object/interface to its internal list. As it tests every integer for primeness, CPrimeCalculator periodically reports how many primes it has found so far:

// in CPrimeCalculator::FindPrimes for (UINT p=2; p<max; p++) { // figure out if p is prime if (/* every now and then */) NotifyProgress(GetNumberOfPrimes()); ... } NotifyDone();

Figure 3 CPrimeCalculator

Prime.h

#pragma once #include <vector> #include <list> using namespace std; ///////////////// // Interface for prime events. Not an interface in the .NET or COM sense, // just an ordinary old abstract base class. Clients that handle prime // events must derive from this and implement the virtual functions. // class IPrimeEvents { protected: IPrimeEvents() { } virtual ~IPrimeEvents() { }; public: virtual void OnProgress(UINT nPrimes) = 0; virtual void OnDone() = 0; }; // List (vector) of prime numbers. typedef vector<UINT> PRIMELIST; ////////////////////////////////////////////////////////////////// // Class to calculate 1st n prime numbers. // class CPrimeCalculator { protected: list<IPrimeEvents*> m_clients; // list of client objects to notify PRIMELIST m_primes; // list (vector) of primes numbers found // Helpers BOOL IsPrime(UINT p); // is prime? void AddPrime(UINT p) // add prime to list { m_primes.push_back(p); } // Raise events void NotifyProgress(UINT nFound); void NotifyDone(); public: CPrimeCalculator() { } ~CPrimeCalculator() { } // Find 1st n prime numbers. UINT FindPrimes(UINT n); // Get number of primes found so far. UINT GetNumberOfPrimes() { return (UINT)m_primes.size(); } // Get all the primes found. const PRIMELIST GetPrimes() { return m_primes; } // Register client to receive events void Register(IPrimeEvents* client) { m_clients.push_back(client); } // Unregister client from receiving events. void Unregister(IPrimeEvents* client) { m_clients.remove(client); } };

Prime.cpp

#include "StdAfx.h" #include "Prime.h" const NOTIFYINCR = 100; // notify every 100 tries const FIRSTPRIME = 2; // important! ////////////////// // Find 1st n prime numbers. // UINT CPrimeCalculator::FindPrimes(UINT nFind) { m_primes.reserve(nFind); for (UINT p=FIRSTPRIME; GetNumberOfPrimes()<nFind; p++) { if (IsPrime(p)) { // if prime: AddPrime(p); } if (p % NOTIFYINCR == 0) { // report progress by raising event NotifyProgress(GetNumberOfPrimes()); Sleep(30); // simulate work; otherwise too fast! } } NotifyDone(); return GetNumberOfPrimes(); } ////////////////// // Helper fn to test if number p is prime: // Try dividing by all primes found so far, up to SQRT(p). // BOOL CPrimeCalculator::IsPrime(UINT p) { PRIMELIST::iterator it; for (it=m_primes.begin(); it!=m_primes.end(); it++) { UINT factor = *it; if (p%factor == 0) { // if divisible: return FALSE; // ..it's not prime } if (factor*factor > p) // if no more primes to try: return TRUE; // ..number must be prime } // Should only get here when p==2 (list is empty). ASSERT(p==2); return TRUE; } ////////////////// // Notify clients of progress so far. // void CPrimeCalculator::NotifyProgress(UINT nFound) { list<IPrimeEvents*>::iterator it; for (it=m_clients.begin(); it!=m_clients.end(); it++) { (*it)->OnProgress(nFound); } } ////////////////// // Notify clients I'm done. // void CPrimeCalculator::NotifyDone() { list<IPrimeEvents*>::iterator it; for (it=m_clients.begin(); it!=m_clients.end(); it++) { (*it)->OnDone(); } }

CPrimeCalculator calls internal helper functions NotifyProgress and NotifyDone to raise the events. These functions iterate the client list, calling the appropriate event handler for each client. In code, it looks like this:

void CPrimeCalculator::NotifyProgress(UINT nFound) { list<IPrimeEvents*>::iterator it; for (it=m_clients.begin(); it!=m_clients.end(); it++) { (*it)->OnProgress(nFound); } }

In case you're not up on your STL, recall that for iterators the dereference operator returns the current object, so the line inside the for loop in the preceding snippet is equivalent to:

IPrimeEvents* obj = *it; obj->OnProgress(nFound);

There's a similar function NotifyDone that raises the Done event, which has no parameters. Figure 3 shows the code. You might think there's no need for a Done event since the client knows CPrimeCalculator is done when FindPrimes returns control. And you'd be right—except for one thing. There may be more than one client registered to receive events, and possibly not the same one that calls CPrimeCalculator::FindPrimes. Figure 4 shows my test program, PrimeCalc. PrimeCalc implements two different event handlers for prime events. The first handler is the main dialog itself, CMyDlg, which implements IPrimeEvents using multiple inheritance. The dialog handles OnProgress and OnDone by displaying the progress in a dialog window (see Figure 5) and beeping when done. The other event handler, CTracePrimeEvents, also implements IPrimeEvents. Its implementation displays information in the diagnostic (TRACE) stream. Figure 6 shows a sample run in my TraceWin applet. (See my March 2004 column, or download TraceWin from www.dilascia.com.) I wrote CTracePrimeEvents to show how more than one client can register for the same events.

Figure 4 PrimeCalc.cpp

#include "stdafx.h" #include "StatLink.h" #include "resource.h" #include "Prime.h" #include "TraceWin.h" const NFIND = 1000; // find first 1000 primes ////////////////// // This class displays IPrimeEvents in the diagnostic (TRACE) stream. Used // to show that multiple objects can register for the same event stream. // class CTracePrimeEvents : public IPrimeEvents { public: void OnProgress(UINT nPrimes) { UNREFERENCED_PARAMETER(nPrimes); TRACE(_T(" PrimeCalc: Found %d primes.\n"), nPrimes); } virtual void OnDone() { TRACE(_T(" PrimeCalc: Done\n")); } }; ////////////////// // Main dialog: implements IPrimveEvents to handle events from // CPrimeCalculator. // class CMyDlg : public CDialog, public IPrimeEvents { protected: . . CEdit m_wndFeedback; // IPrimeEvents handlers void OnProgress(UINT nPrimes); void OnDone(); // message handler void CMyDlg::OnCompute() }; ////////////////// // User pressed "Compute" button. // void CMyDlg::OnCompute() { CWaitCursor wc; // display wait cursor m_wndFeedback.SetWindowText(_T("Calculating...")); // create prime calculator and hook events CPrimeCalculator pc; // Prime calculator CTracePrimeEvents tn; // TRACE notifier pc.Register(this); // register myself for events pc.Register(&tn); // .. and trace notifier too pc.FindPrimes(NFIND); // find prime numbers // Build message showing prime numbers. CString msg,temp; int any = 0; PRIMELIST primes = pc.GetPrimes(); PRIMELIST::iterator it; for (it=primes.begin(); it!=primes.end(); it++) { UINT p = *it; if (any) msg += _T(", "); else any++; temp.Format(_T("%d"),p); msg += temp; } // show results in feedback window m_wndFeedback.SetWindowText(msg); } ////////////////// // Event handler: Show progress. // void CMyDlg::OnProgress(UINT nPrimes) { CString s; s.Format(_T(«Found %d primes so far...»), nPrimes); m_wndFeedback.SetWindowText(s); m_wndFeedback.UpdateWindow(); } ////////////////// // Event handler: Beep when done. // void CMyDlg::OnDone() { MessageBeep(0); }

Figure 5 PrimeCalc in Action

Figure 5** PrimeCalc in Action **

From the perspective of a programmer writing an application that uses CPrimeCalculator, handling events is simple and straightforward. Derive from IPrimeEvents, implement the handler functions, then call Register. For programmers writing classes that raise events, the process is a bit more tedious. First you have to define the event interface. That's not bad. But then you have to write Register and Unregister functions, not to mention a NotifyFoo function for each Foo event. Quite a bore if you have 15 events, especially since each NotifyFoo function has the same pattern:

void CMyClass::NotifyFoo(/* args */) { list<IPrimeEvents*>::iterator it; for (it=m_clients.begin(); it!=m_clients.end(); it++) { (*it)->OnFoo(/* args */); } }

Figure 6 PrimeCalc Output in TraceWin

Figure 6** PrimeCalc Output in TraceWin **

NotifyFoo iterates the client list, calling the appropriate OnFoo handler for each registered client and passing whatever parameters are required. Isn't there some way to implement this generically using macros or templates or something, in order to encapsulate the drudgery and spare yourself the misery of writing such boring boilerplate code? In fact, there is. I'll show you how next month. Same bat time. Same bat channel. Until then—Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site, www.dilascia.com.