Search code examples
c#winapipinvokenull-string

EnumDisplayDevices - passing null causes error


The MSDN documentation for EnumDisplayDevices states that passing NULL as the first parameter to the function returns information about the display adaptor(s) on the machine (passing a string returns information about the device with that name).

Various C# examples that I have seen on-line pass null to the function as follows:

result = EnumDisplayDevices(null, devNum, ref dd, flags);

However, when I pass null as the first parameter, I get a System.AccessViolationException with the message "Attempted to read or write protected memory".

If I change null to any random non-null string (eg, "hello"), then the function call succeeds (I just don't get any device information because there isn't a device called "hello").

So how do I pass null as the first parameter to the EnumDisplayDevices function? (I need to be able to pass names in subsequent calls to the function)

Relevant snippets of my code follow:

    [DllImport("user32.dll")]
    static extern bool EnumDisplayDevices(
        string lpDevice,
        uint iDevNum,
        ref DISPLAY_DEVICE lpDisplayDevice,
        uint dwFlags
        );

    [StructLayout(LayoutKind.Sequential)]
    public struct DISPLAY_DEVICE
    {
        public int cb;
        public string DeviceName;
        public string DeviceString;
        public int StateFlags;
        public string DeviceID;
        public string DeviceKey;
    }

    #region Public Interface
    public ObservableCollection<DisplayDevice> LoadDisplayDevices()
    {
        ObservableCollection<DisplayDevice> displayDevices = new ObservableCollection<DisplayDevice>();

        uint devNum = 0;
        uint flags = 0;
        bool result = false;

        DISPLAY_DEVICE dd = new DISPLAY_DEVICE();
        dd.cb = (int)Marshal.SizeOf(dd);

        try
        {
            result = EnumDisplayDevices(null, devNum, ref dd, flags);

            ...

Solution

  • Original definition from MSDN:

    typedef struct _DISPLAY_DEVICE {
      DWORD cb;
      TCHAR DeviceName[32];
      TCHAR DeviceString[128];
      DWORD StateFlags;
      TCHAR DeviceID[128];
      TCHAR DeviceKey[128];
    } DISPLAY_DEVICE, *PDISPLAY_DEVICE;
    

    All string fields are defined as fixed-size arrays. Your DISPLAY_DEVICE structure definition contains multiple string values without instructions how to marshal them, so they will be passed as pointers. You'll need to use MarshalAsAttribute to fix this:

    [StructLayout(LayoutKind.Sequential)]
    public struct DISPLAY_DEVICE
    {
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        public int StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }