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.

Because the string has a terminating 0, you can use bstrVal as though it were 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