I am trying to create some vhd/vhdx files using the VHD API in C#.
There's a C++ union that looks like this:
typedef struct _CREATE_VIRTUAL_DISK_PARAMETERS
{
CREATE_VIRTUAL_DISK_VERSION Version;
union
{
struct
{
GUID UniqueId;
ULONGLONG MaximumSize;
ULONG BlockSizeInBytes;
ULONG SectorSizeInBytes;
PCWSTR ParentPath;
PCWSTR SourcePath;
} Version1;
struct
{
GUID UniqueId;
ULONGLONG MaximumSize;
ULONG BlockSizeInBytes;
ULONG SectorSizeInBytes;
ULONG PhysicalSectorSizeInBytes;
PCWSTR ParentPath;
PCWSTR SourcePath;
OPEN_VIRTUAL_DISK_FLAG OpenFlags;
VIRTUAL_STORAGE_TYPE ParentVirtualStorageType;
VIRTUAL_STORAGE_TYPE SourceVirtualStorageType;
GUID ResiliencyGuid;
} Version2;
struct
{
GUID UniqueId;
ULONGLONG MaximumSize;
ULONG BlockSizeInBytes;
ULONG SectorSizeInBytes;
ULONG PhysicalSectorSizeInBytes;
PCWSTR ParentPath;
PCWSTR SourcePath;
OPEN_VIRTUAL_DISK_FLAG OpenFlags;
VIRTUAL_STORAGE_TYPE ParentVirtualStorageType;
VIRTUAL_STORAGE_TYPE SourceVirtualStorageType;
GUID ResiliencyGuid;
PCWSTR SourceLimitPath;
VIRTUAL_STORAGE_TYPE BackingStorageType;
} Version3;
};
} CREATE_VIRTUAL_DISK_PARAMETERS, *PCREATE_VIRTUAL_DISK_PARAMETERS;
I'm trying to convert that to C#, but not having much luck. I'm not interested in Version3 at all, so am leaving that out.
I've tried a number of things and the best I could get to was having Version2 working (by doing something really bizarre), but I've never managed to get Version1 and Version2 working at the same time.
The solution that has wielded the best results so far has been this, but there has to be something wrong there because Version1 simply doesn't work, and SectorSizeInBytes
in Version1 is a ulong
rather than uint
(if I change it to uint
like it should be, I break Version2 and Version1 still doesn't work!)
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParameters
{
[FieldOffset(0)] public CreateVirtualDiskParametersVersion1 Version1;
[FieldOffset(0)] public CreateVirtualDiskParametersVersion2 Version2;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion1
{
public CreateVirtualDiskVersion Version;
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public ulong SectorSizeInBytes;
public string ParentPath;
public string SourcePath;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion2
{
public CreateVirtualDiskVersion Version;
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public uint SectorSizeInBytes;
public uint PhysicalSectorSizeInBytes;
public string ParentPath;
public string SourcePath;
public OpenVirtualDiskFlags OpenFlags;
public VirtualStorageType ParentVirtualStorageType;
public VirtualStorageType SourceVirtualStorageType;
public Guid ResiliencyGuid;
}
I know theoretically the Version
field should be set outside the Version structs and I have tried that as well, but it just breaks things even more funnily enough...
So, can someone advise how to properly translate the above to C#, leaving out the Version3 struct as that's not needed?
Using Pack = 1
to StructLayout
attributes eliminates any padding between struct
members.
In TCP connections structs are usually passed around without padding so that all programs using the struct can agree on its layout in memory.
However as @David Heffernan pointed out, that may not be the case when passing structs to Windows DLL's. I didn't test the actual call to CreateVirtualDisk
because it seemed a bit risky, given that I haven't used this call before and didn't want to clobber my disk if I made a mistake. It looks as if the default packing of 8 bytes (Pack = 0
for default or Pack = 8
) may be the correct setting, based on the following quote.
See 64-bit Windows API struct alignment caused Access Denied error on named pipe
The Windows SDK expects packing to be 8 bytes. From Using the Windows Headers
Projects should be compiled to use the default structure packing, which is currently 8 bytes because the largest integral type is 8 bytes. Doing so ensures that all structure types within the header files are compiled into the application with the same alignment the Windows API expects. It also ensures that structures with 8-byte values are properly aligned and will not cause alignment faults on processors that enforce data alignment.
Version
is moved to the top of CreateVirtualDiskParameters
.
The two unions then follow. Both have the same offset sizeof(CREATE_VIRTUAL_DISK_VERSION)
.
Also SectorSizeInBytes
is uint
rather than ulong
.
You can let the marshaller do the work of filling string
members using the attribute, eg
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
Or, you can represent it as it appears in memory, which is a pointer to a Unicode string:
public IntPtr ParentPath;
and then extract the string yourself with
Marshal.PtrToStringAuto(vdp.Version1.ParentPath)
If you're passing the C# struct to an external DLL, populate it with an unmanaged string
vdp.Version1.ParentPath = (IntPtr)Marshal.StringToHGlobalAuto("I am a managed string");
then free the unmanaged string when you're finished with it
Marshal.FreeHGlobal(vdp.Version1.ParentPath);
Try this.
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;
//public IntPtr ParentPath; // PCWSTR in C++ which is a pointer to a Unicode string
//public IntPtr SourcePath; //string
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
[MarshalAs(UnmanagedType.LPWStr)] public string SourcePath;
}
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion2
{
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public uint SectorSizeInBytes;
public uint PhysicalSectorSizeInBytes;
//public IntPtr ParentPath; //string
//public IntPtr SourcePath; //string
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
[MarshalAs(UnmanagedType.LPWStr)] public string SourcePath;
public OPEN_VIRTUAL_DISK_FLAG OpenFlags;
public VIRTUAL_STORAGE_TYPE ParentVirtualStorageType;
public VIRTUAL_STORAGE_TYPE SourceVirtualStorageType;
public Guid ResiliencyGuid;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With