Export (0) Print
Expand All

Marshaling Arrays

Visual Studio .NET 2003

You can easily marshal arrays between managed and unmanaged code, but unless you are using blittable types, marshaling arrays can be expensive in terms of performance. Arrays of blittable types perform better because the array is never copied. Instead, the underlying array is pinned before control passes to the unmanaged method. This is performed with the MarshalAs class.

To demonstrate the array marshalling, the DrawCities function will be used. The prototype for this function, the PInvoke equivalent, and an example of how to call DrawCities look like this:

TRADITIONALDLL_API void DrawCities(HDC hDC,CITY cities[],int numCities);
static public void DrawCities(IntPtr hdc,
      MCITY cities[],
      int n);
// ...
Graphics* g = this->CreateGraphics();
IntPtr hdc;
dc = g->GetHdc();

In the MarshalAs class, the SizeParamIndex property is set to inform the runtime marshaler of how many array elements must be copied. If this property is not set, then all array elements are copied, which can be inefficient. Also, you must ensure that the array is at least the specified size.

The following code demonstrates an unmanaged structure that contains a field that is an array of structures:

typedef struct CITYLIST
   CITY * list;
   int n;

In this case, the managed version of structure, you should not use an array of the MCITY type. An exception will be thrown if you attempt to call the DrawCityList function because the list field cannot be marshaled. Instead, use IntPtr:

__value public struct MCITYLIST
   IntPtr list;
   int n;
// PInvoke definition for DrawCityList
static public void DrawCityList(IntPtr hdc, MCITYLIST cityList);
// Now you can call DrawCityList.
GCHandle h = GCHandle::Alloc(__box(listCity),GCHandleType::Pinned);
listCity.cityList = h.AddrOfPinnedObject();

In managed code, the memory layout for arrays is not the same as for unmanaged C-style arrays. The list field is equivalent to a C-style void pointer, and the runtime marshaler can only copy the value. list must point to unmanaged memory that has already been initialized. Furthermore, you must copy the contents of the managed array into a block of unmanaged memory and have the list field point at this unmanaged memory, as follows:

MCITY cl [];
IntPtr p;

// Creates the data.
cl = new MCITY[2];
cl[0] = MCITY("Kimberly",80,200);
cl[1] = MCITY("DeAar",80,240);

// Defines the size of one element.
int size = Marshal::SizeOf(__typeof(MCITY));

// Allocates an unmanaged block that is big enough to hold all
// the elements.
p = Marshal::AllocCoTaskMem(size*cl->Length);

for(int i=0;i<cl->Length;i++)
   IntPtr temp;
   // Points to the correct offset in the unmanaged block.
   temp = IntPtr(p.ToInt32()+i*size);
   // Copies one element at a time.
// Points cityList at the unmanaged block.
listCity.list = p;
listCity.n = cl->Length;

// Frees the unmanaged memory.

The code demonstrates the following concepts:

  • The Marshal::SizeOf function retrieves the size of the unmanaged equivalent of the MCITY structure.
  • Because the managed array is not contiguous in memory, you must copy the elements of the managed array into the unmanaged block one element at a time.
  • The Marshal::StructureToPtr function requires a System::Object* as its source, so you must box the element before you can copy it.
  • The unmanaged block is freed; otherwise, the unmanaged block will leak.

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

See Also

Managed Extensions for C++ and Data Marshaling Tutorial

© 2015 Microsoft