Visual C++ Orcas Marshaling Library

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).

Visual C++ Orcas Marshaling Library

Extending the Marshaling Library

Extending the Marshaling Library is a relatively simple undertaking. A new marshal function simply needs to be added to a header file, and the code that wants to use the new marshaler uses the marshal_as context in exactly the same manner as the in-built conversion functions. The marshal functions that ship with Beta 1 do not support conversions between the SecureString class in .NET and the common native types, and this is a good candidate for a sample extension. A conversion to and from wchar_t* will be covered because the pattern for the other native string types will be very similar.

The first point to note is that there is no requirement to support a bi-directional conversion, but most users will probably expect one. For the conversion to a secure string, the marshal method is quite simple:

template <>
inline System::Security::SecureString^ msclr::interop::marshal_as
   (wchar_t* const & _from_object)
{
   if (_from_object == NULL)
   {
      return nullptr;
   }
   return gcnew System::Security::SecureString(_from_object,
      (int)wcslen(_from_object));
}

The code to convert to a SecureString using a template specialization through the Marshaling Library is no different or no more complex than the code that would need to be written for any other helper function. The input parameter is checked against NULL, and if a valid parameter has been passed, a new SecureString is created using one of its overloaded constructors.

Going the other way is slightly more complex because of the memory management requirements of C-style strings. The SecureString to wchar_t* conversion requires the use of contexts; this means that a template specialization derived from context_node_base needs to be used. The actual class is reasonably simple. The actual conversion is handled by the constructor, and a destructor is also present to handle the clean-up of the data once the marshal_context class that is holding the context_node_base-derived object is deleted. A Finalize method is also declared (which the destructor actually forwards to in this case) to handle memory clean-up via the .NET finalizer queue if destruction is missed.

template<>
ref class msclr::interop::context_node<wchar_t*,
   System::Security::SecureString^> :
   public context_node_base
{
   private:
   System::IntPtr _ip;
   public:

   context_node(wchar_t*& _to_object,
      System::Security::SecureString^ _from_object)
   {
      _ip = System::Runtime::InteropServices::Marshal::
         SecureStringToGlobalAllocUnicode (_from_object);
      _to_object = static_cast<wchar_t*>(_ip.ToPointer());
   }

   ~context_node()
   {
      this->!context_node();
   }

   protected:
   !context_node()
   {
      if(_ip != System::IntPtr::Zero)
         System::Runtime::InteropServices::Marshal::FreeHGlobal(_ip);
   }
};

The compiler will automatically detect an attempt to use context-free marshaling with a conversion that requires marshaling. If the following code is compiled:

System::Security::SecureString^ mySecureString =
   GetSecureString(...);
wchar_t* c_style_string1 =
   marshal_as<wchar_t*>(mySecureString);

the compiler will produce the following error:

C:\Program Files\Microsoft Visual Studio 9.0\VC\include\msclr\
   marshal.h(200) : error C4996: 'msclr::interop::
   error_reporting_helper<_To_Type,_From_Type>::marshal_as':
   This conversion requires a marshal_context. Please use a
   marshal_context for this conversion.

   with
   [
      _To_Type=wchar_t *,
      _From_Type=System::Security::SecureString ^
   ]
   C:\Program Files\Microsoft Visual Studio 9.0\VC\include\msclr\
      marshal.h(191) : see declaration of 'msclr::interop::
      error_reporting_helper<_To_Type,_From_Type>::marshal_as'
   with
   [
      _To_Type=wchar_t *,
      _From_Type=System::Security::SecureString ^
   ]
   .\myFile.cpp(19) : see reference to function template
      instantiation '_To_Type msclr::interop::marshal_as
      <wchar_t*,System::Security::SecureString^>
      (const _From_Type &)' being compiled
   with
   [
      _To_Type=wchar_t *,
      _From_Type=System::Security::SecureString ^
   ]

This compiler-enforcement of the use of context-based marshaling when required will serve to prevent any inadvertent memory leaks.

Conclusion

C++ Marshaling Library provides a user-friendly, extensible, one-stop-shop for going between native and managed types. By using inline templates, the conversions achieve the greatest possible performance as the correct conversion routine is identified and substituted at compile time, eliminating the potentially expensive lookups and type-safety checks that runtime-based marshaling can impose. The C++ Marshaling Library is extremely easy to use and extend, and by building custom conversion routines on top of the Marshaling Library, developers provide routines that are easy to find and organise in a large code base, and have a familiar syntax that other developers are more likely to use over stand-alone conversion routines, promoting code re-use.

About the Author

Nick Wienholt is a Windows and .NET consultant based in Sydney, Australia. He has worked on a variety of IT projects over the last decade and continues to stay involved in the developer community. Nick is the co-founder and president of the Sydney Deep .NET User group, writes technical articles for Pinnacle Publishing and the Microsoft Developer Network, and is a participant in many .NET-related newsgroups. Nick's most recent book is Maximizing .NET Performance.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds