Marshaling Structures

Marshaling Structures

Visual Studio .NET 2003

In managed code, structures are only created on the stack. In unmanaged code, they can be created on the stack or on the heap.

This topic discusses three ways of marshaling structures:

  • Defining equivalent managed and unmanaged structures
  • Defining equivalent managed and unmanaged structures with embedded pointers
  • Marshaling structures in more complex code

Defining Equivalent Managed and Unmanaged Structures

The simplest example is a structure that has only blittable types or aggregates of blittable types. When using PInvoke, you must define a managed equivalent of the unmanaged structure. You can then define the PInvoke method signature in terms of the managed type.

The minimum requirement is that the managed type is at least as large as the unmanaged structure. To be useful, the memory layout of the managed and unmanaged structures should either be identical or have metadata that allows the runtime marshaler to automatically perform the correct translations.

By default, the layout of managed and unmanaged structures is different even if they contain the same fields and field types. You can use the StructLayoutAttribute to force the managed structure layout to mimic an unmanaged C-style structure layout.

Note   There is no way to automatically check that the managed and unmanaged structure definitions are equivalent; you must ensure that they are.

The following code contains an unmanaged C API, the equivalent PInvoke signature, and code that invokes the C function.

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif

// Unmanaged API
#pragma pack(push, 8)
typedef struct LOCATION
{
   short x;
   short y;
} LOCATION;
#pragma pack(pop)
TRADITIONALDLL_API double DistanceToCity(LOCATION location);

// Equivalent managed version of API.
[StructLayout(LayoutKind::Sequential, Pack=8)]
__value public struct MLOCATION
{
   short x;
   short y;
};

public __value struct TraditionalDLL
{
   [DllImport("TraditionalDLL.dll")]
   static public double DistanceToCity(MLOCATION location);   
};

// Uses the managed API.
int main()
{
   MLOCATION loc;
   loc.x = 80;
   loc.y = 100;
   double distance;
   distance = TraditionalDLL::DistanceToCity(loc);
   Console::WriteLine("The distance to city is {1:F} miles",
   __box(distance));
   return 0;
}

The code demonstrates the following concepts:

  • It is important that the structure packing settings for both the native and managed structures are the same; otherwise, the two may not be equivalent. The previous example assigns the packing setting explicitly for both structures.
  • No type checking is performed, so you must ensure that equivalent data types are used in both the managed and unmanaged structures. For example, the previous example will compile even if one of the structures uses the long data type instead of short despite the fact that an error will result.

Defining Managed and Unmanaged Structures with Embedded Pointers

In your unmanaged code, you can handle embedded pointers by using the System::Runtime::InteropServices::MarshalAsAttribute class to add metadata to the equivalent managed class to help the runtime marshaler correctly translate between managed and unmanaged types.

The following code is the unmanaged CITY structure, and a managed equivalent named MCITY. As with the LOCATION and MLOCATION structures, structure packing is set explicitly, and, because the structure contains a pointer, the MarshalAsAttribute is used.

// Unmanaged version.
#pragma pack(push, 8)
struct CITY
{
   char * name;
   LOCATION location;
};
#pragma pack(pop)
// Managed version.
[StructLayout(LayoutKind::Sequential, Pack=8)]
__value public struct MCITY
{
   MCITY(String* name,int x,int y)
   // Defined on the managed structure to assist with initialization.
   {
      this->location.x = x;
      this->location.y = y;
      this->name = name;
   }
   [MarshalAs(UnmanagedType::LPStr)]
   // Indicates that the name field should be marshaled as a C-style
   // string to the runtime marshaler.
   String* name;
   MLOCATION location;
};

The code demonstrates the following concepts:

  • A constructor and other methods are defined on the managed structure because methods are never marshaled. In this example, the constructor can be used to simplify initialization.
  • The MarshalAs attribute indicates that the name field should be marshaled as a C-style string to the runtime marshaler.

Marshaling Structures in More Complex Code

When data is provided to a function as a pointer argument, the InAttribute attribute should be used. For example, the DrawCity function in TraditionalDLL takes a pointer to the CITY structure. A PInvoke definition can therefore be defined like this:

[DllImport("TraditionalDLL.dll")]
static public void DrawCity(IntPtr hdc ,
               [InAttribute,MarshalAs(UnmanagedType::Struct)]MCITY *city);

The function semantics indicate that a directional InAttribute is needed. The MarshalAs attribute indicates that the data type underlying the MCITY* parameter should be marshaled as a C-style struct. Notice also that the unmanaged HDC GDI handle is represented using a System::IntPtr.

You can then call this function like this:

Graphics* g = this->CreateGraphics();
IntPtr hdc;
hdc = g->GetHdc();
TraditionalDLL::DrawCity(hdc,&cities[2]);
g->ReleaseHdc(hdc);

When a legacy API allocates memory for a structure, initializes it, and then returns the pointer to the caller, the caller is responsible for freeing the memory. For example, the CreateCity function is defined this way:

TRADITIONALDLL_API void CreateCity(CITY** pCity)
{
   if (pCity == 0)
      return;
   *pCity = (CITY*)::CoTaskMemAlloc(sizeof(CITY));
   (*pCity)->location.x = 100;
   (*pCity)->location.y = 150;
   (*pCity)->name = (char*)::CoTaskMemAlloc(7);
   strcpy((*pCity)->name,"Knysna");
}

The runtime does not support a reference to a reference to a type, so, in order to define an equivalent PInvoke definition, you must use the System::IntPtr data type and then use the System::Runtime::InteropServices::Marshal class to retrieve the data from the pointer.

[DllImport("TraditionalDLL.dll")]
static public void CreateCity([Out]IntPtr* city);
// ...
IntPtr aCity;
TraditionalDLL::CreateCity(&aCity);
__box MCITY* newCity;
newCity = __try_cast<__box MCITY*>
                      (Marshal::PtrToStructure(aCity,__typeof(MCITY)));
Console::WriteLine("The city {0} is located at ({1},{2})",
   newCity->name,
   __box(newCity->location.x),
   __box(newCity->location.y));
Marshal::FreeCoTaskMem(aCity);

The code demonstrates the following concepts:

  • The variable aCity will contain a pointer to a CITY structure when the function returns, but aCity is an IntPtr.
  • The Marshal::PtrToStructure method copies the data from the unmanaged memory into the managed heap. This means that newCity points to a boxed instance of MCITY.
  • If you do not call Marshal::FreeCoTaskMem, the unmanaged memory will leak. Even after freeing the unmanaged memory, however, newCity still points to a valid managed block of memory. The contents of the unmanaged memory were copied to the managed memory by the Marshal::PtrToStructure call.
  • Unlike other .NET-enabled languages, Managed Extensions for C++ allows you to access the contents of a boxed object, so you can modify the contents of the box. You could have modified the boxed structure pointed to by newCity without first unboxing the structure, modifying it, and then reboxing it.

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

See Also

Managed Extensions for C++ and Data Marshaling Tutorial

Show:
© 2016 Microsoft