3

I have the following C++ to get information about a specific monitor using the EnumDisplaySettings WinAPI function.

#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;
}

I am trying to use the EnumDisplaySettings function in C#.

To do this, I have recreated DEVMODEW as a C# struct and pass it into the method.

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}");
    }
}

The issue is, when I run the code, I get the following exception.

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()

As far as I can tell, the issue is relating to

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

and this answer to another similar Stack Overflow question seems to suggest I can split the string up to fix the issue.

However, when I tried to do that so that the code so that the values were aligned with "DWORDS" I got the same error.

[FieldOffset(70)]
public char dmFormName1;

[FieldOffset(71)]
public char dmFormName2;

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

How can this problem be resolved whilst still meeting the same data structure as defined by DEVMODEW?


Full C# code

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;
        }
    }
}
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Dan
  • 7,286
  • 6
  • 49
  • 114
  • 1
    @canton7 I have tried that code but I get the exact same error with their `dmFormName` – Dan Aug 05 '19 at 14:00
  • 2
    Comparing your code to http://pinvoke.net/default.aspx/Structures/DEVMODE.html, I see that the `CharSet` is different. Could that be the cause? – canton7 Aug 05 '19 at 14:01
  • You're also not setting `dmSize` -- I doubt that that's the cause, but you'll need to set it at some point, so just check that it's not the cause. See that same pinvoke link for how to set it. – canton7 Aug 05 '19 at 14:03
  • @canton7 As far as I can tell it is not. The ANSI charset does not make any difference. I came across their code when searching for the solution to my issue and as far as I can tell, the main difference is I have used type name aliases and refactored out the structs instead of explicitly defining the position for each field. – Dan Aug 05 '19 at 14:06
  • @canton7 Ah thanks. I missed that. It is now set (and updated in the question) but the issue is still prevalent – Dan Aug 05 '19 at 14:08
  • Remove all the `FieldOffset` attributes. They serve no purpose. – David Heffernan Aug 05 '19 at 14:35
  • 1
    @DavidHeffernan I thought they were required to interact with the C++ unions? I.e. where `PrinterOnlyFields` and `DisplayOnlyFields` both have to be at field 44 – Dan Aug 05 '19 at 14:40
  • Yeah, you have to do the unions differently. My answer demonstrates. – David Heffernan Aug 05 '19 at 14:48
  • @DavidHeffernan Given that his struct is very similar to the one on pinvoke.net, and the one on pinvoke.net will have worked for multiple other people, I'm not convinced that that's the cause of his problem. – canton7 Aug 05 '19 at 14:49
  • @canton7 I am convinced. Perhaps you have not been experienced the low quality of pinvoke.net translations. – David Heffernan Aug 05 '19 at 14:50
  • We'll let the OP report back – canton7 Aug 05 '19 at 14:51
  • @canton7 We don't need to. I've already successfully called the function using the struct in my answer. And it's easy to see that the size of the struct in the Q is wrong. – David Heffernan Aug 05 '19 at 14:53

1 Answers1

3

You should remove all the FieldOffset attributes and implement the unions outside of the DeviceModeStruct structure. Like this:

[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;
}

I've not checked DeviceModeStruct carefully against the documentation, but I am sure you can do that. However, I can confirm that this struct definition at least has the correct size, when compared against the size of the struct defined in the C++ header file.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • That's great thanks for your help. My issue is now, that when I call the method, it returns, `Name: ?D Width: 0 Height: 0`. Is that happening for you? – Dan Aug 05 '19 at 14:57
  • No. I guess somewhere along the way you've screwed up the character set. Start from the code in the question. And change `DeviceModeStruct` to be exactly as in this answer. @canton7 is giving you poor advice regarding the `CharSet`. – David Heffernan Aug 05 '19 at 14:58
  • Hm, that is strange. The code is exactly as it is in your answer, I posted a copy of what is being run down at the bottom of my question (there is just that one file in my VS2019 project). If it is nothing obvious I will keep researching. Thanks again for your help – Dan Aug 05 '19 at 15:03
  • As I was trying to say, I did. After changing the charset, turns out CharSet.Auto needed to be `CharSet.Ansi`. Thanks again for your help anyways – Dan Aug 05 '19 at 15:08
  • 1
    Indeed, you've screwed up the character set. Do exactly what I said in my previous comment. And then look at the difference. You'll see that you changed the attribute on `EnumDisplaySettings`. – David Heffernan Aug 05 '19 at 15:08
  • 1
    NO! Don't make it `CharSet.Ansi`! Please no. It's 2019. Use Unicode. Just slow down and make everything have `CharSet.Auto`. Take your time. Don't change 5 things at once, it is just confusing. – David Heffernan Aug 05 '19 at 15:09
  • (Just to confirm that this works for me, if I fix the character encoding issues) – canton7 Aug 05 '19 at 15:13