在DEVMODE结构中与C#StructLayout.Explicit对齐错误

时间:2017-04-21 15:09:53

标签: c# interop pinvoke

我尝试使用 EnumDisplaySettings ,它使用DEVMODE结构作为结果结构。 DEVMODE 结构在内部使用了几个联合,这使得它在C#中使用起来有点复杂。联合用于数字显示或打印机。 StructLayout.Explicit 中的FieldOffsets应该可以使用联合。

以下是从 pinvoke.net 复制的结构。显然,其他一些人也遇到了这种结构的问题,并通过简单地使它们 StructLayout.Sequential 解决了工会,并创建了两个结构,一个用于显示,一个用于打印机。

抛出的异常是在字段偏移量70上,它告诉该字段未被另一个字段对齐或重叠。这是我不理解的,当然字段可以与所使用的显式布局重叠,并且字段偏移68之前的字段也很短,不能重叠到 fieldoffset 70 。这就是Microsoft定义的结构的工作方式。 将 fieldoffset从70移动到72时,它可以

所以我真的不是在解决我的问题,但我对这里发生的事情的背景感兴趣。

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public struct DEVMODE3
{
    public const int CCHDEVICENAME = 32;
    public const int CCHFORMNAME = 32;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
    [System.Runtime.InteropServices.FieldOffset(0)]
    public string dmDeviceName;
    [System.Runtime.InteropServices.FieldOffset(32)]
    public Int16 dmSpecVersion;
    [System.Runtime.InteropServices.FieldOffset(34)]
    public Int16 dmDriverVersion;
    [System.Runtime.InteropServices.FieldOffset(36)]
    public Int16 dmSize;
    [System.Runtime.InteropServices.FieldOffset(38)]
    public Int16 dmDriverExtra;
    [System.Runtime.InteropServices.FieldOffset(40)]
    public uint dmFields;

    [System.Runtime.InteropServices.FieldOffset(44)]
    Int16 dmOrientation;
    [System.Runtime.InteropServices.FieldOffset(46)]
    Int16 dmPaperSize;
    [System.Runtime.InteropServices.FieldOffset(48)]
    Int16 dmPaperLength;
    [System.Runtime.InteropServices.FieldOffset(50)]
    Int16 dmPaperWidth;
    [System.Runtime.InteropServices.FieldOffset(52)]
    Int16 dmScale;
    [System.Runtime.InteropServices.FieldOffset(54)]
    Int16 dmCopies;
    [System.Runtime.InteropServices.FieldOffset(56)]
    Int16 dmDefaultSource;
    [System.Runtime.InteropServices.FieldOffset(58)]
    Int16 dmPrintQuality;

    [System.Runtime.InteropServices.FieldOffset(44)]
    public POINTL dmPosition;
    [System.Runtime.InteropServices.FieldOffset(52)]
    public Int32 dmDisplayOrientation;
    [System.Runtime.InteropServices.FieldOffset(56)]
    public Int32 dmDisplayFixedOutput;

    [System.Runtime.InteropServices.FieldOffset(60)]
    public short dmColor; // See note below!
    [System.Runtime.InteropServices.FieldOffset(62)]
    public short dmDuplex; // See note below!
    [System.Runtime.InteropServices.FieldOffset(64)]
    public short dmYResolution;
    [System.Runtime.InteropServices.FieldOffset(66)]
    public short dmTTOption;
    [System.Runtime.InteropServices.FieldOffset(68)]
    public short dmCollate; // See note below!
    [System.Runtime.InteropServices.FieldOffset(70)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
    public string dmFormName;
    [System.Runtime.InteropServices.FieldOffset(102)]
    public Int16 dmLogPixels;
    [System.Runtime.InteropServices.FieldOffset(104)]
    public Int32 dmBitsPerPel;
    [System.Runtime.InteropServices.FieldOffset(108)]
    public Int32 dmPelsWidth;
    [System.Runtime.InteropServices.FieldOffset(112)]
    public Int32 dmPelsHeight;
    [System.Runtime.InteropServices.FieldOffset(116)]
    public Int32 dmDisplayFlags;
    [System.Runtime.InteropServices.FieldOffset(116)]
    public Int32 dmNup;
    [System.Runtime.InteropServices.FieldOffset(120)]
    public Int32 dmDisplayFrequency;
}

1 个答案:

答案 0 :(得分:4)

70是正确的偏移量。 CharSet = CharSet.Auto是102,你应该总是喜欢它。

问题是代码不必要地使用[FieldOffset]。这不只是在封送结构中设置字段的偏移量,它还会减少托管结构中字段的偏移量。

这是一个很大的问题,70因为错误对齐内存中的字符串而无效。 .NET内存模型要求在32位模式下,引用类型引用需要与4的倍数对齐。垃圾收集器真的讨厌错位字段,对象引用需要能够原子更新和错位参考文献不能保证。它们可能跨越L1高速缓存线,这需要两个存储器总线周期来设置该值。导致撕裂,只看到更新的一部分,这是一个无法调试的问题。

删除所有[FieldOffset]属性或从Reference Source复制/粘贴。另一个优点是,如果您的程序以64位模式运行,您可以感觉它仍能正常工作。