Export (0) Print
Expand All

Custom Marshaling

Visual Studio .NET 2003

You can add metadata to parameters or fields that are being marshaled to specify a custom marshaler class that will perform translations between managed and unmanaged. The following code demonstrates a managed class definition and PInvoke signatures:

__gc public class MCity
{
public:
   MCity(String* name,int x,int y)
   {
      this->name = name;
      this->x = x;
      this->y = y;
   }
   String* name;
   int x;
   int y;
};
__value public struct CustomMarshalAPI
{
   [DllImport("TraditionalDLL.dll")]
   static public void MDrawCity(IntPtr hdc, MCity* aCity);
   [DllImport("TraditionalDLL.dll")]
   static public void MDrawCities(IntPtr hdc, 
               MCity* aCity [], int n);
};

To translate the MCity* objects to CITY structures, implement a custom marshaler class named CITY_CustomMarshaler to do the marshaling:

__gc public class CITY_CustomMarshaler :
      public System::Runtime::InteropServices::ICustomMarshaler
{
public:
   CITY_CustomMarshaler(void);
   ~CITY_CustomMarshaler(void);
   Object* MarshalNativeToManaged(IntPtr pNativeData);
   IntPtr MarshalManagedToNative(Object* ManagedObj);
   void CleanUpNativeData(IntPtr pNativeData);
   void CleanUpManagedData(Object* ManagedObj);
   static ICustomMarshaler* GetInstance(String* cookie);
   int GetNativeDataSize();
private:
   IntPtr MarshalManagedArrayToNative(MCity * pCity []);
   IntPtr MarshalManagedObjectToNative(MCity* pCity);
   static CITY_CustomMarshaler* marshaler = 0;
};

This definition is a generic custom marshaler definition. It will be able to marshal single objects and arrays of objects. MarshalManagedArrayToNative and MarshalManagedObjectToNative are helper methods that apply to the different cases.

The following code implements the MarshalManagedToNative, MarshalManagedArrayToNative, MarshalManagedObjectToNative, and CleanUpNativeData methods.

IntPtr CITY_CustomMarshaler::MarshalManagedToNative(Object* ManagedObj)
{
   if (ManagedObj == 0)
      throw new System::ArgumentNullException(L"ManagedObj");
   MCity* pCity = dynamic_cast<MCity*>(ManagedObj);
   if (pCity != 0)
   {
      return MarshalManagedObjectToNative(pCity);
   }
   else
   {
      MCity * pCityArr [] = dynamic_cast<MCity* []>(ManagedObj);
      if (pCityArr != 0)
      {
         return MarshalManagedArrayToNative(pCityArr);
      }
   }
   throw new System::ArgumentException("Cannot marshal this type");
};
IntPtr CITY_CustomMarshaler::MarshalManagedArrayToNative(MCity* pCity [])
{
   int size = sizeof(CITY)*pCity->Length+sizeof(int);
   IntPtr ptrBlock = (Marshal::AllocCoTaskMem(size)).ToPointer();
   int offset = ptrBlock.ToInt32()+sizeof(int);
   IntPtr ptrCity(offset);
   int __nogc * pi = (int __nogc *)ptrBlock.ToPointer();
   *pi = pCity->Length;
   CITY* city = static_cast<CITY*>(ptrCity.ToPointer());
   for(int i=0;i<pCity->Length;i++)
   {
      IntPtr p = Marshal::StringToHGlobalAnsi(pCity[i]->name);
      city->name = static_cast<char*>(p.ToPointer());
      city->location.x = pCity[i]->x;
      city->location.y = pCity[i]->y;
      city++;
   }
   return ptrCity;
}
IntPtr CITY_CustomMarshaler::MarshalManagedObjectToNative(MCity* pCity)
{
   int size = sizeof(CITY)+sizeof(int);
   IntPtr ptrBlock = (Marshal::AllocCoTaskMem(size)).ToPointer();
   int offset = ptrBlock.ToInt32()+sizeof(int);
   IntPtr ptrCity(offset); 
   int __nogc * pi = (int __nogc *)ptrBlock.ToPointer();
   *pi = 1;
   CITY* city = static_cast<CITY*>(ptrCity.ToPointer());
   IntPtr p = Marshal::StringToHGlobalAnsi(pCity->name);
   city->name = static_cast<char*>(p.ToPointer());
   city->location.x = pCity->x;
   city->location.y = pCity->y;
   return ptrCity;
}
void CITY_CustomMarshaler::CleanUpNativeData(IntPtr pNativeData)
{
   CITY * pCity = static_cast<CITY*>(pNativeData.ToPointer());
   CITY* pTemp = pCity;
   int offset = pNativeData.ToInt32()-sizeof(int);
   IntPtr pBlock(offset);
   int __nogc * pi= static_cast<int __nogc*>(pBlock.ToPointer());
   for(int i=0;i<*pi;i++)
   {
      if (pTemp->name != 0)
         Marshal::FreeCoTaskMem(pTemp->name);
      pTemp++;
   }
   Marshal::FreeCoTaskMem(pBlock);
};

The last step is to modify the PInvoke signatures to indicate that the CITY_CustomMarshaler class must be used to marshal the attributed parameters.

__value public struct CustomMarshalAPI
{
   [DllImport("TraditionalDLL.dll")]
   static public void DrawCity(IntPtr hdc, 
         [In,MarshalAs(UnmanagedType::CustomMarshaler,
          MarshalTypeRef=__typeof(CITY_CustomMarshaler))]
      MCity* aCity);
      [DllImport("TraditionalDLL.dll")]
   static public void DrawCities(IntPtr hdc, 
         [In,MarshalAs(UnmanagedType::CustomMarshaler,
          MarshalTypeRef=__typeof(CITY_CustomMarshaler))]
      MCity* aCity [],
      int n);

};

The code demonstrates the following concepts:

  • The MarshalManagedToNative uses dynamic_cast to determine which method (MarshalManagedArrayToNative or MarshalManagedObjectToNative) to call.
  • The MarshalManagedArrayToNative or MarshalManagedObjectToNative methods both allocate a block of unmanaged memory that is larger than is required for the data by the size of an int. The number of marshaled objects is stored in this extra space.
  • The MarshalManagedArrayToNative or MarshalManagedObjectToNative methods do not return a System::IntPtr pointing to the start of the unmanaged block. Instead, the methods return the start address of the actual marshaled data.
  • The CleanUpNativeData method uses object count stored at the beginning of the unmanaged block to correctly clean up embedded pointers and then free the entire block.

The following code shows how you can call the attributed methods.

Graphics* g = this->CreateGraphics();
IntPtr hdc =  g->GetHdc();
MCity* mc = new MCity("Bloemfontein",80,380);
CustomMarshalAPI::MDrawCity(hdc,mc);
MCity* arrMC [] = new MCity*[2];
arrMC[0] = new MCity("Jeffries Bay",80,420);
arrMC[1] = new MCity("Richards Bay",80,460);
CustomMarshalAPI::MDrawCities(hdc,arrMC,arrMC->Length);
g->ReleaseHdc(hdc);

For the client, there are no special coding techniques required, and it does not have to be exposed to the underlying unmanaged representation for the marshaled data.

Go to the next step | Go back to the last step

See Also

Managed Extensions for C++ and Data Marshaling Tutorial

Show:
© 2014 Microsoft