15.3 Call an Unmanaged Function That Uses a Structure


Problem

You need to call an unmanaged function that accepts a structure as a parameter.

Solution

Define the structure in your C# code. Use the attribute System.Runtime.InteropServices.StructLayoutAttribute to configure how the structure is allocated in memory. Use the static SizeOf method of the System.Runtime.Interop.Marshal class if you need to determine the size of the unmanaged structure in bytes.

Discussion

In pure C# code, you aren't able to directly control memory allocation. Instead, the CLR is free to move data around in memory at any time to optimize performance. This can cause problems when interacting with legacy C functions that expect structures to be laid out sequentially in memory. Fortunately, .NET allows you to solve this problem using the attribute StructLayoutAttribute , which allows you to specify how the members of a given class or structure should be arranged in memory.

As an example, consider the unmanaged GetVersionEx function provided in the Kernel32.dll file. This function accepts a pointer to an OSVERSIONINFO structure and uses it to return information about the current operating system version. To use the OSVERSIONINFO structure in C# code, you must define it with the attribute StructLayoutAttribute , as shown here:

 [StructLayout(LayoutKind.Sequential)] public class OSVersionInfo {     public int dwOSVersionInfoSize;     public int dwMajorVersion;     public int dwMinorVersion;     public int dwBuildNumber;     public int dwPlatformId;     [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]     public String szCSDVersion; } 

Notice that this structure also uses the attribute System.Runtime.InteropServices.MarshalAsAttribute , which is required for fixed-length strings. In this example, MarshalAsAttribute specifies the string will be passed by value and will contain a buffer of exactly 128 characters , as specified in the OSVERSIONINFO structure. In this example, sequential layout is used, which means the data types in the structure are laid out in the order they are listed in the class or structure. When using sequential layout, you can also configure the packing for the structure by specifying a named Pack field in the StructLayoutAttribute constructor. The default is 8, which means the structure will be packed on 8-byte boundaries.

Instead of using sequential layout, you could use LayoutKind.Explicit , in which case you must define the byte offset of each field using FieldOffsetAttribute . This layout is useful when dealing with an irregularly packed structure or one where you want to omit some of the fields that you don't want to use. Here's an example that defines the OSVersionInfo class with explicit layout:

 [StructLayout(LayoutKind.Explicit)] public class OSVersionInfo {     [FieldOffset(0)] public int dwOSVersionInfoSize;     [FieldOffset(4)]public int dwMajorVersion;     [FieldOffset(8)]public int dwMinorVersion;     [FieldOffset(12)]public int dwBuildNumber;     [FieldOffset(16)]public int dwPlatformId;     [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]         [FieldOffset(20)]public String szCSDVersion; } 

Now that you've defined the structure used by the GetVersionEx function, you can declare the function and then use it. The following console application shows all the code you'll need. Notice that the InAttribute and OutAttribute are applied to the OSVersionInfo parameter to indicate that marshalling should be performed on this structure when it is passed to the function, and when it is returned from the function. In addition, the code uses the Marshal.SizeOf method to calculate the size the marshaled structure will occupy in memory.

 using System; using System.Runtime.InteropServices; public class CallWithStructure {     // (OSVersionInfo class omitted.)     [DllImport("kernel32.dll")]     public static extern bool GetVersionEx([In, Out] OSVersionInfo osvi);     private static void Main() {         OSVersionInfo osvi = new OSVersionInfo();         osvi.dwOSVersionInfoSize = Marshal.SizeOf(osvi);             GetVersionEx(osvi);         Console.WriteLine("Class size: " + osvi.dwOSVersionInfoSize);         Console.WriteLine("Major Version: " + osvi.dwMajorVersion);         Console.WriteLine("Minor Version: " + osvi.dwMinorVersion);         Console.WriteLine("Build Number: " + osvi.dwBuildNumber);         Console.WriteLine("Platform Id: " + osvi.dwPlatformId);         Console.WriteLine("CSD Version: " + osvi.szCSDVersion);         Console.WriteLine("Platform: " + Environment.OSVersion.Platform);         Console.WriteLine( "Version: " + Environment.OSVersion.Version);         Console.ReadLine();     } } 

If you run this application on a Windows XP system, you'll see information such as this:

 Class size: 148 Major Version: 5 Minor Version: 1 Build Number: 2600 Platform Id: 2 CSD Version: Platform: Win32NT Version: 5.1.2600.0 



C# Programmer[ap]s Cookbook
C# Programmer[ap]s Cookbook
ISBN: 735619301
EAN: N/A
Year: 2006
Pages: 266

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net