views:

555

answers:

1

I need to convert a CString instance into a properly allocated BSTR and pass that BSTR into a COM method. To have code that compiles and works indentically for both ANSI and Unicode I use CString::AllocSysString() to convert whatever format CString to a Unicode BSTR.

Since noone owns the returned BSTR I need to take care of it and release it after the call is done in the most exception-safe manner posible and with as little code as possible.

Currently I use ATL::CComBSTR for lifetime management:

 ATL::CComBSTR converted;
 converted.Attach( sourceString.AllocSysString() ); //simply attaches to BSTR, doesn't reallocate it
 interface->CallMethod( converted );

what I don't like here is that I need two separate statements to just construct the ATL::CComBSTR bound to the convertion result.

Is there a better way to accomplish the same task?

+2  A: 

CComBSTR has overloaded constructors for both char* and wchar_t*, which make the call to SysAllocString() on your behalf. So the explicit allocation in your code snippet is actually unnecessary. The following would work just as well:

ATL::CComBSTR converted = sourceString;
interface->CallMethod(converted);

Furthermore, if you have no need to use the converted BSTR elsewhere in your code, you can perform the object construction in-place in the method call, like so:

interface->CallMethod(ATL::CComBSTR(sourceString));

The same applies to the _bstr_t class, which can be used instead of CComBSTR if you don't want a dependency on the ATL.

Phil Booth
That will definitely work, except that with using CComBSTR constructor I will have to check that BSTR allocation was successful (CString::AllocSysString() does the check and throws an exception) and with _bstr_t I'll have to take care of _com_issue_error() function - either override it or catch _com_error thrown from it.
sharptooth
Why not let the called method be responsible for checking the validity of its own arguments instead? If the allocation fails, `CComBSTR::m_str` will be null. So either the called method will check for null and return `E_INVALIDARG`, or it won't and the same `catch` that you already have for `CString::AllocSysString()` can handle the exception for you. It is a cleaner idiom than explicitly performing the check yourself, imho.
Phil Booth
@Phil Booth: The method being called might interpret null BSTR as a special case. For example, the meaning could be "specifies a filename, if empty string is passed the default filename is used". So the method being called might have no chance to know that there's a problem.
sharptooth
It is a fundamental rule of COM that there is no semantic difference between a null BSTR and a zero-length BSTR. If some component that you call differentiates between the two, it is a bug. See this answer for more information: http://stackoverflow.com/questions/171641/should-there-be-a-difference-between-an-empty-bstr-and-a-null-bstr
Phil Booth
@Phil Booth: Yeap, I know. The problem is if I just call InvokeMethod( CComBSTR( sourceString ) ) the method being called will have no idea of why is the passed BSTR null - because it is there to indicate an empty string or because SysAllocString() called inside CComBSTR constructor faiced memory shortage.
sharptooth
Oh, my bad, I misunderstood you, sorry. You've got a good point.
Phil Booth