Pipe BSTR into wstringstream?

138 views Asked by At

There's a tried and tested answer here how to convert BSTR -> std::wstring. But I want to include the BSTR in a wstringstream I am building up.

Clearly I can do (a bit contrived as a MRE):

std::wstring proc(BSTR bStr)
{
 std::wstringstream ss;
 std::wstring ws(bStr, SysStringLen(bStr));
 
 ss << "The string is: " << ws; 
 return ss.str();
}

But is there a way to avoid creating the temporary ws object and pipe bStr in directly?

3

There are 3 answers

1
Remy Lebeau On

BSTR is just a typedef for wchar_t* as far as the compiler is concerned (it is just allocated differently at runtime than a normal wchar_t C string).

And by definition, a non-empty BSTR is always null-terminated (even though it is also length-prefixed).

So, unless your string has embedded nul characters in it (which is possible), you can just use the normal operator<< for wide strings. Just watch out for the case where the BSTR is a null pointer, which will exhibit undefined behavior for the operator.

Try this:

std::wstring proc(BSTR bStr)
{
 std::wstringstream ss; 
 ss << L"The string is: ";
 if (bStr) ss << bStr; 
 return ss.str();
}
0
IInspectable On

[I]s there a way to avoid creating the temporary ws object and pipe bStr in directly?

Yes, of course!

A BSTR is a wchar_t* in disguise that upholds all requirements to make basic_ostream::operator<< happy: It is guaranteed to be either a nullptr or a pointer to a sequence of CharT values, terminated by a NUL character.

Writing

    ss << "The string is: " << bstr;

is perfectly valid.

Albeit, doing so ignores acknowledging two edge cases where a BSTR is more lenient than a conventional C-style string:

  • It can contain NUL characters as part of the sequence, leading to truncation
  • It could be a nullptr producing an implementation-defined string in the output stream

Neither is desirable, and either issue is addressed by introducing the std::wstring ws as an intermediary. This isn't something we can give up. With the intermediary a non-negotiable requirement, the question is: Can we get std::wstring's services without paying the price of a (needless) allocation?

As of C++17, we can. The tool is called std::wstring_view, capable of producing "modern" dangling pointers as well as solving the issue at hand:

    ss << "The string is: " << std::wstring_view(bstr, ::SysStringLen(bStr));

This brings back the std::wstring niceties without a price tag.

6
Larry On

I'll post this as an answer even though it should be a comment (to your own post and the other responses), and it doesn't actually address your question (others correctly have), but just an (off-topic) FYI that a BSTR is not technically a "wchar_t" pointer. It's actually an OLECHAR pointer but since OLECHAR maps to "wchar_t" on modern Windows you can effectively treat it that way (and likely get away with it forever at this point, at least on Windows).

It's therefore a pedantic (really moot) issue at this point (for a very long time now), but to be technically correct (and old-school developers in this stuff often will be), you should be explicitly converting your BSTR to a "wchar_t" pointer (or even TCHAR pointer on Windows) before treating it that way (as a "wchar_t" pointer or whatever TCHAR is for those still using it, normally "wchar_t" as well).

How you do this is off-topic here (ancient MFC has ancient macros for it as one possible way - search for "OLE conversion macros" here), but again, in reality you can safely (usually) get away with just treating it as a "wchar_t" pointer on (modern) Windows as you're now doing (since going through the hoops of explicitly converting it to a "wchar_t" pointer on modern Windows will just treat a BSTR as a "wchar_t" pointer anyway - no conversion necessary and the above MFC macros handle things this way).

If you ever need to target an environment where OLECHAR doesn't map to "wchar_t" however, religiously doing the conversion now will correctly handle things in the future (that's what the conversion is for), compared to how you're now handling it (though most do treat a BSTR as a "wchar_t" pointer these days, though in the context that it's still a BSTR so needs to be treated that way, and even Microsoft itself treats it as a "wchar_t" pointer in some of its own sample code and documentation, but it's still an OLECHAR pointer nevertheless so such code will break if you ever target an environment where OLECHAR doesn't map to "wchar_t").