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.
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.
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:
Figure 20-3. Creating an MFC Automation server.
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 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:
After ClassWizard is finished, it's your job to implement the method by filling in the empty function body.
You can also use ClassWizard to add Automation properties. MFC distinguishes between two types of Automation 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:
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.
To get your feet wet writing a living, breathing MFC Automation server, try this simple exercise:
long CAutoMathDoc::Add (long a, long b) { return a + b; } |
Figure 20-9. Adding the Add method.
long CAutoMathDoc::Subtract (long a, long b) { return a - b; } |
double CAutoMathDoc::GetPi () { return 3.1415926; } void CAutoMathDoc::SetPi (double newValue) { SetNotSupported (); } |
Figure 20-10. Adding the Pi property.
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!
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.
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:
Object | Properties | 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 window RUNTIME_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 > 1000 class 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[0] = 1; m_nRevenues[1] = 1; m_nRevenues[2] = 1; m_nRevenues[3] = 1; return TRUE; } /////////////////////////////////////////////////////////////////////////// // 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_MSG DECLARE_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_MSG DECLARE_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_MSG DECLARE_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 cs return 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[0].CreateSolidBrush (RGB (255, 0, 0)); // Red brFillColor[1].CreateSolidBrush (RGB (255, 255, 0)); // Yellow brFillColor[2].CreateSolidBrush (RGB (255, 0, 255)); // Magenta brFillColor[3].CreateSolidBrush (RGB ( 0, 255, 255)); // Cyan // // Draw the pie chart. // int nTotal = 0; for (int i=0; i<4; i++) nTotal += pDoc->GetRevenue (i); int x1 = 0; int y1 = -1000; int nSum = 0; for (i=0; i<4; i++) { int nRevenue = pDoc->GetRevenue (i); if (nRevenue != 0) { nSum += nRevenue; int x2 = (int) (sin ((((double) nSum * 2 * PI) / (double) nTotal) + PI) * 1000); int y2 = (int) (cos ((((double) nSum * 2 * PI) / (double) nTotal) + PI) * 1000); pDC->SelectObject (&brFillColor[i]); pDC->Pie (-200, -200, 200, 200, x1, y1, x2, y2); x1 = x2; y1 = y2; } } pDC->SelectStockObject (WHITE_BRUSH); } /////////////////////////////////////////////////////////////////////////// // 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.
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.