Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

ATL's ActiveX Control Architecture


Dr. Dobb's Journal February 1998: Undocumented Corner

Scot is a cofounder of Stingray Software. He can be reached at [email protected]. George develops software with Stingray Software and delivers MFC, ATL, and COM seminars for DevelopMentor. He can be contacted at [email protected].


If you're a developer who's decided to begin living the COM lifestyle, using Microsoft's Active Template Library (ATL) is a reasonable alternative to creating COM classes using raw C++. Writing COM classes via raw C++ is a great exercise, since raw C++ is the assembly language of COM. It's the kind of thing everyone should do it at least once. By understanding COM at the C++ level, all the crazy machinations you'll witness when combing through MFC and ATL source code will make sense.

Unfortunately, most developers have real problems to solve (and real ship dates marked on their calendars), and probably don't want to spend time writing code to implement IUnknown and IDispatch over and over. Granted, IUnknown and IDispatch are straightforward -- IUnknown is only three functions and IDispatch is only four more functions. Both interfaces have basically the same amount of "boilerplateness" to them. For example, as you saw in our December 1997 column, writing a solid implementation of IDispatch requires three steps:

  1. Describing the interface in IDL.
  2. Having the MIDL compiler eat the IDL and spit out a type library.
  3. Implementing the IDispatch functions by delegating to the ITypeInfo interface.

ATL exists to save you from this drudgery. However, ATL doesn't stop at IUnknown and IDispatch. ATL is also useful if you want to ship ActiveX controls. ATL implements all the interfaces required by ActiveX Controls. In this column, we'll take a look at how ATL implements ActiveX controls, starting with an examination of ATL's basic control architecture and the accompanying windowing architecture. In subsequent columns, we'll examine how ATL implements specific elements of an ActiveX control including rendering, persistence, and connections.

What is an ActiveX Control?

Even today, there's confusion as to what really constitutes an ActiveX control. In 1994, Microsoft tacked some new things onto its Object Linking and Embedding protocol, packaged the contraptions within DLLs, and called them OLE Controls. Originally, OLE Controls implemented nearly the entire embedding protocol. In addition, OLE Controls supported dynamic invocation (Automation), property pages (so users could modify the control's properties), outbound callback interfaces (event sets), and a standard way for clients and controls to hook up the event callbacks (connections). When the Internet became central to Microsoft's marketing plans, Microsoft intended to plant ActiveX Controls on web pages. At that point, the size of these components became an issue. Microsoft took its OLE Control specifications, renamed them ActiveX Controls, and stated that all the useful features just listed were optional -- the control's only requirement was (and is) that it be based on COM and implement IUnknown. Of course, for a control to be useful, it needs to implement most of those features we've just discussed. So in the end, ActiveX Controls and OLE Controls refer to more or less the same kind of animal -- reusable software gadgets (mostly UI gadgets) built using COM.

Creating a Control

To create a new ActiveX-based control, select File|New from Visual C++'s main menu, then select ATL COM AppWizard as the project type. The AppWizard gives you a choice between an EXE, Service, and DLL. To make a server for housing controls, choose a DLL. Once the ATL COM AppWizard pumps out your project, use the ATL Object Wizard to create a new control. For now, we'll just name the control ATLCtl and go with the defaults. Listing One is the code emitted by the ATL COM Object Wizard.

Listing One shows a pretty long inheritance list. We've already seen the template implementations of IUnknown and IClassFactory. They exist in CComObjectRootEx and CComCoClass. We've also seen how ATL implements IDispatch within the IDispatchImpl template. However, for a basic control, there are about 11 more interfaces required to implement an ActiveX Control. As Table 1 shows, these interfaces can be categorized into several areas of utility.

From the highest level, an ActiveX Control has two aspects -- its external state and its internal state (its properties). Once hosted by some sort of container (like a Visual Basic form or a dialog box), an ActiveX Control maintains a symbiotic relationship with the container. The client code talks to the control through incoming COM interfaces like IDispatch and OLE Document interfaces like IOleObject and IDataObject.

The control also has the opportunity to talk back to the client. For example, the client often implements an IDispatch event to represent the control's event set -- that way, controls can talk back to their clients. The container maintains a set of properties named "ambient" properties that can be used by the control to camouflage itself within the container (the container makes these properties available through an IDispatch interface). The container may implement an interface named IPropertyNotifySink so it can find out when the properties within a control might change. Finally, the container implements IOleClientSite and IOleControlSite as part of the control embedding protocol.

The interfaces in Table 1 allow the client and object to behave as advertised. However, the heart of an ATL-based ActiveX Control lies within CComControl and its base classes. Let's start by taking a look at the CComControl class.

CComControl

You can find CComControl in Microsoft's ATLCTL.H file under ATL's include directory. CComControl is a template class that takes a single class parameter; see Example 1.

CComControl is a pretty lightweight class. It doesn't have a lot going on by itself -- it derives from CComControlBase and CWindowImpl. CComControl expects the template parameter to be an ATL-based COM object derived from CComObjectRootEx. CComControl requires the template parameter for various reasons. For example, the class uses the template parameter to call back to the control's _InternalQueryInterface.

CComControl implements several functions, making it easy for the control to call back to the client. For example, CComControl implements the function FireOnRequestEdit to give controls to the ability to tell the client that a specified property is about to change. FireOnRequestEdit calls back to the client through the client-implemented interface IPropertyNotifySink. This method notifies all connected IPropertyNotifySink interfaces that the property specified by a certain DISPID is about to change.

CComControl also implements FireOnChanged, which is much like FireOnRequestEdit in that it calls back to the client through the IPropertyNotifySink. However, this function is useful for telling the clients of the control (all clients implementing IPropertyNotifySink) that a property specified by a certain DISPID has already changed.

CComControl implements the function ControlQueryInterface, which forwards to the control's IUnknown. Finally, CComControl implements the function CreateControlWindow. The default behavior for this function is to call CWindowImpl::Create (notice CComControl also derives from CWindowImpl). You can override this method to do something other than create a single window. For example, you might want to create multiple windows for your control.

Most of the functionality for CComControl exists within those two other classes -- CComControlBase and CWindowImpl.

CComControlBase

CComControlBase is more substantial than CComControl. For starters, CComControlBase maintains the pointers used by the control to talk back to the client. CComControlBase uses ATL's CComPtr smart pointer to wrap the following interfaces implemented for calling back to the client. These member variables include wrappers for IOleInPlaceSite, an advise holder for the client's data advise sink, an OLE advise holder for the client's OLE advise sink, and a wrapper for IoleClientSite. CComControlBase also uses ATL's CComDispatchDriver to wrap the client's dispatch interface for exposing its ambient properties.

CComControlBase is where you'll find the control's sizing and positioning information. CComControlBase maintains the control's size and position as member variables. The other important data member within CComControlBase is the control's window handle. Most ActiveX Controls are UI gadgets and so maintain a window. The windowing aspects of an ATL-based ActiveX Control are handled by CWindowImpl and CWindowImplBase.

CWindowImpl and CWindowImplBase

CWindowImpl derives from CWindowImplBase, which, in turn, derives from CWindow and CMessageMap. As a template class, CWindowImpl takes a single parameter upon instantiation (the parameter is the control being created). The CWindowImpl needs the control type because it calls back to the control during window creation.

ATL Windowing

Just as CComControl is relatively lightweight (most work happens in CComControlBase), CWindowImpl is relatively lightweight. CWindowImpl more or less just handles window creation. In fact, that's the only function explicitly defined by CWindowImpl. CWindowImpl::Create creates a new window based on the window class information managed by a class named CWndClassInfo; see Listing Two.

CWindowImpl uses the macro DECLARE_WND_CLASS to add window class information to a CWindowImpl-derived class. DECLARE_WND_CLASS also adds a function named GetWndClassInfo. Listing Three shows the DECLARE_WND_CLASS macro.

Because CWndClassInfo manages the information for a single window class, each window created through a specific instance of CWindowImpl is based on the same window class.

CWindowImpl derives from CWindowImplBase, and CWindowImplBase multiply derives from CWindow and CMessageMap. CWindowImplBase manages the window procedure of a CWindowImpl-derived class. CWindow is a lightweight class that wraps window handles in the same way (but not as extensively) as MFC's CWnd class. CMessageMap is a tiny class that defines the pure virtual function ProcessWindowMessage. ATL-based message-mapping machinery assumes this function is available, so ATL-based classes that want to use message maps need to derive from CMessageMap.

ATL Message Maps

The root of ATL's message mapping machinery lies within the CMessageMap class. ATL-based controls expose message maps by virtue of indirectly deriving from CWindowImplBase. In MFC, deriving from CCmdTarget enables message mapping. However, just as in MFC, it's not enough to simply derive from a class that supports message maps -- those message maps are actually implemented via macros.

Implementing a message map in an ATL-based control is a matter of using the BEGIN_MSG_MAP macro in the class' header file. BEGIN_MSG_MAP marks the beginning of the default message map. CWindowImpl::WindowProc uses this default message map to process messages sent to the window. The message map directs messages either to the appropriate handler function or to another message map. ATL defines another macro named END_MSG_MAP to mark the end of a message map. Between the BEGIN_MSG_MAP and END_MSG_MAP macros lie some other macros for mapping window messages to member functions in the control. For example, take a look at the default message map pumped out by the ATL COM Wizard in Listing Four.

As you can see, this message map handles three window messages: WM_PAINT, WM_SETFOCUS, and WM_FOCUS. These are standard window messages mapped using the MESSAGE_HANDLER macro. The macros produce a table relating window messages to member functions in the class. In addition to regular messages, message maps are capable of handling other sorts of events. Table 2 presents a rundown of the kinds of macros that can go in a message map.

Conclusion

While Microsoft's documentation gives good information about how to implement ActiveX Controls using ATL, it doesn't really describe how the machinery works. Basically, ActiveX Controls need to have class objects (handled by CComCoClass) and implement IUnknown (handled by CComObjectRootEx). In addition, ATL-based Controls derive from interfaces necessary for the OLE Control protocol. Finally, ATL-based Controls derive from CComControl to get the rest of the machinery necessary to be an ActiveX Control. By deriving from CComControl, an ATL-based control also gets a window and a message map. In future columns we'll examine other interesting aspects of ATL's support for ActiveX Controls, including how they implement property pages and connections.

DDJ

Listing One

class ATL_NO_VTABLE CATLCtl :    public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CATLCtl, &CLSID_ATLCtl>,
   public CComControl<CATLCtl>,
   public IDispatchImpl<IATLCtl, &IID_IATLCtl, &LIBID_ATLCTLSVRLib>,
   public IProvideClassInfo2Impl<&CLSID_ATLCtl, NULL, &LIBID_ATLCTLSVRLib>,
   public IPersistStreamInitImpl<CATLCtl>,
   public IPersistStorageImpl<CATLCtl>,
   public IQuickActivateImpl<CATLCtl>,
   public IOleControlImpl<CATLCtl>,
   public IOleObjectImpl<CATLCtl>,
   public IOleInPlaceActiveObjectImpl<CATLCtl>,
   public IViewObjectExImpl<CATLCtl>,
   public IOleInPlaceObjectWindowlessImpl<CATLCtl>,
   public IDataObjectImpl<CATLCtl>,
   public ISpecifyPropertyPagesImpl<CATLCtl>
{
 ... 
};

Back to Article

Listing Two

class CWndClassInfo {public:
   WNDCLASSEX m_wc;
   LPCTSTR m_lpszOrigName;
   WNDPROC pWndProc;
   LPCTSTR m_lpszCursorID;   
   BOOL m_bSystemCursor;
   ATOM m_atom;
   TCHAR m_szAutoName[13];
   ATOM Register(WNDPROC*);
};

Back to Article

Listing Three

#define DECLARE_WND_CLASS(WndClassName) \static CWndClassInfo& GetWndClassInfo() \
{ \
   static CWndClassInfo wc = \
   { \
      { sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW, StartWindowProc, \
        0, 0, 0, 0, 0, (HBRUSH)(COLOR_WINDOW+1), 0, WndClassName, 0 }, \
        NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
   }; \
   return wc; \
}

Back to Article

Listing Four

BEGIN_MSG_MAP(CATLCtl)   MESSAGE_HANDLER(WM_PAINT, OnPaint)
   MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
   MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
END_MSG_MAP()

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.