Variant如何使用及相关步骤
VARIANT是一个结构而不是类,这是比较容易混淆的一个地方。所以,当我们要使用或者返回VARIANT类型,必须做以下步骤,不能假设客户端使用的是CComVariant类型。
- Before use, all VARIANTARGs must be initialized by VariantInit.
- For the types VT_UI1, VT_I2, VT_I4, VT_R4, VT_R8, VT_BOOL, VT_ERROR, VT_CY, VT_DECIMAL, and VT_DATE, data is stored within the VARIANT structure. Any pointers to the data become invalid when the type of the variant is changed.
- For VT_BYREF | any type, the memory pointed to by the variant is owned and freed by the caller of the function.
- For VT_BSTR, there is only one owner for the string. All strings in variants must be allocated with the SysAllocString function. When releasing or changing the type of a variant with the VT_BSTR type, SysFreeString is called on the contained string.
- For VT_ARRAY | any type, the rule is analogous to the rule for VT_BSTR. All arrays in variants must be allocated with SafeArrayCreate. When releasing or changing the type of a variant with the VT_ARRAY flag set, SafeArrayDestroy is called.
- For VT_DISPATCH and VT_UNKNOWN, the objects that are pointed to have reference counts that are incremented when they are placed in a variant. When releasing or changing the type of the variant, Release is called on the object that is pointed to.
对于CComVariant类型,安装完VC2008以后可以看到它的源代码(atlcomcli.h),非常有帮助。比如
CComVariant() throw()
{
::VariantInit(this); // 调用了初始化函数,就是上面的1)
}
~CComVariant() throw()
{
return ::VariantClear(this);
}
CComVariant& operator=(_In_ LPCSTR lpszSrc)
{
USES_CONVERSION_EX;
Clear();
vt = VT_BSTR;
// 使用了SysAllocString在系统堆上申请了一块内存。
bstrVal = ::SysAllocString(A2COLE_EX(lpszSrc, _ATL_SAFE_ALLOCA_DEF_THRESHOLD));
return *this;
}
HRESULT Detach(_Out_ VARIANT* pDest)
{
ATLASSERT(pDest != NULL);
if(pDest == NULL)
return E_POINTER;
// Clear out the variant
HRESULT hr = ::VariantClear(pDest);
if (!FAILED(hr))
{
// Copy the contents and remove control from CComVariant
Checked::memcpy_s(pDest, sizeof(VARIANT), this, sizeof(VARIANT));
vt = VT_EMPTY;
hr = S_OK;
}
return hr;
}
我在写代码的时候有个问题,就是对于CComVariant,VT_BSTR类型Detach以后会不会调用SysFreeString,把申请的内存释放掉,导致Detach出来的Variant指针出错呢?因为做了memcpy_s,拷贝VARIANT结构,因为结构里面对于VT_BSTR类型,存放的是我们申请出来的指针,对于VT_BSTR我们没有另外申请内存。
看代码就明白了结果,当成功以后,把自己类型设置为VT_EMPTY,这会使得析构的时候,不去调用SysFreeString,所以对于Detach的目标指针,内存不会有问题。但是根据代码,当我们Detach调用以后,就不能继续使用这个CComVariant变量,否则会有问题的。
附上一个函数,用于得到SafeArray。
HRESULT GetSafeArray(SAFEARRAY** pArray, vector<CString>* ptr)
{
if (!pArray || !ptr)
return E_POINTER;
unsigned int size = ptr->size();
CComSafeArrayBound saBound;
saBound.SetCount(size);
saBound.SetLowerBound(0);
CComSafeArray<BSTR>* psaBuffer = NULL;
try
{
const UINT uDimensions = 1;
psaBuffer = new CComSafeArray<BSTR>(&saBound, uDimensions);
}
catch (…)
{
return E_OUTOFMEMORY;
}
for (unsigned int i= 0; i< size ; i++)
{
CComBSTR bstr = (*ptr)[i];
psaBuffer->SetAt(i, bstr);
}
*pArray = psaBuffer->Detach();
delete psaBuffer;
psaBuffer = NULL;
return S_OK;
};
template<typename T>
HRESULT GetSafeArray(SAFEARRAY** pArray, vector<T>* ptr)
{
if (!pArray || !ptr)
return E_POINTER;
unsigned int size = ptr->size();
CComSafeArrayBound saBound;
saBound.SetCount(size);
saBound.SetLowerBound(0);
CComSafeArray<T>* psaBuffer = NULL;
try
{
const UINT uDimensions = 1;
psaBuffer = new CComSafeArray<T>(&saBound, uDimensions);
}
catch (…)
{
return E_OUTOFMEMORY;
}
for (unsigned int i= 0; i< size ; i++)
{
psaBuffer->SetAt(i, (*ptr)[i]);
}
*pArray = psaBuffer->Detach();
delete psaBuffer;
psaBuffer = NULL;
return S_OK;
};
Reference document:
http://msdn.microsoft.com/en-us/library/ms221673.aspx
对于COM中的BSTR,windows会为我们做一个致命的缓存,但是用户不知道,包括程序员不少也不知道,导致某些情况下出问题。
http://msdn.microsoft.com/en-us/library/ms221105.aspx
简单的说就是运行程序前,环境变量做如下设置OANOCACHE=1。