Results 1 to 22 of 22

Thread: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

  1. #1

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Resolved [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    I thought I'd start a new thread to get out of ucanbizon's way. He seems to be getting what he needs.

    Also, just as an FYI, this is all just out of curiosity for me, as I don't have an immediate need. But, I suppose I just love this stuff.

    I'm just trying to develop a relationship between the display devices we can get with SetupDiEnumDeviceInfo versus the display devices we can get with EnumDisplayDevices. Specifically, I'd like to get the hMonitor value when SetupDiEnumDeviceInfo is used.

    Now, dilettante correctly pointed out that this won't always be possible. We may be viewing some external app via RDT or other remote method. In these cases, we certainly won't have a hMonitor value for whatever comes back from SetupDiEnumDeviceInfo. But, I believe, these cases are the exception rather than the rule. I believe most of us run with monitors plugged directly into our "boxes" and are running apps in the memory (and on the CPUs) of those boxes. In these cases, it seems that we should be able to get the hMonitor value for each "active" monitor returned from SetupDiEnumDeviceInfo.

    This has some nice advantages (over digging through the registry for the EDID information with EnumDisplayDevices. For one, the ordinal numbers of the monitors are the same as they are in control panel. This will help the user to understand what's going on.

    However, I've struggled to accomplish this objective (getting hMonitor for a monitor returned from SetupDiEnumDeviceInfo) for much of the morning. One webpage talks about a relationship between hMonitor and hDevInfo. However, I'm now convinced this isn't correct, as I couldn't make the "correlation" work on my system.

    Now, my system is a rather ideal test environment. It's a high-powered laptop capable of driving two full 4K monitors worth of desktop. Currently, I have three external monitors plugged into it (two identical ASUS monitors, and a third DELL monitor), with the laptop's internal monitor disabled. In addition, I travel a lot (taking only my laptop with me), and I almost always plug one or two external monitors into my laptop when I'm "on location". In other words, probably around 20 other monitors have been plugged into my laptop, making my registry in these monitor areas a bit of a mess. But that's perfect for testing.

    Now, SetupDiEnumDeviceInfo seems to work as expected. It returns all four of my monitors (including the disabled one). The information about them all comes from the following registry area:

    Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY\

    Now, I can also get to that area by using EnumDisplayDevices specifying the lpDevice, and then parsing DeviceID out of the DISPLAY_DEVICEW structure. However, when I use this method, my two ASUS monitors return the same registry key. This is "sort of" okay, but one of them will return the wrong monitor serial number, which is annoying.

    I also tried using EnumDisplayDevices in a more conventional way. First, I tried specifying iDevNum, and enumerating by ordinal number. In the returned DeviceKey, I was directed to the following registry area (via DeviceKey in the returned DISPLAY_DEVICEW structure):

    Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video\

    And I also examined through EnumDisplayDevices using the device name, again examining DeviceKey. This directed me to the following registry area:

    Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\

    After staring until I'm cross-eyed, I can't find the correlation, but I just know there has to be one.

    Any ideas?

    Elroy

    EDIT1: Correction: Actually, the ordinal numbering from neither EnumDisplayDevices nor SetupDiEnumDeviceInfo seems to match the ordinal numbering from control panel (or, as it were, Win10 Settings). That's a secondary concern, but it sure would be nice to get that sorted (pun intended) as well.
    Last edited by Elroy; Feb 21st, 2018 at 03:15 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  2. #2
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    DeviceKey is reserved and undocumented, it is meant to be opaque to user programs. Do not use it.

    DeviceName can be either the adapter device or the monitor device. It is meant only for display purposes.

    Neither should be used for registry spelunking.


    As I told you in the other thread there can be "software monitors" in the sense of a GDI Monitor object, the things enumerated by EnumDisplayDevices. One example being an RDP session. These do not have display adapter driver entries in the registry at all, at least not with EDID data. Similar results would likely be seen for devices like DisplayLink monitors that operate over USB, Ethernet, or even WiFi. These are becoming far more common than in the early years, mainly as secondary monitors for laptops.

  3. #3

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Dilettante, I truly appreciate your expertise on this. But I'd still like to work on correlating hMonitor values with EDID information (including monitor serial numbers). And it seems this should be possible in any/all cases where we've got monitors plugged directly into a computer (and it's a fairly recent monitor).

    I will agree that this is all a bit of a mess, but Windows is somehow keeping it all straight, so we should be able to as well.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  4. #4
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    I'm not sure Windows cares. The stuff it cares about it probably gets from the display drivers using calls that aren't exposed to user programs.

  5. #5

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Well, this is what gives me hope:

    Set this flag to EDD_GET_DEVICE_INTERFACE_NAME (0x00000001) to retrieve the device interface name for GUID_DEVINTERFACE_MONITOR, which is registered by the operating system on a per monitor basis. The value is placed in the DeviceID member of the DISPLAY_DEVICE structure returned in lpDisplayDevice. The resulting device interface name can be used with SetupAPI functions and serves as a link between GDI monitor devices and SetupAPI monitor devices.
    Pulled from here: https://msdn.microsoft.com/en-us/lib...(v=vs.85).aspx
    The MSDN page for EnumDisplayDevices. Emphasis mine in the quote.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  6. #6
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Sounds like you have it. I wonder what you get for software monitors that way?

  7. #7

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Well, dilettante, you see, I don't quite have it. For my two Asus monitors, they rather consistently return hMonitor values of:

    65537
    and
    65541

    I feed each of those into GetMonitorInfoEx, and then examine the szDevice (from MONITORINFOEX structure), and I see:

    \\.\DISPLAY2
    \\.\DISPLAY3

    All is copacetic so for. However, I, in turn, feed those device names into EnumDisplayDevices, and then examine DeviceID (from DISPLAY_DEVICEW structure), and I see the following:

    \\?\DISPLAY#AUO119D#5&2e632891&0&UID4357#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
    \\?\DISPLAY#AUO119D#5&2e632891&0&UID4357#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}

    They're identical, and don't help me to get to the specific EDID area for the monitor. Sure, they're the correct model monitor (the AUO119D and 5&2e632891&0&UID4357 pieces). However, one of the two serial numbers will be wrong. Here's a screenshot of a piece of my "Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY" registry area:

    Name:  edidreg.png
Views: 1434
Size:  7.1 KB

    If things were working correctly, one of those should be pointing to the "1&8713bca&0&UID0" area, rather than both pointing to the "5&2e632891&0&UID4357" area. That's the only thing that's got me hung up on this.

    Take Care,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  8. #8
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Yeah, I dug through the most recent DDK that I have hoping there might be something there to go the other direction. For example some sort of property that might be queried to return the current hMonitor value for the specific device.

    But it really doesn't seem to have access to anything but what is in the registry, which seems to really be static data used to initialize the adapter driver.

  9. #9

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    I'm also having a problem understanding how to complete the bridge, even if DeviceID had the correct data in it. In other words, there are two pieces of the bridge still broken for me. Dilettante, I suspect you can fix one of these though. I studied it for a bit (and will continue), but I haven't gotten it going yet. To illustrate, here's your code (reworked in my humble attempt to simplify).

    Here's a BAS piece:
    Code:
    
    Option Explicit
    '
    Private Enum DIGCFS
        ' Flags controlling what is included in the device information set built by SetupDiGetClassDevs:
        DIGCF_DEFAULT = &H1& 'Only valid with DIGCF_DEVICEINTERFACE.
        DIGCF_PRESENT = &H2&
        DIGCF_ALLCLASSES = &H4&
        DIGCF_PROFILE = &H8&
        DIGCF_DEVICEINTERFACE = &H10&
    End Enum
    '
    Private Enum SPRDPS
        SPDRP_DEVICEDESC = &H0&                  ' DeviceDesc.
        SPDRP_HARDWAREID = &H1&                  ' HardwareID.
        SPDRP_COMPATIBLEIDS = &H2&               ' CompatibleIDs.
        SPDRP_UNUSED0 = &H3&                     ' unused.
        SPDRP_SERVICE = &H4&                     ' Service.
        SPDRP_UNUSED1 = &H5&                     ' unused.
        SPDRP_UNUSED2 = &H6&                     ' unused.
        SPDRP_CLASS = &H7&                       ' Class (R--tied to ClassGUID)
        SPDRP_CLASSGUID = &H8&                   ' ClassGUID.
        SPDRP_DRIVER = &H9&                      ' Driver.
        SPDRP_CONFIGFLAGS = &HA&                 ' ConfigFlags.
        SPDRP_MFG = &HB&                         ' Mfg.
        SPDRP_FRIENDLYNAME = &HC&                ' FriendlyName.
        SPDRP_LOCATION_INFORMATION = &HD&        ' LocationInformation.
        SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = &HE& ' PhysicalDeviceObjectName.
        SPDRP_CAPABILITIES = &HF&                ' Capabilities.
        SPDRP_UI_NUMBER = &H10&                  ' UiNumber.
        SPDRP_UPPERFILTERS = &H11&               ' UpperFilters.
        SPDRP_LOWERFILTERS = &H12&               ' LowerFilters.
        SPDRP_BUSTYPEGUID = &H13&                ' BusTypeGUID.
        SPDRP_LEGACYBUSTYPE = &H14&              ' LegacyBusType.
        SPDRP_BUSNUMBER = &H15&                  ' BusNumber.
        SPDRP_ENUMERATOR_NAME = &H16&            ' Enumerator Name.
        SPDRP_SECURITY = &H17&                   ' Security (binary form).
        SPDRP_SECURITY_SDS = &H18&               ' Security (SDS form).
        SPDRP_DEVTYPE = &H19&                    ' Device Type.
        SPDRP_EXCLUSIVE = &H1A&                  ' Device is exclusive-access.
        SPDRP_CHARACTERISTICS = &H1B&            ' Device Characteristics.
        SPDRP_ADDRESS = &H1C&                    ' Device Address.
        SPDRP_UI_NUMBER_DESC_FORMAT = &H1D&      ' UiNumberDescFormat.
        SPDRP_DEVICE_POWER_DATA = &H1E&          ' Device Power Data.
        SPDRP_REMOVAL_POLICY = &H1F&             ' Removal Policy.
        SPDRP_REMOVAL_POLICY_HW_DEFAULT = &H20&  ' Hardware Removal Policy.
        SPDRP_REMOVAL_POLICY_OVERRIDE = &H21&    ' Removal Policy Override.
        SPDRP_INSTALL_STATE = &H22&              ' Device Install State.
        SPDRP_LOCATION_PATHS = &H23&             ' Device Location Paths.
        '
        [_SPDRP_MAXIMUM_PROPERTY] = &H24&        ' Upper bound on ordinals.
    End Enum
    '
    Private Enum REG_TYPES
        REG_NONE = 0
        REG_SZ = 1
        REG_EXPAND_SZ = 2
        REG_BINARY = 3
        REG_DWORD = 4
        REG_DWORD_LITTLE_ENDIAN = 4
        REG_DWORD_BIG_ENDIAN = 5
        REG_MULTI_SZ = 7
        REG_QWORD = 11
        REG_QWORD_LITTLE_ENDIAN = 11
    End Enum
    '
    Private Enum DICS_FLAGS
        DICS_FLAG_GLOBAL = 1
        DICS_FLAG_CONFIGSPECIFIC = 2
        DICS_FLAG_CONFIGGENERAL = 4
    End Enum
    '
    Private Enum DIREG_TYPES
        DIREG_DEV = 1
        DIREG_DRV = 2
        DIREG_BOTH = 4
    End Enum
    '
    Private Const READ_CONTROL          As Long = &H20000
    Private Const STANDARD_RIGHTS_READ  As Long = READ_CONTROL
    Private Const SYNCHRONIZE           As Long = &H100000
    '
    Private Enum REGSAM
        KEY_QUERY_VALUE = &H1&
        KEY_SET_VALUE = &H2&
        KEY_CREATE_SUB_KEY = &H4&
        KEY_ENUMERATE_SUB_KEYS = &H8&
        KEY_NOTIFY = &H10&
        KEY_CREATE_LINK = &H20&
        KEY_WOW64_32KEY = &H200&
        KEY_WOW64_64KEY = &H100&
        KEY_WOW64_RES = &H300&
        KEY_READ = ((STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY) And Not SYNCHRONIZE)
    End Enum
    '
    Private Type GUID
        Data1 As Long
        Data2 As Integer
        Data3 As Integer
        Data4(7) As Byte
    End Type
    '
    Private Type SP_DEVINFO_DATA
        cbSize As Long
        ClassGuid As GUID
        DevInst As Long
        Reserved As Long
    End Type
    '
    Private Const INVALID_HANDLE_VALUE      As Long = -1&
    Private Const ERROR_INVALID_DATA        As Long = 13&
    Private Const ERROR_INSUFFICIENT_BUFFER As Long = 122&
    Private Const ERROR_NO_MORE_ITEMS       As Long = 259&
    Private Const ERROR_KEY_DOES_NOT_EXIST  As Long = &HE0000204
    '
    Private Const GUID_CLASS_MONITOR_STRING As String = "{4d36e96e-e325-11ce-bfc1-08002be10318}"
    '
    Private Declare Function CLSIDFromString Lib "ole32" (ByVal lpsz As Long, ByRef clsid As GUID) As Long
    Private Declare Function RegCloseKey Lib "advapi32" (ByVal hKey As Long) As Long
    Private Declare Function RegQueryValueEx Lib "advapi32" Alias "RegQueryValueExW" (ByVal hKey As Long, ByVal lpValueName As Long, ByVal lpReserved As Long, ByRef lpType As Long, ByVal lpData As Long, ByRef lpcbData As Long) As Long
    '
    Private Declare Function SetupDiGetClassDevs Lib "setupapi" Alias "SetupDiGetClassDevsW" (ByVal pClassGuid As Long, ByVal lpEnumerator As Long, ByVal hWndParent As Long, ByVal Flags As DIGCFS) As Long
    Private Declare Function SetupDiEnumDeviceInfo Lib "setupapi" (ByVal hDeviceInfoSet As Long, ByVal MemberIndex As Long, ByRef DeviceInfoData As SP_DEVINFO_DATA) As Long
    Private Declare Function SetupDiOpenDevRegKey Lib "setupapi" (ByVal hDeviceInfoSet As Long, ByRef DeviceInfoData As SP_DEVINFO_DATA, ByVal Scope As DICS_FLAGS, ByVal HwProfile As Long, ByVal KeyType As DIREG_TYPES, ByVal samDesired As REGSAM) As Long
    Private Declare Function SetupDiDestroyDeviceInfoList Lib "setupapi" (ByVal hDeviceInfoSet As Long) As Boolean
    '
    Public Type MonitorDataType
        ' This is used to return the monitor info.
        ' As more of the EDID is parsed, additional fields could be added to this.
        hMonitor            As Long
        MakeModel           As String
        SerialNumber        As Long
        Manufactured        As String
        EDIDVersion         As String
        WidthMm             As Integer
        HeightMm            As Integer
        HorizPxNative       As Integer
        VertPxNative        As Integer
    End Type
    '
    
    Public Function GetMonitorData(Data() As MonitorDataType) As Boolean ' Data() is ONE based.
        ' Returns True if successful.
        '
        Dim GUID_CLASS_MONITOR As GUID
        Dim hDeviceInfoSet As Long
        Dim hKey As Long
        '
        Dim SpDevInfo As SP_DEVINFO_DATA
        Dim RegType As REG_TYPES
        '
        Dim DataSize As Long
        Dim EDID() As Byte
        Dim IntVal As Integer
        '
        Dim DataCount As Long           ' Doubles as index for SetupDiEnumDeviceInfo (but zero based when used there).
        '
        ' Get things rolling.
        Erase Data
        CLSIDFromString StrPtr(GUID_CLASS_MONITOR_STRING), GUID_CLASS_MONITOR
        '
        hDeviceInfoSet = SetupDiGetClassDevs(VarPtr(GUID_CLASS_MONITOR), 0&, 0&, DIGCF_PRESENT)                             ' Be sure to close this.
        If hDeviceInfoSet = INVALID_HANDLE_VALUE Then Exit Function
        '
        SpDevInfo.cbSize = LenB(SpDevInfo)
        '
        Do
            ' Get the first/next monitor's SP_DEVINFO_DATA.
            If SetupDiEnumDeviceInfo(hDeviceInfoSet, DataCount, SpDevInfo) = 0& Then                                        ' Returns 1& if successful.
                If Err.LastDllError = ERROR_NO_MORE_ITEMS Then Exit Do
                GoTo ErrorSomewhere
            End If
            '
            ' Using SP_DEVINFO_DATA, get the first/next monitor's hKey into Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY\ area.
            hKey = SetupDiOpenDevRegKey(hDeviceInfoSet, SpDevInfo, DICS_FLAG_GLOBAL, 0&, DIREG_DEV, KEY_READ)
            If hKey = INVALID_HANDLE_VALUE Then GoTo ErrorSomewhere
            '
            ' Get the EDID's size into DataSize.
            DataSize = 0&
            If RegQueryValueEx(hKey, StrPtr("EDID"), 0&, RegType, 0&, DataSize) <> 0& Then GoTo ErrorSomewhere               ' Returns 0& if successful.
            '
            ' Fetch the EDID.
            ReDim EDID(DataSize - 1)
            If RegQueryValueEx(hKey, StrPtr("EDID"), 0&, RegType, VarPtr(EDID(0)), DataSize) <> 0& Then GoTo ErrorSomewhere  ' Returns 0& if successful.
            '
            ' Done with this registry's key.
            RegCloseKey hKey
            '
            ' Now parse the EDID for this monitor.
            DataCount = DataCount + 1
            ReDim Preserve Data(1 To DataCount)
            '
            IntVal = CInt(EDID(9)) Or CInt(EDID(8)) * &H100 'Big endian.
            Data(DataCount).MakeModel = ChrW$((IntVal And &H7C00) \ &H400 + &H40) _
                                      & ChrW$((IntVal And &H3E0) \ &H20 + &H40) _
                                      & ChrW$((IntVal And &H1F) + &H40) _
                                      & Right$("000" & Hex$(CInt(EDID(10)) Or CInt(EDID(11)) * &H100&), 4)
            '
            Data(DataCount).SerialNumber = CLng(EDID(12)) _
                                        Or CLng(EDID(13)) * &H100& _
                                        Or CLng(EDID(14)) * &H10000 _
                                        Or (CLng(EDID(15)) And &H7F&) * &H1000000
            If (EDID(15) And &H80) <> 0 Then Data(DataCount).SerialNumber = Data(DataCount).SerialNumber Or &H80000000
            '
            If EDID(16) = 255 Then
                Data(DataCount).Manufactured = CStr(1990 + EDID(17))
            Else
                Data(DataCount).Manufactured = CStr(1990 + EDID(17)) & " week " & CStr(EDID(16))
            End If
            '
            Data(DataCount).EDIDVersion = CStr(EDID(18)) & "." & CStr(EDID(19))
            Data(DataCount).WidthMm = ((EDID(68) And &HF0) * &H10) + EDID(66)
            Data(DataCount).HeightMm = ((EDID(68) And &HF) * &H100) + EDID(67)
            Data(DataCount).HorizPxNative = CInt(EDID(56)) + CInt(EDID(58) \ &H10) * &H100
            Data(DataCount).VertPxNative = CInt(EDID(59)) + CInt(EDID(61) \ &H10) * &H100
        Loop
        '
        ' Clean-up and get out.
        SetupDiDestroyDeviceInfoList hDeviceInfoSet
        GetMonitorData = True
        '
        Exit Function
        '
    ErrorSomewhere:         ' Just return undimensioned array.
        RegCloseKey hKey
        SetupDiDestroyDeviceInfoList hDeviceInfoSet
        Erase Data
    End Function
    
    
    And here's a piece for Form1 (with a List1 ListBox on it) for testing:
    Code:
    
    Option Explicit
    '
    
    Private Sub Form_Load()
        Dim Monitors() As MonitorDataType
        Dim i As Long
        '
        List1.Font.Name = "Courier New"
        List1.Font.Size = 10
        '
        If Not GetMonitorData(Monitors()) Then
            MsgBox "Error getting data."
        Else
            '
            ' This is the loop that reveals data about the monitors.
            For i = LBound(Monitors) To UBound(Monitors)
                List1.AddItem RPad(Monitors(i).MakeModel, 12) & _
                              RPad(Format$(Monitors(i).SerialNumber), 20) & _
                              RPad(Monitors(i).Manufactured, 20) & _
                              RPad("v" & Monitors(i).EDIDVersion, 10) & _
                              RPad(Format$(Monitors(i).WidthMm) & "mm", 10) & _
                              RPad(Format$(Monitors(i).HeightMm) & "mm", 10) & _
                              RPad(Format$(Monitors(i).HorizPxNative), 10) & _
                              RPad(Format$(Monitors(i).VertPxNative), 10)
    
            Next i
        End If
    End Sub
    
    Private Function RPad(s As String, iPad As Long) As String
        RPad = Left$(s & Space$(iPad), iPad)
    End Function
    
    
    Now, it's the hKey in the GetMonitorData that has me a bit stumped. I've tried a couple of "standard" registry approaches to getting the named-key for that hKey handle, but to no avail. I would need the named-key to complete the bridge (even if I do get the DeviceID problem sorted.

    Also, I thought it would be courteous to post some VB6 code for the other side as well (i.e., code to do what I outlined in post #7). I'll pull that together after I eat breakfast.

    Y'all Take Care,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  10. #10
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    I'm not certain what you want help with. Maybe this is of use to you?

    Code:
    Private Declare Function ZwQueryKey Lib "ntdll" ( _
        ByVal KeyHandle As Long, _
        ByVal KeyInformationClass As KEY_INFORMATION_CLASS, _
        ByVal pKeyInformation As Long, _
        ByVal Length As Long, _
        ByRef ResultLength As Long) As NTSTATUS
    Name:  sshot.png
Views: 1470
Size:  3.8 KB
    Attached Files Attached Files

  11. #11

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Hmmm, ok, I'm about at my wits end. Here's code coming from the EnumDisplayDevices direction:

    Stuff for BAS module:
    Code:
    
    Option Explicit
    '
    Private Type RECT
      Left As Long
      Top As Long
      Right As Long     ' This is +1 (right - left = width)
      Bottom As Long    ' This is +1 (bottom - top = height)
    End Type
    Private Type MONITORINFOEX
        cbSize As Long
        rcMonitor As RECT
        rcWork As RECT
        dwFlags As Long
        szDevice As String * 32
    End Type
    Private Type DISPLAY_DEVICEW
       cbSize As Long
       DeviceName(0 To 63) As Byte
       DeviceString(0 To 255) As Byte
       StateFlags As Long
       DeviceID(0 To 255) As Byte
       DeviceKey(0 To 255) As Byte
    End Type
    '
    Dim hTemp() As Long
    '
    Private Declare Function EnumDisplayMonitors Lib "user32" (ByVal hdc As Long, lprcClip As Any, ByVal lpfnEnum As Long, dwData As Long) As Long
    Private Declare Function GetMonitorInfoEx Lib "user32.dll" Alias "GetMonitorInfoA" (ByVal hMonitor As Long, ByRef lpmi As MONITORINFOEX) As Long
    Private Declare Function EnumDisplayDevices Lib "user32" Alias "EnumDisplayDevicesW" (ByVal lpDevice As Long, ByVal iDevNum As Long, ByRef lpDisplayDevice As DISPLAY_DEVICEW, ByVal dwFlags As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal lpDst As Long, ByVal lpSrc As Long, ByVal byteLength As Long)
    '
    
    Public Function GetMonitorHandles() As Long()  ' ONE based.
        Dim dwData As Long
        '
        Erase hTemp
        EnumDisplayMonitors 0&, ByVal 0&, AddressOf MonitorHandleEnum, dwData
        GetMonitorHandles = hTemp
        Erase hTemp
    End Function
    
    Private Function MonitorHandleEnum(ByVal hMonitor As Long, ByVal hdcMonitor As Long, uRect As RECT, dwData As Long) As Long
        dwData = dwData + 1 ' They come in negative to stay out of the way of handles.
        ReDim Preserve hTemp(1 To dwData)
        hTemp(dwData) = hMonitor
        MonitorHandleEnum = 1
    End Function
    
    Public Function GetMonitorInternaNames(hMonitors() As Long) As String()
        Dim i As Long
        Dim sNames() As String
        Dim uMonInfoEx As MONITORINFOEX
        '
        ReDim sNames(LBound(hMonitors) To UBound(hMonitors))
        uMonInfoEx.cbSize = Len(uMonInfoEx)
        '
        For i = LBound(hMonitors) To UBound(hMonitors)
            uMonInfoEx.szDevice = String$(Len(uMonInfoEx.szDevice), vbNullChar)
            If GetMonitorInfoEx(hMonitors(i), uMonInfoEx) <> 0 Then
                sNames(i) = RTrimNull(uMonInfoEx.szDevice)
            End If
        Next i
        GetMonitorInternaNames = sNames
    End Function
    
    Public Function GetMonitorDeviceIDs(sMonitorInternalNames() As String) As String()
        Dim i As Long
        Dim sDevIDs() As String
        Dim sDevID As String
        Dim InfoDetailed As DISPLAY_DEVICEW
        Const EDD_GET_DEVICE_INTERFACE_NAME = &H1&
        '
        ReDim sDevIDs(LBound(sMonitorInternalNames) To UBound(sMonitorInternalNames))
        InfoDetailed.cbSize = LenB(InfoDetailed)
        For i = LBound(sMonitorInternalNames) To UBound(sMonitorInternalNames)
            If EnumDisplayDevices(StrPtr(sMonitorInternalNames(i)), 0&, InfoDetailed, EDD_GET_DEVICE_INTERFACE_NAME) <> 0& Then
                sDevID = String$(128, 0)
                CopyMemory StrPtr(sDevID), VarPtr(InfoDetailed.DeviceID(0)), 256
                sDevIDs(i) = RTrimNull(sDevID)
            End If
        Next i
        GetMonitorDeviceIDs = sDevIDs
    End Function
    
    Public Function RTrimNull(s As String) As String
        Dim i As Integer
        i = InStr(s, vbNullChar)
        If i Then
            RTrimNull = Left$(s, i - 1)
        Else
            RTrimNull = s
        End If
    End Function
    
    And a bit of code for a Form1 to test:
    Code:
    
    Option Explicit
    '
    
    Private Sub Form_Load()
        Dim hMonitors() As Long
        Dim sInternalNames() As String
        Dim sDeviceIDs() As String
        '
        hMonitors = GetMonitorHandles()
        sInternalNames = GetMonitorInternaNames(hMonitors)
        sDeviceIDs = GetMonitorDeviceIDs(sInternalNames)
        '
        Dim i As Long
        For i = LBound(hMonitors) To UBound(hMonitors)
            Debug.Print hMonitors(i), sInternalNames(i), , sDeviceIDs(i)
        Next i
    End Sub
    
    And here's what I get in the Immediate window when executed:
    Code:
     
     65539        \\.\DISPLAY1                \\?\DISPLAY#AUO119D#5&2e632891&0&UID4357#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
     65537        \\.\DISPLAY2                \\?\DISPLAY#AUO119D#5&2e632891&0&UID4357#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
     65541        \\.\DISPLAY3                \\?\DISPLAY#AUO119D#5&2e632891&0&UID4357#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
    Grrr. Now, as a bit more info, I did bring a new monitor home last night. And my current three monitors are all 1920x1080 with very similar settings. However, two are ASUS and one is Samsung. Therefore, the above makes no sense.

    With a monitor I was using yesterday, it wasn't native at 1920x1080, and it did return a different DeviceID value. Apparently, when Windows is creating "software" monitors, it just finds an EDID "that will work" and uses that. VERY frustrating.

    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  12. #12

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Quote Originally Posted by dilettante View Post
    I'm not certain what you want help with. Maybe this is of use to you?

    Code:
    Private Declare Function ZwQueryKey Lib "ntdll" ( _
        ByVal KeyHandle As Long, _
        ByVal KeyInformationClass As KEY_INFORMATION_CLASS, _
        ByVal pKeyInformation As Long, _
        ByVal Length As Long, _
        ByRef ResultLength As Long) As NTSTATUS
    Name:  sshot.png
Views: 1470
Size:  3.8 KB
    Hi dilettante,

    Yes, at first glance, it seems that that may help with one piece of the bridge. My last post (#9), I'm worried that that one may not be solvable. I was truly hoping to just get all of this completely "hooked up". :/

    Elroy

    EDIT1: Yes, perfect. The ZwQueryKey was the answer. You could have just pointed me in that direction and I would have put it together, but it's all good. Thank you. So, now I'm back to trying to sort out the other (DeviceID) problem, and I'll have it all hooked up.
    Last edited by Elroy; Feb 21st, 2018 at 12:32 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  13. #13
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Great. It can be frustrating to sniff out all of the breadcrumb trails.

  14. #14

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Okay, I give up. I've got about a day into this thing, and I can't figure out how to build the last piece of the bridge. The DeviceID from the DISPLAY_DEVICE structure, used when calling EnumDisplayDevices and specifying the lpDevice (device name) when called, just seems unreliable. And I can't figure out any other way to build the bridge.

    I'm shoving it all into a hole until someone comes up with some idea, or I actually need it.

    ucanbizon, if you've got your ears on in this thread, I'd recommend some code like what I posted in post #9 (or the original that dilettante put together), and get all of the EDID information for all the monitors on a particular computer. And then, you can ask the user which of those they'll be using for this precise screen-to-real-world-dimensions stuff, and then you can use the Millimeters for that answer to adjust your PictureBox scaling.

    On a single-monitor system, all will probably always work fine using the EnumDisplayDevices approach to getting at the EDID (the one in my CodeBank module). However, on multi-monitor systems, I'm now totally convinced that this EnumDisplayDevices approach may fail, and return information about the wrong (albeit another active) monitor. I have a new Samsung 32" monitor on my system (along with two others), and it's not reporting the correct dimensions via EnumDisplayDevices, but it is reporting the correct dimension via SetupDiEnumDeviceInfo.

    And, it's quite aggravating that I can't reliably determine the hMonitor for my Samsung monitor. For a multi-monitor system, I see no bullet-proof solution at the moment.

    Thread Tabled For the Time Being,

    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  15. #15
    Member
    Join Date
    Apr 2019
    Posts
    51

    Re: EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Hi,

    I know it's an old topic but last night I had the same problem and I believe that I have found a workaround with just a few rows of code. Somebody else could find it useful.

    Using the Elroy's code for reading EDID from post #9, just add the following code when populating MonitorDataType(add HardwareId and PNPDeviceId to the type):

    Code:
            Dim dwRequireSize As Long: dwRequireSize = 128
            Dim szBuf As String: szBuf = String$(dwRequireSize, vbNullChar)
                
            SetupDiGetDeviceInstanceId hDeviceInfoSet, SpDevInfo, vbNullString, 0&, dwRequireSize
            szBuf = SPACE$(dwRequireSize)
            If SetupDiGetDeviceInstanceId(hDeviceInfoSet, SpDevInfo, szBuf, Len(szBuf), dwRequireSize) Then
                Data(DataCount).PNPDeviceId = Trim$(Replace$(szBuf, vbNullChar, ""))
            End If
            
            Data(DataCount).HardwareId = GetSetupRegSetting(hDeviceInfoSet, SpDevInfo, SPDRP_HARDWAREID)
            Data(DataCount).HardwareId = Replace(Data(DataCount).HardwareId, vbNullChar, "")
            Data(DataCount).HardwareId = Data(DataCount).HardwareId & "\" & _
                                        GetSetupRegSetting(hDeviceInfoSet, SpDevInfo, SPDRP_DRIVER)

    and this is the function that I use to get DEVICEPROPERTYINDEX:

    Code:
    Private Function GetSetupRegSetting(ByVal hDevInfo As Long, _
                                        DeviceInfoData As SP_DEVINFO_DATA, _
                                        ByVal lPropertyName As Long, _
                                        Optional asLong As Boolean = False) As String
    
        Dim bDevInfo()   As Byte
        Dim lBufferSize  As Long
        Dim lRegDataType As Long
    
        Call SetupDiGetDeviceRegistryProperty(hDevInfo, DeviceInfoData, lPropertyName, lRegDataType, 0, 0, lBufferSize)
    
        If Err.LastDllError = ERROR_INSUFFICIENT_BUFFER Then
            ReDim bDevInfo(lBufferSize * 2 - 1)
            Call SetupDiGetDeviceRegistryProperty(hDevInfo, DeviceInfoData, lPropertyName, lRegDataType, VarPtr(bDevInfo(0)), lBufferSize, ByVal 0)
    
            If asLong Then
                GetSetupRegSetting = bDevInfo(0)
            Else
                GetSetupRegSetting = Trim$(Left$(StrConv(bDevInfo, vbUnicode), lBufferSize - 1))
            End If
        End If
    
    End Function
    When you have HardwareID for the monitor, you can easily compare and pair with MonitorId from EnumDisplayDevices

    In my case, I read the following parameters:

    Driver : "{4d36e96e-e325-11ce-bfc1-08002be10318}\0003"
    HardwareIDs : "MONITOR\LGD03AB"
    PNPDeviceId : "DISPLAY\LGD03AB\4&16FA4A60&0&UID67568640"

    MonitorId : "MONITOR\LGD03AB\{4d36e96e-e325-11ce-bfc1-08002be10318}\0003"


    Or you can read the following reg key to get EDID:
    "SYSTEM\CurrentControlSet\Enum" & PNPDeviceId & "\Device Parameters"

    Thanks.

  16. #16
    Member
    Join Date
    Apr 2009
    Posts
    48

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    So reading "SYSTEM\CurrentControlSet\Enum" & PNPDeviceId & "\Device Parameters\EDID" was able to get me the true name of the monitor
    Using a function to convert the bytearray to chr(each character), then getting the text between (Chr(0) & Chr(252) & Chr(0)) and Chr(10)

    EnumMonitorInfo now returns:
    MakeModel,PNPDeviceId,FriendlyName,SerialNumber,Manufactured,EDIDVersion,WidthMm,HeightMm,HorizPxNat ive,VertPxNative
    SAM0DF7,DISPLAY\SAM0DF7\5&37F9ECB1&0&UID4358,SAMSUNG,16780801,2017 week 1,1.3,1872,1053,3840,2160
    SCE0301,DISPLAY\SCE0301\5&37F9ECB1&0&UID4352,SCEI MONITOR,1042251,2011 week 35,1.3,522,294,1920,1080
    ACR0082,DISPLAY\ACR0082\5&37F9ECB1&0&UID4356,Acer H213H,-1876889128,2009 week 2,1.3,476,268,1920,1080

    How do I convert the monitor ID from EnumDisplayMonitors (which matches the monitor IDs in Windows Advanced Display Settings) to the indexes from EnumMonitorInfo?

    In EnumDisplayMonitors and WADS, SCEI MONITOR is 1, Acer H213H is 2, SAMSUNG is display 3

    It could (hopefully) be sorted by the UID from the PNPDeviceId (SCEI: 4352, ACER: 4356, SAMSUNG: 4358) or the Address key from the registry (SCEI: 272, ACER: 276, SAMSUNG: 278)
    Last edited by neotechni; Feb 21st, 2023 at 11:05 PM.

  17. #17

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    neotechni,

    This thread is somewhat old, but I've long since abandoned trying to get EDID information for a specific hMonitor handle. In post #5, I quote from Microsoft where they say it's possible. And yeah, you can certainly find registry information that's related to EDID information.

    And furthermore, I've successfully gotten code to work using both of those methods. However, using either approach, that code only sometimes works. I used to travel quite a bit, always taking my powerful laptop. When I'd arrive at a client site, I'd always borrow at least one monitor (sometimes two), and I'd also sometimes bring a large monitor with me (if I drove). So, I was always plugging different monitors into my laptop.

    In this situation, I never found a method that would reliably tell me EDID information for the available monitors. And worse yet, using the above approaches, it would often return the wrong information, returning information about a monitor that was once plugged in but not currently. It was always such a mess, that I completely abandoned trying to get EDID information, opting to just ask the user if I needed physical dimensions or some other monitor capability.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  18. #18
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,710

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Just curious since I didn't see it mentioned; did you try WMI?

    E.g. WmiGetMonitorRawEEdidV1Block; or the MSMonitor group

    I know it won't solve the HMONITOR correlation problem but it seems like it could list available monitors and give their E-EDID... and wasn't this about physical size originally? WmiMonitorBasicDisplayParams has parameters for max image size in centimeters.

  19. #19

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Quote Originally Posted by fafalone View Post
    Just curious since I didn't see it mentioned; did you try WMI?

    E.g. WmiGetMonitorRawEEdidV1Block; or the MSMonitor group

    I know it won't solve the HMONITOR correlation problem but it seems like it could list available monitors and give their E-EDID... and wasn't this about physical size originally? WmiMonitorBasicDisplayParams has parameters for max image size in centimeters.
    It was absolutely about physical size for me.

    And no, I don't believe I ever did try a WMI approach. I'm not sure I'm up for looking at it right now, but maybe later. You, or others, are certainly welcome to explore a WMI approach.

    However, my true goal is to correlate the physical size with specific hMonitor handles. That's where things could get useful for me.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  20. #20
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,710

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    You could get names and serial numbers from WMI; if you could accurately get those from an HMONITOR handle you could correlate them.

  21. #21

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,936

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    Ok, with hMonitor (which I can get through EnumDisplayDevices API), I can get the DISPLAY_DEVICEW and MONITORINFOEXW structures (both hyperlinked to Microsoft.com's "learn" pages).

    And that's all I can reliably get. I'm not sure I get the actual name or serial number from those, certainly not the serial number.

    And the only name I can get is typically something like "DISPLAY1", which isn't very helpful.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  22. #22
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,710

    Re: [TABLED] EnumDisplayDevices vs SetupDiEnumDeviceInfo

    The values you're getting from device id look like they might be related to the instance name WMI can give you; I'd have to check it out later.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width