| Tenouk C & C++ | MFC Home | Automation 1 | Automation 3 | Download | Site Index |


 

 

 

 

 

 

Automation 2

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 compiler on Windows XP Pro machine with Service Pack 2. The Excel version is Excel 2003/Office 11. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary notes for this tutorial are Demo.xls, mymfc29A.xls, mymfc29B.xls, automation., variant and COlevariant class.

  1. The VARIANT Type

  2. The COleVariant Class

  3. Parameter and Return Type Conversions for Invoke()

  4. Automation Examples

  5. The MYMFC29A Automation Component - EXE Example: No User Interface

 

The VARIANT Type

 

No doubt you've noticed the VARIANT type used in both Automation client and component functions in the previous example. VARIANT is an all-purpose data type that IDispatch::Invoke uses to transmit parameters and return values. The VARIANT type is the natural type to use when exchanging data with VBA. Let's look at a simplified version of the VARIANT definition in the Windows header files.

 

struct tagVARIANT {

    VARTYPE vt; // unsigned short integer type code

    WORD wReserved1, wReserved2, wReserved3;

    union {

        short      iVal;           // VT_I2  short integer

        long       lVal;           // VT_I4  long integer

        float      fltVal;         // VT_R4  4-byte float

        double     dblVal;         // VT_R8  8-byte IEEE float

        DATE       date;           // VT_DATE stored as dbl

                                   //  date.time

        CY         vtCY            // VT_CY 64-bit integer

        BSTR       bstrVal;        // VT_BSTR

        IUnknown*  punkVal;        // VT_UNKNOWN

        IDispatch* pdispVal;       // VT_DISPATCH

        short*     piVal;          // VT_BYREF | VT_I2

        long*      plVal;          // VT_BYREF | VT_I4

        float*     pfltVal;        // VT_BYREF | VT_R4

        double*    pdblVal;        // VT_BYREF | VT_R8

        DATE*      pdate;          // VT_BYREF | VT_DATE

        CY*        pvtCY;          // VT_BYREF | VT_CY

        BSTR*      pbstrVal;       // VT_BYREF | VT_BSTR

    }

};

 

typedef struct tagVARIANT VARIANT;

 

As you can see, the VARIANT type is a C structure that contains a type code vt, some reserved bytes, and a big union of types that you already know about. If vt is VT_I2, for example, you would read the VARIANT's value from iVal, which contains a 2-byte integer. If vt is VT_R8, you would read this value from dblVal, which contains an 8-byte real value.

A VARIANT object can contain actual data or a pointer to data. If vt has the VT_BYREF bit set, you must access a pointer in piVal, plVal, and so on. Note that a VARIANT object can contain an IUnknown pointer or an IDispatch pointer. This means that you can pass a complete COM object using an Automation call, but if you want VBA to process that object, its class should have an IDispatch interface.

Strings are special. The BSTR type is yet another way to represent character strings. A BSTR variable is a pointer to a zero-terminated character array with a character count in front. A BSTR variable could, therefore, contain binary characters, including zeros. If you had a VARIANT object with vt = VT_BSTR, memory would look like this.

 

Figure 12: VARIANT data type and memory arrangement.

 

Figure 12: VARIANT data type and memory arrangement.

 

Because the string has a terminating 0, you can use bstrVal as though it was an ordinary char pointer, but you have to be very, very careful about memory cleanup. You can't simply delete the string pointer, because the allocated memory begins with the character count. Windows provides the SysAllocString() and SysFreeString() functions for allocating and deleting BSTR objects. SysAllocString() is another COM function that takes a wide string pointer as a parameter. This means that all BSTRs contain wide characters, even if you haven't defined _UNICODE. Be careful.

Windows supplies some useful functions for VARIANTs, including those shown in the following table. If a VARIANT contains a BSTR, these functions ensure that memory is allocated and cleared properly. The VariantInit() and VariantClear() functions set vt to VT_EMPTY. All the variant functions are global functions and take a VARIANT* parameter.

 

Function

Description

VariantInit()

Initializes a VARIANT.

VariantClear()

Clears a VARIANT.

VariantCopy()

Frees memory associated with the destination VARIANT and copies the source VARIANT.

VariantCopyInd()

Frees the destination VARIANT and performs any indirection necessary to copy the source VARIANT.

VariantChangeType()

Changes the type of the VARIANT.

 

Table 2.

 

The COleVariant Class

 

Writing a C++ class to wrap the VARIANT structure makes a lot of sense. Constructors can call VariantInit(), and the destructor can call VariantClear(). The class can have a constructor for each standard type, and it can have copy constructors and assignment operators that call VariantCopy(). When a variant object goes out of scope, its destructor is called and memory is cleaned up automatically. Well, the MFC team created just such a class, mostly for use in the Data Access Objects (DAO) subsystem. It works well in Automation clients and components, however. A simplified declaration is shown here.

 

class COleVariant : public tagVARIANT

{

// Constructors

public:

    COleVariant();

    COleVariant(const VARIANT& varSrc);

    COleVariant(const COleVariant& varSrc);

    COleVariant(LPCTSTR lpszSrc);

    COleVariant(CString& strSrc);

    COleVariant(BYTE nSrc);

    COleVariant(short nSrc, VARTYPE vtSrc = VT_I2);

    COleVariant(long lSrc, VARTYPE vtSrc = VT_I4);

    COleVariant(float fltSrc);

    COleVariant(double dblSrc);

    COleVariant(const COleDateTime& dateSrc);

    // Destructor

    ~COleVariant(); // deallocates BSTR

// Operations

public:

    void Clear(); // deallocates BSTR

    VARIANT Detach(); // more later

    void ChangeType(VARTYPE vartype, LPVARIANT pSrc = NULL);

};

 

In addition, the CArchive and CDumpContext classes have comparison operators, assignment operators, conversion operators, and friend insertion/extraction operators. Now let's see how the COleVariant class helps us write the component's GetFigure() function that you previously saw referenced in the sample dispatch map. Assume that the component stores strings for four figures in a class data member:

 

private:

    CString m_strFigure[4];

 

Here's what we'd have to do if we used the VARIANT structure directly:

 

VARIANT CClock::GetFigure(short n)

{

    VARIANT vaResult;

    ::VariantInit(&vaResult);

    vaResult.vt = VT_BSTR;

    // CString::AllocSysString creates a BSTR

    vaResult.bstrVal = m_strFigure[n].AllocSysString();

    return vaResult; // Copies vaResult without copying BSTR. BSTR still must be freed later

}

 

Here's the equivalent, with a COleVariant return value:

 

VARIANT CClock::GetFigure(short n)

{

    return COleVariant(m_strFigure[n]).Detach();

}

 

Calling the COleVariant::Detach function is critical here. The GetFigure() function is constructing a temporary object that contains a pointer to a BSTR. That object gets bitwise-copied to the return value. If you didn't call Detach(), the COleVariant destructor would free the BSTR memory and the calling program would get a VARIANT that contained a pointer to nothing. A component's variant dispatch function parameters are declared as const VARIANT&. You can always cast a VARIANT pointer to a COleVariant pointer inside the function. Here's the SetFigure() function:

 

void CClock::SetFigure(short n, const VARIANT& vaNew)

{

    COleVariant vaTemp;

    vaTemp.ChangeType(VT_BSTR, (COleVariant*) &vaNew);

    m_strFigure[n] = vaTemp.bstrVal;

}

 

Remember that all BSTRs contain wide characters. The CString class has a constructor and an assignment operator for the LPCWSTR (wide-character pointer) type. Thus, the m_strFigure string will contain single-byte characters, even though bstrVal points to a wide-character array.

Client dispatch function variant parameters are also typed as const VARIANT&. You can call those functions with either a VARIANT or a COleVariant object. Here's an example of a call to the CClockDriver::SetFigure function:

 

pClockDriver->SetFigure(0, COleVariant("XII"));

 

Visual C++ 5.0 added two new classes for BSTRs and VARIANTs. These classes are independent of the MFC library: _bstr_t and _variant_t. The _bstr_t class encapsulates the BSTR data type; the _variant_t class encapsulates the VARIANT type. Both classes manage resource allocation and de-allocation. For more information on these classes, see the online documentation.

 

Parameter and Return Type Conversions for Invoke()

 

All IDispatch::Invoke parameters and return values are processed internally as VARIANTs. Remember that! The MFC library implementation of Invoke() is smart enough to convert between a VARIANT and whatever type you supply (where possible), so you have some flexibility in declaring parameter and return types. Suppose, for example, that your controller's GetFigure() function specifies the return type BSTR. If a component returns an int or a long, all is well: COM and the MFC library convert the number to a string. Suppose your component declares a long parameter and the controller supplies an int. Again, no problem.

An MFC library Automation client specifies the expected return type as a VT_ parameter to the COleDispatchDriver functions GetProperty(), SetProperty(), and InvokeHelper(). An MFC library Automation component specifies the expected parameter types as VTS_ parameters in the DISP_PROPERTY and DISP_FUNCTION macros. Unlike C++, VBA is not a strongly typed language. VBA variables are often stored internally as VARIANTs. Take an Excel spreadsheet cell value, for example. A spreadsheet user can type a text string, an integer, a floating-point number, or a date/time in a cell. VBA treats the cell value as a VARIANT and returns a VARIANT object to an Automation client. If your client function declares a VARIANT return value, it can test vt and process the data accordingly. VBA uses a date/time format that is distinct from the MFC library CTime class. Variables of type DATE hold both the date and the time in one double value. The fractional part represents time (.25 is 6:00 AM), and the whole part represents the date (number of days since December 30, 1899). The MFC library provides a COleDateTime class that makes dates easy to deal with. You could construct a date this way:

 

COleDateTime date(2005, 10, 1, 18, 0, 0);

 

The above declaration initializes the date to October 1, 2005, at 6:00 PM. The COleVariant class has an assignment operator for COleDateTime, and the COleDateTime class has member functions for extracting date/time components. Here's how you print the time:

 

TRACE("time = %d:%d:%d\n", date.GetHour(), date.GetMinute(), date.GetSecond());

 

If you have a variant that contains a DATE, you use the COleVariant::ChangeType function to convert a date to a string, as shown here:

 

COleVariant vaTimeDate = date;

COleVariant vaTemp;

vaTemp.ChangeType(VT_BSTR, &vaTimeDate);

CString str = vaTemp.bstrVal;

TRACE("date = %s\n", str);

 

One last item concerning Invoke() parameters: a dispatch function can have optional parameters. If the component declares trailing parameters as VARIANTs, the client doesn't have to supply them. If the client calls the function without supplying an optional parameter, the VARIANT object's vt value on the component end is VT_ERROR.

 

Automation Project Examples

 

The remainder of this module presents five sample programs. The first three programs are Automation components, an EXE component with no user interface, a DLL component, and a multi-instance SDI EXE component. Each of these component programs comes with a Microsoft Excel driver workbook file. The fourth sample program is an MFC Automation client program that drives the three components and also runs Excel using the COleDispatchDriver class. The last sample is a client program that uses the C++ #import directive instead of the MFC COleDispatchDriver class.

 

The MYMFC29A Automation Component - EXE Example: No User Interface

 

The Visual C++ Autoclik MDSN’s example is a good demonstration of an MDI framework application with the document object as the Automation component. The MYMFC29A example is different from the Autoclik example because MYMFC29A has no user interface. There is one Automation-aware class, and in the first version of the program, a single process supports the construction of multiple Automation component objects. In the second version, a new process starts up each time an Automation client creates an object.

The MYMFC29A example represents a typical use of Automation. A C++ component implements financial transactions. VBA programmers can write User-interface-intensive applications that rely on the audit rules imposed by the Automation component. A production component program would probably use a database, but MYMFC29A is simpler. It implements a bank account with two methods, Deposit and Withdrawal, and one read-only property, Balance. Obviously, Withdrawal can't permit withdrawals that make the balance negative. You can use Excel to control the component, as shown in Figure 13.

 

Figure 13: This Excel workbook is controlling the MYMFC29A component.

 

Figure 13: This Excel workbook is controlling the MYMFC29A component.

 

Here are the steps for creating the MYMFC29A program from scratch:

 

Run AppWizard to create the MYMFC29A project in the \mfcproject\mymfc29A directory or any directory that you have designated for your project.

 

Figure 14: Visual C++ new project dialog, creating MYMFC29A dialog based project.

 

Figure 14: Visual C++ new project dialog, creating MYMFC29A dialog based project.

 

Select the Dialog Based option (Step 1).

 

Figure 15: MYMFC29A - AppWizard step 1 of 4.

 

Figure 15: MYMFC29A - AppWizard step 1 of 4.

 

Deselect all options in Step 2, and accept the remaining default settings. This is the simplest application that AppWizard can generate.

 

Figure 16: MYMFC29A - AppWizard step 2 of 4.

 

Figure 16: MYMFC29A - AppWizard step 2 of 4.

 

Figure 17: MYMFC29A - AppWizard step 3 of 4.

 

Figure 17: MYMFC29A - AppWizard step 3 of 4.

 

Figure 18: MYMFC29A - AppWizard step 4 of 4.

 

Figure 18: MYMFC29A - AppWizard step 4 of 4.

 

Figure 19: MYMFC29A project summary.

 

Figure 19: MYMFC29A project summary.

 

Eliminate the dialog class from the project. Using Windows Explorer or the command-line prompt, delete the files mymfc29ADlg.cpp and mymfc29ADlg.h. Remove mymfc29ADlg.cpp and mymfc29ADlg.h from the project by deleting them from the project's Workspace window (FileView) by selecting the file and using the Edit Delete menu.

 

Figure 20: Deleting unnecessary project files.

 

Figure 20: Deleting unnecessary project files.

 

 

Figure 21: MYMFC29A files.

 

Figure 21: MYMFC29A files.

 

Edit mymfc29A.cpp. Remove the dialog #include (or commented out) and remove all dialog-related code from the InitInstance() function. In ResourceView, delete the IDD_MYMFC29A_DIALOG dialog resource template.

 

MFC and Automation program example - C++ code snippet

 

Listing 1.

 

MFC and Automation program example - C++ code snippet

 

Listing 2.

 

Add code to enable Automation. Add this line in StdAfx.h:

 

#include <afxdisp.h>

 

MFC and Automation program example - C++ code snippet

 

Listing 3.

 

Then, edit the InitInstance() function (in mymfc29A.cpp) to look something like this:

 

BOOL CMymfc29AApp::InitInstance()

{

    AfxOleInit();

    if(RunEmbedded() || RunAutomated())

    {

        // component started by COM

        COleTemplateServer::RegisterAll();

        return TRUE;

    }

    // Component is being run directly by the user

    COleObjectFactory::UpdateRegistryAll();

    AfxMessageBox("Bank component is registered");

    return FALSE;

}

 

MFC and Automation program example - C++ code snippet

 

Listing 4.

 

Rebuild ClassWizard database. Delete the CLW file and invoke the ClassWizard.

 

Figure 22: Deleting CLW, a ClassWizard database file.

 

Figure 22: Deleting CLW, a ClassWizard database file.

 

Just click the Yes button.

 

Figure 23: ClassWizard database rebuilding conformation prompt.

 

Figure 23: ClassWizard database rebuilding conformation prompt.

 

And OK button to include those files for ClassWizard database rebuilding.

 

Figure 24: List of files for ClassWizard database rebuilding.

 

Figure 24: List of files for ClassWizard database rebuilding.

 

We need to generate the ODL file manually in order to create and update the type library (TLB file). ODL cannot be rebuilt as ClassWizard. Create an empty ODL file under the project directory using the project name as the file name.

 

Figure 25: Creating ODL file.

 

Figure 25: Creating ODL file.

 

Then add the ODL file to the project.

 

Figure 26: Adding new file to project.

 

Figure 26: Adding new file to project.

 

Figure 27: Adding ODL file to project.

 

Figure 27: Adding ODL file to project.

 

Finally enter the following codes into the mymfc29A.odl file. The content will be updated automatically when we add components later on.

 

// Originally just a manually created odl template

[ uuid(40980C17-DB2B-498F-85AC-BE88F2B549CF), version(1.0) ]

library mymfc29A

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

 

//{{AFX_APPEND_ODL}}

};

 

The uuid 40980C17-DB2B-498F-85AC-BE88F2B549CF is copied from the StdAfx.h’s #if !define directive as shown below.

 

Figure 28: The uuid.

 

Figure 28: The uuid.

 

Use ClassWizard to add a new class, CBank, as shown here.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 29: Adding new class to project.

 

Figure 29: Adding new class to project.

 

Be sure to select the Createable By Type ID option. Now you can see the funny icon of the IBank component in the ClassView window and the ODL file has been updated.

 

Use ClassWizard to add two methods and a property. Click on the Automation tab, and then add a Withdrawal method, as shown here.

 

Figure 30: Adding method to project.

 

Figure 30: Adding method to project.

 

Figure 31: Entering Withdrawal method information.

 

Figure 31: Entering Withdrawal method information.

 

The dAmount parameter is the amount to be withdrawn, and the return value is the actual amount withdrawn. If you try to withdraw $100 from an account that contains $60, the amount withdrawn is $60.

Add a similar Deposit method that returns void.

 

Figure 32: Entering Deposit method information.

 

Figure 32: Entering Deposit method information.

 

Then add the Balance property, as shown here. Make sure you select the Get/Set methods.

 

Figure 33: Adding and entering Balance property information.

 

Figure 33: Adding and entering Balance property information.

 

We could have chosen direct access to a component data member, but then we wouldn't have read-only access. We choose Get/Set methods so that we can code the SetBalance() function to do nothing. Meanwhile you can check ClassView window for the component, it should display methods and property that we have added. The ODL file content also been updated.

 

Figure 34: The added methods and property.

 

Figure 34: The added methods and property.

 

Using a ClassView, add a public m_dBalance data member of type double to the CBank class.

 

Figure 35: Adding member variable.

 

Figure 35: Adding member variable.

 

MFC and Automation program example - C++ code snippet

 

Listing 5.

 

Because we've chosen the Get/Set methods option for the Balance property, ClassWizard doesn't generate a data member for us. You already declared m_dBalance in the Bank.h file then you should initialize m_dBalance to 0.0 in the CBank constructor located in the Bank.cpp file manually.

 

MFC and Automation program example - C++ code snippet

 

Listing 6.

 

Edit the generated method and property functions. Add the following code (Bank.cpp):

 

double CBank::Withdrawal(double dAmount)

{

        if (dAmount < 0.0){

        return 0.0;

        }

        if (dAmount <= m_dBalance){

            m_dBalance -= dAmount;

            return dAmount;

        }

        double dTemp = m_dBalance;

        m_dBalance = 0.0;

        return dTemp;

}

 

void CBank::Deposit(double dAmount)

{

        if (dAmount < 0.0){

            return;

        }

        m_dBalance += dAmount;

}

 

double CBank::GetBalance()

{ return m_dBalance; }

 

void CBank::SetBalance(double newValue)

{ TRACE("Sorry, Doe, I can't do that!\n"); }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

MFC and Automation program example - C++ code snippet

 

Listing 7.

 

Build the MYMFC29A program; run it once to register the component. The following is the output, just a prompt message box telling us that the component successfully registered.

 

Figure 36: MYMFC29A output, prompting the component successfully registered.

 

Figure 36: MYMFC29A output, prompting the component successfully registered.

 

 

Further reading and digging:

  1. DCOM at MSDN.

  2. COM+ at MSDN.

  3. COM at MSDN.

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

  5. MSDN MFC 7.0 class library online documentation.

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

  7. MSDN Library

  8. Windows data type.

  9. Win32 programming Tutorial.

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

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

 

 

 

 

 

 


 

| Tenouk C & C++ | MFC Home | Automation 1 | Automation 3 | Download | Site Index |