您正在查看: MFC 分类下的文章

MFC Automation Servers

mfc Automation Servers

You can use mfc to write stand-alone Automation components, but more often, you'll use its Automation support to expose an application's features to Automation clients. Exposing features this way has the very desirable effect of making the application scriptable.

You don't have to be an expert on IDispatch interfaces and VARIANTs to write MFC Automation servers because MFC disguises methods and properties as ordinary class member functions. In fact, it's so easy to write an MFC Automation server that Visual C++ programmers often use Automation components in situations where ordinary COM objects might make more sense.

Writing MFC Automation servers is easy because of the wizards. AppWizard adds the infrastructure needed to transform an application into an Automation server. ClassWizard reduces the chore of adding methods and properties to a few button clicks. The code generated by these wizards relies extensively on the Automation support already present in MFC. Before we go over the steps required to build an Automation server, let's look inside MFC and see what it does to make Automation possible.

MFC, IDispatch, and Dispatch Maps

The cornerstone of MFC's support for Automation servers is a built-in implementation of IDispatch. That implementation comes from a class named COleDispatchImpl, which is instantiated and folded into a CCmdTarget object by the CCmdTarget::EnableAutomation function. This correctly implies that an MFC class that supports Automation must be derived, either directly or indirectly, from CCmdTarget. EnableAutomation is typically called in the class constructor.

When MFC's implementation of IDispatch::Invoke is called, MFC must somehow translate the method call or property access into a call to a class member function. Similarly, when IDispatch::GetIDsOfNames is called, MFC must translate the accompanying property or method name into a dispatch ID. It accomplishes both tasks using a dispatch map.

A dispatch map is a table that begins with BEGIN_DISPATCH_MAP and ends with END_DISPATCH_MAP. Statements in between define the object's methods and properties. Through the dispatch map, MFC's implementation of IDispatch::Invoke translates calls to Automation methods into calls to member functions in the class that houses the dispatch map. Automation properties are accessed through the dispatch map, too. The following dispatch map defines a method named DebitAccount and a property named Balance in a CCmdTarget-derived class named CAutoClass:

BEGIN_DISPATCH_MAP (CAutoClass, CCmdTarget)
 DISP_FUNCTION (CAutoClass, "DebitAccount", Debit, VT_I4, VTS_I4)
 DISP_PROPERTY_EX (CAutoClass, "Balance", GetBalance, SetBalance,VT_I4)
END_DISPATCH_MAP()

The DISP_FUNCTION macro names an Automation method and the member function that's called when the method is called. The VT_ and VTS_ values passed in the macro's argument list identify the method's return type and the types of arguments it accepts. DISP_PROPERTY_EX defines an Automation property and the get and set functions used to read and write the property's value. The fifth parameter to DISP_PROPERTY_EX defines the property's type. In this example, CAutoClass::Debit will be called when the Automation object's DebitAccount method is called. CAutoClass::GetBalance will be called to read Balance, and CAutoClass::SetBalance will be called to assign a value to it. DISP_FUNCTION and DISP_PROPERTY_EX are just two of several dispatch map macros defined in Afxdisp.h.

You might have noticed that neither of the dispatch map macros shown in the previous paragraph accepts a dispatch ID. MFC has a curious way of assigning dispatch IDs to methods and properties based on their position in the dispatch map and the derivation depth. MFC Technical Note 39 has the gory details. The positional dependency of the items in a dispatch map has one very serious implication for Automation programmers: The order of those items must agree with the dispatch IDs in the ODL file. This means that if you hand-edit a wizard-generated dispatch map and change the order of the items in any way, you must edit the ODL file, too. You can get away with editing the dispatch map and leaving the ODL file unchanged for clients that use late binding, but early binding clients will get terribly confused if the type library says one thing and IDispatch says another. For this reason, MFC provides alternative dispatch map macros that accept dispatch IDs; they, too, are documented in Technical Note 39. You still have to make sure that the dispatch IDs in the dispatch map and the ODL file agree, but the order of the statements in a dispatch map built with these macros is inconsequential. ClassWizard doesn't use the dispatch ID macros, so if you want to take advantage of them, you'll have to code them yourself.

Writing an Automation Server

You can write dispatch maps by hand if you want to, but it's more convenient to let ClassWizard write them for you. Here are the three basic steps involved in writing an MFC Automation server:

  • Run AppWizard and check the Automation box in the Step 3 dialog box (Step 2 if you choose Dialog Based in Step 1), as shown in Figure 20-3. In the Step 4 dialog box, click the Advanced button and type the server's ProgID into the File Type ID box. (See Figure 20-4.)
  • Figure 20-3. Creating an MFC Automation server.

  • Use the Add Method button on ClassWizard's Automation page to add Automation methods. (See Figure 20-5.)
  • Use the Add Property button on ClassWizard's Automation page to add Automation properties.
  • Figure 20-4. Specifying an Automation server's ProgID.

    Figure 20-5. ClassWizard's Automation Page.

    By default, only one of the classes present in an application created by AppWizard can have Automation properties and methods added to it. For a doc/view application, that class is the document class. For a dialog-based application, the "Automatable" class is a proxy class that's derived from CCmdTarget and attached to the dialog class. Why are these the only classes that will support Automation methods and properties? Because these are the only classes that AppWizard endows with the infrastructure necessary to act as Automation objects. Later in this chapter, you'll learn how to add other Automatable classes to an MFC Automation server so that it can host as many Automation objects as you like.

    Adding Automation Methods

    Adding an Automation method to an MFC Automation server is as simple as clicking ClassWizard's Add Method button and filling in the Add Method dialog box. (See Figure 20-6.) In the dialog box, External Name is the Automation method's name, and Internal Name is the name of the corresponding member function. The two names don't have to be the same, although they usually are. Return Type is the method's return type; it can be any Automation-compatible data type. Method parameters are defined in the Parameter List box. MFC handles the chore of unpackaging the VARIANTARGs containing the method parameters and packaging the method's return value in the VARIANT passed to IDispatch::Invoke.

    Figure 20-6. ClassWizard's Add Method dialog box.

    When it adds an Automation method, ClassWizard makes four modifications to the project's source code files:

    • The function that implements the method is declared in the class's header file.
    • An empty function implementation is added to the class's CPP file.
    • A DISP_FUNCTION statement is added to the class's dispatch map.
    • The method and its dispatch ID are added to the project's ODL file.

    After ClassWizard is finished, it's your job to implement the method by filling in the empty function body.

    Adding Automation Properties

    You can also use ClassWizard to add Automation properties. MFC distinguishes between two types of Automation properties:

    • Member variable properties
    • Get/set properties

    A member variable property exposes a member variable as an Automation property. A get/set property is a property that's implemented by get and set functions in your source code. A member variable property makes sense if the property value lends itself to being stored in a class member variable and if the Automation server doesn't need control over values assigned to the property. You should use a get/set property instead if any of the following conditions is true:

    • The property value can't be stored in a simple member variable. For example, a Visible property controlling the visibility of an Automation server's window is usually implemented as a get/set property so that the get function can call CWnd::IsWindowVisible and the set function can call CWnd::ShowWindow.
    • The server wants to control the values assigned to a property. For example, if legitimate values range from 1 to 10, the set function could constrain property values to numbers in this range.
    • The property is a read-only property. In this case, the set function should call the SetNotSupported function an Automatable class inherits from CCmdTarget to generate a run-time error if a client attempts to alter the property value.
    • The property is a write-only property—for example, a password. A write-only property's get function should call GetNotSupported to generate a run-time error if a client attempts to read the property value.

    To add a member variable property, click ClassWizard's Add Property button and select Member Variable. Then fill in the other fields of the Add Property dialog box pictured in Figure 20-7. External Name specifies the property name. Type is the property's Automation-compatible data type. Variable Name identifies the member variable that stores the property value. ClassWizard will add this member variable for you and wire it into the dispatch map. Notification Function specifies the name of the member function that's called when a client assigns a value to the property. You can enter any name you want into this box, and ClassWizard will add the function for you. If you don't care when the property value changes, leave this box blank, and no notification function will be added. Notification functions are useful when you want to respond immediately to changes in property values—for example, to repaint a window whose background color is exposed as a member variable property.

    Under the hood, ClassWizard adds a DISP_PROPERTY statement to the class's dispatch map when a member variable property without a notification function isadded and a DISP_PROPERTY_NOTIFY macro when a member variable property with a notification function is added. It also declares the property in the project's ODL file.

    Figure 20-7. Adding a member variable Automation property.

    If the Add Property dialog box's Get/Set Methods option is checked, ClassWizard adds a get/set property to the Automation server. (See Figure 20-8.) Besides adding member functions named GetPropertyName and SetPropertyName to the Automation class and declaring the property in the ODL file, ClassWizard adds either a DISP_PROPERTY_EX or a DISP_PROPERTY_PARAM statement to the class's dispatch map. DISP_PROPERTY_PARAM defines a property with parameters; DISP_PROPERTY_EX defines a property without parameters. If you define parameters in the Parameter List box, a client must supply those input parameters when reading or writing the property. Automation servers sometimes use get/set properties with parameters to implement indexed properties, which are described later in this chapter in the section "A More Complex Automation Server"

    Figure 20-8. Adding a get/set Automation property.

    A Simple Automation Server

    To get your feet wet writing a living, breathing MFC Automation server, try this simple exercise:

  • Use AppWizard to start a new project named AutoMath. Choose Single Document in AppWizard's Step 1 dialog box to make the server a single document interface (SDI) application. Check the Automation box in Step 3 to make the application an Automation server, and in Step 4, click the Advanced button and type AutoMath.Object into the File Type ID box. This is the Automation object's ProgID.
  • On ClassWizard's Automation page, select CAutoMathDoc from the Class Name drop-down list, click Add Method, and fill in the Add Method dialog box as shown in Figure 20-9. Click OK followed by Edit Code to go to the method's empty function body, and implement it as follows:
  • long CAutoMathDoc::Add (long a, long b)
    {
     return a + b;
    }
    

    Figure 20-9. Adding the Add method.

  • Repeat step 2 to add an Automation method named Subtract. Implement the method as follows:
  • long CAutoMathDoc::Subtract (long a, long b)
    {
     return a - b;
    }
    

  • On ClassWizard's Automation page, click Add Property and add a get/set property named Pi. (See Figure 20-10.) Implement the property's get and set functions like this:
  • double CAutoMathDoc::GetPi ()
    {
     return 3.1415926;
    }
    void CAutoMathDoc::SetPi (double newValue)
    {
     SetNotSupported ();
    }
    

    Figure 20-10. Adding the Pi property.

  • Build the application and run it once to register it on your system. (An MFC Automation server registers itself each time it's run. Registration involves writing the server's ProgID and other information to the host system's registry.)
  • Now you're ready to test the AutoMath server that you just created. To perform the test, enter the following VBScript statements into a text file named Test.vbs:

    Set Math = CreateObject ("AutoMath.Object")
    Sum = Math.Add (2, 2)
    MsgBox ("2 + 2 = " + CStr (Sum))
    MsgBox ("pi = " + CStr (Math.Pi))
    

    Then execute the script by double-clicking the Test.vbs file icon. This will run the script under the auspices of the Windows Scripting Host. Two message boxes should appear on the screen. The first displays the sum of 2 and 2. The second displays the value of pi.

    See? Automation is easy when you use MFC!

    Automation Hierarchies

    You can build Automation servers of arbitrary complexity by adding methods and properties ad infinitum. But Automation servers can grow unwieldy if they're weighted down with hundreds of methods and properties. That's why Automation programmers often "objectify" their servers' feature sets by implementing Automation hierarchies.

    An Automation hierarchy is a set of Automation objects joined together to form a tree-structured object model. Figure 20-11 shows the top four levels of Microsoft Excel's Automation hierarchy. Rather than hang all its methods and properties off a single object, Excel divides them among a top-level Application object and numerous subobjects. The following Visual Basic code starts Excel and turns on the Caps Lock Correct feature, which gives Excel permission to fIX wORDS lIKE tHESE:

    Dim Excel as Object
    Set Excel = CreateObject ("Excel.Application")
    Excel.AutoCorrect.CorrectCapsLock = 1
    

    Caps Lock Correct is exposed to Automation clients as a property of the AutoCorrect object. AutoCorrect, in turn, is a subobject of the Application object. A hierarchical object model such as this one lends organization to the server's dispinterfaces and makes the programming model easier to learn.

    Figure 20-11. The Excel object model.

    How difficult is it to implement Automation hierarchies in MFC Automation servers? Not difficult at all—once you know how. The secret is twofold. First, you add one Automatable class to the application for each subobject you want to implement. To each Automatable class, you add Automation methods and properties. Second, you wire up the hierarchy by connecting child objects to their parents. An object is made a child of another by adding a get/set property of type LPDISPATCH to the parent object and implementing the get function by returning the child's IDispatch interface pointer. You can retrieve the child object's IDispatch pointer by calling the GetIDispatch function the child object inherits from CCmdTarget.

    Adding Automatable classes is easy, too. Simply click ClassWizard's Add Class button, select New, enter a class name, select CCmdTarget as the base class, and check the Automation option near the bottom of the dialog box. (See Figure 20-12.) To make the class externally createable (that is, to give it its own ProgID so that it, too, can be created by Automation clients), check Createable By Type ID instead and enter a ProgID in the box to its right.

    Figure 20-12. Adding an Automatable class.

    A More Complex Automation Server

    The AutoPie application in Figure 20-13 is an MFC Automation server that implements the two-level object model shown in Figure 20-14. AutoPie draws pie charts depicting quarterly revenue values. The revenue values are exposed through an indexed property named Revenue, which belongs to the Chart object. The property is said to be indexed because accesses to it must be accompanied by a number from 1 to 4 specifying a quarter (first quarter, second quarter, and so on). Internally, Revenue is implemented as a get/set Automation property with one parameter in its parameter list.

    Figure 20-13. The AutoPie window.

    Figure 20-14. AutoPie's object model.

    Revenue is just one of several properties that AutoPie exposes. The following list identifies all the Automation methods and properties that AutoPie supports as well as the objects to which those methods and properties belong:

    ObjectProperties Methods
    Application N/A Quit ()
    Chart Revenue (quarter) Save (pathname)
    Window Visible Refresh ()
    Toolbar Visible N/A

    The top-level Application object represents the application itself. Its lone method, Quit, terminates the application. The Chart object represents the pie chart. Save saves the quarterly revenue values to disk. Window represents the application's window. Its Visible property can be used to hide or display the window, and Refresh forces the window (and the chart displayed inside it) to repaint. Finally, the Toolbar object represents the window's toolbar, which can be toggled on and off by setting Visible to a 0 (off) or a nonzero (on) value.

    You can test AutoPie using the following VBScript applet:

    Set Pie = CreateObject ("AutoPie.Application")
    Pie.Chart.Revenue (1) = 420
    Pie.Chart.Revenue (2) = 234
    Pie.Chart.Revenue (3) = 380
    Pie.Chart.Revenue (4) = 640
    Pie.Window.Visible = 1
    MsgBox ("Click OK to double third-quarter revenues")
    Pie.Chart.Revenue (3) = Pie.Chart.Revenue (3) * 2
    Pie.Window.Refresh
    Pie.Chart.Save ("C:\Chart.pie")
    MsgBox ("Test completed")
    

    When executed, the script starts the Automation server by passing AutoPie's ProgID to CreateObject. It then assigns revenue values and makes the AutoPie window visible. (By default, MFC Automation servers that aren't dialog-based don't show their windows when they're started by Automation clients.) Next the script displays a message box. When the message box is dismissed, the third-quarter revenue is read, multiplied by 2, and written back to the Automation server. Afterward, Refresh is called to update the pie chart. Finally, the Chart object's Save method is called to save the pie chart to a file, and a message box is displayed announcing that the test is complete.

    Pertinent portions of AutoPie's source code are reproduced in Figure 20-15. The top-level Application object is represented by the application's document class. When I used AppWizard to generate the project, I entered "AutoPie.Application" for the ProgID. Because AppWizard automated the document class, CAutoPieDoc became a proxy of sorts for the Application object at the top of the hierarchy. The subobjects are represented by CAutoChart, CAutoWindow, and CAutoToolbar, which I derived from CCmdTarget using ClassWizard. Each is an Automatable class. (Refer to Figure 20-12.) After generating these classes, I used ClassWizard to add Automation methods and properties.

    Figure 20-15. The AutoPie program.

    AutoPie.h

    // AutoPie.h : main header file for the AUTOPIE application
    //
    

    #if !defined(
    AFX_AUTOPIE_H__3B5BA30B_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_AUTOPIE_H__3B5BA30B_3B72_11D2_AC82_006008A8274D__INCLUDED_

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    #ifndef AFX)WIN_H
    #error include `stdafx.h' before including this file for PCH
    #endif

    #include "resource.h" // main symbols

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieApp:
    // See AutoPie.cpp for the implementation of this class
    //

    class CAutoPieApp : public CWinApp
    {
    public:
    CAutoPieApp();

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAutoPieApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL

    // Implementation
    COleTemplateServer m_server;// Server object for document creation
    //{{AFX_MSG(CAutoPieApp)
    afx_msg void OnAppAbout();
    // NOTE - the ClassWizard will add and remove member functions here.
    // DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(AFX_AUTOPIE_H__3B5BA30B_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    AutoPie.cpp

    // AutoPie.cpp : Defines the class behaviors for the application.
    //
    

    #include "stdafx.h"
    #include "AutoPie.h"

    #include "MainFrm.h"
    #include "AutoPieDoc.h"
    #include "AutoPieView.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieApp

    BEGIN_MESSAGE_MAP(CAutoPieApp, CWinApp)
    //{{AFX_MSG_MAP(CAutoPieApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)// NOTE - the ClassWizard will add and remove mapping macros here.// DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
    END_MESSAGE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieApp construction

    CAutoPieApp::CAutoPieApp()
    {
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
    }

    ///////////////////////////////////////////////////////////////////////////
    // The one and only CAutoPieApp object

    CAutoPieApp theApp;

    // This identifier was generated to be statistically unique for your app.
    // You may change it if you prefer to choose a specific identifier.

    // {3B5BA306-3B72-11D2-AC82-006008A8274D}
    static const CLSID clsid =
    { 0x3b5ba306, 0x3b72, 0x11d2,{ 0xac, 0x82, 0x0, 0x60, 0x8, 0xa8, 0x27, 0x4d } };

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieApp initialization

    BOOL CAutoPieApp::InitInstance()
    {
    // Initialize OLE libraries
    if (!AfxOleInit())
    {AfxMessageBox(IDP_OLE_INIT_FAILED);return FALSE;
    }// Standard initialization
    // If you are not using these features and wish to reduce the size
    // of your final executable, you should remove from the following
    // the specific initialization routines you do not need.// Change the registry key under which our settings are stored.
    // TODO: You should modify this string to be something appropriate
    // such as the name of your company or organization.
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));LoadStdProfileSettings(); // Load standard INI file options// (including MRU)// Register the application's document templates. Document templates
    // serve as the connection between documents, frame windows and views.CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CAutoPieDoc),RUNTIME_CLASS(CMainFrame), // main SDI frame windowRUNTIME_CLASS(CAutoPieView));
    AddDocTemplate(pDocTemplate);// Connect the COleTemplateServer to the document template.
    // The COleTemplateServer creates new documents on behalf
    // of requesting OLE containers by using information
    // specified in the document template.m_server.ConnectTemplate(clsid, pDocTemplate, TRUE);
    // Note: SDI applications register server objects only if /Embedding
    //or /Automation is present on the command line.// Enable DDE Execute open
    EnableshellOpen();
    RegistershellFileTypes(TRUE);// Parse command line for standard shell commands, DDE, file open
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);// Check to see if launched as OLE server
    if (cmdInfo.m_bRunEmbedded œœ cmdInfo.m_bRunAutomated)
    {// Register all OLE server (factories) as running. This enables // the OLE libraries to create objects from other applications.COleTemplateServer::RegisterAll(); // Application was run with /Embedding or /Automation. // Don't show themain window in this case.return TRUE;
    }// When a server application is launched stand-alone, it is a good idea
    // to update the system registry in case it has been damaged.
    m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
    COleObjectFactory::UpdateRegistryAll();// Dispatch commands specified on the command line
    if (!ProcessShellCommand(cmdInfo))return FALSE;// The one and only window has been initialized, so show and update it.
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();// Enable drag/drop open
    m_pMainWnd->DragAcceptFiles();return TRUE;
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAboutDlg dialog used for App About

    class CAboutDlg : public CDialog
    {
    public:
    CAboutDlg();

    // Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA// ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    //{{AFX_MSG(CAboutDlg)// No message handlers
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

    CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
    {
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
    }

    void CAboutDlg::DoDataExchange(CDataExchange* pDX)
    {
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
    }

    BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)// No message handlers
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    // App command to run the dialog
    void CAutoPieApp::OnAppAbout()
    {
    CAboutDlg aboutDlg;
    aboutDlg.DoModal();
    }
    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieApp message handlers

    AutoPieDoc.h

    // AutoPieDoc.h : interface of the CAutoPieDoc class
    //
    ///////////////////////////////////////////////////////////////////////////
    

    #if !defined(
    AFX_AUTOPIEDOC_H__3B5BA312_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_AUTOPIEDOC_H__3B5BA312_3B72_11D2_AC82_006008A8274D__INCLUDED_

    #include "AutoChart.h"// Added by ClassView
    #include "AutoWindow.h" // Added by ClassView
    #include "AutoToolbar.h" // Added by ClassView
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000class CAutoPieDoc : public CDocument
    {
    protected: // create from serialization only
    CAutoPieDoc();
    DECLARE_DYNCREATE(CAutoPieDoc)

    // Attributes
    public:

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAutoPieDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual void Serialize(CArchive& ar);
    //}}AFX_VIRTUAL

    // Implementation
    public:
    void SetRevenue (int nQuarter, int nNewValue);int GetRevenue (int nQuarter);
    virtual ~CAutoPieDoc();
    #ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
    #endif

    protected:

    // Generated message map functions
    protected:
    CAutoToolbar m_autoToolbar;
    CAutoWindow m_autoWindow;
    CAutoChart m_autoChart;
    int m_nRevenues[4];
    //{{AFX_MSG(CAutoPieDoc)
    // NOTE - the ClassWizard will add and remove member functions here.
    // DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()// Generated OLE dispatch map functions
    //{{AFX_DISPATCH(CAutoPieDoc)
    afx_msg LPDISPATCH GetChart();
    afx_msg void SetChart(LPDISPATCH newValue);
    afx_msg LPDISPATCH GetWindow();
    afx_msg void SetWindow(LPDISPATCH newValue);
    afx_msg LPDISPATCH GetToolbar();
    afx_msg void SetToolbar(LPDISPATCH newValue);
    afx_msg void Quit();
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()
    DECLARE_INTERFACE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_AUTOPIEDOC_H__3B5BA312_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    AutoPieDoc.cpp

    // AutoPieDoc.cpp : implementation of the CAutoPieDoc class
    //
    

    #include "stdafx.h"
    #include "AutoPie.h"

    #include "AutoPieDoc.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieDoc

    IMPLEMENT_DYNCREATE(CAutoPieDoc, CDocument)

    BEGIN_MESSAGE_MAP(CAutoPieDoc, CDocument)
    //{{AFX_MSG_MAP(CAutoPieDoc)// NOTE - the ClassWizard will add and remove mapping macros here.// DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    BEGIN_DISPATCH_MAP(CAutoPieDoc, CDocument)
    //{{AFX_DISPATCH_MAP(CAutoPieDoc)
    DISP_PROPERTY_EX(CAutoPieDoc, "Chart", GetChart, SetChart, VT_DISPATCH)
    DISP_PROPERTY_EX(CAutoPieDoc, "Window", GetWindow, SetWindow, VT_DISPATCH)
    DISP_PROPERTY_EX(CAutoPieDoc, "Toolbar", GetToolbar, SetToolbar, VT_DISPATCH)
    DISP_FUNCTION(CAutoPieDoc, "Quit", Quit, VT_EMPTY, VTS_NONE)
    //}}AFX_DISPATCH_MAP
    END_DISPATCH_MAP()

    // Note: we add support for IID_IAutoPie to support typesafe binding
    // from VBA. This IID must match the GUID that is attached to the
    // dispinterface in the .ODL file.

    // {3B5BA308-3B72-11D2-AC82-006008A8274D}
    static const IID IID_IAutoPie =
    { 0x3b5ba308, 0x3b72, 0x11d2, { 0xac, 0x82, 0x0, 0x60, 0x8, 0xa8, 0x27, 0x4d } };

    BEGIN_INTERFACE_MAP(CAutoPieDoc, CDocument)
    INTERFACE_PART(CAutoPieDoc, IID_IAutoPie, Dispatch)
    END_INTERFACE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieDoc construction/destruction

    CAutoPieDoc::CAutoPieDoc()
    {
    EnableAutomation();AfxOleLockApp();
    }

    CAutoPieDoc::~CAutoPieDoc()
    {
    AfxOleUnlockApp();
    }

    BOOL CAutoPieDoc::OnNewDocument()
    {
    if (!CDocument::OnNewDocument())return FALSE;m_nRevenues[

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieDoc serialization

    void CAutoPieDoc::Serialize(CArchive& ar)
    {
    if (ar.IsStoring())
    {for (int i=0; i<4; i++)
    ar << m_nRevenues[i];
    }
    else
    {for (int i=0; i<4; i++)
    ar >> m_nRevenues[i]; }
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieDoc diagnostics

    #ifdef _DEBUG
    void CAutoPieDoc::AssertValid() const
    {
    CDocument::AssertValid();
    }

    void CAutoPieDoc::Dump(CDumpContext& dc) const
    {
    CDocument::Dump(dc);
    }
    #endif //_DEBUG

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieDoc commands

    int CAutoPieDoc::GetRevenue(int nQuarter)
    {
    ASSERT (nQuarter >= 0 && nQuarter <= 3);
    return m_nRevenues[nQuarter];
    }

    void CAutoPieDoc::SetRevenue(int nQuarter, int nNewValue)
    {
    ASSERT (nQuarter >= 0 && nQuarter <= 3);
    m_nRevenues[nQuarter] = nNewValue;
    }

    void CAutoPieDoc::Quit()
    {
    AfxGetMainWnd ()->PostMessage (WM_CLOSE, 0, 0);
    }

    LPDISPATCH CAutoPieDoc::GetChart()
    {
    return m_autoChart.GetIDispatch (TRUE);
    }

    void CAutoPieDoc::SetChart(LPDISPATCH newValue)
    {
    SetNotSupported ();
    }

    LPDISPATCH CAutoPieDoc::GetWindow()
    {
    return m_autoWindow.GetIDispatch (TRUE);
    }

    void CAutoPieDoc::SetWindow(LPDISPATCH newValue)
    {
    SetNotSupported ();
    }

    LPDISPATCH CAutoPieDoc::GetToolbar()
    {
    return m_autoToolbar.GetIDispatch (TRUE);
    }

    void CAutoPieDoc::SetToolbar(LPDISPATCH newValue)
    {
    SetNotSupported ();
    }

    AutoChart.h

    #if !defined(
     AFX_AUTOCHART_H__3B5BA31E_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_AUTOCHART_H__3B5BA31E_3B72_11D2_AC82_006008A8274D__INCLUDED_
    

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    // AutoChart.h : header file
    //

    #define ID_ERROR_OUTOFRANGE 100

    ///////////////////////////////////////////////////////////////////////////
    // CAutoChart command target

    class CAutoChart : public CCmdTarget
    {
    DECLARE_DYNCREATE(CAutoChart)CAutoChart();// protected constructor used by dynamic creation

    // Attributes
    public:
    virtual ~CAutoChart();

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAutoChart)
    public:
    virtual void OnFinalRelease();
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    // Generated message map functions
    //{{AFX_MSG(CAutoChart)
    // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSGDECLARE_MESSAGE_MAP()
    // Generated OLE dispatch map functions
    //{{AFX_DISPATCH(CAutoChart)
    afx_msg BOOL Save(LPCTSTR pszPath);
    afx_msg long GetRevenue(short nQuarter);
    afx_msg void SetRevenue(short nQuarter, long nNewValue);
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()
    DECLARE_INTERFACE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_AUTOCHART_H__3B5BA31E_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    AutoChart.cpp

    // AutoChart.cpp : implementation file
    //
    

    #include "stdafx.h"
    #include "AutoPie.h"
    #include "AutoChart.h"
    #include "AutoPieDoc.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CAutoChart

    IMPLEMENT_DYNCREATE(CAutoChart, CCmdTarget)

    CAutoChart::CAutoChart()
    {
    EnableAutomation();
    }

    CAutoChart::~CAutoChart()
    {
    }

    void CAutoChart::OnFinalRelease()
    {
    // When the last reference for an automation object is released
    // OnFinalRelease is called. The base class will automatically
    // deletes the object. Add additional cleanup required for your
    // object before calling the base class.CCmdTarget::OnFinalRelease();
    }

    BEGIN_MESSAGE_MAP(CAutoChart, CCmdTarget)
    //{{AFX_MSG_MAP(CAutoChart)// NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    BEGIN_DISPATCH_MAP(CAutoChart, CCmdTarget)
    //{{AFX_DISPATCH_MAP(CAutoChart)
    DISP_FUNCTION(CAutoChart, "Save", Save, VT_BOOL, VTS_BSTR)
    DISP_PROPERTY_PARAM(CAutoChart, "Revenue", GetRevenue, SetRevenue, VT_I4, VTS_I2)
    //}}AFX_DISPATCH_MAP
    END_DISPATCH_MAP()

    // Note: we add support for IID_IAutoChart to support typesafe binding
    // from VBA. This IID must match the GUID that is attached to the
    // dispinterface in the .ODL file.

    // {3B5BA31D-3B72-11D2-AC82-006008A8274D}
    static const IID IID_IAutoChart =
    { 0x3b5ba31d, 0x3b72, 0x11d2,{ 0xac, 0x82, 0x0, 0x60, 0x8, 0xa8, 0x27, 0x4d } };

    BEGIN_INTERFACE_MAP(CAutoChart, CCmdTarget)
    INTERFACE_PART(CAutoChart, IID_IAutoChart, Dispatch)
    END_INTERFACE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CAutoChart message handlers

    BOOL CAutoChart::Save(LPCTSTR pszPath)
    {
    CFrameWnd* pFrame = (CFrameWnd) AfxGetMainWnd ();
    CAutoPieDoc
    pDoc = (CAutoPieDoc*) pFrame->GetActiveDocument ();
    return pDoc->OnSaveDocument (pszPath);
    }

    long CAutoChart::GetRevenue(short nQuarter)
    {
    long lResult = -1; if (nQuarter >= 1 && nQuarter <= 4) {CFrameWnd* pFrame = (CFrameWnd) AfxGetMainWnd ();CAutoPieDoc pDoc = (CAutoPieDoc*) pFrame->GetActiveDocument ();lResult = (long) pDoc->GetRevenue (nQuarter - 1);
    }
    else {//// If the quarter number is out of range, fail the call// and let the caller know precisely why it failed.//AfxThrowOleDispatchException (ID_ERROR_OUTOFRANGE,
    _T ("Invalid parameter specified when reading Revenue"));
    }
    return lResult;
    }

    void CAutoChart::SetRevenue(short nQuarter, long nNewValue)
    {
    if (nQuarter >= 1 && nQuarter <= 4) {CFrameWnd* pFrame = (CFrameWnd) AfxGetMainWnd ();CAutoPieDoc pDoc = (CAutoPieDoc*) pFrame->GetActiveDocument ();pDoc->SetRevenue (nQuarter - 1, nNewValue);
    }
    else {//// If the quarter number is out of range, fail the call// and let the caller know precisely why it failed.//AfxThrowOleDispatchException (ID_ERROR_OUTOFRANGE,
    _T ("Invalid parameter specified when setting Revenue"));
    }
    }

    AutoWindow.h

    #if !defined(
     AFX_AUTOWINDOW_H__3B5BA321_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_AUTOWINDOW_H__3B5BA321_3B72_11D2_AC82_006008A8274D__INCLUDED_
    

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    // AutoWindow.h : header file
    //

    ///////////////////////////////////////////////////////////////////////////
    // CAutoWindow command target

    class CAutoWindow : public CCmdTarget
    {
    DECLARE_DYNCREATE(CAutoWindow)CAutoWindow(); // protected constructor used by dynamic creation

    // Attributes
    public:
    virtual ~CAutoWindow();

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAutoWindow)
    public:
    virtual void OnFinalRelease();
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    // Generated message map functions
    //{{AFX_MSG(CAutoWindow)
    // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSGDECLARE_MESSAGE_MAP()
    // Generated OLE dispatch map functions
    //{{AFX_DISPATCH(CAutoWindow)
    afx_msg BOOL GetVisible();
    afx_msg void SetVisible(BOOL bNewValue);
    afx_msg void Refresh();
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()
    DECLARE_INTERFACE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_AUTOWINDOW_H__3B5BA321_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    AutoWindow.cpp

    // AutoWindow.cpp : implementation file
    //
    

    #include "stdafx.h"
    #include "AutoPie.h"
    #include "AutoWindow.h"
    #include "AutoPieDoc.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif
    ///////////////////////////////////////////////////////////////////////////
    // CAutoWindow

    IMPLEMENT_DYNCREATE(CAutoWindow, CCmdTarget)

    CAutoWindow::CAutoWindow()
    {
    EnableAutomation();
    }

    CAutoWindow::~CAutoWindow()
    {
    }

    void CAutoWindow::OnFinalRelease()
    {
    // When the last reference for an automation object is released
    // OnFinalRelease is called. The base class will automatically
    // deletes the object. Add additional cleanup required for your
    // object before calling the base class.CCmdTarget::OnFinalRelease();
    }

    BEGIN_MESSAGE_MAP(CAutoWindow, CCmdTarget)
    //{{AFX_MSG_MAP(CAutoWindow)// NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    BEGIN_DISPATCH_MAP(CAutoWindow, CCmdTarget)
    //{{AFX_DISPATCH_MAP(CAutoWindow)
    DISP_PROPERTY_EX(CAutoWindow, "Visible", GetVisible, SetVisible, VT_BOOL)
    DISP_FUNCTION(CAutoWindow, "Refresh", Refresh, VT_EMPTY, VTS_NONE)
    //}}AFX_DISPATCH_MAP
    END_DISPATCH_MAP()

    // Note: we add support for IID_IAutoWindow to support typesafe binding
    // from VBA. This IID must match the GUID that is attached to the
    // dispinterface in the .ODL file.

    // {3B5BA320-3B72-11D2-AC82-006008A8274D}
    static const IID IID_IAutoWindow =
    { 0x3b5ba320, 0x3b72, 0x11d2,{ 0xac, 0x82, 0x0, 0x60, 0x8, 0xa8, 0x27, 0x4d } };

    BEGIN_INTERFACE_MAP(CAutoWindow, CCmdTarget)
    INTERFACE_PART(CAutoWindow, IID_IAutoWindow, Dispatch)
    END_INTERFACE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CAutoWindow message handlers

    void CAutoWindow::Refresh()
    {
    CFrameWnd* pFrame = (CFrameWnd) AfxGetMainWnd ();
    CAutoPieDoc
    pDoc = (CAutoPieDoc*) pFrame->GetActiveDocument ();
    pDoc->UpdateAllViews (NULL);
    }

    BOOL CAutoWindow::GetVisible()
    {
    return AfxGetMainWnd ()->IsWindowVisible ();
    }

    void CAutoWindow::SetVisible(BOOL bNewValue)
    {
    AfxGetMainWnd ()->ShowWindow (bNewValue ? SW_SHOW : SW_HIDE);
    }

    AutoToolbar.h

    #if !defined(
     AFX_AUTOTOOLBAR_H__3B5BA324_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_AUTOTOOLBAR_H__3B5BA324_3B72_11D2_AC82_006008A8274D__INCLUDED_
    

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    // AutoToolbar.h : header file
    //

    ///////////////////////////////////////////////////////////////////////////
    // CAutoToolbar command target

    class CAutoToolbar : public CCmdTarget
    {
    DECLARE_DYNCREATE(CAutoToolbar)CAutoToolbar(); // protected constructor used by dynamic creation
    // Attributes
    public:
    virtual ~CAutoToolbar();

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAutoToolbar)
    public:
    virtual void OnFinalRelease();
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    // Generated message map functions
    //{{AFX_MSG(CAutoToolbar)
    // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSGDECLARE_MESSAGE_MAP()
    // Generated OLE dispatch map functions
    //{{AFX_DISPATCH(CAutoToolbar)
    afx_msg BOOL GetVisible();
    afx_msg void SetVisible(BOOL bNewValue);
    //}}AFX_DISPATCH
    DECLARE_DISPATCH_MAP()
    DECLARE_INTERFACE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_AUTOTOOLBAR_H__3B5BA324_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    AutoToolbar.cpp

    // AutoToolbar.cpp : implementation file
    //
    

    #include "stdafx.h"
    #include "AutoPie.h"
    #include "AutoToolbar.h"
    #include "MainFrm.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CAutoToolbar

    IMPLEMENT_DYNCREATE(CAutoToolbar, CCmdTarget)

    CAutoToolbar::CAutoToolbar()
    {
    EnableAutomation();
    }

    CAutoToolbar::~CAutoToolbar()
    {
    }

    void CAutoToolbar::OnFinalRelease()
    {
    // When the last reference for an automation object is released
    // OnFinalRelease is called. The base class will automatically
    // deletes the object. Add additional cleanup required for your
    // object before calling the base class.CCmdTarget::OnFinalRelease();
    }

    BEGIN_MESSAGE_MAP(CAutoToolbar, CCmdTarget)
    //{{AFX_MSG_MAP(CAutoToolbar)// NOTE - the ClassWizard will add and remove mapping macros here.
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    BEGIN_DISPATCH_MAP(CAutoToolbar, CCmdTarget)
    //{{AFX_DISPATCH_MAP(CAutoToolbar)
    DISP_PROPERTY_EX(CAutoToolbar, "Visible", GetVisible, SetVisible, VT_BOOL)
    //}}AFX_DISPATCH_MAP
    END_DISPATCH_MAP()

    // Note: we add support for IID_IAutoToolbar to support typesafe binding
    // from VBA. This IID must match the GUID that is attached to the
    // dispinterface in the .ODL file.

    // {3B5BA323-3B72-11D2-AC82-006008A8274D}
    static const IID IID_IAutoToolbar =
    { 0x3b5ba323, 0x3b72, 0x11d2,{ 0xac, 0x82, 0x0, 0x60, 0x8, 0xa8, 0x27, 0x4d } };

    BEGIN_INTERFACE_MAP(CAutoToolbar, CCmdTarget)
    INTERFACE_PART(CAutoToolbar, IID_IAutoToolbar, Dispatch)
    END_INTERFACE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CAutoToolbar message handlers

    BOOL CAutoToolbar::GetVisible()
    {
    CMainFrame* pFrame = (CMainFrame*) AfxGetMainWnd ();
    return (pFrame->m_wndToolBar.GetStyle () & WS_VISIBLE) ?TRUE : FALSE;
    }

    void CAutoToolbar::SetVisible(BOOL bNewValue)
    {
    CMainFrame* pFrame = (CMainFrame*) AfxGetMainWnd ();
    pFrame->ShowControlBar (&pFrame->m_wndToolBar, bNewValue, FALSE);
    }

    AutoPieView.h

    // AutoPieView.h : interface of the CAutoPieView class
    //
    ///////////////////////////////////////////////////////////////////////////
    

    #if !defined(
    AFX_AUTOPIEVIEW_H__3B5BA314_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_AUTOPIEVIEW_H__3B5BA314_3B72_11D2_AC82_006008A8274D__INCLUDED_

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    #define PI 3.1415926

    class CAutoPieView : public CView
    {
    protected: // create from serialization only
    CAutoPieView();
    DECLARE_DYNCREATE(CAutoPieView)

    // Attributes
    public:
    CAutoPieDoc* GetDocument();

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAutoPieView)
    public:
    virtual void OnDraw(CDC* pDC); // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    //}}AFX_VIRTUAL

    // Implementation
    public:
    virtual ~CAutoPieView();
    #ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
    #endif

    protected:

    // Generated message map functions
    protected:
    //{{AFX_MSG(CAutoPieView)
    // NOTE - the ClassWizard will add and remove member functions here.
    // DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

    #ifndef _DEBUG // debug version in AutoPieView.cpp
    inline CAutoPieDoc* CAutoPieView::GetDocument()
    { return (CAutoPieDoc*)m_pDocument; }
    #endif

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_AUTOPIEVIEW_H__3B5BA314_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    AutoPieView.cpp

    // AUTOPIEVIEW.CPP : IMPLEMENTATION OF THE CAUTOPIEVIEW CLASS
    //
    

    #include "stdafx.h"
    #include "AutoPie.h"
    #include "AutoPieDoc.h"
    #include "AutoPieView.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieView

    IMPLEMENT_DYNCREATE(CAutoPieView, CView)

    BEGIN_MESSAGE_MAP(CAutoPieView, CView)
    //{{AFX_MSG_MAP(CAutoPieView)// NOTE - the ClassWizard will add and remove mapping macros here.// DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieView construction/destruction

    CAutoPieView::CAutoPieView()
    {
    // TODO: add construction code here

    }

    CAutoPieView::~CAutoPieView()
    {
    }

    BOOL CAutoPieView::PreCreateWindow(CREATESTRUCT& cs)
    {
    // TODO: Modify the Window class or styles here by modifying
    // the CREATESTRUCT csreturn CView::PreCreateWindow(cs);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieView drawing

    void CAutoPieView::OnDraw(CDC* pDC)
    {
    CAutoPieDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);CRect rect;
    GetClientRect (&rect);//
    // Initialize the mapping mode.
    //
    pDC->SetMapMode (MM_ANISOTROPIC);
    pDC->SetWindowExt (500, 500);
    pDC->SetWindowOrg (-250, -250);
    pDC->SetViewportExt (rect.Width (), rect.Height ());//
    // Create a set of brushes.
    //
    CBrush brFillColor[4];
    brFillColor[

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieView diagnostics

    #ifdef _DEBUG
    void CAutoPieView::AssertValid() const
    {
    CView::AssertValid();
    }

    void CAutoPieView::Dump(CDumpContext& dc) const
    {
    CView::Dump(dc);
    }

    CAutoPieDoc* CAutoPieView::GetDocument() // non-debug version is inline
    {
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CAutoPieDoc)));
    return (CAutoPieDoc*)m_pDocument;
    }
    #endif //_DEBUG

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPieView message handlers

    To expose CAutoWindow, CAutoChart , and CAutoToolbar as subobjects of the Application object, I added CAutoWindow, CAutoChart, and CAutoToolbar data members named m_autoWindow, m_autoChart, and m_autoToolbar to the document class. I then added LPDISPATCH get/set properties named Window, Chart, and Toolbar to the document class and implemented the get functions by calling GetIDispatch on the embedded objects. If a client tries to write to these properties, the SetNotSupported calls in the set functions will serve notice that the properties are read-only:

    LPDISPATCH CAutoPieDoc::GetChart() 
    {
     return m_autoChart.GetIDispatch (TRUE);
    }
    

    void CAutoPieDoc::SetChart(LPDISPATCH newValue)
    {
    SetNotSupported ();
    }

    LPDISPATCH CAutoPieDoc::GetWindow()
    {
    return m_autoWindow.GetIDispatch (TRUE);
    }

    void CAutoPieDoc::SetWindow(LPDISPATCH newValue)
    {
    SetNotSupported ();
    }

    LPDISPATCH CAutoPieDoc::GetToolbar()
    {
    return m_autoToolbar.GetIDispatch (TRUE);
    }

    void CAutoPieDoc::SetToolbar(LPDISPATCH newValue)
    {
    SetNotSupported ();
    }

    Passing TRUE to GetIDispatch ensures that AddRef is called on the IDispatch pointers retrieved from the subobjects. This protects the subobjects from premature deletion. It's up to the client to release the IDispatch pointers. Fortunately, VBScript clients do this automatically.

    The AfxThrowOleDispatchException Function

    SetNotSupported uses MFC's AfxThrowOleDispatchException function to fail attempts to write to read-only Automation properties. Sometimes it's useful to call AfxThrowOleDispatchException yourself. AutoPie does just that if a client specifies an invalid quarter number (a value outside the range 1 through 4) when reading or writing the Chart object's Revenue property. Here's an excerpt from AutoChart.cpp:

    AfxThrowOleDispatchException (ID_ERROR_OUTOFRANGE,
     _T ("Invalid parameter specified when reading Revenue"));
    

    AfxThrowOleDispatchException fails the call and provides a descriptive error message to the client. Most clients, particularly VBScript clients, display this error message to their users.

    MFC Automation Clients

    mfc Automation Clients

    mfc vastly simplifies the writing of Automation servers, but what about Automation clients? Good news: with a little help from ClassWizard, it's almost as easy to write an Automation client with MFC as it is to write it with Visual Basic.

    The key is a class named COleDispatchDriver, which puts a friendly face on IDispatch pointers exported by running Automation servers. The COleDispatchDriver helper functions InvokeHelper, SetProperty, and GetProperty simplify method and property accesses, but interacting with an Automation object using these functions is only slightly better than calling IDispatch::Invoke directly. The real value of COleDispatchDriver lies in creating type-safe classes whose member functions provide easy access to Automation methods and properties. After all, it's easier for a C++ programmer to call a class member function than to call IDispatch::Invoke.

    To derive a class from COleDispatchDriver that's tailored to a specific Automation server, click ClassWizard's Add Class button, select From A Type Library, and point ClassWizard to the server's type library. ClassWizard will read the type library and generate the new class. Inside that class you'll find member functions for calling the server's methods and get and set functions for accessing its properties. For example, if the server supports a method named Add and a property named Pi, the ClassWizard-generated class will include a member function named Add and accessor functions named GetPi and SetPi. If the wrapper class were named CAutoMath and the object's ProgID were "Math.Object," the object could be instantiated and programmed using statements like these:

    CAutoMath math;
    math.CreateDispatch (_T ("Math.Object"));
    int sum = math.Add (2, 2);
    double pi = math.GetPi ();
    

    CreateDispatch uses ::CoCreateInstance to create the Automation object. It caches the object's IDispatch pointer in a member variable named m_lpDispatch. Method calls and property accesses performed via CAutoMath member functions are translated into IDispatch calls to the object by InvokeHelper and other COleDispatchDriver functions.

    The PieClient Application

    Let's close out this chapter with an MFC Automation client. PieClient, a picture of which appears in Figure 20-16 and whose source code appears in Figure 20-17, is a dialog-based application whose main window features edit controls for entering and editing quarterly revenue values. Values entered in the controls are charted by AutoPie. PieClient drives AutoPie via Automation.

    Figure 20-16. PieClient acting as an Automation client to AutoPie.

    When started, PieClient calls CreateDispatch on a CAutoPie object named m_autoPie to start the Automation server:

    BOOL bSuccess = m_autoPie.CreateDispatch (_T ("AutoPie.Application"));
    

    When its Set button is clicked, PieClient gathers the revenue values from the edit controls and transmits them to the server by writing them to the Chart object's Revenue property:

    m_autoChart.SetRevenue (1, GetDlgitemInt (IDC_Q1));
    m_autoChart.SetRevenue (2, GetDlgitemInt (IDC_Q2));
    m_autoChart.SetRevenue (3, GetDlgItemInt (IDC_Q3));
    m_autoChart.SetRevenue (4, GetDlgItemInt (IDC_Q4));
    

    It then repaints the pie chart by calling the Window object's Refresh method:

    m_autoWindow.Refresh ();
    

    Conversely, if the Get button is clicked, PieClient reads the property values from the Automation object and displays them in the edit controls.

    m_autoChart and m_autoWindow are instances of CAutoChart and CAutoWindow. These classes and others—namely, CAutoPie and CAutoToolbar—are COleDispatchDriver derivatives that ClassWizard created from AutoPie's type library. CAutoPie represents the server's top-level Application object. The remaining classes represent the Chart, Window, and Toolbar subobjects. m_autoPie is initialized by CreateDispatch, but m_autoChart and m_autoWindow must be initialized separately because the corresponding subobjects are automatically created when the server is started. These initializations are performed by passing the IDispatch pointers returned by CAutoPie's GetChart and GetWindow functions to AttachDispatch:

    m_autoChart.AttachDispatch (m_autoPie.GetChart ());
    m_autoWindow.AttachDispatch (m_autoPie.GetWindow ());
    

    Because m_autoPie, m_autoChart, and m_autoWindow are embedded data members, they're automatically destroyed when the dialog object is destroyed. And when a COleDispatchDriver-object is destroyed, the IDispatch pointer that it wraps is released by the class destructor. That's why AutoPie closes when PieClient is closed. When the last pointer to an MFC Automation server's dispinterface is released, the server obediently shuts itself down.

    PieClient.h

    // PieClient.h : main header file for the PIECLIENT application
    //
    

    #if !defined(
    AFX_PIECLIENT_H__3B5BA32A_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_PIECLIENT_H__3B5BA32A_3B72_11D2_AC82_006008A8274D__INCLUDED_

    Figure 20-17. The PieClient program.

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    #ifndef __AFXWIN_H__
     #error include `stdafx.h' before including this file for PCH
    #endif
    

    #include "resource.h" // main symbols

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientApp:
    // See PieClient.cpp for the implementation of this class
    //

    class CPieClientApp : public CWinApp
    {
    public:
    CPieClientApp();

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CPieClientApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL

    // Implementation//{{AFX_MSG(CPieClientApp)
    // NOTE - the ClassWizard will add and remove member functions here.
    // DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_PIECLIENT_H__3B5BA32A_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    PieClient.cpp

    // PieClient.cpp : Defines the class behaviors for the application.
    //
    

    #include "stdafx.h"
    #include "PieClient.h"
    #include "PieClientDlg.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientApp

    BEGIN_MESSAGE_MAP(CPieClientApp, CWinApp)
    //{{AFX_MSG_MAP(CPieClientApp)// NOTE - the ClassWizard will add and remove mapping macros here.// DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    ON_COMMAND(ID_HELP, CWinApp::OnHelp)
    END_MESSAGE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientApp construction

    CPieClientApp::CPieClientApp()
    {
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
    }

    ///////////////////////////////////////////////////////////////////////////
    // The one and only CPieClientApp object

    CPieClientApp theApp;

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientApp initialization

    BOOL CPieClientApp::InitInstance()
    {
    if (!AfxOleInit ()) {AfxMessageBox (_T ("AfxOleInit failed"));return FALSE;
    }
    // Standard initialization
    // If you are not using these features and wish to reduce the size
    // of your final executable, you should remove from the following
    // the specific initialization routines you do not need.CPieClientDlg dlg;
    m_pMainWnd = &dlg;
    int nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {// TODO: Place code here to handle when the dialog is// dismissed with OK
    }
    else if (nResponse == IDCANCEL)
    {// TODO: Place code here to handle when the dialog is// dismissed with Cancel
    }// Since the dialog has been closed, return FALSE so that we exit the
    // application, rather than start the application's message pump.
    return FALSE;
    }

    PieClientDlg.h

    // PieClientDlg.h : header file
    //
    

    #if !defined(
    AFX_PIECLIENTDLG_H__3B5BA32C_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_PIECLIENTDLG_H__3B5BA32C_3B72_11D2_AC82_006008A8274D__INCLUDED_

    #include "autopie.h" // Added by ClassView
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientDlg dialog

    class CPieClientDlg : public CDialog
    {
    // Construction
    public:
    CPieClientDlg(CWnd* pParent = NULL); // standard constructor

    // Dialog Data
    //{{AFX_DATA(CPieClientDlg)
    enum { IDD = IDD_PIECLIENT_DIALOG };
    CButton m_wndSet;
    CButton m_wndGet;
    //}}AFX_DATA// ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CPieClientDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    CAutoWindow m_autoWindow;
    CAutoChart m_autoChart;
    CAutoPie m_autoPie;
    HICON m_hIcon;// Generated message map functions
    //{{AFX_MSG(CPieClientDlg)
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg void OnGet();
    afx_msg void OnSet();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_PIECLIENTDLG_H__3B5BA32C_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    PieClientDlg.cpp

    // PieClientDlg.cpp : implementation file
    //
    

    #include "stdafx.h"
    #include "PieClient.h"
    #include "PieClientDlg.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientDlg dialog

    CPieClientDlg::CPieClientDlg(CWnd* pParent /=NULL/)
    : CDialog(CPieClientDlg::IDD, pParent)
    {
    //{{AFX_DATA_INIT(CPieClientDlg)
    //}}AFX_DATA_INIT
    // Note that LoadIcon does not require a subsequent// DestroyIcon in win32
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    }

    void CPieClientDlg::DoDataExchange(CDataExchange* pDX)
    {
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CPieClientDlg)
    DDX_Control(pDX, IDC_SET, m_wndSet);
    DDX_Control(pDX, IDC_GET, m_wndGet);
    //}}AFX_DATA_MAP
    }

    BEGIN_MESSAGE_MAP(CPieClientDlg, CDialog)
    //{{AFX_MSG_MAP(CPieClientDlg)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_GET, OnGet)
    ON_BN_CLICKED(IDC_SET, OnSet)
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CPieClientDlg message handlers

    BOOL CPieClientDlg::OnInitDialog()
    {
    CDialog::OnInitDialog();
    SetIcon(m_hIcon, TRUE);// Set big icon
    SetIcon(m_hIcon, FALSE); // Set small icon //
    // Start the Automation server.
    //
    BOOL bSuccess = m_autoPie.CreateDispatch (_T ("AutoPie.Application"));//
    // If CreateDispatch succeeded, initialize the m_autoChart and
    // m_autoWindow data members to represent the Chart and Window
    // subobjects, respectively. Then initialize the controls in
    // the dialog and make the server window visible.
    //
    if (bSuccess) {m_autoChart.AttachDispatch (m_autoPie.GetChart ());ASSERT (m_autoChart.m_lpDispatch != NULL);m_autoWindow.AttachDispatch (m_autoPie.GetWindow ());ASSERT (m_autoWindow.m_lpDispatch != NULL);OnGet ();m_autoWindow.SetVisible (TRUE);
    }//
    // If CreateDispatch failed, let the user know about it.
    //
    else {MessageBox (_T ("Error launching AutoPie. Run it once to " &quot;register it on this system and then try again."), _T ("Error"));m_wndGet.EnableWindow (FALSE);m_wndSet.EnableWindow (FALSE);
    }
    return TRUE; // return TRUE unless you set the focus to a control
    }

    void CPieClientDlg::OnPaint()
    {
    if (IsIconic())
    {CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle.int cxIcon = GetSystemMetrics(SM_CXICON);int cyIcon = GetSystemMetrics(SM_CYICON);CRect rect;GetClientRect(&rect);int x = (rect.Width() - cxIcon + 1) / 2;int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon.dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {CDialog::OnPaint();
    }
    }

    HCURSOR CPieClientDlg::OnQueryDragIcon()
    {
    return (HCURSOR) m_hIcon;
    }

    void CPieClientDlg::OnGet()
    {
    //
    // Retrieve revenue values from the Automation server and display them.
    //
    SetDlgItemInt (IDC_Q1, m_autoChart.GetRevenue (1));
    SetDlgItemInt (IDC_Q2, m_autoChart.GetRevenue (2));
    SetDlgItemInt (IDC_Q3, m_autoChart.GetRevenue (3));
    SetDlgItemInt (IDC_Q4, m_autoChart.GetRevenue (4));
    }

    void CPieClientDlg::OnSet()
    {
    //
    // Retrieve the revenue values displayed in the edit controls// and provide them to the Automation server.
    //
    m_autoChart.SetRevenue (1, GetDlgItemInt (IDC_Q1));
    m_autoChart.SetRevenue (2, GetDlgItemInt (IDC_Q2));
    m_autoChart.SetRevenue (3, GetDlgItemInt (IDC_Q3));
    m_autoChart.SetRevenue (4, GetDlgItemInt (IDC_Q4));//
    // Repaint the pie chart.
    //
    m_autoWindow.Refresh ();
    }

    AutoPie.h

    // Machine generated IDispatch wrapper class(es) created with ClassWizard
    ///////////////////////////////////////////////////////////////////////////
    // CAutoPie wrapper class
    

    class CAutoPie : public COleDispatchDriver
    {
    public:
    CAutoPie() {} // Calls COleDispatchDriver default constructor
    CAutoPie(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
    CAutoPie(const CAutoPie& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

    // Attributes
    public:
    LPDISPATCH GetChart();
    void SetChart(LPDISPATCH);
    LPDISPATCH GetWindow();
    void SetWindow(LPDISPATCH);
    LPDISPATCH GetToolbar();
    void SetToolbar(LPDISPATCH);

    // Operations
    public:
    void Quit();
    };
    ///////////////////////////////////////////////////////////////////////////
    // CAutoChart wrapper class

    class CAutoChart : public COleDispatchDriver
    {
    public:
    CAutoChart() {} // Calls COleDispatchDriver default constructor
    CAutoChart(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
    CAutoChart(const CAutoChart& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

    // Attributes
    public:

    // Operations
    public:
    BOOL Save(LPCTSTR pszPath);
    long GetRevenue(short nQuarter);
    void SetRevenue(short nQuarter, long nNewValue);
    };
    ///////////////////////////////////////////////////////////////////////////
    // CAutoWindow wrapper class

    class CAutoWindow : public COleDispatchDriver
    {
    public:
    CAutoWindow() {} // Calls COleDispatchDriver default constructor
    CAutoWindow(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
    CAutoWindow(const CAutoWindow& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

    // Attributes
    public:
    BOOL GetVisible();
    void SetVisible(BOOL);

    // Operations
    public:
    void Refresh();
    };
    ///////////////////////////////////////////////////////////////////////////
    // CAutoToolbar wrapper class

    class CAutoToolbar : public COleDispatchDriver
    {
    public:
    CAutoToolbar() {} // Calls COleDispatchDriver default constructor
    CAutoToolbar(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
    CAutoToolbar(const CAutoToolbar& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

    // Attributes
    public:
    BOOL GetVisible();
    void SetVisible(BOOL);

    // Operations
    public:
    };

    AutoPie.cpp

    // Machine generated IDispatch wrapper class(es) created with ClassWizard
    

    #include "stdafx.h"
    #include "autopie.h"
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPie properties

    LPDISPATCH CAutoPie::GetChart()
    {
    LPDISPATCH result;
    GetProperty(0x1, VT_DISPATCH, (void*)&result);
    return result;
    }

    void CAutoPie::SetChart(LPDISPATCH propVal)
    {
    SetProperty(0x1, VT_DISPATCH, propVal);
    }

    LPDISPATCH CAutoPie::GetWindow()
    {
    LPDISPATCH result;
    GetProperty(0x2, VT_DISPATCH, (void*)&result);
    return result;
    }

    void CAutoPie::SetWindow(LPDISPATCH propVal)
    {
    SetProperty(0x2, VT_DISPATCH, propVal);
    }

    LPDISPATCH CAutoPie::GetToolbar()
    {
    LPDISPATCH result;
    GetProperty(0x3, VT_DISPATCH, (void*)&result);
    return result;
    }

    void CAutoPie::SetToolbar(LPDISPATCH propVal)
    {
    SetProperty(0x3, VT_DISPATCH, propVal);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoPie operations

    void CAutoPie::Quit()
    {
    InvokeHelper(0x4, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoChart properties

    ///////////////////////////////////////////////////////////////////////////
    // CAutoChart operations

    BOOL CAutoChart::Save(LPCTSTR pszPath)
    {
    BOOL result;
    static BYTE parms[] =VTS_BSTR;
    InvokeHelper(0x1, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms,pszPath);
    return result;
    }

    long CAutoChart::GetRevenue(short nQuarter)
    {
    long result;
    static BYTE parms[] =VTS_I2;
    InvokeHelper(0x2, DISPATCH_PROPERTYGET, VT_I4, (void*)&result, parms,nQuarter);
    return result;
    }

    void CAutoChart::SetRevenue(short nQuarter, long nNewValue)
    {
    static BYTE parms[] =VTS_I2 VTS_I4;
    InvokeHelper(0x2, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, parms,nQuarter, nNewValue);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoWindow propertiesBOOL CAutoWindow::GetVisible()
    {
    BOOL result;
    GetProperty(0x1, VT_BOOL, (void*)&result);
    return result;
    }

    void CAutoWindow::SetVisible(BOOL propVal)
    {
    SetProperty(0x1, VT_BOOL, propVal);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoWindow operations

    void CAutoWindow::Refresh()
    {
    InvokeHelper(0x2, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoToolbar properties

    BOOL CAutoToolbar::GetVisible()
    {
    BOOL result;
    GetProperty(0x1, VT_BOOL, (void*)&result);
    return result;
    }

    void CAutoToolbar::SetVisible(BOOL propVal)
    {
    SetProperty(0x1, VT_BOOL, propVal);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAutoToolbar operations

    Stdafx.h

    // stdafx.h : include file for standard system include files,
    //  or project specific include files that are used frequently, but
    //are changed infrequently
    //
    

    #if !defined(AFX_STDAFX_H__3B5BA32E_3B72_11D2_AC82_006008A8274D__INCLUDED_)
    #define AFX_STDAFX_H__3B5BA32E_3B72_11D2_AC82_006008A8274D__INCLUDED_

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    #define vc_EXTRALEAN// Exclude rarely-used stuff from Windows headers

    #include <afxwin.h> // MFC core and standard components
    #include <afxext.h> // MFC extensions
    #include <afxdtctl.h> // MFC support for Internet Explorer 4 // Common Controls
    #ifndef _AFX_NO_AFXCMN_SUPPORT
    #include <afxcmn.h>// MFC support for Windows Common Controls
    #endif // _AFX_NO_AFXCMN_SUPPORT
    #include <afxdisp.h>

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(AFX_STDAFX_H__3B5BA32E_3B72_11D2_AC82_006008A8274D__INCLUDED_)

    Keep in mind that checking the Automation box in AppWizard makes an application an Automation server, not an Automation client. With the Automation option unchecked, however, AppWizard will not add an AfxOleInit call to InitInstance, nor will it #include Afxdisp.h in Stdafx.h. Both are necessary for Automation clients, so I added them by hand to PieClient. Without these additions, the code will compile just fine, but CreateDispatch will fail every time.

    Connecting to a Running Automation Server

    Thanks to ClassWizard-generated wrapper classes and their member functions, accessing an Automation server's methods and properties from a C++ program is almost as easy as accessing them from Visual Basic. But what if you want to connect two or more instances of PieClient to one instance of AutoPie? As it stands now, that's not possible because each instance of PieClient calls CreateDispatch, which creates a brand new instance of the Automation object.

    You can modify PieClient and AutoPie to support multiple simultaneous connections by adding a few lines of code to each. On the server side, AutoPie needs to call the API function ::RegisterActiveObject to register itself as an active object. Here's a modified version of CAutoPieDoc's constructor that demonstrates how.

    CAutoPieDoc::CAutoPieDoc ()
    {
     // Wizard-generated code
     EnableAutomation ();
     AfxOleLockApp ();// Additional code that registers the running object
     IUnknown* pUnknown;
     GetIDispatch (FALSE)->QueryInterface (IID_IUnknown, (void**) &pUnknown);
     GetIDispatch (FALSE)->Release (); // Undo the AddRef // performed by QueryInterface.
     ::RegisterActiveObject (pUnknown, clsid, ACTIVEOBJECT_WEAK, &m_ulID);
    }
    

    In this example, m_ulID is an unsigned long member variable added to CAutoPieDoc. It receives a 32-bit value identifying the entry that ::RegisterActiveObject added to COM's running object table. clsid is the object's CLSID; it's declared in AutoPie.cpp and made visible in AutoPieDoc.cpp by adding the statement

    extern CLSID clsid;
    

    to AutoPieDoc.cpp. For this extern statement to compile and link, you must remove the keywords static and const from the variable declaration in AutoPie.cpp.

    So that clients won't attempt to connect to an Automation server that is no longer running, a version of AutoPie that registers itself in the running object table must unregister itself before it shuts down. The best way to do this is to override OnFinalRelease in CAutoPieDoc and call ::RevokeActiveObject, as shown here:

    void CAutoPieDoc::OnFinalRelease() 
    {
     ::RevokeActiveObject (m_ulID, NULL);CDocument::OnFinalRelease();
    }
    

    ::RevokeActiveObject does the opposite of ::RegisterActiveObject: Given a registration ID, it removes an object from the running object table. OnFinalRelease is a CCmdTarget function that's called just before an MFC COM object self-deletes.

    The final modification needed to support multiple connections applies to the client, not the server. Before calling CreateDispatch to create the Automation object, PieClient should call ::GetActiveObject to find out whether the object is already running. The following code connects to an existing object instance if such an instance exists or creates a new instance if it doesn't:

    BOOL bSuccess = FALSE;
    

    CLSID clsid;
    if (SUCCEEDED (CLSIDFromProgID (OLESTR ("AutoPie.Application"), &clsid))) {
    IUnknown* pUnknown;
    if (SUCCEEDED (::GetActiveObject (clsid, NULL, &pUnknown))) {IDispatch* pDispatch;if (SUCCEEDED (pUnknown->QueryInterface (IID_IDispatch,
    (void**) &pDispatch))) {
    pDispatch->Release (); // Undo the AddRef performed // by QueryInterface.
    m_autoPie.AttachDispatch (pDispatch);
    bSuccess = TRUE;}
    }
    }

    if (!bSuccess)
    bSuccess = m_autoPie.CreateDispatch (_T ("AutoPie.Application"));

    if (!bSuccess) {
    // Error: Unable to connect to an existing object instance or
    // launch a new one.
    }

    If you apply these modifications to AutoPie and PieClient, you'll find that no matter how many instances of PieClient you start, each will connect to the same Automation object. One drawback to the ::RegisterActiveObject/::GetActiveObject method is that it's powerless over a network, even though Automation itself works just fine between machines. Attaching multiple clients to an Automation server on another machine requires an altogether different approach to the problem. That, however, is a topic for another day.

    Putting It All Together: The Widget Application

    Putting It All Together: The Widget Application

    The application shown in Figure 19-3 demonstrates one way to apply the concepts, principles, and code fragments presented in this chapter to the real world. Widget creates triangular "widgets" of various colors in response to commands on the Insert menu. You can transfer widgets to and from the OLE clipboard using the commands on the Edit menu. Before you can use the Cut and Copy commands, you must select a widget by clicking it. The widget will turn green to indicate that it is in a selected state. You can also move and copy widgets using OLE drag-and-drop. If you hold down the Ctrl key when a drop is performed, the widget is copied; otherwise, it's moved. For a graphical demonstration of OLE drag-and-drop in action, run two instances of Widget side by side and drag widgets back and forth between them.

    Figure 19-3. The Widget window.

    Figure 19-4 shows the pertinent parts of Widget's source code. WidgetView.cpp contains most of the good stuff, including the handlers for the Cut, Copy, and Paste commands. It also contains the overridden versions of OnDragEnter, OnDragOver, OnDragLeave, and OnDrop as well as the code that initiates a drag-and-drop data transfer when the left mouse button is clicked. (See OnLButtonDown.) Widgets are transferred through global memory. Widget registers a private clipboard format for widgets and uses it in calls to COleDataSource::CacheGlobalData and COleDataObject::GetGlobalData. The ID is stored in the application object and retrieved using CWidgetApp::GetClipboardFormat.

    Figure 19-4. The Widget application.

    Widget.h

    // Widget.h : main header file for the WIDGET application
    //
    

    #if !defined(AFX_WIDGET_H__02909A45_3F5C_11D2_AC89_006008A8274D__INCLUDED_)
    #define AFX_WIDGET_H__02909A45_3F5C_11D2_AC89_006008A8274D__INCLUDED_

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    #ifndef AFXWIN_H
    #error include `stdafx.h' before including this file for PCH
    #endif

    #include "resource.h" // main symbols

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetApp:
    // See Widget.cpp for the implementation of this class
    //

    class CWidgetApp : public CWinApp
    {
    public:
    UINT GetClipboardFormat ();
    CWidgetApp();

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWidgetApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL

    // Implementation
    //{{AFX_MSG(CWidgetApp)
    afx_msg void OnAppAbout();
    // NOTE - the ClassWizard will add and remove member functions here.
    // DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    protected:
    UINT m_nFormat;
    };///////////////////////////////////////////////////////////////////////////

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(AFX_WIDGET_H__02909A45_3F5C_11D2_AC89_006008A8274D__INCLUDED_)

    Widget.cpp

    // Widget.cpp : Defines the class behaviors for the application.
    //
    

    #include "stdafx.h"
    #include "Widget.h"

    #include "MainFrm.h"
    #include "WidgetDoc.h"
    #include "WidgetView.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetApp

    BEGIN_MESSAGE_MAP(CWidgetApp, CWinApp)
    //{{AFX_MSG_MAP(CWidgetApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)// NOTE - the ClassWizard will add and remove mapping macros here.// DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
    END_MESSAGE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetApp construction

    CWidgetApp::CWidgetApp()
    {
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
    }

    ///////////////////////////////////////////////////////////////////////////
    // The one and only CWidgetApp object

    CWidgetApp theApp;

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetApp initialization

    BOOL CWidgetApp::InitInstance()
    {
    if (!AfxOleInit ()) {AfxMessageBox (_T ("AfxOleInit failed"));return FALSE;
    }SetRegistryKey(_T("Local AppWizard-Generated Applications"));
    LoadStdProfileSettings(); // Load standard INI file// options (including MRU)// Register the application's document templates. Document templates
    // serve as the connection between documents, frame windows and views.CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CWidgetDoc),RUNTIME_CLASS(CMainFrame), // main SDI frame windowRUNTIME_CLASS(CWidgetView));
    AddDocTemplate(pDocTemplate);// Enable DDE Execute open
    EnableshellOpen();
    RegistershellFileTypes(TRUE);// Parse command line for standard shell commands, DDE, file open
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);// Dispatch commands specified on the command line
    if (!ProcessShellCommand(cmdInfo))return FALSE;// The one and only window has been initialized, so show and update it.
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();// Enable drag/drop open
    m_pMainWnd->DragAcceptFiles();//
    // Register a private clipboard format for widgets.
    //
    m_nFormat = ::RegisterClipboardFormat (_T ("Widget"));
    return TRUE;
    }

    ///////////////////////////////////////////////////////////////////////////
    // CAboutDlg dialog used for App About

    class CAboutDlg : public CDialog
    {
    public:
    CAboutDlg();

    // Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA// ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    //{{AFX_MSG(CAboutDlg)// No message handlers
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };
    CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
    {
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
    }

    void CAboutDlg::DoDataExchange(CDataExchange* pDX)
    {
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
    }

    BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)// No message handlers
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    // App command to run the dialog
    void CWidgetApp::OnAppAbout()
    {
    CAboutDlg aboutDlg;
    aboutDlg.DoModal();
    }

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetApp message handlers

    UINT CWidgetApp::GetClipboardFormat()
    {
    return m_nFormat;
    }

    WidgetDoc.h

    // WidgetDoc.h : interface of the CWidgetDoc class
    //
    ///////////////////////////////////////////////////////////////////////////
    

    #if !defined(
    AFX_WIDGETDOC_H__02909A4B_3F5C_11D2_AC89_006008A8274D__INCLUDED_)
    #define AFX_WIDGETDOC_H__02909A4B_3F5C_11D2_AC89_006008A8274D__INCLUDED_

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    #include "WidgetObj.h"
    typedef CTypedPtrArray<CObArray, CWidget*> CWidgetArray;

    class CWidgetDoc : public CDocument
    {
    protected: // create from serialization only
    CWidgetDoc();
    DECLARE_DYNCREATE(CWidgetDoc)

    // Attributes
    public:

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWidgetDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual void Serialize(CArchive& ar);
    virtual void DeleteContents();
    //}}AFX_VIRTUAL

    // Implementation
    public:
    BOOL RemoveWidget (int nIndex);
    int AddWidget (int x, int y, COLORREF color);
    CWidget* GetWidget (int nIndex);
    int GetWidgetCount ();
    virtual ~CWidgetDoc();
    #ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
    #endif

    protected:
    CWidgetArray m_arrWidgets;

    // Generated message map functions
    protected:
    //{{AFX_MSG(CWidgetDoc)
    afx_msg void OnInsertRedWidget();
    afx_msg void OnInsertBlueWidget();
    afx_msg void OnInsertYellowWidget();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_WIDGETDOC_H__02909A4B_3F5C_11D2_AC89_006008A8274D__INCLUDED_)

    WidgetDoc.cpp

    CWidgetDoc::~CWidgetDoc()
    {
    }
    

    BOOL CWidgetDoc::OnNewDocument()
    {
    if (!CDocument::OnNewDocument())return FALSE;return TRUE;
    }
    ///////////////////////////////////////////////////////////////////////////
    // CWidgetDoc serialization

    void CWidgetDoc::Serialize(CArchive& ar)
    {
    m_arrWidgets.Serialize (ar);
    }

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetDoc diagnostics

    #ifdef _DEBUG
    void CWidgetDoc::AssertValid() const
    {
    CDocument::AssertValid();
    }

    void CWidgetDoc::Dump(CDumpContext& dc) const
    {
    CDocument::Dump(dc);
    }
    #endif //_DEBUG

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetDoc commands

    void CWidgetDoc::DeleteContents()
    {
    int i = m_arrWidgets.GetSize ();
    while (i)delete m_arrWidgets[--i];
    m_arrWidgets.RemoveAll ();
    CDocument::DeleteContents();
    }

    int CWidgetDoc::GetWidgetCount()
    {
    return m_arrWidgets.GetSize ();
    }

    CWidget* CWidgetDoc::GetWidget(int nIndex)
    {
    if (nIndex >= m_arrWidgets.GetSize ())return NULL;
    return (CWidget*) m_arrWidgets[nIndex];
    }

    int CWidgetDoc::AddWidget(int x, int y, COLORREF color)
    {
    int nIndex = -1;
    CWidget* pWidget = NULL;try {pWidget = new CWidget (x, y, color);nIndex = m_arrWidgets.Add (pWidget); SetModifiedFlag ();
    }
    catch (CMemoryException* e) {AfxMessageBox (_T ("Out of memory"));if (pWidget != NULL)
    delete pWidget;e->Delete ();return -1;
    }
    return nIndex;
    }

    BOOL CWidgetDoc::RemoveWidget(int nIndex)
    {
    if (nIndex >= m_arrWidgets.GetSize ())return FALSE;delete m_arrWidgets[nIndex];
    m_arrWidgets.RemoveAt (nIndex);
    return TRUE;
    }

    void CWidgetDoc::OnInsertBlueWidget()
    {
    AddWidget (10, 10, RGB (0, 0, 255));
    UpdateAllViews (NULL);
    }

    void CWidgetDoc::OnInsertRedWidget()
    {
    AddWidget (10, 10, RGB (255, 0, 0));
    UpdateAllViews (NULL);
    }

    void CWidgetDoc::OnInsertYellowWidget()
    {
    AddWidget (10, 10, RGB (255, 255, 0));
    UpdateAllViews (NULL);
    }

    WidgetView.h

    // WidgetView.h : interface of the CWidgetView class
    //
    ///////////////////////////////////////////////////////////////////////////
    

    #if !defined(
    AFX_WIDGETVIEW_H__02909A4D_3F5C_11D2_AC89_006008A8274D__INCLUDED_)
    #define AFX_WIDGETVIEW_H__02909A4D_3F5C_11D2_AC89_006008A8274D__INCLUDED_

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000

    typedef struct tagWIDGETINFO {
    int x; // x coordinate of widget's upper left corner
    int y; // y coordinate of widget's upper left corner
    int cx; // Horizontal drag offset
    int cy; // Vertical drag offset
    COLORREF color; // The widget's color
    } WIDGETINFO;

    class CWidgetView : public CScrollView
    {
    protected: // create from serialization only
    CWidgetView();
    DECLARE_DYNCREATE(CWidgetView)

    // Attributes
    public:
    CWidgetDoc* GetDocument();

    // Operations
    public:

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWidgetView)
    public:
    virtual void OnDraw(CDC* pDC); // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    virtual DROPEFFECT OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
    virtual DROPEFFECT OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);
    virtual void OnDragLeave();
    virtual BOOL OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);
    protected:
    virtual void OnInitialUpdate(); // called first time after construct
    //}}AFX_VIRTUAL

    // Implementation
    public:
    virtual ~CWidgetView();
    #ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
    #endif

    protected:
    CWidget* m_pTempWidget;
    CSize m_offset;
    CPoint m_pointLastImage;
    CPoint m_pointLastMsg;
    int m_nSel;
    COleDropTarget m_oleDropTarget;

    // Generated message map functions
    protected:
    //{{AFX_MSG(CWidgetView)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnEditCut();
    afx_msg void OnEditCopy();
    afx_msg void OnEditPaste();
    afx_msg void OnEditDelete();
    afx_msg void OnUpdateEditCut(CCmdUI* pCmdUI);
    afx_msg void OnUpdateEditCopy(CCmdUI* pCmdUI);
    afx_msg void OnUpdateEditPaste(CCmdUI* pCmdUI);
    afx_msg void OnUpdateEditDelete(CCmdUI* pCmdUI);
    afx_msg void OnSetFocus(CWnd* pOldWnd);
    afx_msg void OnKillFocus(CWnd* pNewWnd);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };

    #ifndef _DEBUG // debug version in WidgetView.cpp
    inline CWidgetDoc* CWidgetView::GetDocument()
    { return (CWidgetDoc*)m_pDocument; }
    #endif

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_WIDGETVIEW_H__02909A4D_3F5C_11D2_AC89_006008A8274D__INCLUDED_)

    WidgetView.cpp

    // WidgetView.cpp : implementation of the CWidgetView class
    //
    

    #include "stdafx.h"
    #include "Widget.h"
    #include "WidgetDoc.h"
    #include "WidgetView.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetView

    IMPLEMENT_DYNCREATE(CWidgetView, CScrollView)

    BEGIN_MESSAGE_MAP(CWidgetView, CScrollView)
    //{{AFX_MSG_MAP(CWidgetView)
    ON_WM_CREATE()
    ON_WM_LBUTTONDOWN()
    ON_COMMAND(ID_EDIT_CUT, OnEditCut)
    ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
    ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
    ON_COMMAND(ID_EDIT_DELETE, OnEditDelete)
    ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
    ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
    ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
    ON_UPDATE_COMMAND_UI(ID_EDIT_DELETE, OnUpdateEditDelete)
    ON_WM_SETFOCUS()
    ON_WM_KILLFOCUS()
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetView construction/destruction

    CWidgetView::CWidgetView()
    {
    }

    CWidgetView::~CWidgetView()
    {
    }

    BOOL CWidgetView::PreCreateWindow(CREATESTRUCT& cs)
    {
    return CScrollView::PreCreateWindow(cs);
    }
    ///////////////////////////////////////////////////////////////////////////
    // CWidgetView drawing

    void CWidgetView::OnDraw(CDC* pDC)
    {
    CWidgetDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);int nCount = pDoc->GetWidgetCount ();
    if (nCount) {//// Draw all widgets.//for (int i=0; i<nCount; i++)
    pDoc->GetWidget (i)->Draw (pDC); //// Draw the selected widget if this view has the input focus.//if (m_nSel != -1 && CWnd::GetFocus () == this)
    pDoc->GetWidget (m_nSel)->DrawSelected (pDC);
    }
    }

    void CWidgetView::OnInitialUpdate()
    {
    CScrollView::OnInitialUpdate();
    SetScrollSizes(MM_TEXT, CSize (1280, 1024));
    m_pTempWidget = NULL;
    m_nSel = -1;
    }

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetView diagnostics

    #ifdef _DEBUG
    void CWidgetView::AssertValid() const
    {
    CScrollView::AssertValid();
    }

    void CWidgetView::Dump(CDumpContext& dc) const
    {
    CScrollView::Dump(dc);
    }

    CWidgetDoc* CWidgetView::GetDocument() // non-debug version is inline
    {
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWidgetDoc)));
    return (CWidgetDoc*)m_pDocument;
    }
    #endif //_DEBUG

    ///////////////////////////////////////////////////////////////////////////
    // CWidgetView message handlers

    DROPEFFECT CWidgetView::OnDragEnter(COleDataObject* pDataObject,
    DWORD dwKeyState, CPoint point)
    {
    CScrollView::OnDragEnter(pDataObject, dwKeyState, point);//
    // If a widget is available from the drop source, create a temporary
    // widget for drag imaging.
    //
    UINT nFormat = ((CWidgetApp) AfxGetApp ())->GetClipboardFormat ();
    HGLOBAL hData = pDataObject->GetGlobalData (nFormat);if (hData != NULL) {WIDGETINFO
    pWidgetInfo = (WIDGETINFO*) ::GlobalLock (hData);int x = point.x - pWidgetInfo->cx;int y = point.y - pWidgetInfo->cy;m_offset.cx = pWidgetInfo->cx;m_offset.cy = pWidgetInfo->cy;COLORREF color = pWidgetInfo->color;::GlobalUnlock (hData);::GlobalFree (hData); m_pTempWidget = new CWidget (x, y, color);m_pointLastImage.x = m_pointLastImage.y = -32000;m_pointLastMsg = point; //// Return DROPEFFECT_COPY if the Ctrl key is down, or // DROPEFFECT_MOVE if it is not.//return (dwKeyState & MK_CONTROL) ?
    DROPEFFECT_COPY : DROPEFFECT_MOVE;
    }
    //
    // The cursor isn't carrying a widget. Indicate that the drop target// will not accept a drop.
    //
    m_pTempWidget = NULL;
    return DROPEFFECT_NONE;
    }

    DROPEFFECT CWidgetView::OnDragOver(COleDataObject* pDataObject,
    DWORD dwKeyState, CPoint point)
    {
    CScrollView::OnDragOver(pDataObject, dwKeyState, point);//
    // Return now if the object being dragged is not a widget.
    //
    if (m_pTempWidget == NULL)return DROPEFFECT_NONE;//
    // Convert the drag point to logical coordinates.
    //
    CClientDC dc (this);
    OnPrepareDC (&dc);
    dc.DPtoLP (&point);//
    // If the cursor has moved, erase the old drag image and// draw a new one.
    //
    if (point != m_pointLastMsg) {CPoint pt (point.x - m_offset.cx, point.y - m_offset.cy);m_pTempWidget->DrawDragImage (&dc, m_pointLastImage);m_pTempWidget->DrawDragImage (&dc, pt);m_pointLastImage = pt;m_pointLastMsg = point;
    }//
    // Return DROPEFFECT_COPY if the Ctrl key is down, or DROPEFFECT_MOVE
    // if it is not.
    //
    return (dwKeyState & MK_CONTROL) ?DROPEFFECT_COPY : DROPEFFECT_MOVE;
    }

    void CWidgetView::OnDragLeave()
    {
    CScrollView::OnDragLeave();//
    // Erase the last drag image and delete the temporary widget.
    //
    if (m_pTempWidget != NULL) {CClientDC dc (this);OnPrepareDC (&dc);m_pTempWidget->DrawDragImage (&dc, m_pointLastImage);delete m_pTempWidget;m_pTempWidget = NULL;
    }
    }

    BOOL CWidgetView::OnDrop(COleDataObject* pDataObject,DROPEFFECT dropEffect, CPoint point)
    {
    CScrollView::OnDrop(pDataObject, dropEffect, point);//
    // Convert the drop point to logical coordinates.
    //
    CClientDC dc (this);
    OnPrepareDC (&dc);
    dc.DPtoLP (&point);//
    // Erase the last drag image and delete the temporary widget.
    //
    if (m_pTempWidget != NULL) {m_pTempWidget->DrawDragImage (&dc, m_pointLastImage);delete m_pTempWidget;m_pTempWidget = NULL;
    }//
    // Retrieve the HGLOBAL from the data object and create a widget.
    //
    UINT nFormat = ((CWidgetApp) AfxGetApp ())->GetClipboardFormat ();
    HGLOBAL hData = pDataObject->GetGlobalData (nFormat);if (hData != NULL) {WIDGETINFO
    pWidgetInfo = (WIDGETINFO) ::GlobalLock (hData);int x = point.x - pWidgetInfo->cx;int y = point.y - pWidgetInfo->cy;COLORREF color = pWidgetInfo->color;::GlobalUnlock (hData);::GlobalFree (hData); CWidgetDoc pDoc = GetDocument ();m_nSel = pDoc->AddWidget (x, y, color);pDoc->UpdateAllViews (NULL);return TRUE;
    }
    return FALSE;
    }

    int CWidgetView::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    if (CScrollView::OnCreate(lpCreateStruct) == -1)return -1; m_oleDropTarget.Register (this);return 0;
    }

    void CWidgetView::OnLButtonDown(UINT nFlags, CPoint point)
    {
    CScrollView::OnLButtonDown(nFlags, point);CWidgetDoc* pDoc = GetDocument ();
    int nCount = pDoc->GetWidgetCount ();if (nCount) {//// Convert the click point to logical coordinates.//CClientDC dc (this);OnPrepareDC (&dc);dc.DPtoLP (&point); //// Find out whether a widget was clicked.//int i;BOOL bHit = FALSE;for (i=nCount - 1; i>=0 && !bHit; i--) {
    CWidget* pWidget = pDoc->GetWidget (i);
    if (pWidget->PtInWidget (point)) {
    bHit = TRUE;
    }} //// If no widget was clicked, change the selection to NULL and exit.//if (!bHit) {
    m_nSel = -1;
    InvalidateRect (NULL, FALSE);
    return;}//// Select the widget that was clicked.//int nWidgetIndex = i + 1; if (m_nSel != nWidgetIndex) {
    m_nSel = nWidgetIndex;
    InvalidateRect (NULL, FALSE);
    UpdateWindow ();} //// Begin a drag-and-drop operation involving the selected widget.//HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, sizeof (WIDGETINFO)); WIDGETINFO* pWidgetInfo = (WIDGETINFO) ::GlobalLock (hData);CWidget pWidget = pDoc->GetWidget (nWidgetIndex);ASSERT (pWidget != NULL);CRect rect = pWidget->GetRect ();pWidgetInfo->cx = point.x - rect.left;pWidgetInfo->cy = point.y - rect.top;pWidgetInfo->color = pWidget->GetColor ();::GlobalUnlock (hData); COleDataSource ods;UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat ();ods.CacheGlobalData (nFormat, hData); int nOldSel = m_nSel;DROPEFFECT de = ods.DoDragDrop (DROPEFFECT_COPY | DROPEFFECT_MOVE); if (de == DROPEFFECT_MOVE) {
    pDoc->RemoveWidget (nWidgetIndex);
    int nCount = pDoc->GetWidgetCount ();
    if (nOldSel == m_nSel || nCount == 0)
    m_nSel = -1;
    else if (m_nSel >= nCount)
    m_nSel = nCount - 1;
    pDoc->UpdateAllViews (NULL);}
    }
    }

    void CWidgetView::OnEditCut()
    {
    if (m_nSel != -1) {OnEditCopy ();OnEditDelete ();
    }
    }

    void CWidgetView::OnEditCopy()
    {
    if (m_nSel != -1) {//// Copy data describing the currently selected widget to a// global memory block.//HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, sizeof (WIDGETINFO)); WIDGETINFO* pWidgetInfo = (WIDGETINFO) ::GlobalLock (hData);CWidgetDoc pDoc = GetDocument ();CWidget* pWidget = pDoc->GetWidget (m_nSel);ASSERT (pWidget != NULL);CRect rect = pWidget->GetRect ();pWidgetInfo->x = rect.left;pWidgetInfo->y = rect.top;pWidgetInfo->color = pWidget->GetColor ();::GlobalUnlock (hData); //// Place the widget on the clipboard.//COleDataSource* pods = new COleDataSource;UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat ();pods->CacheGlobalData (nFormat, hData);pods->SetClipboard ();
    }
    }

    void CWidgetView::OnEditPaste()
    {
    //
    // Create a COleDataObject and attach it to the clipboard.
    //
    COleDataObject odo;
    odo.AttachClipboard ();//
    // Retrieve the HGLOBAL from the clipboard and create a widget.
    //
    UINT nFormat = ((CWidgetApp) AfxGetApp ())->GetClipboardFormat ();
    HGLOBAL hData = odo.GetGlobalData (nFormat);
    if (hData != NULL) {WIDGETINFO
    pWidgetInfo = (WIDGETINFO) ::GlobalLock (hData);int x = pWidgetInfo->x;int y = pWidgetInfo->y;COLORREF color = pWidgetInfo->color;::GlobalUnlock (hData);::GlobalFree (hData); CWidgetDoc pDoc = GetDocument ();m_nSel = pDoc->AddWidget (x, y, color);pDoc->UpdateAllViews (NULL);
    }
    }

    void CWidgetView::OnEditDelete()
    {
    if (m_nSel != -1) {CWidgetDoc* pDoc = GetDocument ();pDoc->RemoveWidget (m_nSel);m_nSel = -1;pDoc->UpdateAllViews (NULL);
    }
    }

    void CWidgetView::OnUpdateEditCut(CCmdUI* pCmdUI)
    {
    pCmdUI->Enable (m_nSel != -1);
    }

    void CWidgetView::OnUpdateEditCopy(CCmdUI* pCmdUI)
    {
    pCmdUI->Enable (m_nSel != -1);
    }

    void CWidgetView::OnUpdateEditPaste(CCmdUI* pCmdUI)
    {
    UINT nFormat = ((CWidgetApp*) AfxGetApp ())->GetClipboardFormat ();
    pCmdUI->Enable (::IsClipboardFormatAvailable (nFormat));
    }

    void CWidgetView::OnUpdateEditDelete(CCmdUI* pCmdUI)
    {
    pCmdUI->Enable (m_nSel != -1);
    }

    void CWidgetView::OnKillFocus(CWnd* pNewWnd)
    {
    CScrollView::OnKillFocus(pNewWnd);
    InvalidateRect (NULL, FALSE);
    }

    void CWidgetView::OnSetFocus(CWnd* pOldWnd)
    {
    CScrollView::OnSetFocus(pOldWnd);
    InvalidateRect (NULL, FALSE);
    }

    WidgetObj.h

    #if !defined(
     AFX_WIDGETOBJ_H__02909A57_3F5C_11D2_AC89_006008A8274D__INCLUDED_)
    #define AFX_WIDGETOBJ_H__02909A57_3F5C_11D2_AC89_006008A8274D__INCLUDED_
    

    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    // WidgetObj.h : header file
    //

    ///////////////////////////////////////////////////////////////////////////
    // CWidget command target

    class CWidget : public CObject
    {
    DECLARE_SERIAL(CWidget)

    // Attributes
    public:

    // Operations
    public:
    CWidget();
    CWidget (int x, int y, COLORREF color);
    virtual ~CWidget();
    void DrawSelected (CDC* pDC);
    BOOL PtInWidget (POINT point);
    virtual void DrawDragImage (CDC* pDC, POINT point);
    virtual void Draw (CDC* pDC);
    COLORREF GetColor ();
    CRect GetRect ();

    // Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWidget)//}}AFX_VIRTUAL
    virtual void Serialize (CArchive& ar);

    // Implementation
    protected:
    COLORREF m_color;
    CRect m_rect;
    };

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

    //{{AFX_INSERT_LOCATION}}
    // Microsoft Visual C++ will insert additional declarations
    // immediately before the previous line.

    #endif
    // !defined(
    // AFX_WIDGETOBJ_H__02909A57_3F5C_11D2_AC89_006008A8274D__INCLUDED_)

    WidgetObj.cpp

    // WidgetObj.cpp : implementation file
    //
    

    #include "stdafx.h"
    #include "Widget.h"
    #include "WidgetObj.h"

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = FILE;
    #endif

    ///////////////////////////////////////////////////////////////////////////
    // CWidget

    IMPLEMENT_SERIAL(CWidget, CObject, 1)

    CWidget::CWidget()
    {
    m_rect = CRect (0, 0, 90, 90);
    m_color = RGB (255, 0, 0);
    }
    CWidget::CWidget (int x, int y, COLORREF color)
    {
    m_rect = CRect (x, y, x + 90, y + 90);
    m_color = color;
    }

    CWidget::~CWidget()
    {
    }

    ///////////////////////////////////////////////////////////////////////////
    // CWidget message handlers

    CRect CWidget::GetRect()
    {
    return m_rect;
    }

    COLORREF CWidget::GetColor()
    {
    return m_color;
    }

    void CWidget::Serialize (CArchive& ar)
    {
    CObject::Serialize (ar);if (ar.IsStoring ())ar << m_rect << m_color;
    else ar >> m_rect >> m_color;
    }

    void CWidget::Draw(CDC pDC)
    {
    CBrush brush (m_color);
    CBrush
    pOldBrush = pDC->SelectObject (&brush);CPoint points[3];
    points[

    void CWidget::DrawSelected(CDC pDC)
    {
    CBrush brush (RGB (0, 255, 0));
    CBrush
    pOldBrush = pDC->SelectObject (&brush);CPoint points[3];
    points[

    void CWidget::DrawDragImage(CDC pDC, POINT point)
    {
    int nOldMode = pDC->SetROP2 (R2_NOT);
    CBrush
    pOldBrush = (CBrush*) pDC->SelectStockObject (NULL_BRUSH);CPoint points[3];
    points[].x = point.x;
    points[

    BOOL CWidget::PtInWidget(POINT point)
    {
    if (!m_rect.PtInRect (point))return FALSE;int cx = min (point.x - m_rect.left, m_rect.right - point.x);
    return ((m_rect.bottom - point.y) <= (2 * cx));
    }

    Widgets are represented by objects of the CWidget class, whose source code is found in WidgetObj.h and WidgetObj.cpp. To derive CWidget, I used ClassWizard to derive from CCmdTarget and then manually edited the source code to change the base class to CObject. I also changed the DYNCREATE macros inserted by ClassWizard into SERIAL macros and overrode CObject::Serialize to make CWidget a serializable class. These tweaks reduced the document's Serialize function to one simple statement:

    m_arrWidgets.Serialize (ar);
    

    m_arrWidgets is the CWidgetDoc member variable that stores CWidget pointers. A CWidget object is created when a command is selected from the Insert menu, when a widget is pasted from the clipboard, and when a widget is dropped over the Widget window.

    The CWidget class has a pair of member functions named Draw and DrawSelected that draw a widget to an output device. Draw draws the widget in the unselected state; DrawSelected draws it in the selected state. The view's OnDraw code is a simple loop that retrieves CWidget pointers from the document one by one and asks each widget to draw itself. If the view has the input focus and a widget is currently selected (that is, if CWidgetView::m_nSel != -1), the selected widget is drawn again after all the other widgets are drawn:

    for (int i=0; i<nCount; i++)
     pDoc->GetWidget (i)->Draw (pDC);
    if (m_nSel != -1 && CWnd::GetFocus () == this)
     pDoc->GetWidget (m_nSel)->DrawSelected (pDC);
        

    Drawing the selected widget last ensures that it is always visible on top of the others.

    Another CWidget drawing function, DrawDragImage, is used for drag imaging. As you drag a widget across the screen, notice the triangular outline that follows the cursor. That's drag imaging. The operating system shell uses drag images for a similar effect when file system objects are dragged. Because the drop source is responsible for displaying the cursor during a drag-and-drop operation, programmers often assume that the drop source draws drag images by making them part of the cursor. That's generally not true. What really happens is that the drop target (not the drop source) draws the drag image in OnDragOver. For this to work, the drop target has to know what kind of payload the cursor is carrying so that it can draw an outline on the screen.

    Widget handles drag imaging by creating a temporary widget object in OnDragEnter, caching the pointer in CWidgetView::m_pTempWidget, and calling the object's DrawDragImage function each time OnDragOver is called. Actually, OnDragOver calls DrawDragImage twice: once to erase the old drag image and once to draw a new one. DrawDragImage does its drawing in the R2_NOT drawing mode, so drawing a drag image on top of an old one effectively erases the old drag image. The position of the previous drag image is stored in CWidgetView's m_pointLastImage data member. The temporary widget is deleted when OnDragLeave or OnDrop is called. This example demonstrates why overriding OnDragLeave is sometimes useful. In this case, OnDragEnter allocates a resource that must be freed even if a drop doesn't occur.

    You can see drop target scrolling in action by dragging a widget and pausing within a few pixels of the view's lower or right border. After a brief delay, the view will begin scrolling and will continue scrolling until a drop occurs or the cursor moves away from the border. Drop target scrolling enables you to drop a widget in any part of the view without taking your finger off the mouse button to click a scroll bar. Again, drop target scrolling comes absolutely for free when the drop target is a CScrollView.

    The AfxOleInit Function

    When I used AppWizard to create the Widget project, I selected none of the OLE options in Step 3. When AppWizard is run this way, the generated source code doesn't include a call to the all-important AfxOleInit function, which initializes the OLE libraries. This function must be called before an mfc application touches COM or OLE in any way. Therefore, I added a call to AfxOleInit to the beginning of the application class's InitInstance function. This meant that I also had to add the statement

    #include <afxole.h>
    

    to Stdafx.h. Otherwise, the call to AfxOleInit wouldn't have compiled.

    I mention this because if you write an application that uses COM or OLE but you don't select one of the OLE options in AppWizard, you must add the AfxOleInit call and the statement that #includes Afxole.h manually. If you don't, your application will compile just fine, but calls to functions such as COleDataSource::DoDragDrop will fail. I once lost half a day of work wondering why my clipboard code wasn't working when, by all appearances, I had done everything right. Then I realized that I had forgotten to include these crucial statements in my source code. If you write an application and find that calls to DoDragDrop or other OLE functions mysteriously fail, make sure that AfxOleInit is called when the application starts up. You'll save yourself a lot of grief.

    Automation

    Chapter 20

    Automation

    Imagine a world in which you could program any Microsoft Windows application using a sophisticated yet easy-to-use scripting language. Imagine further that all scripting languages were the same so that once you learned to write scripts for one application, you could write scripts for others, too. While you're at it, consider how useful it would be if programs written in Microsoft Visual C++, Microsoft Visual Basic, and other programming languages could also access the features exposed to this hypothetical language.

    Sound too good to be true? It's not. Thanks to the COM-based technology known as Automation, any mfc application can be turned into a scriptable application. Automation is a standardized means for exposing an application's features to clients written in Visual Basic; Visual Basic for Applications (VBA); Visual Basic, Scripting Edition (VBScript); and other languages. It solves the problem posed by these languages' inability to talk to COM objects using conventional COM interfaces, but it isn't restricted to dialects of Visual Basic; clients written in C++ and other languages can also use Automation.

    Microsoft is actively encouraging application developers to build Automation support into their programs and has led the way by building Automation capabilities into many of its flagship applications. Microsoft Excel, for example, is a powerful Automation server. So are Microsoft Word, Microsoft PowerPoint, and other Microsoft Office applications. By exposing an application's feature set through Automation, you make that application a powerful tool in the hands of users who are willing to learn a scripting language. You also give developers the means to use your application as a platform for building their own applications.

    Automation is a versatile tool with talents that extend beyond mere scripting. It's also used to build software components for Active Server Pages, and it's one of the key technologies employed by ActiveX controls. In this chapter, you'll learn what Automation is, how it works, and how you can use mfc to write Automation-enabled applications. A Visual C++ programmer can have a simple Automation server up and running in no time flat. With a little know-how, you can build Automation servers that expose complex, hierarchically structured object models. Visual C++ and MFC also simplify the creation of Automation clients—programs that use the services provided by Automation servers.

    Automation Basics

    Automation Basics

    Unlike a typical COM object, which exposes interfaces and methods, an Automation object exposes methods and properties. A method is a function that can be called by the object's clients. A property is an attribute of the object, such as a color or a file name.

    Automation-aware languages such as Visual Basic shield the programmer from reference counting, interface pointers, and other idioms of COM. They also permit you to access Automation methods and properties as easily as you access local subroutines and variables. The following Visual Basic statements instantiate an Automation object and invoke a method named Add to add 2 and 2:

    Dim Math as Object
    Set Math = CreateObject ("Math.Object")
    Sum = Math.Add (2, 2)
    Set Math = Nothing
    

    In this example, Math is a variable of type Object. "Math.Object" is the Automation object's ProgID. (Recall from Chapter 18 that a ProgID is the string analogue of a COM CLSID.) The final statement frees the object by performing the Visual Basic equivalent of calling Release through an interface pointer.

    In Visual Basic, accessing an Automation property is syntactically similar to calling an Automation method. The next example creates a bank account object and sets the balance in the account by assigning a value to a property named Balance:

    Dim Account as Object
    Set Account = CreateObject ("BankAccount.Object")
    Account.Balance = 100
    

    Checking the balance in the account is as simple as reading the property value:

    Amount = Account.Balance
    

    Reading or writing an Automation property is analogous to accessing a public member variable in a C++ class. In truth, COM objects can't expose member variables any more than C++ objects can expose private data members. The illusion that an Automation object can expose values as well as methods is part of the magic of Automation.

    Automation clients written in VBScript look very much like Automation clients written in Visual Basic. The following script uses VBScript's built-in FileSystemObject object, which is in reality an Automation object, to create a text file containing the string "Hello, world":

    Set fso = CreateObject ("Scripting.FileSystemObject")
    Set TextFile = fso.CreateTextFile ("C:\Hello.txt", True)
    TextFile.WriteLine ("Hello, world")
    TextFile.Close
    

    To try this script for yourself, use Notepad or a program editor to enter these statements in a text file and save the file with the extension .vbs. Then double-click the file in the operating system shell or type START filename.vbs in a command prompt window. On Windows 98 and Windows 2000 systems, this will invoke the built-in Windows Scripting Host, which will open the file and execute the statements found inside it.

    You can also write Automation clients in C++. The next section shows you how, but be warned that it isn't pretty because a C++ client doesn't have the Visual Basic run-time or a scripting engine to serve as a mediator between it and the Automation server. The good news is that Visual C++ aids in the creation of Automation clients by generating easy-to-use wrapper classes based on mfc's COleDispatchDriver class. You'll see what I mean later in this chapter.

    IDispatch: The Root of All Automation

    Automation looks fairly simple from the outside, and for a Visual Basic programmer, Automation is simple. But the fact that COM is involved should be a clue that what goes on under the hood is a far different story.

    The key to understanding how Automation works lies in understanding the COM interface known as IDispatch. An Automation object is a COM object that implements IDispatch. IDispatch contains four methods (which are listed in the table below), not counting the three IUnknown methods common to all COM interfaces. Of the four, Invoke and GetIDsOfNames are the most important. A client calls Invoke to call an Automation method or to read or write an Automation property. Invoke doesn't accept a method or a property name such as "Add" or"Balance." Instead, it accepts an integer dispatch ID, or dispid, that identifies the property or method. GetIDsOfNames converts a property name or a method name into a dispatch ID that can be passed to Invoke. Collectively, the methods and properties exposed through an IDispatch interface form a dispinterface.

    The IDispatch Interface

    Method Description
    Invoke Calls an Automation method or accesses an Automation property
    GetIDsOfNames Returns the dispatch ID of a property or a method
    GetTypeInfo Retrieves an ITypeInfo pointer (if available) for accessing the Automation object's type information
    GetTypeInfoCount Returns 0 if the Automation object doesn't offer type information; returns 1 if it does

    When Visual Basic encounters a statement like

    Sum = Math.Add (2, 2)
    

    it calls GetIDsOfNames to convert "Add" into a dispatch ID. It then calls Invoke, passing in the dispatch ID retrieved from GetIDsOfNames. Before calling Invoke, Visual Basic initializes an array of structures with the values of the method parameters—in this case, 2 and 2. It passes the array's address to Invoke along with the address of an empty structure that receives the method's return value (the sum of the two input parameters). The Automation object examines the dispatch ID, sees that it corresponds to the Add method, unpacks the input values, adds them together, and copies the sum to the structure provided by the caller.

    A good way to picture this process is to see how a C++ programmer would call an Automation method. The following code is the C++ equivalent of the first example in the previous section:

    // Convert the ProgID into a CLSID.
    CLSID clsid;
    ::CLSIDFromProgID (OLESTR ("Math.Object"), &clsid);
    

    // Create the object, and get a pointer to its IDispatch interface.
    IDispatch* pDispatch;
    ::CoCreateInstance (clsid, NULL, CLSCTX_SERVER, IID_IDispatch,
    (void**) &pDispatch);

    // Get the Add method's dispatch ID.
    DISPID dispid;
    OLECHAR* szName = OLESTR ("Add");
    pDispatch->GetIDsOfNames (IID_NULL, &szName, 1,
    ::GetUserDefaultLCID (), &dispid);

    // Prepare an argument list for the Add method.
    VARIANTARG args[2];
    DISPPARAMS params = { args, NULL, 2, 0 };
    for (int i=0; i<2; i++) {
    ::VariantInit (&args[i]); // Initialize the VARIANT.
    args[i].vt = VT_I4; // Data type = 32-bit long
    V_I4 (&args[i]) = 2;// Value = 2
    }

    // Call Add to add 2 and 2.
    VARIANT result;
    ::VariantInit (&result);
    pDispatch->Invoke (dispid, IID_NULL, ::GetUserDefaultLCID (),
    DISPATCH_METHOD, &params, &result, NULL, NULL);

    // Extract the result.
    long lResult = V_I4 (&result);

    // Clear the VARIANTs.
    ::VariantClear (&args[

    // Release the Automation object.
    pDispatch->Release ();

    You can plainly see the calls to IDispatch::GetIDsOfNames and IDispatch::Invoke, as well as the ::CoCreateInstance statement that creates the Automation object. You can also see that input and output parameters are packaged in structures called VARIANTARGs, a subject that's covered in the next section.

    You also use IDispatch::Invoke to access Automation properties. You can set the fourth parameter, which was equal to DISPATCH_METHOD in the preceding example, to DISPATCH_PROPERTYPUT or DISPATCH_PROPERTYGET to indicate that the value of the property named by the dispatch ID in parameter 1 is being set or retrieved. In addition, IDispatch::Invoke can return error information in an EXCEPINFO structure provided by the caller. The structure's address is passed in Invoke's seventh parameter; a NULL pointer means the caller isn't interested in such information. Invoke also supports Automation methods and properties with optional and named arguments, which matters little to C++ clients but can simplify client code written in Visual Basic.

    It should be evident from these examples that Automation leaves something to be desired for C++ programmers. It's faster and more efficient for C++ clients to call conventional COM methods than it is for them to call Automation methods. Automation looks easy in Visual Basic for the simple reason that Visual Basic goes to great lengths to make it look easy. Peel away the façade, however, and Automation looks very different indeed.

    Automation Data Types

    One of the more interesting aspects of IDispatch::Invoke is how it handles input and output parameters. In Automation, all parameters are passed in data structures called VARIANTs. (Technically, input parameters are passed in VARIANTARGs and output parameters in VARIANTs, but because these structures are identical, developers often use the term VARIANT to describe both.) A VARIANT is, in essence, a self-describing data type. Inside a VARIANT is a union of data types for holding the VARIANT's data and a separate field for defining the data type. Here's how the structure is defined in Oaidl.idl:

    struct tagVARIANT {
     union {struct __tagVARIANT {
    VARTYPE vt;
    WORD wReserved1;
    WORD wReserved2;
    WORD wReserved3;
    union {
     LONG lVal;/* VT_I4 */
     BYTE bVal;/* VT_UI1*/
     SHORTiVal;/* VT_I2 */
     FLOATfltVal; /* VT_R4 */
     DOUBLE  dblVal; /* VT_R8 */
     VARIANT_BOOL  boolVal;/* VT_BOOL  */
     _VARIANT_BOOL bool;/* (obsolete)  */
     SCODEscode;  /* VT_ERROR */
     CYcyVal;  /* VT_CY */
     DATE date;/* VT_DATE  */
     BSTR bstrVal;/* VT_BSTR  */
     IUnknown * punkVal;/* VT_UNKNOWN  */
     IDispatch *pdispVal;  /* VT_DISPATCH */
     SAFEARRAY *parray; /* VT_ARRAY */
     BYTE *  pbVal;  /* VT_BYREFVT_UI1 */
     SHORT * piVal;  /* VT_BYREFVT_I2  */
     LONG *  plVal;  /* VT_BYREFVT_I4  */
     FLOAT * pfltVal;/* VT_BYREFVT_R4  */
     DOUBLE *pdblVal;/* VT_BYREFVT_R8  */
     VARIANT_BOOL *pboolVal;  /* VT_BYREFVT_BOOL*/
     _VARIANT_BOOL *pbool; /* (obsolete)  */
     SCODE * pscode; /* VT_BYREFVT_ERROR  */
     CY * pcyVal; /* VT_BYREFVT_CY  */
     DATE *  pdate;  /* VT_BYREFVT_DATE*/
     BSTR *  pbstrVal;  /* VT_BYREFVT_BSTR*/
     IUnknown **ppunkVal;  /* VT_BYREFVT_UNKNOWN*/
     IDispatch **  ppdispVal; /* VT_BYREFVT_DISPATCH  */
     SAFEARRAY **  pparray;/* VT_BYREFVT_ARRAY  */
     VARIANT *  pvarVal;/* VT_BYREFVT_VARIANT*/
     PVOIDbyref;  /* Generic ByRef  */
     CHAR cVal;/* VT_I1 */
     USHORT  uiVal;  /* VT_UI2*/
     ULONGulVal;  /* VT_UI4*/
     INT  intVal; /* VT_INT*/
     UINT uintVal;/* VT_UINT  */
     DECIMAL *  pdecVal;/* VT_BYREFVT_DECIMAL*/
     CHAR *  pcVal;  /* VT_BYREFVT_I1  */
     USHORT *puiVal; /* VT_BYREFVT_UI2 */
     ULONG * pulVal; /* VT_BYREFVT_UI4 */
     INT *pintVal;/* VT_BYREFVT_INT */
     UINT *  puintVal;  /* VT_BYREFVT_UINT*/
     struct __tagBRECORD {PVOIDpvRecord;IRecordInfo * pRecInfo;
     } __VARIANT_NAME_4;/* VT_RECORD*/
    } __VARIANT_NAME_3;} __VARIANT_NAME_2; DECIMAL decVal;
     } __VARIANT_NAME_1;
    };
    

    The vt field holds one or more VT_ flags identifying the data type. Another field holds the actual value. For example, a VARIANT that represents a 32-bit long equal to 128 has a vt equal to VT_I4 and an lVal equal to 128. The header file Oleauto.h defines macros for reading and writing data encapsulated in VARIANTs. In addition, the system file Oleaut32.dll includes API functions, such as ::VariantInit and ::VariantClear, for managing and manipulating VARIANTs, and mfc's COleVariant class places a friendly wrapper around VARIANT data structures and the operations that can be performed on them.

    When you write Automation objects, you must use Automation-compatible data types—that is, data types that can be represented with VARIANTs—for all the objects' properties and methods. The table on the below summarizes the available data types.

    VARIANT-Compatible Data Types

    Data Type Description
    BSTR Automation string
    BSTR* Pointer to Automation string
    BYTE 8-bit byte
    BYTE* Pointer to 8-bit byte
    BYTE 8-bit byte
    CHAR 8-bit character
    CHAR* Pointer to 8-bit character
    CY* 64-bit currency value
    DATE 64-bit date and time value
    DATE* Pointer to 64-bit date and time value
    DECIMAL* Pointer to DECIMAL data structure
    DOUBLE Double-precision floating point value
    DOUBLE* Pointer to double-precision floating point value
    FLOAT Single-precision floating point value
    FLOAT* Pointer to single-precision floating point value
    IDispatch* IDispatch interface pointer
    IDispatch** Pointer to IDispatch interface pointer
    INT Signed integer (32 bits on win32 platforms)
    INT* Pointer to signed integer
    IUnknown* COM interface pointer*
    IUnknown** Pointer to COM interface pointer
    LONG 32-bit signed integer
    LONG* Pointer to 32-bit signed integer
    PVOID Untyped pointer
    SAFEARRAY* SAFEARRAY pointer
    SAFEARRAY** Pointer to SAFEARRAY pointer
    SCODE COM HRESULT
    SCODE* Pointer to COM HRESULT
    SHORT 16-bit signed integer
    SHORT* Pointer to 16-bit signed integer
    UINT Pointer unsigned integer
    UINT* Pointer unsigned integer
    ULONG 32-bit unsigned integer
    ULONG* Pointer to 32-bit unsigned integer
    USHORT 16-bit unsigned integer
    USHORT* Pointer to 16-bit unsigned integer
    VARIANT* Pointer to VARIANT data structure
    VARIANT_BOOL Automation Boolean
    VARIANT_BOOL* Pointer to Automation Boolean

    Generally speaking, Automation's dependence on VARIANT-compatible data types is a limitation that can frustrate developers who are accustomed to building "pure" COM objects—objects that use conventional COM interfaces rather than dispinterfaces and are therefore less restricted in the types of data that they can use. However, using only Automation-compatible data types offers one advantage: COM knows how to marshal VARIANTs, so Automation objects don't require custom proxy/stub DLLs. The trade-off is that you can't use structures (or pointers to structures) in methods' parameter lists, and arrays require special handling because they must be encapsulated in structures called SAFEARRAYs.

    The BSTR Data Type

    Most of the data types presented in the preceding section are self-explanatory. Two, however, merit further explanation. BSTR (pronounced "Bee'-ster") is Automation's string data type. Unlike a C++ string, which is an array of characters followed by a zero delimiter, a BSTR is a counted string. The first four bytes hold the number of bytes (not characters) in the string; subsequent bytes hold the characters themselves. All characters in a BSTR are 16-bit Unicode characters. A BSTR value is actually a pointer to the first character in the string. (See Figure 20-1.) The string is, in fact, zero-delimited, which means that you can convert a BSTR into a C++ string pointer by casting it to an LPCWSTR.

    Figure 20-1. The BSTR data type.

    In MFC, dealing with BSTRs frequently means converting CStrings to BSTRs and BSTRs to CStrings. CString::AllocSysString creates a BSTR from a CString:

    CString string = _T ("Hello, world");
    BSTR bstr = string.AllocSysString ();
    

    AllocSysString will automatically convert ANSI characters to Unicode characters if the preprocessor symbol _UNICODE is not defined, indicating that this is an ANSI program build. CString also includes a member function named SetSysString that can be used to modify an existing BSTR.

    Converting a BSTR into a CString is equally easy. CString's LPCWSTR operator initializes a CString from a BSTR and conveniently converts the characters to 8-bit ANSI characters if the CString is of the ANSI variety:

    CString string = (LPCWSTR) bstr;
    

    Be aware that if a BSTR contains embedded zeros (a very real possibility since BSTRs are counted strings), turning it into a CString in this way will effectively truncate the string.

    The SAFEARRAY Data Type

    SAFEARRAY is Automation's array data type. It's called a "safe" array because in addition to holding the data comprising the array elements, it houses information regarding the number of dimensions in the array, the bounds of each dimension, and more.

    A SAFEARRAY is actually a structure. It is defined this way in Oaidl.h:

    typedef struct  tagSAFEARRAY
     {
     USHORT cDims;
     USHORT fFeatures;
     ULONG cbElements;
     ULONG cLocks;
     PVOID pvData;
     SAFEARRAYBOUND rgsabound[ 1 ];
     } SAFEARRAY;
    

    SAFEARRAYBOUND is also a structure. It is defined like this:

    typedef struct  tagSAFEARRAYBOUND
     {
     ULONG cElements;
     LONG lLbound;
     } SAFEARRAYBOUND; 
    

    The cDims field holds the number of dimensions in the SAFEARRAY. rgsabound is an embedded array that contains one element for each dimension. Each element defines the bounds (number of storage elements) of one dimension as well as the index of that dimension's lower bound. Unlike C++ arrays, which number their elements from 0 to n, a SAFEARRAY's elements can be numbered using any contiguous range of integers—for example, -5 to n-5. fFeatures holds flags specifying what kind of data the SAFEARRAY stores and how the SAFEARRAY is allocated. cbElements holds the size, in bytes, of each element. Finally, pvData points to the elements themselves.

    The Windows API includes numerous functions for creating and using SAFEARRAYs; all begin with the name SafeArray. MFC has its own way of dealing with SAFEARRAYs in the form of a class named COleSafeArray. The following code creates a COleSafeArray object that represents a one-dimensional SAFEARRAY containing the integers 1 through 10:

    COleSafeArray sa;
    LONG lValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    sa.CreateOneDim (VT_I4, 10, lValues);
    

    The address of the VARIANT data structure in which the SAFEARRAY is stored can be retrieved with COleSafeArray's LPVARIANT or LPCVARIANT operator:

    VARIANT* pVariant = (LPVARIANT) sa;
    

    One-dimensional arrays are relatively easy to create with COleSafeArray, but multidimensional arrays require more effort. Suffice it to say that even COleSafeArray can't make SAFEARRAYs as palatable to C++ programmers as ordinary arrays.

    Late Binding vs. Early Binding

    A C++ programmer seeing Automation for the first time might wonder why dispinterfaces should even exist: the reason for them is far from obvious. Given that Automation objects are inherently more complex and less efficient than their conventional COM counterparts, why not use custom COM interfaces instead of IDispatch interfaces and save developers a lot of time and trouble?

    Dispinterfaces were created to allow Visual Basic programmers to use COM objects at a time when Visual Basic flatly didn't support conventional COM interfaces. In its early incarnations, Visual Basic couldn't call COM methods through ordinary interface pointers. Current versions of Visual Basic have partially eliminated this limitation, but to this day, many scripting languages, including VBScript, can talk to COM objects only through IDispatch interfaces.

    What's so special about IDispatch? In a nutshell, it prevents a compiler (or an interpreter, as the case may be) from having to understand vtables. A COM interface pointer is really a pointer to a location inside the object that holds the address of a table of function pointers—in C++ parlance, a virtual function table, or vtable. If pMath holds an IMath interface pointer, when a C++ compiler encounters a statement like

    pMath->Add (2, 2, &sum);
    

    it resolves the call by emitting code that extracts the address of the Add method from the interface's vtable. It knows the vtable's layout because the interface definition was #included in a header file. And therein lies the problem. Scripting languages don't understand C++ interface definitions. These languages can't resolve a statement like the following one unless they can somehow pass the method name to the object and ask the object itself to resolve the call:

    Sum = Math.Add (2, 2)
    

    Scripting languages may not understand vtables, but they know IDispatch very well. Given a pointer to an IDispatch interface, they know where they can go in the vtable to find the addresses of GetIDsOfNames and Invoke. It's therefore a simple matter for them to call IDispatch::Invoke and "bind" to a method at run time.

    That's the crux of IDispatch: shifting the responsibility for resolving method calls from the client to the object. Programmers call this late binding because the actual binding is done at run time. By contrast, we say that C++ clients use early binding because the bulk of the work required to resolve a method call is performed at compile time.

    Dual Interfaces

    The drawback to late binding is that it requires a run-time lookup that's not necessary in early binding. That impacts performance. And because of IDispatch's reliance on dispatch IDs, each property or method access nominally requires two round-trips to the server: a call to GetIDsOfNames followed by a call to Invoke. A smart Automation client can minimize round-trips by caching dispatch IDs, but the fact remains that late binding is inherently less efficient than early binding.

    The choice between an IDispatch interface and a conventional COM interface is a choice between flexibility and speed. An object that exposes its features through an IDispatch interface serves a wider variety of clients, but an object that uses ordinary COM interfaces serves late binding clients (particularly C++ clients) more efficiently.

    Dual interfaces are the COM equivalent of having your cake and eating it, too. A dual interface is an interface that derives from IDispatch. Its vtable includes entries for IDispatch methods (GetIDsOfNames, Invoke, and so on) as well as custom methods. Figure 20-2 shows the layout of the vtable for a dual interface that permits methods named Add and Subtract to be accessed indirectly through IDispatch::Invoke or directly through the vtable. Clients that rely exclusively on IDispatch can call Add and Subtract through IDispatch::Invoke; they won't even realize that the custom portion of the vtable exists. C++ clients, on the other hand, will effectively ignore the IDispatch section of the vtable and use early binding to call Add and Subtract. Thus, the same object can support early and late binding. Notice that methods defined in the custom half of the vtable must use Automation-compatible data types, just as methods exposed through IDispatch::Invoke must.

    Figure 20-2. Virtual function table for a dual interface.

    For MFC programmers, the greatest impediment to dual interfaces is the amount of effort required to implement them. MFC Technical Note 65 describes how to add dual interfaces to an MFC Automation server, but the procedure isn't for the faint of heart. The best way to do dual interfaces today is to forego MFC and instead use the Active Template Library (ATL), which makes creating dual interfaces truly effortless.

    Type Libraries

    Most Automation servers are accompanied by type libraries. In his book Inside COM (1997, Microsoft Press), Dale Rogerson describes a type library as "a bag of information about interfaces and components." Given a type library, a client can find out all sorts of interesting things about a COM object, including which interfaces it implements, what methods are present in those interfaces, and what each method's parameter list looks like. A type library can be provided in a separate file (usually with the extension .tlb, although .olb is sometimes used instead) or as a resource embedded in the object's executable file. Regardless of how they're packaged, type libraries are registered in the registry so that clients can easily locate them.

    Type libraries can be used in a variety of ways. ActiveX controls, for example, use type information (the kind of data found in type libraries) to tell control containers what kinds of events they're capable of firing. Type libraries can also be used to implement IDispatch interfaces and to provide information to object browsers. But the big reason type libraries are important to Automation programmers is that they permit Visual Basic clients to access a server's Automation methods and properties using the custom portion of a dual interface. Given type information, today's Visual Basic programs can even use conventional COM objects—ones that expose their functionality through custom COM interfaces instead of IDispatch interfaces. Type libraries aren't only for Visual Basic users, however; C++ programmers can use them, too. Shortly, you'll see how you can use ClassWizard to generate wrapper classes that simplify the writing of MFC Automation clients. Significantly, ClassWizard can work its magic only if a type library is available.

    How do type libraries get created? You can create them programmatically using COM API functions and methods, but most are created from IDL files. MIDL will read an IDL file and produce a type library from the statements inside it. You can also create type libraries by defining objects and their interfaces in ODL files and compiling them with a special tool called MkTypeLib. IDL files are the preferred method, but Visual C++ still uses ODL files for MFC Automation servers. The following ODL statements define a type library that contains descriptions of an Automation component named Math and a dispinterface named IAutoMath:

    [uuid (B617CC83-3C57-11D2-8E53-006008A82731), version (1.0)]
    library AutoMath
    {
     importlib ("stdole32.tlb");[uuid (B617CC84-3C57-11D2-8E53-006008A82731)]
     dispinterface IAutoMath
     {properties:
    [id(1)] double Pi;methods:
    [id(2)] long Add (long a, long b);
    [id(3)] long Subtract (long a, long b);
     };[uuid (B617CC82-3C57-11D2-8E53-006008A82731)]
     coclass Math
     {[default] dispinterface IAutoMath;
     };
    };
    

    The importlib statement in ODL is analogous to #include in C++. uuid assigns a GUID to an object or interface, and dispinterface defines a dispinterface. Statements inside a dispinterface block declare Automation methods and properties as well as their dispatch IDs. The object in this example features a property named Pi and methods named Add and Subtract. Their dispatch IDs are 1, 2, and 3, respectively.

    When you write an MFC Automation server, AppWizard creates an ODL file and adds it to the project. Each time a method or property is added, ClassWizard modifies the ODL file so that the next build will produce an up-to-date type library. As long as you use the MFC wizards to craft MFC Automation servers, type libraries are a natural consequence of the build process and require no extra effort to generate.