Virtual Developer Workshop: Containerized Development with Docker
One of the frequent criticisms of C++ is that it offers too many options to perform the same task. Consider the number of different options that exist for writing a text file that ship out-of-the-box with Visual C++—the C Runtime Libraries, the Standard C++ libraries, the Windows SDK I/O APIs, the .NET file APIs, MFC, and even the FileSystemObjects available through COM. All of these libraries can write an identical text file, yet each has their own subtle advantages and pitfalls.
In the same way, the C++ developer has a wide array of options for converting between types in the .NET and COM world. Although most developers may resort to hand-coded conversion that involves boiling the data of a type down to blittable primitives (blittable data types are those that have the same in-memory format in the managed and native world, such as four-byte signed integers or Unicode characters), there is also the option of using the .NET type Marshal, which has various static methods for converting from one data type to another. Another option available to a C++ code compiled with /clr:pure or /clr:safe is the use of properties on the DllImport attribute to specify how a managed type is converted to a native type. With Visual C++ Orcas, there is a new technique for conversion: the C++ Marshaling Library.
The Marshaling Library provides a template-based approach to converting between native and managed data types. For simple conversions that have no memory management complexities, the syntax for converting from one data type to another is very simple:
#include <msclr\marshal.h> TCHAR* c_style_string = _T("My C style string"); System::String^ dotNetString = msclr::interop::marshal_as<System::String^>(c_style_string);
When the returned object requires explicit memory clean-up, context-based marshaling needs to be used. With context-based marshaling, a managed marshal_context object needs to be created and passed to the marshaling method, and the results of the marshaling call are only valid for the lifetime of the marshal_context object. To convert the managed .NET string back to a C-style string, the following code would be required.
//declare new marshal_context marshal_context^ mc = gcnew marshal_context(); //convert string from .NET to C-style const TCHAR* new_c_style_string = mc->marshal_as<const TCHAR*>(dotNetString); //get the length of the convert string int strLen = (int)_tcslen(new_c_style_string) + 1; //allocate a new character array to hold the string TCHAR* copy_of_new_c_style_string = new TCHAR(strLen); //copy to the new array _tcscpy_s(copy_of_new_c_style_string, strLen, new_c_style_string); //delete the marshaling context delete mc;
When the marshal_context is deleted, any memory that has been allocated during marshaling calls is deleted, and this means a copy of the marshaled data needs to be made if the converted object will be accessed once the marshal_context object has been deleted.
A wide range of conversions is currently supported in the Beta 1 release of Visual C++ Orcas. The main header file (marshal.h) defines the base marshal type and string conversions between System::String and C-style strings (char* and wchar_t*). Marshal_atl.h provides conversion from COM strings (CComBSTR) and MFC strings(CStringA and CStringW) to managed strings, and marshal_cppstd.h goes between the standard C++ strings (std::wstring and std::string) to .NET. The final marshaling header file (marshal_windows.h) provides conversion between the managed IntPtr type and the native HANDLE type, and also supports the conversion to and from System::String for another two COM string types (_bstr_t and BSTR).