具有VARIANT成员的VC ++类对象具有奇怪的行为

时间:2016-10-20 01:13:02

标签: c++ windows class visual-c++ variant

我有一个定义如下的课程:

class CVariable  
{
   public:
     CVariable(CString strData, int nNum);
     CVariable(BSTR bsData);
     ~CVariable();
   public:
     VARIANT GetVariant(){return m_bsVa;};
   private:
     VARIANT m_bsVa;
   VARIANT m_nVa;
};

工具是:

CVariable::CVariable(CString strData, int nNum)
{
  VariantInit(&m_bsVa);
  BSTR bsData = ::SysAllocString(strData);
  m_bsVa.vt = VT_BSTR;
  m_bsVa.bstrVal = bsData;
  ::SysFreeString(bsData);

  VariantInit(&m_nVa);
  m_nVa.vt = VT_I2;
  m_nVa.lVal = nNum;
}

CVariable::CVariable(BSTR bsData) {
  m_bsVa.vt = VT_BSTR;
  m_bsVa.bstrVal = bsData;
}

CVariable::~CVariable()
{
  VariantClear(&m_bsVa);
  VariantClear(&m_nVa);
}

当我尝试使用构造函数CVariable(CString,int)构造两个实例时, 类成员 m_bsVa 总是具有相同的值,而 m_nVa 是不同的。结果如下: enter image description here

如您所见, v1 v2 具有相同的m_bsVa但m_nVa不同,而使用构造函数CVariable(BSTR)会导致正确的结果。我不知道为什么会发生这种情况? 任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:1)

我发现您的代码存在一些问题。

  1. CVariable(CString, int)构造函数为BSTR分配m_bsVa,但随后立即释放BSTR,让m_bsVa指向无效内存,允许下一个CVariable实例可能为其分配的BSTR重用相同的内存地址。在完成BSTR之前,您需要保留已分配的m_bsVa(或者至少在您想为其分配新值之前)。 VariantClear()将为您释放BSTR

  2. CVariable(BSTR)构造函数根本没有初始化m_nVa,这将导致后续操作出现问题,包括VariantClear()。此外,构造函数取得了调用者BSTR的所有权。根据您使用此构造函数的方式,这可能会也可能不会。如果来电者不希望您取得所有权,则需要使用BSTR制作SysAllocString/Len()的副本。

  3. VARIANT并非易于复制。您需要使用VariantCopy()函数将数据从一个VARIANT复制到另一个CVariable。这意味着您的GetVariant()类需要实现复制构造函数和复制赋值运算符。无论如何你需要做什么才能使你的班级符合Rule of Three

  4. m_bsVa按原样返回m_bsVa,因此编译器只会将VARIANT的字段的值原样复制到调用者的接收{{1}中}。由于BSTR是指针,因此调用者将直接访问到您班级内的原始BSTR。根据您使用GetVariant()的方式,这可能会也可能不会。在当前实现中,对返回的BSTR的任何访问都应被视为只读 - 调用者不得在其上调用SysFreeString(),并且必须预期对其进行任何更改。 CVariable对象可能使BSTR无效。如果这不符合您的需求,那么GetVariant()应该返回通过VARIANT复制了数据的新VariantCopy(),然后调用者可以在完成后使用VariantClear()调用VARIANT已退回class CVariable { public: CVariable(const CString &strData, int nNum); CVariable(BSTR bsData); CVariable(const CVariable &src); ~CVariable(); VARIANT GetVariant() const; CVariable& operator=(const CVariable &src); CVariable& operator=(BSTR src); private: VARIANT m_bsVa; VARIANT m_nVa; };

  5. 话虽如此,尝试更像这样的事情:

    CVariable::CVariable(const CString &strData, int nNum)
    {
        ::VariantInit(&m_bsVa);
        m_bsVa.vt = VT_BSTR;
        m_bsVa.bstrVal = ::SysAllocString(strData);
    
        ::VariantInit(&m_nVa);
        m_nVa.vt = VT_I2;
        m_nVa.lVal = nNum;
    }
    
    CVariable::CVariable(BSTR bsData)
    {
        ::VariantInit(&m_bsVa);
        m_bsVa.vt = VT_BSTR;
        m_bsVa.bstrVal = bsData;
        /* or this, if needed:
        m_bsVa.bstrVal = ::SysAllocStringLen(bsData, ::SysStringLen(bsData));
        */
    
        ::VariantInit(&m_nVa);
    }
    
    CVariable::~CVariable()
    {
        ::VariantClear(&m_bsVa);
        ::VariantClear(&m_nVa);
    }
    
    VARIANT CVariable::GetVariant() const
    {
        return m_bsVa;
        /* or this, if needed:
        VARIANT result;
        ::VariantInit(&result);
        ::VariantCopy(&result, &m_bsVa);
        return result;
        */
    }
    
    CVariable& CVariable::operator=(const CVariable &src)
    {
        if (&src != this)
        {
            ::VariantClear(&m_bsVa);
            ::VariantCopy(&m_bsVa, &src.m_bsVa);
    
            ::VariantClear(&m_nVa);
            ::VariantCopy(&m_nVa, &src.m_nVa);
        }
    
        return *this;
    }
    
    CVariable& CVariable::operator=(BSTR src)
    {
        ::VariantClear(&m_bsVa);
        m_bsVa.vt = VT_BSTR;
        m_bsVa.bstrVal = src;
        /* or this, if needed:
        m_bsVa.bstrVal = ::SysAllocStringLen(src, ::SysStringLen(src));
        */
    
        ::VariantClear(&m_nVa);
    
        return *this;
    }
    

    VARIANT

    如果直接使用variant_t类而不是class CVariable { public: CVariable(const CString &strData, int nNum); CVariable(BSTR bsData); variant_t GetVariant() const; private: variant_t m_bsVa; variant_t m_nVa; }; ,则可以大大简化代码,同时仍然可以解决上述所有问题:

    CVariable::CVariable(const CString &strData, int nNum)
        : m_bsVa(strData), m_nVa(nNum)
    {
    }
    
    CVariable::CVariable(BSTR bsData)
        : m_bsVa(bsData)
    {
    }
    
    variant_t CVariable::GetVariant() const
    {
        return m_bsVa;
    }
    

    ProcessBuilder

答案 1 :(得分:0)

在这个构造函数中:

CVariable::CVariable(BSTR bsData) {
  m_bsVa.vt = VT_BSTR;
  m_bsVa.bstrVal = bsData;
}

您要离开m_nVa未初始化 - 它会获得一些随机值。它应该是这样的:

CVariable::CVariable(BSTR bsData) {
  VariantInit(&m_bsVa);
  m_bsVa.vt = VT_BSTR;
  m_bsVa.bstrVal = bsData;

  VariantInit(&m_nVa);
}

在这个构造函数中:

CVariable::CVariable(CString strData, int nNum)
{
  VariantInit(&m_bsVa);
  BSTR bsData = ::SysAllocString(strData);
  m_bsVa.vt = VT_BSTR;
  m_bsVa.bstrVal = bsData;
  ::SysFreeString(bsData);

  VariantInit(&m_nVa);
  m_nVa.vt = VT_I2;
  m_nVa.lVal = nNum;
}

请勿致电::SysFreeString(bsData);,因为bsDatam_bsVa所有。 SysFreeString()释放内存,下一个SysAllocString()调用可能会在同一内存地址创建一个新的BSTR字符串。

我建议您改用_variant_t class,而不是使用 VARIANT。在这种情况下,您根本不需要担心VariantInit() / VariantClear(),因为它为您实现了C ++风格的所有权策略。

答案 2 :(得分:0)

我建议你在原始C VARIANT周围使用一个方便的C ++ RAII包装器,比如来自ATL的CComVariant。

这将简化您的代码,因为CComVariant将正确初始化其包装的原始VARIANT,并将其清理。

您可以使用更安全的CComVariant包装替换VARIANT数据成员:

CComVariant m_bsVa;
CComVariant m_nVa;

然后你可以在这样的构造函数中初始化它们:

CVariable::CVariable(const CString& strData, int nNum)
    : m_bsVa(strData), m_nVa(nNum)
{}

CVariable::CVariable(BSTR bsData)
    : m_bsVa(bsData)
{}

请注意,您不需要显式定义析构函数,因为在这种情况下,CComVariant的析构函数将正确清理数据成员。

你的getter可以像这样实现:

const CComVariant& CVariable::GetVariant() const
{
    return m_bsVa;
}