Inside C# 4 Data Structs |
Written by Mike James | |||||||
Tuesday, 07 September 2010 | |||||||
Page 3 of 6
Calling the APIAs an example of using structs with layout requirements we can use the EnumDisplayDevices function which is defined as: BOOL EnumDisplayDevices( This is fairly easy to convert into a C# declaration: [DllImport("User32.dll", The DISPLAY_DEVICE structure is defined, in C, as: typedef struct _DISPLAY_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, 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(); 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, 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, 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. <ASIN:1430229799> <ASIN:0262201755> <ASIN:0596800959> <ASIN:047043452X> <ASIN:193435645X> <ASIN:0596007124> |
|||||||
Last Updated ( Tuesday, 28 September 2010 ) |