The need to marshal between native and managed types is a very frequent scenario in mixed-mode programming. This is specially true when it comes to strings – when you’ve got MFC strings, COM strings, standard C++ strings and CLR strings and need to convert between those types. In fact that’s what prompted me to write the StringConvertor class for managed-unmanaged string conversions. While I did realize that I had misspelled converter as convertor I decided to leave it like that so I got a unique class-name and that way I could avoid Google dilution (there are dozens of other StringConverter classes, specially Java based ones).
Anyway, in the Orcas release of Visual C++, the VC++ team have added a mixed-mode marshalling library which primarily consists of the
marshal_as template function. While
marshal_as is technically not limited to string conversions, that is its most important role as of now. Using it is very similar to using one of the C++ cast operators such as
static_cast – though be aware that you are not really casting here, you are doing a conversion – which may or may not be a different thing from a mere cast.
Here’s a code snippet that shows how to convert various native strings to
String^ clrString; const char* pcszHello = "hello world"; clrString = marshal_as<String^>(pcszHello); wchar_t* pwszHello = L"hello wide world"; clrString = marshal_as<String^>(pwszHello); bstr_t bstrtHello("hello bstr_t world"); clrString = marshal_as<String^>(bstrtHello); std::string stdHello = "hello from std::string"; clrString = marshal_as<String^>(stdHello); CString mfcString("hello from CString"); clrString = marshal_as<String^>(mfcString); CComBSTR atrBSTR(L"hello from CComBSTR"); clrString = marshal_as<String^>(atrBSTR);
The reverse conversion is also similar.
String^ clrString = "Original System::String"; std::string stdHello = marshal_as<std::string>(clrString); CString mfcString = marshal_as<CString>(clrString); CComBSTR atrBSTR = marshal_as<CComBSTR>(clrString);
You cannot directly use
marshal_as for converting a
String^ to a
const char*, a
const wchar_t* or a
BSTR – because those conversions require the unmanaged resources to be freed after use. For those, you need to use a context object as shown below.
marshal_context context; const char* pcszHello = context.marshal_as<const char*>(clrString); const wchar_t* pcwszHello = context.marshal_as<const wchar_t*>(clrString); BSTR bstrString = context.marshal_as<BSTR>(clrString); Console::WriteLine(context._clean_up_list.Count);
The context object keeps track of the allocated objects and frees them in its destructor. Internally it maintains a linked list of objects that are allocated, and the output of the
Console::WriteLine in the above code will be 3 (as we have allocated three objects using the context object). Initially I found this a little annoying to do, but I couldn’t think of any alternate solution that was more elegant and where we could avoid using the context object. In fact, in my
StringConvertor class I had internally used an
std::vector to store all allocated objects so I could free them in the destructor.