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 |