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 to
do the proper comparison based on the underlying type. The
operator!=() method returns the negation of the
operator==() method.
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.
|