Combining C ++ in C # - weird behavior

I am trying to create some vhd / vhdx files using the VHD API in C #.

There is C ++ here - a connection 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 am trying to convert this to C # but don't have much luck. I'm not interested in Version3 at all, so I leave that alone.

I tried several things, and the best I could get was that I was running version 2 (doing something really weird), but I never managed to work with versions 1 and Version2.

The solution that has had the best results so far has been like this, but there should be something wrong, because Version1 just doesn't work, and SectorSizeInBytes in Version 1 is ulong , not uint (if I changed it to uint , like and it must be that I am violating Version2 and Version1, still not working!)

 [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 that theoretically the Version field should be set outside the Version structures, and I tried it too, but it just broke things even more fun ...

So, can someone advise how to correctly translate the above into C #, leaving the Version3 structure as you don't need?

+6
source share
1 answer

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; //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; } 
+1
source

All Articles