【问题标题】:Incorrectly Aligned Field不正确对齐的字段
【发布时间】:2019-12-13 01:38:11
【问题描述】:

我有以下 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;
        }
    }
}

【问题讨论】:

  • @canton7 我已经尝试过该代码,但我得到了与他们的dmFormName完全相同的错误
  • 将您的代码与pinvoke.net/default.aspx/Structures/DEVMODE.html 进行比较,我发现CharSet 不同。会不会是这个原因?
  • 你也没有设置dmSize——我怀疑这是原因,但你需要在某个时候设置它,所以只需检查它是否不是原因。请参阅相同的 pinvoke 链接了解如何设置它。
  • @canton7 据我所知不是。 ANSI 字符集没有任何区别。我在搜索我的问题的解决方案时遇到了他们的代码,据我所知,主要区别在于我使用了类型名称别名并重构了结构,而不是显式定义每个字段的位置。
  • @DavidHeffernan 我认为他们需要与 C++ 联合进行交互? IE。其中PrinterOnlyFieldsDisplayOnlyFields 都必须在字段 44

标签: c# c++ .net winapi interop


【解决方案1】:

您应该删除所有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;
}

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

【讨论】:

  • 非常感谢您的帮助。我现在的问题是,当我调用该方法时,它会返回Name: ?D Width: 0 Height: 0。你会遇到这种情况吗?
  • 没有。我猜你在某个地方搞砸了字符集。从问题中的代码开始。并将DeviceModeStruct 更改为与此答案完全相同。 @canton7 给你关于 CharSet 的糟糕建议。
  • 嗯,这很奇怪。该代码与您的答案完全相同,我在我的问题底部发布了一份正在运行的内容的副本(我的 VS2019 项目中只有一个文件)。如果没有什么明显的,我会继续研究。再次感谢您的帮助
  • 确实,你搞砸了字符集。完全按照我在之前的评论中所说的去做。然后看看区别。您会看到您更改了EnumDisplaySettings 上的属性。
  • 不!不要让它CharSet.Ansi!请不。现在是 2019 年。使用 Unicode。只要放慢速度,让一切都拥有CharSet.Auto。慢慢来。不要一次更改 5 件事,这只会令人困惑。
猜你喜欢
  • 2013-06-19
  • 1970-01-01
  • 2021-08-08
  • 2013-02-06
  • 2019-12-28
  • 2020-10-05
  • 1970-01-01
  • 2014-01-04
  • 1970-01-01
相关资源
最近更新 更多