Using the attributes Pack = 1 to StructLayout eliminates any indentation between struct members. In TCP connections, structures are usually passed without filling, so that all programs using a struct can coordinate their layout in memory.
However, as @David Heffernan noted, this may not be the case if transferring structures to a Windows DLL. I did not test the actual call to CreateVirtualDisk because it seemed a little risky, given that I had not used this call before and did not want to compress the disk if I made a mistake. It looks like standard 8 byte packing ( Pack = 0 by default or Pack = 8 ) might be the correct setting based on the following quote.
See 64-bit alignment of the Windows API structure caused a denied access error on a named pipe
The Windows SDK expects packaging to be 8 bytes. From Using Windows Headers
Projects must be compiled to use the default structure structure, which is currently 8 bytes, because the largest integral type is 8 bytes. This ensures that all structure types in the header files are compiled into the application with the same alignment that the Windows API expects. It also ensures that structures with 8 byte values ββare correctly aligned and will not cause alignment errors for processors that provide data alignment.
Version moves to the beginning of CreateVirtualDiskParameters . Then two unions follow. Both have the same sizeof(CREATE_VIRTUAL_DISK_VERSION) offset sizeof(CREATE_VIRTUAL_DISK_VERSION) .
Also SectorSizeInBytes is uint , not ulong .
You can let the marshaller do the work of populating string members with an attribute, like
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
Or you can imagine it as it appears in memory, which is a pointer to a Unicode string:
public IntPtr ParentPath;
and then extract the string yourself
Marshal.PtrToStringAuto(vdp.Version1.ParentPath)
If you pass a C # structure to an external DLL, fill it with an unmanaged string
vdp.Version1.ParentPath = (IntPtr)Marshal.StringToHGlobalAuto("I am a managed string");
then release the unmanaged string when you are done with it
Marshal.FreeHGlobal(vdp.Version1.ParentPath);
Try it.
public enum CREATE_VIRTUAL_DISK_VERSION { CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0, CREATE_VIRTUAL_DISK_VERSION_1 = 1, CREATE_VIRTUAL_DISK_VERSION_2 = 2 }; public enum OPEN_VIRTUAL_DISK_FLAG { OPEN_VIRTUAL_DISK_FLAG_NONE = 0x00000000, OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 0x00000001, OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE = 0x00000002, OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE = 0x00000004, OPEN_VIRTUAL_DISK_FLAG_CACHED_IO = 0x00000008, OPEN_VIRTUAL_DISK_FLAG_CUSTOM_DIFF_CHAIN = 0x00000010 }; [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] public struct VIRTUAL_STORAGE_TYPE { uint DeviceId; Guid VendorId; }; [StructLayout(LayoutKind.Explicit, Pack = 8, CharSet = CharSet.Unicode)] public struct CreateVirtualDiskParameters { [FieldOffset(0)] public CREATE_VIRTUAL_DISK_VERSION Version; [FieldOffset(8))] public CreateVirtualDiskParametersVersion1 Version1; [FieldOffset(8))] public CreateVirtualDiskParametersVersion2 Version2; } [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)] public struct CreateVirtualDiskParametersVersion1 { public Guid UniqueId; public ulong MaximumSize; public uint BlockSizeInBytes; public uint SectorSizeInBytes;