VARIANT研究

Variant如何使用及相关步骤

VARIANT是一个结构而不是类,这是比较容易混淆的一个地方。所以,当我们要使用或者返回VARIANT类型,必须做以下步骤,不能假设客户端使用的是CComVariant类型。

  1. Before use, all VARIANTARGs must be initialized by VariantInit.
  2. 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.
  3. For VT_BYREF | any type, the memory pointed to by the variant is owned and freed by the caller of the function.
  4. 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.
  5. 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.
  6. 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。

发表评论