Shall we treat BSTR type in COM as value or reference?
Asked Answered
G

2

5

From book ATL Internals, I knew BSTR is different from OLECHAR*, and there are CComBSTR and CString for BSTR.

According MSDN Allocating and Releasing Memory for a BSTR, I knew memory management responsibility for caller/callee.

Take this line from MSDN,

HRESULT CMyWebBrowser::put_StatusText(BSTR bstr)

I still do not know how to handle bstr properly in my implementation. Since I still have a basic question for BSTR -- should we treat bstr as a value (like int) or as a reference (like int*), at least on COM interface boundary.

I want to convert BSTR as soon as possible to CString/CComBSTR in my implementation. Value or Reference semantic will be totally different case for the conversion. I've digged into CComBSTR.Attach, CComBSTR.AssignBSTR, etc. But the code cannot solve my doubts.

MSDN CComBSTR.Attach has some code snip, I feel it is wrong since it is not obey Allocating and Releasing Memory for a BSTR. ATL Internals said SetSysString will 'free the original BSTR passed in', if I used it, it will violate BSTR argument convention, just like CComBSTR.Attach.

All in all, I want to using CString to handle raw BSTR in implementation, but do not know the correct way...I've written some just work code in my projects, but I always feel nervous since I don't know whether I am correct.

Let me talk coding language

HRESULT CMyWebBrowser::put_StatusText(BSTR bstr)
{
// What I do NOT know
CString str1;  // 1. copy bstr (with embeded NUL)
CString str2;  // 2. ref bstr

// What I know
CComBSTR cbstr1;
cbstr1.AssignBSTR(bstr); // 3. copy bstr 
CComBSTR cbstr2;
cbstr2.Attach(bstr); // 4. ref bstr, do not copy

// What I do NOT know
// Should we copy or ref bstr ???
}
Gillard answered 15/3, 2013 at 20:4 Comment(0)
M
12

CComBSTR is just a RAII wrapper around raw BSTR. So feel free to use CComBSTR instead of raw BSTR to help writing code that is exception-safe, that makes it harder to leak resources (i.e. the raw BSTR), etc.

If the BSTR is an input parameter, it's just like a const wchar_t* (with length prefixed, and potentially some NULs L'\0' characters inside). If the BSTR doesn't have NULs embedded inside, you can simply pass it to a CString constructor, that will make a deep-copy of it, and you can locally work with your CString. Modifications to that CString won't be visible on the original BSTR. You can use std::wstring as well (and note that std::wstring can handle embedded NULs as well).

void DoSomething(BSTR bstrInput)
{
    std::wstring myString(bstrInput);
    // ... work with std::wstring (or CString...) inside here
}

Instead, if the BSTR is an output parameter, then it is passed using another level of indirection, i.e. BSTR*. In this case, you can use CComBSTR::Detach() inside your method to release the BSTR safely wrapped into the CComBSTR, and transfer its ownership to the caller:

HRESULT DoSomething( BSTR* pbstrOut )
{
    // Check parameter pointer
    if (pbstrOut == nullptr)
        return E_POINTER;

    // Guard code with try-catch, since exceptions can't cross COM module boundaries.
    try
    {
        std::wstring someString;
        // ... work with std::wstring (or CString...) inside here

        // Build a BSTR from the ordinary string     
        CComBSTR bstr(someString.c_str());

        // Return to caller ("move semantics", i.e. transfer ownership
        // from current CComBSTR to the caller)
        *pbstrOut = bstr.Detach();

        // All right
        return S_OK;
    }
    catch(const std::exception& e)
    {
        // Log exception message...
        return E_FAIL;
    }
    catch(const CAtlException& e)
    {
        return e; // implicit cast to HRESULT
    }
}

Basically, the idea is to use BSTR (wrapped in a RAII class like CComBSTR) only at the boundary, and do the local work using std::wstring or CString.

As a "bouns reading", consider Eric Lippert's guide to BSTR semantics.

Methodical answered 15/3, 2013 at 20:23 Comment(3)
My big question is Should we copy or ref bstr ? Small qeustion is how to ref bstr with CString ? how to copy bstr with embedded NULL?Gillard
What do you mean with copy or ref? If it's an input param pass BSTR by value, else pass "by pointer" BSTR*. If you have embedded NULs you can copy it in a std::wstring, not in a CString.Methodical
Maybe you are observing the problem from a wrong point of view... Think of a BSTR like a const wchar_t* allocated with a special COM allocator, and length-prefixed (so it can have embedded NULs).Methodical
C
4

Having BSTR on input, you are not responsible to release it. Converting to CString is easy:

CString sValue(bstr);

or, if you prefer to keep Unicode characters on MBCS build:

CStringW sValue(bstr);

If you need to convert back when you have [out] parameter, you do (simple version):

VOID Foo(/*[out]*/ BSTR* psValue)
{
  CString sValue;
  *psValue = CComBSTR(sValue).Detach();
}

Full version is:

STDMETHODIMP Foo(/*[out]*/ BSTR* psValue)
{
    _ATLTRY
    {
        ATLENSURE_THROW(psValue, E_POINTER); // Parameter validation
        *psValue = NULL; // We're responsible to initialize this no matter what
        CString sValue;
        // Doing our stuff to get the string value into variable
        *psValue = CComBSTR(sValue).Detach();
    }
    _ATLCATCH(Exception)
    {
        return Exception;
    }
    return S_OK;
}
Continuance answered 15/3, 2013 at 20:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.