previous page
next page

The CComVariant Smart VARIANT Class

A Review of the COM VARIANT Data Type

Occasionally while using COM, you'll want to pass parameters to a method without any knowledge of the data types the method requires. For the method to be capable of interpreting its received parameters, the caller must specify the format of the data as well as its value.

Alternatively, you can call a method that returns a result that consists of varying data types, depending on context. Sometimes it returns a string, sometimes a long, and sometimes even an interface pointer. This requires the method to return data in a self-describing data format. For each value transmitted, you send two fields: a code specifying a data type and a value represented in the specified data type. Clearly, for this to work, the sender and receiver must agree on the set of possible formats.

COM specifies one such set of possible formats (the VARTYPE enumeration) and specifies the structure that contains both the format and an associated value (the VARIANT structure). The VARIANT structure looks like this:

typedef struct FARSTRUCT tagVARIANT VARIANT;                         
typedef struct FARSTRUCT tagVARIANT VARIANTARG;                      
                                                                     
typedef struct tagVARIANT {                                          
    VARTYPE vt;                                                      
    unsigned short wReserved1;                                       
    unsigned short wReserved2;                                       
    unsigned short wReserved3;                                       
    union {                                                          
        unsigned char         bVal;           // VT_UI1              
        short                 iVal;           // VT_I2               
        long                  lVal;           // VT_I4               
        float                 fltVal;         // VT_R4               
        double                dblVal;         // VT_R8               
        VARIANT_BOOL          boolVal;        // VT_BOOL             
        SCODE                 scode;          // VT_ERROR            
        CY                    cyVal;          // VT_CY               
        DATE                  date;           // VT_DATE             
        BSTR                  bstrVal;        // VT_BSTR             
        IUnknown         FAR* punkVal;        // VT_UNKNOWN          
        IDispatch        FAR* pdispVal;       // VT_DISPATCH         
        SAFEARRAY        FAR* parray;         // VT_ARRAY|*          
        unsigned char    FAR* pbVal;          // VT_BYREF|VT_UI1     
        short            FAR* piVal;          // VT_BYREF|VT_I2      
        long             FAR* plVal;          // VT_BYREF|VT_I4      
        float            FAR* pfltVal;        // VT_BYREF|VT_R4      
        double           FAR* pdblVal;        // VT_BYREF|VT_R8      
        VARIANT_BOOL     FAR* pboolVal;       // VT_BYREF|VT_BOOL    
        SCODE            FAR* pscode;         // VT_BYREF|VT_ERROR   
        CY               FAR* pcyVal;         // VT_BYREF|VT_CY      
        DATE             FAR* pdate;          // VT_BYREF|VT_DATE    
        BSTR             FAR* pbstrVal;       // VT_BYREF|VT_BSTR    
        IUnknown FAR*    FAR* ppunkVal;       // VT_BYREF|VT_UNKNOWN 
        IDispatch FAR*   FAR* ppdispVal;     // VT_BYREF|VT_DISPATCH
        SAFEARRAY FAR*   FAR* pparray;       // VT_ARRAY|*          
        VARIANT          FAR* pvarVal;       // VT_BYREF|VT_VARIANT 
        void             FAR* byref;         // Generic ByRef       
    };                                                               
};                                                                   

You initialize the VARIANT structure by storing a value in one of the fields of the tagged union and then storing the corresponding type code for the value in the vt member of the VARIANT structure. The VARIANT data type has a number of semantics that make using it tedious and error prone for C++ developers.

Correctly using a VARIANT requires that you remember the following rules:

  • A VARIANT must be initialized before use by calling the VariantInit function on it. Alternatively, you can initialize the type and associated value field to a valid state (such as setting the vt field to VT_EMPTY).

  • A VARIANT must be copied by calling the VariantCopy function on it. This performs the proper shallow or deep copy, as appropriate for the data type stored in the VARIANT.

  • A VARIANT must be destroyed by calling the VariantClear function on it. This performs the proper shallow or deep destroy, as appropriate for the data type stored in the VARIANT. For example, when you destroy a VARIANT containing a SAFEARRAY of BSTRs, VariantClear frees each BSTR element in the array and then frees the array itself.

  • A VARIANT can optionally represent, at most, one level of indirection, which is specified by adding the VT_BYREF bit setting to the type code. You can call VariantCopyInd to remove a single level of indirection from a VARIANT.

  • You can attempt to change the data type of a VARIANT by calling Variant-ChangeType[Ex].

With all these special semantics, it is useful to encapsulate these details in a reusable class. ATL provides such a class: CComVariant.

The CComVariant Class

The CComVariant class is an ATL utility class that is a useful encapsulation for the COM self-describing data type, VARIANT. The atlcomcli.h file contains the definition of the CComVariant class. The only state the class maintains is an instance of the VARIANT structure, which the class obtains by inheritance from the tagVARIANT structure. Conveniently, this means that a CComVariant instance is a VARIANT structure, so you can pass a CComVariant instance to any function that expects a VARIANT structure.

class CComVariant: public tagVARIANT {
    ...                               
};                                    

You often use a CComVariant instance when you need to pass a VARIANT argument to a COM method. The following code passes a string argument to a method that expects a VARIANT. The code uses the CComVariant class to do the conversion from a C-style string to the required VARIANT:

STDMETHODIMP put_Name(/* [in] const VARIANT* name);

HRESULT SetName (LPCTSTR pszName) {
    // Initializes the VARIANT structure
    // Allocates a BSTR copy of pszName
    // Sets the VARIANT to the BSTR
    CComVariant v (pszName);

    // Pass the raw VARIANT to the method
    return pObj->put_Name(&v);

    // Destructor clears v freeing the BSTR
}

Constructors and Destructor

Twenty-three constructors are available for CComVariant objects. The default constructor simply invokes the COM function VariantInit, which sets the vt flag to VT_EMPTY and properly initializes the VARIANT so that it is ready to use. The destructor calls the Clear member function to release any resources potentially held in the VARIANT.

CComVariant() {         
    ::VariantInit(this);
}                       
                        
~CComVariant() {        
    Clear();            
}                       

The other 22 constructors initialize the VARIANT structure appropriately, based on the type of the constructor argument.

Many of the constructors simply set the vt member of the VARIANT structure to the value representing the type of the constructor argument, and store the value of the argument in the appropriate member of the union.

CComVariant(BYTE nSrc) {                           
    vt = VT_UI1;                                   
    bVal = nSrc;                                   
}                                                  
CComVariant(short nSrc) {                          
    vt = VT_I2;                                    
    iVal = nSrc;                                   
}                                                  
CComVariant(long nSrc, VARTYPE vtSrc = VT_I4) {    
    ATLASSERT(vtSrc == VT_I4 || vtSrc == VT_ERROR);
    vt = vtSrc;                                    
    lVal = nSrc;                                   
}                                                  
CComVariant( float fltSrc) {                       
    vt = VT_R4;                                    
    fltVal = fltSrc;                               
}                                                  

A few of the constructors are more complex. An SCODE looks like a long to the compiler. Therefore, constructing a CComVariant that specifies an SCODE or a long initialization value invokes the constructor that accepts a long. To enable you to distinguish these two cases, this constructor also takes an optional argument that allows you to specify whether the long should be placed in the VARIANT as a long or as an SCODE. When you specify a variant type other than VT_I4 or VT_ERROR, this constructor asserts in a debug build.

Windows 16-bit COM defined an HRESULT (a handle to a result code) as a data type that contained an SCODE (a status code). Therefore, you'll occasionally see older legacy code that considers the two data types to be different. In fact, some obsolete macros convert an SCODE to an HRESULT and extract the SCODE from an hrESULT. However, the SCODE and HRESULT data types are identical in 32-bit COM applications. The VARIANT data structure contains an SCODE field instead of an hrESULT because that's the way it was originally declared:

CComVariant(long nSrc, VARTYPE vtSrc = VT_I4) ;

The constructor that accepts a bool initialization value sets the contents of the VARIANT to VARIANT_TRUE or VARIANT_FALSE, as appropriate, not to the bool value specified. A logical trUE value, as represented in a VARIANT, must be VARIANT_TRUE (1 as a 16-bit value), and logical FALSE is VARIANT_FALSE (0 as a 16-bit value). The Microsoft C++ compiler defines the bool data type as an 8-bit (0 or 1) value. This constructor provides the conversion between the two representations of a Boolean value:

CComVariant(bool bSrc) {                                  
    vt = VT_BOOL;                                         
    boolVal = bSrc ? ATL_VARIANT_TRUE : ATL_VARIANT_FALSE;
}                                                         

Two constructors accept an interface pointer as an initialization value and produce a CComVariant instance that contains an AddRef'ed copy of the interface pointer. The first constructor accepts an IDispatch* argument. The second accepts an IUnknown* argument.

CComVariant(IDispatch* pSrc) ;
CComVariant(IUnknown* pSrc) ; 

Two constructors enable you to initialize a CComVariant instance from another VARIANT structure or CComVariant instance:

CComVariant(const VARIANT& varSrc) {                           
    vt = VT_EMPTY; InternalCopy (&varSrc);                     
}                                                              
CComVariant(const CComVariant& varSrc); { /* Same as above */ }

Both have identical implementations, which brings up a subtle side effect, so let's look at the InternalCopy helper function both constructors use:

void InternalCopy(const VARIANT* pSrc) {
    HRESULT hr = Copy(pSrc);            
    if (FAILED(hr)) {                   
        vt = VT_ERROR;                  
        scode = hr;                     
#ifndef _ATL_NO_VARIANT_THROW           
        AtlThrow(hr);                   
#endif                                  
    }                                   
}                                       

Notice how InternalCopy attempts to copy the specified VARIANT into the instance being constructed; when the copy fails, InternalCopy initializes the CComVariant instance as holding an error code (VT_ERROR). The Copy method used to attempt the copy returns the actual error code. This seems like an odd approach until you realize that a constructor cannot return an error, short of throwing an exception. ATL doesn't require support for exceptions, so this constructor must initialize the instance even when the Copy method fails. You can enable exceptions in this code via the _ATL_NO_VARIANT_THROW macro shown in the code.

I once had a CComVariant instance that always seemed to have a VT_ERROR code in it, even when I thought it shouldn't. As it turned out, I was constructing the CComVariant instance from an uninitialized VARIANT structure that resided on the stack. Watch out for code like this:

void func () {       // The following code is incorrect
  VARIANT v;         // Uninitialized stack garbage in vt member
  CComVariant sv(v); // Indeterminate state
}

Three constructors accept a string initialization value and produce a CComVariant instance that contains a BSTR. The first constructor accepts a CComBSTR argument and creates a CComVariant that contains a BSTR copied from the CComBSTR argument. The second accepts an LPCOLESTR argument and creates a CComVariant that contains a BSTR that is a copy of the specified string of OLECHAR characters. The third accepts an LPCSTR argument and creates a CComVariant that contains a BSTR that is a converted-to-OLECHAR copy of the specified ANSI character string. These three constructors also possibly can "fail." In all three constructors, when the constructor cannot allocate memory for the new BSTR, it initializes the CComVariant instance to VT_ERROR with SCODE E_OUTOFMEMORY. The constructors also throw exceptions if the project settings are set appropriately.

CComVariant(const CComBSTR& bstrSrc);    
CComVariant(LPCOLESTR lpszSrc);          
CComVariant(LPCSTR lpszSrc);             

Although both the first and second constructors logically accept the same fundamental data type (a string of OLECHAR), only the first constructor properly handles BSTRs that contain an embedded NULand only then if the CComBSTR object supplied has been properly constructed so that it has properly handled embedded NULs. The following example makes this clear:

LPCOLESTR osz = OLESTR("This is a\0BSTR string");
BSTR bstrIn = ::SysAllocStringLen(osz, 21) ;
// bstrIn contains "This is a\0BSTR string"

CComBSTR bstr(::SysStringLen(bstrIn), bstrIn);

CComVariant v1(bstr); // Correct  v1 contains
                      // "This is a\0BSTR string"
CComVariant v2(osz);  // Wrong!  v2 contains "This is a"

::SysFreeString (bstrIn) ;

Assignment

The CComVariant class defines an army of assignment operators33 in all. All the assignment operators do the following actions:

  • Clear the variant of its current contents

  • Set the vt member of the VARIANT structure to the value representing the type of the assignment operator argument

  • Store the value of the argument in the appropriate member of the union

CComVariant& operator=(char cSrc);           // VT_I1 
CComVariant& operator=(int nSrc);            // VT_I4 
CComVariant& operator=(unsigned int nSrc);   // VT_UI4
CComVariant& operator=(BYTE nSrc);           // VT_UI1
CComVariant& operator=(short nSrc);          // VT_I2 
CComVariant& operator=(unsigned short nSrc); // VT_UI2
CComVariant& operator=(long nSrc);           // VT_I4 
CComVariant& operator=(unsigned long nSrc);  // VT_UI4
                                                      
CComVariant& operator=(LONGLONG nSrc);       // VT_I8 
CComVariant& operator=(ULONGULONG nSrc);     // VT_UI8
CComVariant& operator=(float fltSrc);        // VT_R4 
CComVariant& operator=(double dblSrc);       // VT_R8 
CComVariant& operator=(CY cySrc);            // VT_CY 

Other overloads accept a pointer to all the previous types. Internally, these produce a vt value that is the bitwise OR of VT_BYREF and the vt member listed earlier. For example, operator=(long*) sets vt to VT_I4 | VT_BYREF.

The remaining operator= methods have additional semantics. Like the equivalent constructor, the assignment operator that accepts a bool initialization value sets the contents of the VARIANT to VARIANT_TRUE or VARIANT_FALSE, as appropriate, not to the bool value specified.

CComVariant& operator=(bool bSrc) ;

Two assignment operators accept an interface pointer and produce a CComVariant instance that contains an AddRef'ed copy of the interface pointer. One assignment operator accepts an IDispatch* argument. Another accepts an IUnknown* argument.

CComVariant& operator=(IDispatch* pSrc) ;
CComVariant& operator=(IUnknown* pSrc) ; 

Two assignment operators allow you to initialize a CComVariant instance from another VARIANT structure or CComVariant instance. They both use the InternalCopy method, described previously, to make a copy of the provided argument. Therefore, these assignments can "fail" and produce an instance initialized to the VT_ERROR type (and possibly an exception).

CComVariant& operator=(const CComVariant& varSrc);
CComVariant& operator=(const VARIANT& varSrc);    

One version of operator= accepts a SAFEARRAY as an argument:

CComVariant& operator=(const SAFEARRAY *pSrc);

This method uses the COM API function SafeArrayCopy to produce an independent duplicate of the SAFEARRAY passed in. It properly sets the vt member to be the bitwise OR of VT_ARRAY and the vt member of the elements of pSrc.

The remaining three assignment operators accept a string initialization value and produce a CComVariant instance that contains a BSTR. The first assignment operator accepts a CComBSTR argument and creates a CComVariant that contains a BSTR that is a copy of the specified CComBSTR's string data. The second accepts an LPCOLESTR argument and creates a CComVariant that contains a BSTR that is a copy of the specified string of OLECHAR characters. The third accepts an LPCSTR argument and creates a CComVariant that contains a BSTR that is a converted-to-OLECHAR copy of the specified ANSI character string.

CComVariant& operator=(const CComBSTR& bstrSrc);    
CComVariant& operator=(LPCOLESTR lpszSrc);          
CComVariant& operator=(LPCSTR lpszSrc);             

The previous remarks about the constructors with a string initialization value apply equally to the assignment operators with a string initialization value. In fact, the constructors actually use these assignment operators to perform their initialization.

One final option to set the value of a CComVariant is the SetByRef method:

template< typename T >                  
void SetByRef( T* pT ) {                
    Clear();                            
    vt = CVarTypeInfo< T >::VT|VT_BYREF;
    byref = pT;                         
}                                       

This method clears the current contents and then sets the vt field to the appropriate type with the addition of the VT_BYREF flag; this indicates that the variant contains a pointer to the actual data, not the data itself. The CVarTypeInfo class is another traits class. The generic version follows:

template< typename T >                                       
class CVarTypeInfo {                                         
// VARTYPE corresponding to type T                           
//    static const VARTYPE VT;                               
// Pointer-to-member of corresponding field in VARIANT struct
//    static T VARIANT::* const pmField;                     
};                                                           

The comments indicate what each template specialization does: provides the VT constant that gives the appropriate VARTYPE value to use for this type and the pmField pointer that indicates which field of a variant stores this type. These are two of the specializations:

template<>                                                  
class CVarTypeInfo< unsigned char > {                       
public:                                                     
    static const VARTYPE VT = VT_UI1;                       
    static unsigned char VARIANT::* const pmField;          
};                                                          
                                                            
__declspec( selectany ) unsigned char VARIANT::* const      
    CVarTypeInfo< unsigned char >::pmField = &VARIANT::bVal;
                                                            
template<>                                                  
class CVarTypeInfo< BSTR > {                                
public:                                                     
    static const VARTYPE VT = VT_BSTR;                      
    static BSTR VARIANT::* const pmField;                   
};                                                          
__declspec( selectany ) BSTR VARIANT::* const              
    CVarTypeInfo< BSTR >::pmField = &VARIANT::bstrVal;     

The initializers are necessary to set the pmField pointers appropriately. The pmField value is not currently used anywhere in ATL 8, but it could be useful for writing your own code that deals with VARIANTs.

CComVariant Operations

It's important to realize that a VARIANT is a resource that must be managed properly. Just as memory from the heap must be allocated and freed, a VARIANT must be initialized and cleared. Just as the ownership of a memory block must be explicitly managed so it's freed only once, the ownership of the contents of a VARIANT must be explicitly managed so it's cleared only once. Four methods give you control over any resources a CComVariant instance owns.

The Clear method releases any resources the instance contains by calling the VariantClear function. For an instance that contains, for example, a long or a similar scalar value, this method does nothing except set the variant type field to VT_EMPTY. However, for an instance that contains a BSTR, the method releases the string. For an instance that contains an interface pointer, the method releases the interface pointer. For an instance that contains a SAFEARRAY, this method iterates over each element in the array, releasing each element, and then releases the SAFEARRAY itself.

HRESULT Clear() { return ::VariantClear(this); }

When you no longer need the resource contained in a CComVariant instance, you should call the Clear method to release it. The destructor does this automatically for you. This is one of the major advantages of using CComVariant instead of a raw VARIANT: automatic cleanup at the end of a scope. So, if an instance will quickly go out of scope when you're finished using its resources, let the destructor take care of the cleanup. However, a CComVariant instance as a global or static variable doesn't leave scope for a potentially long time. The Clear method is useful in this case.

The Copy method makes a unique copy of the specified VARIANT. The Copy method produces a CComVariant instance that has a lifetime that is separate from the lifetime of the VARIANT that it copies.

HRESULT Copy(const VARIANT* pSrc)                                 
       { return ::VariantCopy(this, const_cast<VARIANT*>(pSrc)); }

Often, you'll use the Copy method to copy a VARIANT that you receive as an [in] parameter. The caller providing an [in] parameter is loaning the resource to you. When you want to hold the parameter longer than the scope of the method call, you must copy the VARIANT:

STDMETHODIMP SomeClass::put_Option (const VARIANT* pOption) {
    // Option saved in member m_Option of type CComVariant
    return m_varOption.Copy (pOption) ;
}

When you want to transfer ownership of the resources in a CComVariant instance from the instance to a VARIANT structure, use the Detach method. It clears the destination VARIANT structure, does a memcpy of the CComVariant instance into the specified VARIANT structure, and then sets the instance to VT_EMPTY. Note that this technique avoids extraneous memory allocations and AddRef/Release calls.

HRESULT Detach(VARIANT* pDest);

Do not use the Detach method to update an [out] VARIANT argument without special care! An [out] parameter is uninitialized on input to a method. The Detach method clears the specified VARIANT before overwriting it. Clearing a VARIANT filled with random bits produces random behavior.

STDMETHODIMP SomeClass::get_Option (VARIANT* pOption) {
    CComVariant varOption ;
    ... Initialize the variant with the output data

    // Wrong! The following code can generate an exception,
    // corrupt your heap, and give at least seven years bad luck!
    return varOption.Detach (pOption);
}

Before detaching into an [out] VARIANT argument, be sure to initialize the output argument:

// Special care taken to initialize [out] VARIANT
::VariantInit (pOption) ;
// or
pOption->vt = VT_EMPTY ;

return vOption.Detach (pOption); // Now we can Detach safely.

When you want to transfer ownership of the resources in a VARIANT structure from the structure to a CComVariant instance, use the Attach method. It clears the current instance, does a memcpy of the specified VARIANT into the current instance, and then sets the specified VARIANT to VT_EMPTY. Note that this technique avoids extraneous memory allocations and AddRef/Release calls.

HRESULT Attach(VARIANT* pSrc);

Client code can use the Attach method to assume ownership of a VARIANT that it receives as an [out] parameter. The function providing an [out] parameter transfers ownership of the resource to the caller.

STDMETHODIMP SomeClass::get_Option (VARIANT* pOption);

void VerboseGetOption () {
    VARIANT v;
    pObj->get_Option (&v) ;

    CComVariant cv;
    cv.Attach (&v);   // Destructor now releases the VARIANT
}

Somewhat more efficiently, but potentially more dangerously, you could code this differently:

void FragileGetOption() {
    CComVariant v;          // This is fragile code!!
    pObj->get_Option (&v) ; // Directly update the contained
                            // VARIANT. Destructor now releases
                            // the VARIANT.
}

Note that, in this case, the get_Option method overwrites the VARIANT structure contained in the CComVariant instance. Because the method expects an [out] parameter, the get_Option method does not release any resources contained in the provided argument. In the preceding example, the instance was freshly constructed, so it is empty when overwritten. The following code, however, causes a memory leak:

void LeakyGetOption() {
    CComVariant v (OLESTR ("This string leaks!")) ;
    pObj->get_Option (&v) ; // Directly updates the contained
                            // VARIANT. Destructor now releases
                            // the VARIANT.
}

When you use a CComVariant instance as an [out] parameter to a method that expects a VARIANT, you must first clear the instance if there is any possibility that the instance is not empty.

void NiceGetOption() {
    CComVariant v (OLESTR ("This string doesn't leak!")) ;
    ...
    v.Clear ();
    pObj->get_Option (&v) ; // Directly updates the contained
                            // VARIANT. Destructor now releases
                            // the VARIANT.

}

The ChangeType method converts a CComVariant instance to the new type specified by the vtNew parameter. When you specify a second argument, ChangeType uses it as the source for the conversion. Otherwise, ChangeType uses the CComVariant instance as the source for the conversion and performs the conversion in place.

HRESULT ChangeType(VARTYPE vtNew, const VARIANT* pSrc = NULL);

ChangeType converts between the fundamental types (including numeric-to-string and string-to-numeric coercions). ChangeType coerces a source that contains a reference to a type (that is, the VT_BYREF bit is set) to a value by retrieving the referenced value. ChangeType always coerces an object reference to a value by retrieving the object's Value property. This is the property with the DISPID_VALUE DISPID. The ChangeType method can be useful not only for COM programming, but also as a general data type conversion library.

ComVariant Comparison Operators

The operator==() method compares a CComVariant instance for equality with the specified VARIANT structure:

bool operator==(const VARIANT& varSrc) const ;
bool operator!=(const VARIANT& varSrc) const ;

When the two operands have differing types, the operator returns false. If they are of the same type, the implementation calls the VarCmp API function[1] to do the proper comparison based on the underlying type. The operator!=() method returns the negation of the operator==() method.

[1] A number of operating system functions manipulate VARIANTs. The functions VarAbs, VarAdd, VarAnd, VarCat, VarCmp, VarDiv, VarEqv, VarFix, VarIdiv, VarImp, VarInt, VarMod, VarMul, VarNeg, VarNot, VarOr, VarPow, VarRound, VarSub, and VarXor collectively comprise the Variant Math API.

Both the operator<() and operator>() methods perform their respective comparisons using the Variant Math API function VarCmp:

bool operator<(const VARIANT& varSrc) const ;
bool operator>(const VARIANT& varSrc) const ;

CComVariant Persistence Support

You can use the last three methods of the CComVariant class to read and write a VARIANT to and from a stream:

HRESULT WriteToStream(IStream* pStream); 
HRESULT ReadFromStream(IStream* pStream);
ULONG GetSize() const;                   

The WriteToStream method writes the vt type code to the stream. For simple types such as VT_I4, VT_R8, and similar scalar values, it writes the value of the VARIANT to the stream immediately following the type code. For an interface pointer, WriteToStream writes the GUID CLSID_NULL to the stream when the pointer is NULL. When the interface pointer is not NULL, WriteToStream queries the referenced object for its IPersistStream interface. If that fails, it queries for IPersistStreamInit. When the object supports one of these interfaces, WriteToStream calls the COM OleSaveToStream function to save the object to the stream. When the interface pointer is not NULL and the object does not support either the IPersistStream or IPersistStreamInit interfaces, WriteToStream fails.

For complex types, including VT_BSTR, all by-reference types, and all arrays, WriteToStream attempts to convert the value, if necessary, to a BSTR and writes the string to the stream using CComBSTR::WriteToStream.

The ReadFromStream method performs the inverse operation. First, it clears the current CComVariant instance. Then it reads the variant type code from the stream. For the simple types, such as VT_I4, VT_R8, and similar scalar values, it reads the value of the VARIANT from the stream. For an interface pointer, it calls the COM OleLoadFromStream function to read the object from the stream, requesting the IUnknown or IDispatch interface, as appropriate. When OleLoadFromStream returns REGDB_E_CLASSNOTREG (usually because of reading CLSID_NULL), ReadFromStream silently returns an S_OK status.

For all other types, including VT_BSTR, all by-reference types, and all arrays, ReadFromStream calls CComBSTR::ReadFromStream to read the previously written string from the stream. The method then coerces the string back to the original type.

GetSize provides an estimate of the amount of space that the variant will require in the stream. This is needed for implementing various methods on the persistence interfaces. The size estimate is exactly correct for simple types (int, long, double, and so on). For variants that contain interface pointers, CComVariant does a QueryInterface for the IPersistStream or IPersistStreamInit interfaces and uses the GetSizeMax( ) method to figure out the size. For other types, the variant is converted to a BSTR and the length of the string is used.

In general, the GetSize( ) method is used as an initial estimate for things such as buffer sizes. When the code inside ATL calls GetSize( ) (mainly in persistence support, discussed in Chapter 7, "Persistence in ATL") it correctly grows the buffers if the estimate is low. In your own use, be aware that sometimes the GetSize( ) method won't be exactly correct.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)