[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:
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):
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.
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.
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.
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.
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.
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:
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:
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.
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.
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.
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
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:
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.
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
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.
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.
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
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)
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.
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.
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.
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.
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.