Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
When you are using the IDispatch interface from a C++ client, you will need to work with the VARIANT data type. The Invoke() method of IDispatch requires you to send in all parameters to the dispinterface member as an array of VARIANTs. While we already have a good idea what the possible values of a VARIANT can be, the VARIANT structure itself is realized in C++ as a union of all possible [oleautomation] compatible data types. Beyond specifying the union of all possible data types, the VARIANT structure also specifies a VARTYPE field. You use this field to specify what sort of thing the VARIANT represents (a BSTR, long, short, IUnknown pointer, and so forth). The definition of the VARIANT is expressed in IDL (<oaidl.idl>) as the following:
// The VARIANT structure may take on the value of any possible automation // data type. struct tagVARIANT { union { VARTYPE vt; // What is my current type? union { LONG lVal; /* VT_I4 */ BYTE bVal; /* VT_UI1 */ SHORT iVal; /* VT_I2 */ FLOAT fltVal; /* VT_R4 */ DOUBLE dblVal; /* VT_R8 */ VARIANT_BOOL boolVal; /* VT_BOOL */ _VARIANT_BOOL bool; /* (obsolete) */ SCODE scode; /* VT_ERROR */ CY cyVal; /* VT_CY */ DATE date; /* VT_DATE */ BSTR bstrVal; /* VT_BSTR */ IUnknown * punkVal; /* VT_UNKNOWN */ IDispatch * pdispVal; /* VT_DISPATCH */ SAFEARRAY * parray; /* VT_ARRAY */ BYTE * pbVal; /* VT_BYREF|VT_UI1 */ SHORT * piVal; /* VT_BYREF|VT_I2 */ LONG * plVal; /* VT_BYREF|VT_I4 */ FLOAT * pfltVal; /* VT_BYREF|VT_R4 */ DOUBLE * pdblVal; /* VT_BYREF|VT_R8 */ VARIANT_BOOL *pboolVal; /* VT_BYREF|VT_BOOL */ _VARIANT_BOOL *pbool; /* (obsolete) */ SCODE * pscode; /* VT_BYREF|VT_ERROR */ CY * pcyVal; /* VT_BYREF|VT_CY */ DATE * pdate; /* VT_BYREF|VT_DATE */ BSTR * pbstrVal; /* VT_BYREF|VT_BSTR */ IUnknown ** ppunkVal; /* VT_BYREF|VT_UNKNOWN */ IDispatch ** ppdispVal; /* VT_BYREF|VT_DISPATCH */ SAFEARRAY ** pparray; /* VT_BYREF|VT_ARRAY */ VARIANT * pvarVal; /* VT_BYREF|VT_VARIANT */ PVOID byref; /* Generic ByRef */ CHAR cVal; /* VT_I1 */ USHORT uiVal; /* VT_UI2 */ ULONG ulVal; /* VT_UI4 */ INT intVal; /* VT_INT */ UINT uintVal; * VT_UINT */ DECIMAL * pdecVal; /* VT_BYREF|VT_DECIMAL */ CHAR * pcVal; /* VT_BYREF|VT_I1 */ USHORT * puiVal; /* VT_BYREF|VT_UI2 */ ULONG * pulVal; /* VT_BYREF|VT_UI4 */ INT * pintVal; /* VT_BYREF|VT_INT */ UINT * puintVal; /* VT_BYREF|VT_UINT */ };
The comments that appear in the definition of the VARIANT type are the flags used to set the underlying type of VARIANT you are working with. In essence, the VARIANT structure allows you to express any automation-compatible data type, which can be used by all COM languages. The whole of these data types is expressed as a C union. To specify the sort of VARIANT you are defining, set the VARTYPE field of the structure using the correct VT_ flag.
Building a VARIANT
When you wish to create a VARIANT data type in the C++ programming language, you will make use of a handful of COM library functions, which shield you from the need to manage the memory associated to a given VARIANT. In many respects, working with the VARIANT is much like working with the BSTR data type, given that you manipulate it indirectly using API calls. To create a brand new VARIANT, you will begin by defining a VARIANT variable and initializing it using the VariantInit() COM library function:
// Create and initialize a VARIANT in C++ VARIANT myVar; VariantInit(&myVar);
At this point, you have an empty (but safe) VARIANT structure. To establish what sort of data the variant is holding (BSTR, long, short, pointer to a BSTR, and so on), you set the VARTYPE field by specifying the correct VT_ flag. Let's say you want to create a VARIANT that starts out life as a short, which is to say VT_I2:
// Set VARIANT type VARIANT myVar; VariantInit(&myVar); myVar.vt = VT_I2
Next, you will need to establish the value of this short by setting the correct member in the union with an appropriate value. As you can see from the definition of the VARIANT structure, a short is identified as the "iVal" member of the union:
// Each item in the VARIANT union has an associated VT flag and union member. struct tagVARIANT { union { VARTYPE vt; union { LONG lVal; /* VT_I4 */ BYTE bVal; /* VT_UI1 */ SHORT iVal; /* VT_I2 */ ... }
Thus, to create a short with the value of 20 using the VARIANT data type:
// Create and initialize a VARIANT in C++ VARIANT myVar; VariantInit(&myVar); myVar.vt = VT_I2; myVar.iVal = 20; VariantClear(&myVar);
As another example, here is a VARIANT of type long, with the value of 5000:
// Another VARIANT. VARIANT myOtherVar; VariantInit(&myOtherVar); myotherVar.vt = VT_I4; myotherVar.lVal = 5000; VariantClear(&myotherVar);
In addition to VariantInit() and VariantClear(), the COM library defines a set of additional functions that operate on the VARIANT data type. Some of the most common are shown below:
VARIANT Function | Meaning in Life |
---|---|
VariantInit() | Initializes a VARIANT structure. |
VariantClear() | Frees up any memory consumed by the current VARIANT. |
VariantCopy() | Copies the content of one VARIANT to another VARIANT. This method will also free any memory of the destination before performing the copy. |
VariantChangeType() | Sets the underlying type of the VARIANT to another type. |
CComVariant: An ATL Wrapper for the VARIANT Data Type
If you are going to be spending a good amount of time with this polymorphic data type, you will be happy to know that ATL provides a wrapper class to hide the raw library functions from your view. Much like CComBSTR, CComVariant provides a number of functions and overloaded operators which make working with the VARIANT structure much less painful. The member functions of this class make calls to the same VARIANT library functions that you would by hand, in a more OO-based manner.
Note | Recall that the Visual C++ compiler extensions supply the _variant_t type to help simplify VARIANT programming. |
| < Free Open Study > |
|