C# 中的结构对齐

本文关键字:对齐 结构 | 更新日期: 2023-09-27 18:35:37

我正在使用SQL VDI,并尝试通过COM接口将结构从C#传递到C++。 该结构在C++标头中定义为:

#pragma pack(8)
struct VDConfig
    {
    unsigned long deviceCount;
    unsigned long features;
    unsigned long prefixZoneSize;
    unsigned long alignment;
    unsigned long softFileMarkBlockSize;
    unsigned long EOMWarningSize;
    unsigned long serverTimeOut;
    unsigned long blockSize;
    unsigned long maxIODepth;
    unsigned long maxTransferSize;
    unsigned long bufferAreaSize;
    } ;

为了模拟这一点,我在 C# 中将结构定义为:

[StructLayout(LayoutKind.Explicit)]
public struct VDConfig 
{
    [FieldOffset(0)]
    public uint deviceCount;
    [FieldOffset(4)]
    public uint features;
    [FieldOffset(8)]
    public uint prefixZoneSize;
    [FieldOffset(12)]
    public uint alignment;
    [FieldOffset(16)]
    public uint softFileMarkBlockSize;
    [FieldOffset(20)]
    public uint EOMWarningSize;
    [FieldOffset(24)]
    public uint serverTimeout;
    [FieldOffset(28)]
    public uint blockSize;
    [FieldOffset(32)]
    public uint maxIODepth;
    [FieldOffset(36)]
    public uint maxTransferSize;
    [FieldOffset(40)]
    public uint bufferAreaSize;
}

我还尝试将结构定义为LayoutKind.Sequential,并使用Pack=8进行了尝试。 但是我定义结构,当我尝试将其传递给函数时,它失败了,并且收到错误"对齐必须是 2**n 和 <= 系统分配粒度"。 我尝试将接受结构的函数定义为:

int CreateEx([MarshalAs(UnmanagedType.LPWStr)]string instanceName,
            [MarshalAs(UnmanagedType.LPWStr)]string name,
            IntPtr config);

int CreateEx([MarshalAs(UnmanagedType.LPWStr)]string instanceName,
            [MarshalAs(UnmanagedType.LPWStr)]string name,
            ref VDConfig config);

无论哪种定义,我都会得到相同的结果。 谁能告诉我我在这里做错了什么?

编辑:仔细观察时,我还收到错误"设备计数必须在 [1..64] 中"。 我将设备计数设置为 1,并且与上面的错误一致,看起来该功能根本没有获得我的结构。 不知道这是否有帮助,但也许它会为某人带来一些东西。

根据请求,以下是接口结构。 C++:

MIDL_INTERFACE("d0e6eb07-7a62-11d2-8573-00c04fc21759")
IClientVirtualDeviceSet2 : public IClientVirtualDeviceSet
{
public:
    virtual HRESULT STDMETHODCALLTYPE CreateEx( 
        /* [in] */ LPCWSTR lpInstanceName,
        /* [in] */ LPCWSTR lpName,
        /* [in] */ struct VDConfig *pCfg) = 0;
    virtual HRESULT STDMETHODCALLTYPE OpenInSecondaryEx( 
        /* [in] */ LPCWSTR lpInstanceName,
        /* [in] */ LPCWSTR lpSetName) = 0;
};

还有我的 C# 版本:

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("d0e6eb07-7a62-11d2-8573-00c04fc21759")]
public interface IClientVirtualDeviceSet2
{
    void CreateEx([In, MarshalAs(UnmanagedType.LPWStr)]string instanceName,
        [In, MarshalAs(UnmanagedType.LPWStr)]string name,
        [In]ref VDConfig config);
    void OpenInSecondaryEx([MarshalAs(UnmanagedType.LPWStr)]string instanceName,
        [MarshalAs(UnmanagedType.LPWStr)]string lpSetName);
}

C# 中的结构对齐

对于遇到此问题的任何其他人来说,这就是答案:

正如您在vdi.h中看到的,IClientVirtualDeviceSet2"继承"自IClientVirtualDeviceSet。 就 COM 而言,没有接口继承这样的东西。

因此,当在IClientVirtualDeviceSet2上调用CreateEx时,您实际上是在IClientVirtualDeviceSet上调用Create(因为Create是组合IClientVirtualDeviceSet + IClientVirtualDeviceSet2 vtable中的第一个方法)。 这就是为什么您最终会得到无效的参数。

解决此问题的是创建一个包含所有方法的单个接口(IClientVirtualDeviceSet2),首先IClientVirtualDeviceSet,然后是两个IClientVirtualDeviceSet2方法(显然是按顺序排列的)。 这可确保在调用CreateEx()时,它使用正确的DispId

我相信您可能可以使用继承并相应地设置 DispIdAttribute:

https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dispidattribute(v=vs.110).aspx

但可能没有什么意义。