【问题标题】:Using _bstr_t to pass parameter of type BSTR* in function使用 _bstr_t 在函数中传递 BSTR* 类型的参数
【发布时间】:2011-05-19 19:27:11
【问题描述】:

这样做的正确方法是什么:

_bstr_t description;
errorInfo->GetDescription( &description.GetBSTR() );

或:

_bstr_t description;
errorInfo->GetDescription( description.GetAddress() );

其中IError:GetDescription定义为:

HRESULT GetDescription (BSTR *pbstrDescription);

我知道我可以轻松做到这一点:

BSTR description= SysAllocString (L"Whateva"));
errorInfo->GetDescription (&description);
SysFreeString (description);

谢谢

【问题讨论】:

    标签: c++ bstr


    【解决方案1】:

    BSTR 是引用计数的,我严重怀疑如果您使用 GetAddress() 是否能正常工作。可悲的是,源代码不可用于仔细检查。我一直都是这样的:

    BSTR temp = 0;
    HRESULT hr = p->GetDescription(&temp);
    if (SUCCEEDED(hr)) {
        _bstr_t wrap(temp, FALSE);
        // etc..
    }
    

    【讨论】:

    • +1,关于 _bstr_t 的 BSTR 被其他实例共享的说法让我对可能直接分配给它的任何事情都感到厌烦。
    • 你不应该使用Attach()而不是赋值运算符吗?
    • 如图所示,函数GetDescription 将内存分配给tempSysAllocString,并且该内存永远不会被释放。要么,您必须调用SysFreeString(temp),或者确保wrap 附加到该内存并释放它。
    • 没有帮助,如果您仍然将 ref-counted _bstr_t 与宏 SUCCEEDED 混合。使用_bstr_t的目的是RAII
    【解决方案2】:

    跟进@Hans 的回答 - 构造_bstr_t 的适当方法取决于GetDescription 是返回您拥有的BSTR,还是引用您不必释放的内存。

    这里的目标是尽量减少副本数量,同时避免对返回的数据进行任何手动调用SysFreeString。我会修改代码以澄清这一点:

    BSTR temp = 0;
    HRESULT hr = p->GetDescription(&temp);
    if (SUCCEEDED(hr)) {
        _bstr_t wrap(temp, false);    // do not copy returned BSTR, which
                                      // will be freed when wrap goes out of scope.
                                      // Use true if you want a copy.
        // etc..
    }
    

    【讨论】:

    • 使用_bstr_t的目的是RAII。不要再使用宏了。
    【解决方案3】:

    可能不适用于较早(或更高)版本的 Visual Studio 的较晚答案;然而, VS 12.0 具有内联的 _bstr_t 实现,显然,当在处女 _bstr_t 上调用 GetBSTR() 时,使用 1 的 m_RefCount 创建内部 Data_t 实例。因此,您的第一个示例中的 _bstr_t 生命周期看起来没问题:

    _bstr_t description;
    errorInfo->GetDescription( &description.GetBSTR() );
    

    但是如果_bstr_t 是脏的,现有的内部m_wstr 指针将被覆盖,泄漏它引用的先前内存。

    通过使用以下operator&,可以使用脏的_bstr_t,因为它首先通过Assign(nullptr) 清除。重载还提供了使用地址运算符而不是GetBSTR() 的便利;

    BSTR *operator&(_bstr_t &b) {
        b.Assign(nullptr);
        return &b.GetBSTR();
    }
    

    因此,您的第一个示例可能如下所示:

    _bstr_t description(L"naughty");
    errorInfo->GetDescription(&description);
    

    此评估基于 VS 12.0 中的 comutil.h

    【讨论】:

    • 非常感谢,在过去的二十年里,我一直想要这样的运算符。
    【解决方案4】:

    我的回答也晚了。假设您有签名HRESULT PutDescription (BSTR NewDescription);。在这种情况下,请执行以下操作

    _bstr_t NewAdvice = L"Great advice!";
    HRESULT hr1 = PutDescription(NewAdvice.GetBSTR());
    

    根据COM的规则,函数PutDescription是不允许改变甚至破坏传递的BSTR的。

    对于相反的HRESULT GetDescription (BSTR *pActualDescription);,通过函数GetAddress()传递一个处女_bstr_t

    _bstr_t GetAdvice;
    HRESULT hr2 = GetDescription(GetAdvice.GetAddress());
    

    函数GetAddress() 释放任何现有字符串并返回新分配的字符串的地址。所以,如果你传递一个有一些内容的_bstr_t,这个内容将被释放,因此会丢失。共享相同BSTR 的所有_bstr_ts 都会发生同样的情况。但我认为这是一件愚蠢的事情。为什么将带有内容的参数传递给应该更改该内容的函数?

    _bstr_t GetAdvice = L"This content will not survive the next function call!";
    HRESULT hr = GetDescription(GetAdvice.GetAddress());
    

    一个真正的白痴甚至可能传递一个分配给原始BSTR_bstr_t

    BSTR Bst = ::SysAllocString(L"Who would do that?");
    _bstr_t GetDescr;
    GetDescr.Attach(Bst);//GetDescr wraps Bst, no copying!
    HRESULT hr = GetDescription(GetDescr.GetAddress());
    

    在这种情况下,GetDescr 获得预期值,但 Bst 的内容是不可预测的。

    【讨论】:

    • 你举反例让事情变得更糟。
    【解决方案5】:
        int GetDataStr(_bstr_t & str) override {
        BSTR data = str.Detach();
        int res = m_connection->GetDataStr( &data );
        str.Attach(data);
        return res;
    }
    

    【讨论】:

      【解决方案6】:

      _bstr_t(及其 ATL 兄弟 CComBSTR)是 BSTR 的资源所有者。从代码中窥探,“GetAddress”似乎是专门为使用 BSTR 输出参数的用例而设计的,在这种情况下,预计客户端会释放 BSTR。

      在 _bstr_t 已经拥有 BSTR 的情况下,使用 'GetAddress()' 不等同于使用 '&GetBSTR()'。 MSDN 声明:“释放任何现有字符串并返回新分配的字符串的地址。”。

      _bstr_t bstrTemp;
      HRESULT hr = p->GetDescription(bstrTemp.GetAddress());
      

      警告:文档中没有说明“GetAddress”的这个特定用例;这是我通过查看源代码和使用其 ATL 对应部分 CComBSTR 的经验得出的结论。

      由于用户 'caoanan' 对这个解决方案提出质疑,我将源代码贴在这里:微软:

      inline BSTR* _bstr_t::GetAddress()
      {
          Attach(0);
          return &m_Data->GetWString();
      }
      
      inline wchar_t*& _bstr_t::Data_t::GetWString() throw()
      {
          return m_wstr;
      }
      
      inline void _bstr_t::Attach(BSTR s)
      {
          _Free();
      
          m_Data = new Data_t(s, FALSE);
          if (m_Data == NULL) {
              _com_issue_error(E_OUTOFMEMORY);
          }
      }
      
      inline _bstr_t::Data_t::Data_t(BSTR bstr, bool fCopy)
          : m_str(NULL), m_RefCount(1)
      {
          if (fCopy && bstr != NULL) {
              m_wstr = ::SysAllocStringByteLen(reinterpret_cast<char*>(bstr),
                                               ::SysStringByteLen(bstr));
      
              if (m_wstr == NULL) {
                  _com_issue_error(E_OUTOFMEMORY);
              }
          }
          else {
              m_wstr = bstr;
          }
      }
      

      【讨论】:

      • 内存泄漏,在将描述分配给这样的“新分配字符串的地址”之后。
      • @caoanan:你能举个例子吗?
      • 查看@steve-townsend stackoverflow.com/a/4347754/3353857 的答案,应该使用GetBSTR(),而不是GetAddress()
      • 否,因为使用 &GetBSTR 确实会导致内存泄漏。它返回被包装的 m_wstr 成员,这会被新的 BSTR 覆盖(不释放旧的)。相反,GetAddress 在返回地址之前首先释放 BSTR。
      • 不能同意。请参阅 MSDN 的 _bstr_t::Assign 示例,docs.microsoft.com/en-us/cpp/cpp/bstr-t-assign
      猜你喜欢
      • 1970-01-01
      • 2021-03-20
      • 1970-01-01
      • 2013-08-12
      • 1970-01-01
      • 2014-02-06
      • 2019-05-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多