| Tenouk C & C++ | MFC Home | ATL and ActiveX Controls 3 | ATL and ActiveX Controls 5 | Download | Site Index |


 

 

 

 

 

 

ATL and ActiveX Controls 4

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary note for this tutorial is control class.

  1. The Myatldicesvr Program From Scratch...continue

  2. The Story

  3. ATL's Control Architecture

  4. CComControl

  5. CComControlBase

  6. CWindowImpl and CWindowImplBase

  7. ATL Windowing

  8. ATL Message Maps

 

 

Build and make sure there is no error. Try using the dice control in Visual C++ dialog based program or Visual Basic form. The following shows the dice control using the ActiveX Control Test Container. Double click the dice or in the square area. Here, we cannot see/use the property page.

 

Figure 51:  myatldicesvr in ActiveX Control Test Container.

 

Figure 51:  myatldicesvr in ActiveX Control Test Container.

 

The Story

 

As always, the easiest way to create a COM server in ATL is to use the ATL COM Object Wizard. To use the ATL COM Object Wizard, select New from the File menu. Select the Project tab in the New dialog, and highlight the ATL COM AppWizard item. Name the project something clever like myatldicesvr. As you step through AppWizard, just leave the defaults checked. Doing so will ensure that the server you create is a DLL. Once the DLL server has been created, perform the following steps:

 

Select New ATL Object from the Insert menu to insert a new ATL object into the project.

 

In the ATL Object Wizard, select Controls from the Category list and then select Full Control from the Objects list.

 

Click Next to open the ATL Object Wizard Properties dialog. In the Short Name text box on the Names tab, give the control some clever name (like myatldiceob). The dialog box should look similar to Figure 52.

 

Figure 52:  The ATL Object Wizard Properties dialog box.

 

Figure 52:  The ATL Object Wizard Properties dialog box.

 

Select the Attributes tab. Here's where you configure the control. For example, you can:

  1. Designate the threading model for the control.

  2. Decide whether the main interface is a dual or custom interface.

  3. Indicate whether your control supports aggregation.

  4. Choose whether you want to use COM exceptions and connection points in your control.

 

To make your life easier for now, select Support Connection Points. This will save you some typing later on. Leave everything else as the default value. Figure 53 shows what the Attributes tab on the ATL Object Wizard Properties dialog box looks like now.

 

Select the Miscellaneous tab. Here you have the option of applying some miscellaneous traits to your control. For example, you can give the control behaviors based on regular Microsoft Windows controls such as buttons and edit controls. You might also select other options for your control, such as having your control appear invisible at runtime or giving your control an opaque background. Figure 54 shows the available options.

 

Figure 53:  The Attributes tab on the ATL Object Wizard Properties dialog box.

 

Figure 53:  The Attributes tab on the ATL Object Wizard Properties dialog box.

 

Figure 54: The Miscellaneous control properties tab on the ATL Object Wizard Properties dialog box.

 

Figure 54: The Miscellaneous control properties tab on the ATL Object Wizard Properties dialog box.

 

Finally, select the Stock Properties tab if you want to give your control some stock properties. Stock properties are those properties that you might expect any control to have, including background colors, border colors, foreground colors, and a caption. Figure 55 shows the Stock Properties tab.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---------------------------------------------------------------------------------------------------------

Figure 55:  The Stock Properties tab on the ATL Object Wizard Properties dialog box.

 

Figure 55:  The Stock Properties tab on the ATL Object Wizard Properties dialog box.

 

When you've finished selecting the attributes for the control, click OK.

 

The ATL Object Wizard adds a header file and a source file defining the new control. In addition, the ATL Object Wizard sets aside space in the IDL file to hold the control's main interface and assigns a GUID to the interface. Here's the C++ definition of the control produced by the ATL Object Wizard (myatldiceob.h):

 

// myatldiceob.h : Declaration of the Cmyatldiceob

 

#ifndef __MYATLDICEOB_H_

#define __MYATLDICEOB_H_

 

#include "resource.h"       // main symbols

#include <atlctl.h>

 

/////////////////////////////////////////////////////////////////////////////

// Cmyatldiceob

class ATL_NO_VTABLE Cmyatldiceob :

       public CComObjectRootEx<CComSingleThreadModel>,

       public IDispatchImpl<Imyatldiceob, &IID_Imyatldiceob, &LIBID_MYATLDICESVRLib>,

       public CComControl<Cmyatldiceob>,

       public IPersistStreamInitImpl<Cmyatldiceob>,

       public IOleControlImpl<Cmyatldiceob>,

       public IOleObjectImpl<Cmyatldiceob>,

       public IOleInPlaceActiveObjectImpl<Cmyatldiceob>,

       public IViewObjectExImpl<Cmyatldiceob>,

       public IOleInPlaceObjectWindowlessImpl<Cmyatldiceob>,

       public IConnectionPointContainerImpl<Cmyatldiceob>,

       public IPersistStorageImpl<Cmyatldiceob>,

       public ISpecifyPropertyPagesImpl<Cmyatldiceob>,

       public IQuickActivateImpl<Cmyatldiceob>,

       public IDataObjectImpl<Cmyatldiceob>,

       public IProvideClassInfo2Impl<&CLSID_myatldiceob, &DIID__ImyatldiceobEvents, &LIBID_MYATLDICESVRLib>,

       public IPropertyNotifySinkCP<Cmyatldiceob>,

       public CComCoClass<Cmyatldiceob, &CLSID_myatldiceob>

{

   ...

   ...

   ...

};

 

#endif //__MYATLDICEOB_H_

 

That's a pretty long inheritance list. You've already seen the template implementations of IUnknown and support for class objects. They exist in CComObjectRootEx and CComCoClass. You've also seen how ATL implements IDispatch within the IDispatchImpl template. However, for a basic control there are about 11 more interfaces required to make everything work. These interfaces can be categorized into several areas as shown in the following table.

 

Category

Interface

Interfaces for handling self-description

IProvideClassInfo2

Interfaces for handling persistence

IPersistStreamInit

IPersistStorage

Interfaces for handling activation

IQuickActivate (and some of IOleObject)

Interfaces from the original OLE Control specification

IOleControl

Interfaces from the OLE Document specification

IOleObject

Interfaces for rendering

IOleInPlaceActiveObject

IViewObject

IOleInPlaceObjectWindowless

IDataObject

Interfaces for helping the container manage property pages

ISpecifyPropertyPages

Interfaces for handling connections

IPropertyNotifySinkCP

IConnectionPointContainer

 

Table 2.

 

These are by and large boilerplate interfaces, ones that a COM class must implement to qualify as an ActiveX control. Most of the implementations are standard and vary only slightly (if at all) from one control to the next. The beauty of ATL is that it implements this standard behavior and gives you programmatic hooks where you can plug in your custom code. That way, you don't have to burn your eyes out by looking directly at the COM code. You can live a full and rich life without understanding exactly how these interfaces work.

 

 

ATL's Control Architecture

 

From the highest level, an ActiveX control has two aspects to it: its external state (what it renders on the screen) and its internal state (its properties). Once an ActiveX control is hosted by some sort of container (such as a Microsoft Visual Basic form or an MFC dialog box), it maintains a symbiotic relationship with that container. The client code talks to the control through incoming COM interfaces such as IDispatch and OLE Document interfaces like IOleObject and IDataObject.

The control also has the opportunity to talk back to the client. One method of implementing this two-way communication is for the client to implement an IDispatch interface to represent the control's event set. The container maintains a set of properties called ambient properties that the control can use to find out about its host. For instance, a control can camouflage itself within the container because the container makes the information stored in these properties available through a specifically named IDispatch interface. The container can implement an interface named IPropertyNotifySink to 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 listed allow the client and the object to exhibit the behaviors expected of an ActiveX control. We'll tackle some of these interfaces as we go along. The best place to begin looking at ATL-based controls is the CComControl class and its base classes.

 

CComControl

 

You can find the definition of CComControl in Microsoft's ATLCTL.H file under ATL's Include directory. CComControl is a template class that takes a single class parameter:

 

template <class T>

class ATL_NO_VTABLE CComControl :  public CComControlBase, public CWindowImpl<T>

{

  ...

  ...

};

 

CComControl is a rather lightweight class that does little by itself; it derives functionality 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, the primary reason being that from time to time the control class uses the template parameter to call back to the control's InternalQueryInterface().

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

CComControl also implements the FireOnChanged() function. FireOnChanged() is very much like FireOnRequestEdit() in that it calls back to the client through the IPropertyNotifySink interface. This function tells the clients of the control (all clients connected to the control through IPropertyNotifySink) that a property specified by a certain DISPID has already changed.

In addition to mapping the IPropertyNotifySink interface to some more easily understood functions, CComControl implements a function named ControlQueryInterface(), which simply forwards on to the control's IUnknown interface. This is how you can get a control's IUnknown interface from inside the control. Finally, CComControl implements a function named CreateControlWindow(). The default behavior for this function is to call CWindowImpl::Create. Notice that CComControl also derives from CWindowImpl. If you want to, you can override this function to do something other than create a single window. For example, you might want to create multiple windows for your control. Most of the real functionality for CComControl exists within those two other classes, CComControlBase and CWindowImpl. Let's take a look at those classes now.

 

CComControlBase

 

CComControlBase is a much more substantial class than CComControl. To begin with, CComControlBase maintains all the pointers used by the control to talk back to the client. CComControlBase uses ATL's CComPtr smart pointer to include member variables that wrap the following interfaces implemented for calling back to the client:

  1. A wrapper for IOleInPlaceSite(m_spInPlaceSite).

  2. An advise holder for the client's data advise sink (m_spDataAdviseHolder).

  3. An OLE advise holder for the client's OLE advise sink (m_spOleAdviseHolder).

  4. A wrapper for IOleClientSite (m_spClientSite).

  5. A wrapper for IAdviseSink (m_spAdviseSink).

 

CComControlBase also uses ATL's CComDispatchDriver to wrap the client's dispatch interface for exposing its ambient properties.

CComControlBase is also where you'll find the member variables that contain the control's sizing and positioning information: m_sizeNatural, m_sizeExtent, and m_rcPos. The other important data member within CComControlBase is the control's window handle. Most ActiveX controls are UI gadgets and as such maintain a window. CWindowImpl and CWindowImplBase handle the windowing aspects of an ATL-based ActiveX control.

 

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 template parameter is the control being created. CWindowImpl needs the control type because CWindowImpl calls back to the control during window creation. Let's take a closer look at how ATL handles windowing

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ATL Windowing

 

Just as CComControl is relatively lightweight (most work happens in CComControlBase), CWindowImpl is also relatively lightweight. CWindowImpl more or less handles only 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 _ATLWNDCLASSINFO. There's an ASCII character version and a wide character version.

 

struct _ATL_WNDCLASSINFOA

{

    WNDCLASSEXA m_wc;

    LPCSTR m_lpszOrigName;

    WNDPROC pWndProc;

    LPCSTR m_lpszCursorID;

    BOOL m_bSystemCursor;

    ATOM m_atom;

    CHAR m_szAutoName[13];

    ATOM Register(WNDPROC* p)

    {

        return AtlModuleRegisterWndClassInfoA(&_Module, this, p);

    }

};

 

struct _ATL_WNDCLASSINFOW

{

    WNDCLASSEXW m_wc;

    LPCWSTR m_lpszOrigName;

    WNDPROC pWndProc;

    LPCWSTR m_lpszCursorID;

    BOOL m_bSystemCursor;

    ATOM m_atom;

    WCHAR m_szAutoName[13];

    ATOM Register(WNDPROC* p)

    {

        return AtlModuleRegisterWndClassInfoW(&_Module, this, p);

    }

};

 

Then ATL uses typedefs to alias this structure to a single class named CWndClassInfo:

typedef _ATL_WNDCLASSINFOA CWndClassInfoA;

typedef _ATL_WNDCLASSINFOW CWndClassInfoW;

#ifdef UNICODE

#define CWndClassInfo CWndClassInfoW

#else

#define CWndClassInfo CWndClassInfoA

#endif

 

CWindowImpl uses a macro named DECLARE_WND_CLASS to add window class information to a CWindowImpl-derived class. DECLARE_WND_CLASS also adds a function named GetWndClassInfo(). Here's the DECLARE_WND_CLASS macro (take note that the backslash used to indicate that the line of code is continue to the next line) :

#define DECLARE_WND_CLASS(WndClassName) \

static CWndClassInfo& GetWndClassInfo() \

{ \

    static CWndClassInfo wc = \

    { \

        { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,\

          StartWindowProc, \

          0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), \

          NULL, WndClassName, NULL }, \

          NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \

    }; \

    return wc; \

}

This macro expands to provide a CWndClassInfo structure for the control class. Because CWndClassInfo manages the information for a single window class, each window created through a specific instance of CWindowImpl will be based on the same window class.

CWindowImpl derives from CWindowImplBaseT. CWindowImplBaseT derives from CWindowImplRoot, which is specialized around the CWindow class and the CControlWinTraits classes like this:

template <class TBase = CWindow, class TWinTraits = CControlWinTraits>

class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >

{

 public:

   ...

   ...

   ...

};

 

CWindowImplRoot derives from CWindow (by default) and CMessageMap. CWindowImplBaseT 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 a single pure virtual function named 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. Let's take a quick look at ATL message maps.

 

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, by contrast, deriving from CCmdTarget enables message mapping. However, just as in MFC, it's not enough to derive from a class that supports message maps. The message maps actually have to be there, and those message maps are implemented via macros.

To implement a message map in an ATL-based control, use message map macros. First ATL's BEGIN_MSG_MAP macro goes into the control class's 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 BEGIN_MSG_MAP and END_MSG_MAP lie some other macros for mapping window messages to member functions in the control. For example, here's a typical message map you might find in an ATL-based control:

BEGIN_MSG_MAP(CAFullControl)

    CHAIN_MSG_MAP(CComControl<CAFullControl>)

    DEFAULT_REFLECTION_HANDLER()

    MESSAGE_HANDLER(WM_TIMER, OnTimer);

    MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButton);

END_MSG_MAP()

This message map delegates most of the message processing to the control through the CHAIN_MSG_MAP macro and handles message reflection through the DEFAULT_REFLECTION_HANDLER macro. The message map also handles two window messages explicitly: WM_TIMER and WM_LBUTTONDOWN. These are standard window messages that are mapped using the MESSAGE_HANDLER macro. The macros simply 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. Here's a rundown of the kinds of macros that can go in a message map.

 

Macro

Description

MESSAGE_HANDLER

Maps a Windows message to a handler function.

MESSAGE_RANGE_HANDLER

Maps a contiguous range of Windows messages to a handler function.

COMMAND_HANDLER

Maps a WM_COMMAND message to a handler function, based on the identifier and the notification code of the menu item, control, or accelerator.

COMMAND_ID_HANDLER

Maps a WM_COMMAND message to a handler function, based on the identifier of the menu item, control, or accelerator.

COMMAND_CODE_HANDLER

Maps a WM_COMMAND message to a handler function, based on the notification code.

COMMAND_RANGE_HANDLER

Maps a contiguous range of WM_COMMAND messages to a handler function, based on the identifier of the menu item, control, or accelerator.

NOTIFY_HANDLER

Maps a WM_NOTIFY message to a handler function, based on the notification code and the control identifier.

NOTIFY_ID_HANDLER

Maps a WM_NOTIFY message to a handler function, based on the control identifier.

NOTIFY_CODE_HANDLER

Maps a WM_NOTIFY message to a handler function, based on the notification code.

NOTIFY_RANGE_HANDLER

Maps a contiguous range of WM_NOTIFY messages to a handler function, based on the control identifier.

 

Table 3.

 

Handling messages within ATL works much the same as in MFC. ATL includes a single window procedure through which messages are routed. Technically, you can build your controls effectively without understanding everything about ATL's control architecture. However, this knowledge is sometimes helpful as you develop a control, and it's even more useful when debugging a control

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

  4. Win32 process, thread and synchronization story can be found starting from Module R.

  5. MSDN MFC 7.0 class library online documentation.

  6. MSDN MFC 9.0 class library online documentation - latest version.

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | ATL and ActiveX Controls 3 | ATL and ActiveX Controls 5 | Download | Site Index |