使用 _bstr_t 将 BSTR* 类型的参数传递给函数

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);

感谢

6个回答

9

BSTR是引用计数的,如果使用GetAddress(),我非常怀疑它是否能正常工作。遗憾的是源代码不可用,无法进行双重检查。我一直像这样做:

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

+1,_bstr_t的BSTR被其他实例共享的讨论使我对任何可能直接分配给它的东西感到不安。 - Leo Davidson
1
你应该使用 Attach() 而不是赋值运算符吗? - Afriza N. Arief
如下所示,函数 GetDescription 使用 SysAllocStringtemp 分配内存,但是该内存从未被释放。你必须调用 SysFreeString(temp) 或者确保 wrap 附加到该内存并释放它。 - abelenky
如果您仍然在使用宏SUCCEEDED与混合的引用计数_bstr_t,则无法提供帮助。使用_bstr_t的目的是RAII。 - caoanan

4

这是一个可能不能适用于早期(或晚期)版本的Visual Studio的晚回答;然而,VS 12.0内部有 Data_t 实例和 _bstr_t 实现内联,并且当在原始的 _bstr_t 上调用 GetBSTR() 时会创建一个 m_RefCount为1的内部实例。因此,在您第一个示例中,_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文件。


1
非常感谢这个,我已经想要像这样的操作符大约二十年了。 - Phil Jollans

4

跟进 @Hans 的回答 - 构建 _bstr_t 的适当方式取决于 GetDescription 是否返回你拥有的 BSTR,或者是引用了无需释放内存的 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。不再使用宏。 - caoanan

2
_bstr_t(以及其ATL兄弟CComBSTR)是BSTR的资源所有者。从代码中观察,'GetAddress'似乎是专门为与BSTR输出参数一起使用的情况设计的,客户端需要接管所有权并且需要释放BSTR。MSDN说明:“释放任何现有字符串并返回新分配字符串的地址。”。
_bstr_t bstrTemp;
HRESULT hr = p->GetDescription(bstrTemp.GetAddress());

请注意,如果 _bstr_t 已经拥有一个 BSTR,则使用 'GetAddress()' 与使用 '&GetBSTR()' 不等效。'GetBSTR' 只返回存储位置而不释放现有的 BSTR。

注意:这个特定用例的 'GetAddress' 没有在文档中说明;这是我从查看源代码和使用 ATL 的 CComBSTR 的经验中推断出来的。

由于用户 'caoanan' 对此解决方案提出了质疑,我在这里粘贴了 Microsoft 的源代码:

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:你能否提供一个例子? - gast128
参考@steve-townsend的答案:https://dev59.com/zm855IYBdhLWcg3wbztl#4347754,在这种情况下应该使用GetBSTR()而不是GetAddress()。 - caoanan
不要使用&GetBSTR,因为它会导致内存泄漏。它返回封装的m_wstr成员,而这个成员会被新的BSTR覆盖(没有释放旧的)。相比之下,GetAddress在返回地址之前先释放了BSTR。 - gast128
无法同意。请参考MSDN上_bstr_t::Assign的示例,https://learn.microsoft.com/en-us/cpp/cpp/bstr-t-assign - caoanan
@caoanan:我知道MSDN上的那个例子。对我来说,它并没有反驳我的观点和微软的声明,即在返回地址之前释放嵌入式BSTR。我已经粘贴了微软的代码,所以你可以自己看一下。请指出我在源代码中错误的位置,我会撤回这篇文章。 - gast128
显示剩余2条评论

-1
    int GetDataStr(_bstr_t & str) override {
    BSTR data = str.Detach();
    int res = m_connection->GetDataStr( &data );
    str.Attach(data);
    return res;
}

-1
感谢bvj和gast128的答案,很明显你可以通过GetAddress()将一个空的bstr_t传递给带有签名GetDescription(BSTR* BS)的函数,该函数将会创建一个新的BSTR。但是,你绝对不能将包含值的bstr_t以这种方式传递给一个旨在使用封装的BSTR的函数,因为GetAddress()会释放封装的BSTR。我提供一个完整的示例。
#include <iostream>
#include <comdef.h>

void CreateBSTR(BSTR* bstr)
{
  *bstr = SysAllocString(L"Read the manual!");
}

void UseBSTR(BSTR* bstr)
{
  std::wcout << L"Content: " << *bstr << std::endl;
}

int main()
{
  bstr_t bs_t;
  CreateBSTR(bs_t.GetAddress());
  std::wcout << L"Description: " << bs_t << std::endl;

  BSTR BS = bs_t.Detach();
  UseBSTR(&BS);
  bs_t.Attach(BS);
  //Don't: UseBSTR(bs_t.GetAddress());
}

你通过提供反例子,使情况变得更糟。 - caoanan
你正在将它变成一个更复杂的情况。 - caoanan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接