Using _bstr_t to pass parameter of type BSTR* in function
Asked Answered
F

6

11

What is the correct way of doing this:

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

or:

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

Where IError:GetDescription is defined as:

HRESULT GetDescription (BSTR *pbstrDescription);

I know I could easily do this:

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

Thanks

Flew answered 3/12, 2010 at 15:6 Comment(0)
W
9

The BSTR is reference counted, I seriously doubt that will work right if you use GetAddress(). Sadly the source code isn't available to double-check that. I've always done it like this:

BSTR temp = 0;
HRESULT hr = p->GetDescription(&temp);
if (SUCCEEDED(hr)) {
    _bstr_t wrap(temp, FALSE);
    // etc..
}
Welch answered 3/12, 2010 at 15:39 Comment(4)
+1, the talk of the _bstr_t's BSTR being shared by other instances put me off anything that might assign directly to it.Stuyvesant
shouldn't you use Attach() instead of assignment operator?Isbella
As shown, the function GetDescription allocates memory to temp with SysAllocString, and that memory is never freed. Either, you must call SysFreeString(temp), or make sure that wrap attaches to that memory and frees it.Cyanohydrin
not helping, if you mixing ref-counted _bstr_t still with the macro SUCCEEDED. The aim of using _bstr_t is RAIIRockrose
C
4

To follow up on @Hans's answer - the appropriate way to construct the _bstr_t depends on whether GetDescription returns you a BSTR that you own, or one that references memory you don't have to free.

The goal here is to minimize the number of copies, but also avoid any manual calls to SysFreeString on the returned data. I would modify the code as shown to clarify this:

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..
}
Clemens answered 3/12, 2010 at 16:41 Comment(1)
The aim of using _bstr_t is RAII. Don't use the macro anymore.Rockrose
T
4

A late answer that may not apply to earlier (or later) versions of Visual Studio; however, VS 12.0 has the _bstr_t implementation inline, and evidently an internal Data_t instance is created with a m_RefCount of 1 when calling GetBSTR() on a virgin _bstr_t. So the _bstr_t lifecycle in your first example looks to be okay:

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

But if _bstr_t is dirty, the existing internal m_wstr pointer will be overwritten, leaking the previous memory it referenced.

By using the following operator&, a dirty _bstr_t can be used given that it's first cleared via Assign(nullptr). The overload also provides the convenience of utilizing the address operator instead of GetBSTR();

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

So, your first example could instead look like the following:

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

This evaluation was based on comutil.h from VS 12.0.

Tearing answered 19/7, 2016 at 4:38 Comment(1)
Thanks very much for this, I have wanted something like this operator for about the last twenty years.Threaten
B
3

_bstr_t (and its ATL sibling CComBSTR) are resource owners of BSTR. Spying from the code it seems that 'GetAddress' is specifically designed for the use case of working with BSTR output parameters where client takes ownership and expected to free the BSTR. MSDN states: 'Frees any existing string and returns the address of a newly allocated string.'.

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

Note that using 'GetAddress()' is not equivalent to using '&GetBSTR()' in case the _bstr_t already owns a BSTR. 'GetBSTR'' only returns the store location without freeing an existing one.

Caveat: this specific use case of 'GetAddress' is not stated in the documentation; it is my deduction from looking at the source code and experience with its ATL counter part CComBSTR.

Since user 'caoanan' questioned this solution, I paste here the source code 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;
    }
}
Boaz answered 8/3, 2021 at 12:8 Comment(7)
@caoanan: could you please post an example?Boaz
see the answer from @steve-townsend https://mcmap.net/q/983537/-using-_bstr_t-to-pass-parameter-of-type-bstr-in-function, should use GetBSTR(), not GetAddress()Rockrose
No because using &GetBSTR does lead to a memory leak. It returns the wrapped m_wstr member and this gets overwritten by the newly BSTR (without releasing the old one). In contrast GetAddress first frees the BSTR before returning the address.Boaz
can't agree. see MSDN's example of _bstr_t::Assign, learn.microsoft.com/en-us/cpp/cpp/bstr-t-assignRockrose
@caoanan: I know that example from MSDN. To me it doesn't counter argument my and Microsoft's statement that it releases the embedded BSTR before returning the address. I have pasted Microsoft's code so you can have a look at yourself. Please point the location in the source where I am wrong and I will retract this contribution.Boaz
I like your latest edit of the answer. "Using 'GetAddress()' is not equivalent to using '&GetBSTR()' in case the _bstr_t already owns a BSTR."Rockrose
OP's question was all about virgin _bstr_t, you guys make it grow. To avoid confusion, it's better to clarify the "casese", answer OP's question first, then extend the discussion.Rockrose
T
-1

Thanks to the answers by bvj and gast128, it is clear that you can pass an empty bstr_t via GetAddress() to a function with the signature GetDescription(BSTR* BS), which will create a new BSTR, but you must never pass a bstr_t containing a value in this way to a function intended to use the wrapped BSTR, since GetAddress() will free the wrapped BSTR. I present a complete example.

#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());
}
Thatch answered 12/3, 2020 at 9:55 Comment(3)
you're making it worse by giving anti-examples.Rockrose
see OP's original question. You're growing it into a more complicated case.Rockrose
When using the _bstr_t value, if you need a BSTR* pointer then use GetBSTR() instead of GetAddress(), that way you don't have to worry about the BSTR being destroyed prematurely: UseBSTR(&bs_t.GetBSTR());Abraham
B
-1
    int GetDataStr(_bstr_t & str) override {
    BSTR data = str.Detach();
    int res = m_connection->GetDataStr( &data );
    str.Attach(data);
    return res;
}
Buckskin answered 30/11, 2021 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.