未正确对齐的字段

Incorrectly Aligned Field

我有以下 C++ 可以使用 EnumDisplaySettings WinAPI 函数获取有关特定监视器的信息。

#include <iostream>
#include <Windows.h>

int main()
{
    DEVMODE dm;
    dm.dmSize = sizeof dm;

    EnumDisplaySettings(L"\\.\DISPLAY1", ENUM_CURRENT_SETTINGS, &dm);
    std::wcout << "Name: " << dm.dmDeviceName << std::endl;
    std::wcout << "Width: " << dm.dmPelsWidth << std::endl;
    std::wcout << "Height: " << dm.dmPelsHeight << std::endl;
}

我正在尝试使用 C# 中的 EnumDisplaySettings 函数。

为此,我重新创建了 DEVMODEW 作为 C# 结构并将其传递到方法中。

static void Main()
{
    DeviceModeStruct deviceMode = new DeviceModeStruct();
    deviceMode.dmSize = (ushort)Marshal.SizeOf(deviceMode);

    bool successfullyGotScale = EnumDisplaySettings("\\.\DISPLAY1",
        ENUM_CURRENT_SETTINGS,
        ref deviceMode);

    if (successfullyGotScale)
    {
        Console.WriteLine($@"Name: {deviceMode.dmDeviceName}");
        Console.WriteLine($@"Width: {deviceMode.dmPelsWidth}");
        Console.WriteLine($@"Height: {deviceMode.dmPelsHeight}");
    }
}

问题是,当我 运行 代码时,出现以下异常。

Unhandled Exception: System.TypeLoadException: Could not load type 'DeviceModeStruct'
from assembly 'DevModeSo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
because it contains an object field at offset 70 that is incorrectly aligned or
overlapped by a non-object field.
    at DevModeSo.Program.Main()

据我所知,问题与

有关
[FieldOffset(70), MarshalAs(UnmanagedType.ByValTStr, SizeConst = STRING_SIZE)]
public string dmFormName;

this answer 另一个类似的 Stack Overflow 问题似乎表明我可以拆分字符串来解决问题。

但是,当我尝试这样做以便代码使值与 "DWORDS" 对齐时,我遇到了同样的错误。

[FieldOffset(70)]
public char dmFormName1;

[FieldOffset(71)]
public char dmFormName2;

[FieldOffset(72), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
public string dmFormName3;

如何解决这个问题,同时仍然满足 DEVMODEW 定义的相同数据结构?


完整的 C# 代码

using System;
using System.Runtime.InteropServices;

namespace DevModeSo
{
    class Program
    {
        private const int ENUM_CURRENT_SETTINGS = -1;

        static void Main()
        {
            DeviceModeStruct deviceMode = new DeviceModeStruct();
            deviceMode.dmSize = (ushort)Marshal.SizeOf(deviceMode);

            bool successfullyGotScale = EnumDisplaySettings("\\.\DISPLAY1",
                ENUM_CURRENT_SETTINGS,
                ref deviceMode);

            if (successfullyGotScale)
            {
                Console.WriteLine($@"Name: {deviceMode.dmDeviceName}");
                Console.WriteLine($@"Width: {deviceMode.dmPelsWidth}");
                Console.WriteLine($@"Height: {deviceMode.dmPelsHeight}");
            }
        }

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool EnumDisplaySettings(string deviceName,
            int modeNum,
            ref DeviceModeStruct deviceMode);

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
        struct DeviceModeStruct
        {
            private const int STRING_SIZE = 32;

            [FieldOffset(0), MarshalAs(UnmanagedType.ByValTStr, SizeConst = STRING_SIZE)]
            public string dmDeviceName;

            [FieldOffset(32)] public ushort dmSpecVersion;

            [FieldOffset(34)] public ushort dmDriverVersion;

            [FieldOffset(36)] public ushort dmSize;

            [FieldOffset(38)] public ushort dmDriverExtra;

            [FieldOffset(40)] public uint dmFields;

            [FieldOffset(44)] public PrinterOnlyFields printerMode;

            [FieldOffset(44)] public DisplayOnlyFields displayMode;

            [FieldOffset(60)] public short dmColor;

            [FieldOffset(62)] public short dmDuplex;

            [FieldOffset(64)] public short dmYResolution;

            [FieldOffset(66)] public short dmTTOption;

            [FieldOffset(68)] public short dmCollate;

            [FieldOffset(70), MarshalAs(UnmanagedType.ByValTStr, SizeConst = STRING_SIZE)]
            public string dmFormName;

            [FieldOffset(102)] public ushort dmLogPixels;

            [FieldOffset(104)] public uint dmBitsPerPel;

            [FieldOffset(108)] public uint dmPelsWidth;

            [FieldOffset(112)] public uint dmPelsHeight;

            [FieldOffset(116)] public uint dmDisplayFlags;

            [FieldOffset(116)] public uint dmNup;

            [FieldOffset(120)] public uint dmDisplayFrequency;

            [FieldOffset(124)] public uint dmICMMethod;

            [FieldOffset(128)] public uint dmICMIntent;

            [FieldOffset(132)] public uint dmMediaType;

            [FieldOffset(136)] public uint dmDitherType;

            [FieldOffset(140)] public uint dmReserved1;

            [FieldOffset(144)] public uint dmReserved2;

            [FieldOffset(148)] public uint dmPanningWidth;

            [FieldOffset(152)] public uint dmPanningHeight;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct PrinterOnlyFields
        {
            public short dmOrientation;
            public short dmPaperSize;
            public short dmPaperLength;
            public short dmPaperWidth;
            public short dmScale;
            public short dmCopies;
            public short dmDefaultSource;
            public short dmPrintQuality;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct Point
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct DisplayOnlyFields
        {
            public Point dmPosition;
            public uint dmDisplayOrientation;
            public uint dmDisplayFixedOutput;
        }
    }
}

您应该删除所有 FieldOffset 属性并在 DeviceModeStruct 结构之外实施联合。像这样:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
struct DeviceModeUnion
{
    [FieldOffset(0)]
    PrinterOnlyFields Printer;
    [FieldOffset(0)]
    Point Position;
    [FieldOffset(0)]
    DisplayOnlyFields Display;
}

....

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct DeviceModeStruct
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string dmDeviceName;
    public ushort dmSpecVersion;
    public ushort dmDriverVersion;
    public ushort dmSize;
    public ushort dmDriverExtra;
    public uint dmFields;
    public DeviceModeUnion union;
    public short dmColor;
    public short dmDuplex;
    public short dmYResolution;
    public short dmTTOption;
    public short dmCollate;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string dmFormName;
    public ushort dmLogPixels;
    public uint dmBitsPerPel;
    public uint dmPelsWidth;
    public uint dmPelsHeight;
    public uint dmDisplayFlags;
    public uint dmDisplayFrequency;
    public uint dmICMMethod;
    public uint dmICMIntent;
    public uint dmMediaType;
    public uint dmDitherType;
    public uint dmReserved1;
    public uint dmReserved2;
    public uint dmPanningWidth;
    public uint dmPanningHeight;
}

我没有根据 documentation 仔细检查 DeviceModeStruct,但我相信你可以做到。但是,与 C++ 头文件中定义的结构的大小相比,我可以确认此结构定义至少具有正确的大小。