Inside C# 4 Data Structs
Written by Mike James   
Tuesday, 07 September 2010
Article Index
Inside C# 4 Data Structs
Being exact
The API
Serialising structs
Manual mashaling
Struct-to-pointer

Banner

Calling the API

As an example of using structs with layout requirements we can use the EnumDisplayDevices function which is defined as:

BOOL EnumDisplayDevices(
LPCTSTR lpDevice,// device name
DWORD iDevNum, // display device
PDISPLAY_DEVICE lpDisplayDevice,
// device information
DWORD dwFlags // reserved
);

This is fairly easy to convert into a C# declaration:

[DllImport("User32.dll",
CharSet=CharSet.Unicode )]
extern static bool EnumDisplayDevices(
string lpDevice,
uint iDevNum,
ref DISPLAY_DEVICE lpDisplayDevice,
uint dwFlags);

The DISPLAY_DEVICE structure is defined, in C, as:

typedef struct _DISPLAY_DEVICE {
DWORD cb;
WCHAR DeviceName[32];
WCHAR DeviceString[128];
DWORD StateFlags;
WCHAR DeviceID[128];
WCHAR DeviceKey[128];
} DISPLAY_DEVICE, *PDISPLAY_DEVICE;

It is clear that it contains four fixed-sized character arrays. This can be translated into C# using an Explicit layout as:

[StructLayout(LayoutKind.Explicit, 
Pack = 1,Size=714)]
public struct DISPLAY_DEVICE
{
[FieldOffset(0)]
public int cb;
[FieldOffset(4)]
public  char DeviceName;
[FieldOffset(68)]
public char DeviceString;
[FieldOffset(324)]
public int StateFlags;
[FieldOffset(328)]
public  char DeviceID;
[FieldOffset(584)]
public  char DeviceKey;
}

Notice the use of Size= to specify the storage needed by the DeviceKey field.

When this is used in an actual call:

DISPLAY_DEVICE info=new DISPLAY_DEVICE();
info.cb = Marshal.SizeOf(info);
bool result = EnumDisplayDevices(
null,
0,
ref info,
0);

all you can directly access are the first characters in each of the buffers using the field variables.

For example, DeviceString holds the first character of the device string buffer.

If you want to get at the rest of the buffer you have to get a pointer to DeviceString and use pointer arithmetic to step through the array.

As long as you are using C# 2 or better then a simpler solution is to use a fixed array as in:

[StructLayout(LayoutKind.Sequential, 
Pack = 1)]
public unsafe struct DISPLAY_DEVICE
{
public int cb;
public fixed char DeviceName[32];
public fixed char DeviceString[128];
public int StateFlags;
public fixed char DeviceID[128];
public fixed char DeviceKey[128];
}

Notice that now the struct has to be declared as “unsafe” but now after the API call we can access the character arrays without using pointers. Pointers are still used behind the scenes, however, and any code that uses the arrays has to be marked as unsafe.

The third and final method is to use custom marshaling. 

Many C# programmers don’t realise that marshaling isn’t just about the way that the system types data for passing to DLLs – instead it is an active process that copies and transforms the managed data.

For example, if you choose to pass a reference to an array of typed elements then you can ask for it to be marshaled as a value array and the system will convert it into a fixed length buffer, and back to a managed array, without any extra effort on your part.

In this case all we have to do is add the MarshalAs attribute, specify the type and size of the arrays:

[StructLayout(LayoutKind.Sequential, 
Pack = 1,
CharSet = CharSet.Unicode)]
public struct DISPLAY_DEVICE
{
public int cb;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst=32)]
public char[] DeviceName;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst=128)]
public char[] DeviceString;
public int  StateFlags;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 128)]
public char[] DeviceID;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 128)]
public char[] DeviceKey;
}

What happens in this case is that, when you make the DLL call, the fields are marshaled by creating unmanaged buffers of the correct size within the copy of the struct that is to be passed to the DLL function.

When the function returns the unmanaged buffers are converted into managed char arrays and the field variables are set to reference them.

As a result when the function is complete you will discover that the struct has char arrays of the correct size containing the data.

Clearly, as far as calling a DLL is concerned, the custom marshal is the best option as it produces safe code – although using p/Invoke to call a DLL isn’t really safe in any reasonable sense of the word.

Banner

 <ASIN:1430229799>

<ASIN:0262201755>

<ASIN:0596800959>

<ASIN:047043452X>

<ASIN:193435645X>

<ASIN:0596007124>

Last Updated ( Tuesday, 28 September 2010 )