4

In the snippet bellow (simplified scenario of a loop)

_bstr_t original(OLESTR("MyString")); // ref-count = 1
_bstr_t another;
another = original; // ref-count = 2
// do something with another
another.Assign(NULL); // expected: ref-count = 1, and another = NULL
// reset another to NULL before doing other operations

What I expect after another.Assign(NULL) is:

  • SysFreeString() is NOT called
  • another is set to NULL
  • ref-count is decremented to 1
  • original has ref count=1 with existing BSTR content.

What happened:

  • SysFreeString() is called for underlying BSTR of both another and original
  • another is set to NULL
  • ref-count of original remains 2

another.Assign(NULL) seems to deallocate the underlying BSTR for both original and another.
We had unexpected crash because during coding I thought _bstr_t::Assign() will decrement the ref count instead of straight away deallocating the BSTR.

How do I properly reset another to NULL without affecting original?

Please find bellow implementation of Assign from VC++ 6.

// assignment operator copies internal data and increases reference count
inline _bstr_t& _bstr_t::operator=(const _bstr_t& s) throw()
{
    const_cast<_bstr_t*>(&s)->_AddRef();
    _Free();
    m_Data = s.m_Data;

    return *this;
}
// but _bstr_t::Assign() calls _bstr_t::Data_t::Assign() 
// without touching ref count
inline void _bstr_t::Assign(BSTR s) throw(_com_error)
{
    if (m_Data != NULL) {
        m_Data->Assign(s); 
    } 
    else {
        m_Data = new Data_t(s, TRUE);
        if (m_Data == NULL) {
            _com_issue_error(E_OUTOFMEMORY);
        }
    }
}
// it calls _bstr_t::Data_t::_Free() instead of _bstr_t::_Free() !
inline void _bstr_t::Data_t::Assign(BSTR s) throw(_com_error)
{
    _Free();
    if (s != NULL) {
        m_wstr = ::SysAllocStringByteLen(reinterpret_cast<char*>(s), 
                                            ::SysStringByteLen(s));
    }
}
// this _Free() straight away deallocates the BSTR!
inline void _bstr_t::Data_t::_Free() throw()
{
    if (m_wstr != NULL) {
        ::SysFreeString(m_wstr);
    }

    if (m_str != NULL) {
        delete [] m_str;
    }
}
// properly decrements ref count
inline void _bstr_t::_Free() throw()
{
    if (m_Data != NULL) {
        m_Data->Release();
        m_Data = NULL;
    }
}
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74
  • +1 Good question. I've googled it and found _bstr_t::copy might help. I've never used it, so there is no guarantee. I want to know the answer, too. – lulyon Jul 23 '13 at 08:16
  • I think you misspelt `std::string` – Mike Vine Jul 23 '13 at 08:43
  • Can you describe what it is you want to achieve in a bit more detail - it is not clear what you expect `copy.assign(NULL)` to actually do. – Mats Petersson Jul 23 '13 at 09:13
  • @lulyon I can use `_bstr_t::copy` like `another = _bstr_t(original.copy(), false)` or even `another = (LPCWSTR)original` but is there other way without changing the assignment? – Afriza N. Arief Jul 23 '13 at 10:15
  • 1
    For what it's worth, this problem does **not** exist in VC2008 and newer; `Assign(NULL)` does exactly what you expect. Looks like a bug in VC6. Note that VC6 is ancient, long out of support. – Igor Tandetnik Jul 24 '13 at 18:51

1 Answers1

0

The implementation of _bstr_t::Assign() has been updated as mentioned by Igor Tandetnik in his comment.

Here's the implementation in VS2010 and it works as expected:

inline void _bstr_t::Assign(BSTR s) 
{
    _COM_ASSERT(s == NULL || m_Data == NULL || m_Data->GetWString() != s);

    if (s == NULL || m_Data == NULL || m_Data->GetWString() != s)
    {
        _Free();

        m_Data = new Data_t(s, TRUE);
        if (m_Data == NULL) {
            _com_issue_error(E_OUTOFMEMORY);
        }
    }
}
Community
  • 1
  • 1
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74