OLE Controls: Understanding Events

Dale Rogerson
Microsoft Developer Network Technology Group

Created: October 5, 1994

Abstract

This article explores the purpose and use of OLE Control events, using the Spindial application source code to illustrate concepts. Spindial is included in the OLE Control Developer's Kit (CDK), which is provided with Visual C++™ version 2.0. This article is intended for Microsoft® Foundation Class Library (MFC) programmers who may not have any knowledge of OLE beyond the MFC OLE classes.

Introduction

When my colleague Ruediger Asche reviewed a draft copy of my article "OLE Controls: State of the Union" for the July 1994 edition of the Microsoft® Developer Network (MSDN) Library, he jotted in the margin: "What's an event? Define it." To answer his question, I dug through the vast amount of information in the MSDN Library looking for the definition of an event. Here's a sampling of what I found:

In "OLE Comes to Custom Controls" (Microsoft Developer Network News, May 1994, Number 3, May Features):

"[Events are] actions triggered by the control in response to some external actions on the control, such as clicking a mouse button or pressing a key. . . . To work properly with controls, the container also implements an IDispatch interface for managing a control's events . . . ."

Solveig Whittle

In "OLE Custom Controls Backgrounder" (MSDN Library Archive, Backgrounders and White Papers, Operating System Extensions):

"The OLE custom control architecture also adds support for events, a standard way for the control to invoke a call back in its container."

In "Developing OLE Custom Controls" (MSDN Library Archive, Conferences and Seminars, Tech*Ed, March 1994, Visual C++):

"Each control defines its own events that it will trigger when things happen to the control; each event is a function with parameters. When the control wants to fire an event, it invokes this IDispatch::Invoke with its own dispatch ID and appropriate parameters. By default, the container will do nothing with these events unless it has been specifically programmed with knowledge of those events and has something to do with them. . . .

. . . Note that control events, while defined by the control and described in its type library, are handled through the events IDispatch in the container. The control itself does not implement any events in its own IDispatch interface."

Scott Randell

As you can see, one of the definitions (see first quotation above) is so vague as to be useless, while another (see third quotation above) seems to require a doctoral degree in OLE. In the "OLE Controls: State of the Union" article, I took the easy road—I pretended that the reader was familiar with OLE Control events. Well, the question has been haunting me. Just before I fall asleep at night, I hear this strange disembodied voice (Ruediger's) repeating over and over: "What is an OLE Control event?"

The product of my insomnia is this article you are now reading. In this article, we will together explore OLE Control events. We will start with the Microsoft Foundation Class Library (MFC) code generated by ControlWizard and ClassWizard and head towards the depths of the native OLE code found in the MFC OLE Control classes. In the process, you will learn about MFC, OLE Controls, and OLE. You will also understand Scott Randell's words on the IDispatch interface in the quotation above.

In our quest, we will look at:

I will be using Spindial, a sample application included with the OLE Control Developer's Kit (CDK), which is provided in Visual C++™ version 2.0, to illustrate an OLE Control event. Spindial is a simple OLE Control. It looks like an ellipse with four tick marks (Figure 1).

Figure 1. Spindial control as it appears in the Test Container

Clicking the Spindial control sends a SpinPositive event to the control and moves the line 90 degrees clockwise (Figure 2).

Figure 2. Spindial control after single click

As you can see, the control doesn't do much, but it illustrates the concepts I'll be discussing in this article.

Event Maps

Our journey starts with the CDK sample application Spindial. Like all OLE Controls created by ControlWizard, Spindial has an event map. An event map is to OLE Control events what a message map is to messages. The event map and message map from SPINDIAL.H are shown below. You can see how similar the two types of maps are.

// Message maps
   //{{AFX_MSG(CSpindialCtrl)
         afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()

// Event maps
   //{{AFX_EVENT(CSpindialCtrl)
   void FireSpinPositive()
         {FireEvent(eventidSpinPositive,EVENT_PARAM(VTS_NONE));}
   //}}AFX_EVENT
   DECLARE_EVENT_MAP()

The event map above defines and implements the FireSpinPositive function. To fire a SpinPositive event from our control, we would call FireSpinPositive. ClassWizard wraps FireEvent to implement FireSpinPositive. It is important to note the difference between the event map and the message map: The message map defines functions that respond to messages; the event map defines functions that generate events.

FireEvent takes an event ID and a list of parameters. The SpinPositive event does not have any associated parameters. Its ID is defined below.

// Dispatch and event IDs
public:
   enum {
   //{{AFX_DISP_ID(CSpindialCtrl)
   dispidNeedlePosition = 1L,
   eventidSpinPositive = 1L,
   //}}AFX_DISP_ID
   };

We will come back and look at the implementation of COleControl::FireEvent and event IDs later. First, let's finish with the event map. DECLARE_EVENT_MAP is next on our journey. It adds the following code to the class:

 private: 
   static AFX_DATA AFX_EVENTMAP_ENTRY BASED_CODE _eventEntries[];
protected: 
   static AFX_DATA AFX_EVENTMAP BASED_CODE eventMap; 
   virtual AFX_EVENTMAP FAR* GetEventMap() const;

This is nothing new. It is the same as DECLARE_MESSAGE_MAP except that the names have been changed to protect the innocent. The macros declare an array of event map entries (_eventEntries), a variable to hold a pointer to the event maps of the current class and the base class (eventMap), and a function to get a pointer to an event map (GetEventMap). The DECLARE_MESSAGE_MAP macro is expanded below to provide a comparison.

private: 
   static AFX_MSGMAP_ENTRY BASED_CODE _messageEntries[];
protected: 
   static AFX_MSGMAP AFXAPP_DATA messageMap; 
   virtual AFX_MSGMAP* GetMessageMap() const;

The variables and functions declared by DECLARE_EVENT_MAP and DECLARE_MESSAGE_MAP are implemented in the SPINCTL.CPP file. This code is listed below.

BEGIN_EVENT_MAP(CSpindialCtrl, COleControl)
   //{{AFX_EVENT_MAP(CSpindialCtrl)
   EVENT_CUSTOM("SpinPositive", FireSpinPositive, VTS_NONE)
   //}}AFX_EVENT_MAP
END_EVENT_MAP()

The macros above can be expanded as follows:

AFX_EVENTMAP FAR* CSpindialCtl::GetEventMap() const 
{ 
   return &eventMap; 
} 

AFX_DATADEF AFX_EVENTMAP BASED_CODE CSpindialCtl::eventMap = 
{ 
   &(COleControl::eventMap), 
   CSpindialCtl::_eventEntries 
}; 

AFX_DATADEF AFX_EVENTMAP_ENTRY BASED_CODE CSpindialCtl::_eventEntries[] = 
{
   { afxEventCustom, DISPID_UNKNOWN, _T("SpinPositive"), VTS_NONE},
   { afxEventCustom, DISPID_UNKNOWN, NULL, NULL }, 
 };

The VTS_NONE parameter means that the SpinPositive event does not take any parameters.

Thus, we have a function (GetEventMap) that gets a pointer to the event map, a variable (eventMap) that has a pointer to the event maps of the current class and the base class, and an array of event entries (_eventEntries) that contain the name and parameter list for a custom event. FireSpinPositive is not included in the _eventEntries array; it is included as a parameter for EVENT_CUSTOM for documentation purposes. The system does not need to keep track of how an event is fired—it only needs to track the events that make up the event set.

Grepping (that means "searching," for those without a UNIX® background) the code reveals that the _eventEntries array is used only for initializing stock events. The code currently steps over events with DISPID_UNKNOWN. Another interesting anomaly is that we know the event ID for SpinPositive: eventidSpinPositive, although we use DISPID_UNKNOWN.

Now, that didn't get us very far, so it's time to go back and look at COleControl::FireEvent.

The COleControl::FireEvent Function

In SPINDIAL.H, Spindial declares its event map. The firing function, FireSpinPositive, is defined in the event map:

void FireSpinPositive()
{
   FireEvent(eventidSpinPositive,EVENT_PARAM(VTS_NONE));
}

Again, our control calls FireSpinPositive whenever it wants to send an event to its container. The Spindial control translates mouse button click events into SpinPositive events by calling FireSpinPositive after a mouse button click.

The first parameter to FireEvent is a DISPID. What's a DISPID? Like Ruediger's simple question that prompted this article, this question isn't all that simple. If you search the MSDN Library for "DISPID", you won't find a simple, straightforward definition. A DISPID is a 32-bit identifier (sometimes called a "magic cookie") that describes a method or property in a dispatch interface. For a particular dispatch interface, each method or property has a unique DISPID. Different dispatch interfaces may have methods or properties with the same DISPID. In other words, a DISPID is not universally unique; it is unique only within its own dispatch interface. If you are familiar with dynamic-link libraries (DLLs), a DISPID is similar to an ordinal for an exported function.

In the SpinControl, the DISPID for eventidSpinPositive was generated for us by MFC and happens to be one (1). See MFC Technical Note 39, "MFC/OLE Automation Implementation" (MSDN Library Archive, Technical Articles, Visual C++ 1.5 [16-bit] Articles, MFC 2.5 Technical Notes) for more information on DISPIDs and how MFC assigns a DISPID to a method or property.

Dispatch Interface

What's a dispatch interface? (See how one question leads to another?) OLE Automation defines a process by which one application, the automation server, can expose an interface to another application, the automation client. The automation client can control the automation server programmatically through an interface, which is called the dispatch interface.

Properties and methods exposed by OLE Controls are implemented using dispatch interfaces. In MFC, dispatch interfaces are implemented through dispatch maps. The following is Spindial's declaration of a dispatch map:

// Dispatch maps
   //{{AFX_DISPATCH(CSpindialCtrl)
   afx_msg short GetNeedlePosition();
   afx_msg void SetNeedlePosition(short nNewValue);
   //}}AFX_DISPATCH
   DECLARE_DISPATCH_MAP()

Spindial exposes the NeedlePosition property. It implements NeedlePosition using get and set methods so it can track changes to this property and act accordingly.

Spindial implements the dispatch map as follows:

BEGIN_DISPATCH_MAP(CSpindialCtrl, COleControl)
   //{{AFX_DISPATCH_MAP(CSpindialCtrl)
   DISP_PROPERTY_EX(CSpindialCtrl, "NeedlePosition", 
            GetNeedlePosition, SetNeedlePosition, VT_I2)
   DISP_DEFVALUE(CSpindialCtrl, "NeedlePosition")
   //}}AFX_DISPATCH_MAP
   DISP_FUNCTION_ID(CSpindialCtrl, "AboutBox", 
                    DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

For efficiency, OLE Automation uses the 32-bit DISPID instead of the string name to reference properties and methods in a dispatch interface. The careful reader will notice that the DISPID is not listed in the dispatch map. To see where the DISPID is paired with a dispatch method or property, we need to look at the type library, which we'll discuss later. Now that we know what a DISPID is, let's examine the code for FireEvent.

Back to FireEvent

The CTLEVENT.CPP file contains the code for COleControl::FireEvent. (When you install the OLE Control Developer's Kit, you'll find this file in the \CDK16\SRC or \CDK32\SRC directory.) I will not trace this code in detail in this article. Basically, FireEvent gets a pointer to the dispatch interface in the control container and executes IDispatch::Invoke on the DISPID passed in its first parameter. Now we're getting somewhere!

"Wait a minute," I hear MFC programmers who are unfamiliar with OLE saying, "What is IDispatch::Invoke?" In OLE, an interface is prefixed by the letter "l", so IDispatch is the OLE interface for manipulating dispatch interfaces.

You can think of an OLE interface as a C++ class. (Actually, there are differences between the way OLE interfaces and C++ classes operate, but this analogy will do for now. See Inside OLE 2 by Kraig Brockschmidt [MSDN Library, Books] for more information.) So, IDispatch::Invoke is a function that executes a method exposed in an OLE dispatch interface.

Therefore, FireSpinPositive executes the SpinPositive dispatch method in the control container by calling IDispatch::Invoke with the DISPID eventidSpinPositive.

The upshot of all of this is that the OLE Control defines a dispatch interface (dispinterface) as its events list. The OLE Control container then implements this dispatch interface. You can think of an event as an outgoing dispatch method. By defining a dispatch interface, the control says, "Here are some methods you can call to control me." By defining an event list, the control tells the container, "Here are some methods I can call to tell you what is happening to me." Figure 3 illustrates this concept: The control defines an event. The container implements an event handler. The control fires the event.

Figure 3. Firing an OLE control event

Note how this process differs from OLE Automation, where the automation methods are defined and implemented by the automation server and called by the automation client. As far as events are concerned, an OLE Control is like an automation client that defines the interface to the automation server.

Scott Randell's words should now make a little more sense.

The Type Library

In addition to adding, changing, and modifying the message maps, ControlWizard and ClassWizard also maintain the .ODL file, which contains an object description language script. The MkTypLib tool provided in Visual C++ compiles an .ODL script into a type library. The type library for an OLE Control contains descriptions of the dispatch interfaces supported by the control, including the event dispatch interface. The type library is manipulated by the ITypeLib interface.

The .ODL file for the Spindial control is shown below. You can see the dispatch interface _DSpindial and the event dispatch interface _DSpindialEvents. The DISPID for these events is assigned by ClassWizard. The NeedlePosition property has a DISPID of 1, which is generated by the ODL script [id(1)].

#include <olectl.h>

[ uuid(06889608-B8D0-101A-91F1-00608CEAD5B3), version(1.0),
  helpstring("Spindial OLE Custom Control module") ]
library SpindialLib
{
   importlib(STDOLE_TLB);
   importlib(STDTYPE_TLB);

   //  Primary dispatch interface for CSpindialCtrl

   [ uuid(37446B89-5870-101B-B57B-00608CC96AFA),
     helpstring("Dispatch interface for Spindial Control") ]
   dispinterface _DSpindial
   {
      properties:
         // NOTE - ClassWizard will maintain property information here.
         //    Use extreme caution when editing this section.
         //{{AFX_ODL_PROP(CSpindialCtrl)
         [id(1)] short NeedlePosition;
         //}}AFX_ODL_PROP

      methods:
         // NOTE - ClassWizard will maintain method information here.
         //    Use extreme caution when editing this section.
         //{{AFX_ODL_METHOD(CSpindialCtrl)
         //}}AFX_ODL_METHOD

         [id(DISPID_ABOUTBOX)] void AboutBox();
   };

   //  Event dispatch interface for CSpindialCtrl

   [ uuid(37446B8A-5870-101B-B57B-00608CC96AFA),
     helpstring("Event interface for Spindial Control") ]
   dispinterface _DSpindialEvents
   {
      properties:
         //  Event interface has no properties.

      methods:
         // NOTE - ClassWizard will maintain event information here.
         //    Use extreme caution when editing this section.
         //{{AFX_ODL_EVENT(CSpindialCtrl)
         [id(1)] void SpinPositive();
         //}}AFX_ODL_EVENT
   };

   //  Class information for CSpindialCtrl

   [ uuid(06889605-B8D0-101A-91F1-00608CEAD5B3),
     helpstring("Spindial Control") ]
   coclass Spindial
   {
      [default] dispinterface _DSpindial;
      [default, source] dispinterface _DSpindialEvents;
   };

   //{{AFX_APPEND_ODL}}
};

A type library contains information not only about interfaces, but also about a class. The interfaces supported by the Spindial class are listed at the end of Spindial's .ODL file. An event dispatch interface is differentiated from a normal dispatch interface by the source attribute, which indicates that the control is a source for these events. To handle these events, a container would define the events dispatch interface without the source attribute.

Conclusion

Now that we've explored OLE Control events in detail, it's time to bite the bullet and give you a definition of an OLE Control event:

An OLE Control event is a method that is defined in a source dispatch interface in an OLE Control and implemented in a dispatch interface in the OLE Control container. The OLE Control fires events that are handled by the OLE Control container.

Bibliography

Brockschmidt, Kraig. Inside OLE 2. (MSDN Library, Books)

Brockschmidt, Kraig. "Notes on Implementing an OLE Control Container". January 1995.

Microsoft Foundation Class Library Technical Notes. "Technical Note 39: "MFC/OLE Automation Implementation." (MSDN Library Archive, Technical Articles, Visual C++ 1.5 Articles, MFC 2.5 Technical Notes)

Microsoft OLE Control Developer's Kit (CDK). User's Guide and Reference. (MSDN Library Archive, Product Documentation, Languages)

Microsoft Visual C++ version 1.5 OLE 2 Classes. Tutorial. Chapter 6, "Autoclik: Creating an Automation Server." (MSDN Library Archive, Product Documentation, Languages)

OLE 2.0 Programmer's Reference. Vol. 2, Overview of OLE Automation.