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, described in Chapter 32. 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. See the online documentation for a complete description of this useful MFC COleVariant class.

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 deallocation. 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(1998, 10, 1, 18, 0, 0);

The above declaration initializes the date to October 1, 1998, 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.