C#中的C++联合 - 奇怪的行为

cog*_*el0 6 c# c++ pinvoke

我正在尝试使用C#中的VHD API创建一些vhd/vhdx文件.

有一个C++联合看起来像这样:

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;
Run Code Online (Sandbox Code Playgroud)

我正在尝试将其转换为C#,但没有太多运气.我根本不对Version3感兴趣,所以我就把它排除在外.

我已经尝试过很多东西,而且我能做的最好的就是让Version2工作(通过做一些非常奇怪的事情),但我从来没有设法让Version1和Version2同时工作.

到目前为止已经取得了最好结果的解决方案就是这样,但是那里有一些错误,因为Version1根本不起作用,而SectorSizeInBytes在Version1中是一个ulong而不是uint(如果我把它改成uint它应该是,我打破版本2和版本1仍然无效!)

[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;
}
Run Code Online (Sandbox Code Playgroud)

我从理论上知道该Version字段应该在Version结构之外设置,我也尝试过,但它只是让事情更加有趣......

那么,有人可以建议如何正确地将上述内容转换为C#,而不需要那样的Version3结构吗?

小智 1

使用Pack = 1toStructLayout属性可以消除成员之间的任何填充struct。在 TCP 连接中,结构体通常在不进行填充的情况下传递,以便使用该结构体的所有程序都可以就其在内存中的布局达成一致。

然而,正如 @David Heffernan 指出的那样,将结构传递给 Windows DLL 时情况可能并非如此。我没有测试实际的调用,CreateVirtualDisk因为它看起来有点冒险,因为我以前没有使用过这个调用,并且不想在犯错误时破坏我的磁盘。根据以下引用,看起来 8 字节的默认打包(Pack = 0对于 default 或)可能是正确的设置。Pack = 8

请参阅64 位 Windows API 结构对齐导致命名管道上的访问被拒绝错误

Windows SDK 期望打包为 8 个字节。来自使用 Windows 标头

项目应编译为使用默认的结构打包,当前为 8 字节,因为最大的整型类型为 8 字节。这样做可以确保头文件中的所有结构类型都按照 Windows API 期望的相同对齐方式编译到应用程序中。它还确保具有 8 字节值的结构正确对齐,并且不会在强制数据对齐的处理器上导致对齐错误。

Version被移动到 的顶部CreateVirtualDiskParameters。两个工会随后跟进。两者具有相同的偏移量sizeof(CREATE_VIRTUAL_DISK_VERSION)

SectorSizeInBytesuint而非ulong

string您可以让编组器使用属性来完成填充成员的工作,例如

[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
Run Code Online (Sandbox Code Playgroud)

或者,您可以将其表示为内存中出现的形式,即指向 Unicode 字符串的指针:

public IntPtr ParentPath;
Run Code Online (Sandbox Code Playgroud)

然后自己提取字符串

Marshal.PtrToStringAuto(vdp.Version1.ParentPath)
Run Code Online (Sandbox Code Playgroud)

如果要将 C# 结构传递给外部 DLL,请使用非托管字符串填充它

vdp.Version1.ParentPath = (IntPtr)Marshal.StringToHGlobalAuto("I am a managed string");
Run Code Online (Sandbox Code Playgroud)

然后在完成后释放非托管字符串

Marshal.FreeHGlobal(vdp.Version1.ParentPath);
Run Code Online (Sandbox Code Playgroud)

尝试这个。

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;
}
Run Code Online (Sandbox Code Playgroud)