开发者

Using _bstr_t to pass parameter of type BSTR* in function

开发者 https://www.devze.com 2023-01-28 13:21 出处:网络
What is t开发者_开发知识库he correct way of doing this: _bstr_t description; errorInfo->GetDescription( &description.GetBSTR() );

What is t开发者_开发知识库he 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


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..
}


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..
}


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.


_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 it is expected that client frees the BSTR.

Using 'GetAddress()' is not equivalent to using '&GetBSTR()' in case the _bstr_t already owns a 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());

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


My answer is also late. Suppose you have the signature HRESULT PutDescription (BSTR NewDescription);. In that case do the following

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

By the rules of COM, the function PutDescription is not allowed to change or even destroy the passed BSTR.

For the opposite HRESULT GetDescription (BSTR *pActualDescription); pass a virgin _bstr_t by means of the function GetAddress():

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

The function GetAddress() frees any existing string and returns the address of a newly allocated string. So, if you pass a _bstr_t that has some content, this content will be freed and is therefore lost. The same happens to all _bstr_ts that share the same BSTR. But I think this is a stupid thing to do. Why passing an argument with content to a function that is supposed to change that content?

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

A real moron might even pass a _bstr_t that is assigned to a raw BSTR:

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

In that case GetDescr gets the expected value but the content of Bst is unpredictable.


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

精彩评论

暂无评论...
验证码 换一张
取 消