Using the same methods as in answers to other questions related to getting display names through WinApi (using EnumDisplayDevicesW
while passing the device name as the first parameter, similarly to e.g. this one), I've been able to achieve partial success. The issue I'm having is that I'm getting incomplete information. The "Advanced display settings" panel which can be accessed by right clicking on the desktop, selecting "Display settings", and then selecting "Advanced display settings" on the bottom displays the following displays:
使用与通过WinApi获取显示名称相关的其他问题的答案相同的方法(使用EnumDisplayDevicesW,同时将设备名称作为第一个参数传递,类似于例如this),我已经能够取得部分成功。我的问题是我得到的信息不完整。“高级显示设置”面板可以通过右键单击桌面,选择“显示设置”,然后选择底部的“高级显示设置”来访问,显示以下显示:
DELL P2414H(DisplayPort)
AOC AG271QG
BenQ PJ
However, through the use of EnumDisplayDevicesW
calls, I extracted the following:
但是,通过使用EnumDisplayDevicesW调用,我提取了以下内容:
AOC AG271QG
DELL P2414H(DisplayPort)
Generic PnP Monitor
While the order doesn't matter to me, the issue is that I'm getting "Generic PnP Monitor" rather than the more helpful "BenQ PJ" (which is not the exact model that I was hoping for, but still provides at least some information). What can I do to extract "BenQ PJ" rather than "Generic PnP Monitor" (preferably remaining within WinApi)?
虽然顺序对我来说并不重要,但问题是我得到的是“通用即插即用显示器”,而不是更有用的“明基PJ”(它不是我所希望的型号,但至少提供了一些信息)。我可以做些什么来提取“BenQ PJ”而不是“General PnP Monitor”(最好保留在WinApi中)?
更多回答
The nested calls is what I already use (they're also implemented in the question I linked) - without them, I wouldn't get information on the other monitors (and instead just on the adapter, so the GPU). I'm sure that this API call won't get me any further. In fact, the same "Generic PnP Monitor" pops up when I click "Display adapter properties for Display 3" in the window I mentioned - so there are certainly 2 sources of different information (and EnumDisplayDevicesW
apparently isn't the one I'm looking for).
嵌套调用是我已经使用的(它们也在我链接的问题中实现)--如果没有它们,我就不会获得关于其他监视器的信息(而只是关于适配器的信息,比如GPU)。我确信这个API调用不会让我更进一步。事实上,当我在我提到的窗口中点击“Display 3的显示适配器属性”时,同样的“通用PnP监视器”就会弹出--所以肯定有两个不同的信息来源(而EnumDisplayDevicesW显然不是我要找的那个)。
Have you looked through the long combobox of device properties in the properties dialog of the monitor in Device Manager to see if the string you want appears somewhere there?
您是否在设备管理器的监视器属性对话框中查看过设备属性的长组合框,以查看所需的字符串是否出现在那里的某个位置?
The screen's have no driver installed and therefore the actual device itself is running with the Generic PnP Monitor Driver. I think you'd better check it. Look in Device Manager and you will see.
屏幕上没有安装驱动程序,因此实际设备本身运行的是通用Pandroid监视器驱动程序。我想你最好检查一下。看看设备管理器你就知道了。
You can get Monitor information with EDID
您可以使用EDID获取监视器信息
EDID can be read with WMI
可以使用WMI读取EDID
For example, test with WmiOpenBlock and so on, to reduce code of WMI in C++ =>
例如,使用WmiOpenBlock等进行测试,以减少C++中的WMI代码=>
I get for my monitor :
对于我的显示器,我得到:
Instance Name = DISPLAY\PHLC085\4&20634529&0&UID65793_0
User Friendly Name = 247ELH
Manufacturer Name = PHL
Product Code ID = C085
Serial Number ID = AU01307001613
Includes and defines =>
包括和定义=>
#define _CRT_NON_CONFORMING_SWPRINTFS
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <tchar.h>
#include <initguid.h>
#include <wmistr.h>
DEFINE_GUID(WmiMonitorID_GUID, 0x671a8285, 0x4edb, 0x4cae, 0x99,0xfe,0x69,0xa1,0x5c,0x48,0xc0,0xbc );
typedef struct WmiMonitorID {
USHORT ProductCodeID[16];
USHORT SerialNumberID[16];
USHORT ManufacturerName[16];
UCHAR WeekOfManufacture;
USHORT YearOfManufacture;
USHORT UserFriendlyNameLength;
USHORT UserFriendlyName[1];
} WmiMonitorID, *PWmiMonitorID;
#define OFFSET_TO_PTR(Base, Offset) ((PBYTE)((PBYTE)Base + Offset))
typedef HRESULT(WINAPI*WOB) (IN LPGUID lpGUID, IN DWORD nAccess, OUT LONG*);
WOB WmiOpenBlock;
typedef HRESULT(WINAPI*WQAD) (IN LONG hWMIHandle, ULONG* nBufferSize, OUT UCHAR * pBuffer);
WQAD WmiQueryAllData;
typedef HRESULT(WINAPI*WCB) (IN LONG);
WCB WmiCloseBlock;
Test code =>
测试代码=>
HRESULT hr = E_FAIL;
LONG hWmiHandle;
PWmiMonitorID MonitorID;
HINSTANCE hDLL = LoadLibrary(L"Advapi32.dll");
WmiOpenBlock = (WOB)GetProcAddress(hDLL, "WmiOpenBlock");
WmiQueryAllData = (WQAD)GetProcAddress(hDLL, "WmiQueryAllDataW");
WmiCloseBlock = (WCB)GetProcAddress(hDLL, "WmiCloseBlock");
if (WmiOpenBlock != NULL && WmiQueryAllData && WmiCloseBlock)
{
WCHAR pszDeviceId[256] = L"";
hr = WmiOpenBlock((LPGUID)&WmiMonitorID_GUID, GENERIC_READ, &hWmiHandle);
if (hr == ERROR_SUCCESS)
{
ULONG nBufferSize = 0;
UCHAR *pAllDataBuffer = 0;
PWNODE_ALL_DATA pWmiAllData;
hr = WmiQueryAllData(hWmiHandle, &nBufferSize, 0);
if (hr == ERROR_INSUFFICIENT_BUFFER)
{
pAllDataBuffer = (UCHAR*)malloc(nBufferSize);
hr = WmiQueryAllData(hWmiHandle, &nBufferSize, pAllDataBuffer);
if (hr == ERROR_SUCCESS)
{
while (1)
{
pWmiAllData = (PWNODE_ALL_DATA)pAllDataBuffer;
if (pWmiAllData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE)
MonitorID = (PWmiMonitorID)&pAllDataBuffer[pWmiAllData->DataBlockOffset];
else
MonitorID = (PWmiMonitorID)&pAllDataBuffer[pWmiAllData->OffsetInstanceDataAndLength[0].OffsetInstanceData];
ULONG nOffset = 0;
WCHAR *pwsInstanceName = 0;
nOffset = (ULONG)pAllDataBuffer[pWmiAllData->OffsetInstanceNameOffsets];
pwsInstanceName = (WCHAR*)OFFSET_TO_PTR(pWmiAllData, nOffset + sizeof(USHORT));
WCHAR wsText[255] = L"";
swprintf(wsText, L"Instance Name = %s\r\n", pwsInstanceName);
OutputDebugString(wsText);
WCHAR *pwsUserFriendlyName;
pwsUserFriendlyName = (WCHAR*)MonitorID->UserFriendlyName;
swprintf(wsText, L"User Friendly Name = %s\r\n", pwsUserFriendlyName);
OutputDebugString(wsText);
WCHAR *pwsManufacturerName;
pwsManufacturerName = (WCHAR*)MonitorID->ManufacturerName;
swprintf(wsText, L"Manufacturer Name = %s\r\n", pwsManufacturerName);
OutputDebugString(wsText);
WCHAR *pwsProductCodeID;
pwsProductCodeID = (WCHAR*)MonitorID->ProductCodeID;
swprintf(wsText, L"Product Code ID = %s\r\n", pwsProductCodeID);
OutputDebugString(wsText);
WCHAR *pwsSerialNumberID;
pwsSerialNumberID = (WCHAR*)MonitorID->SerialNumberID;
swprintf(wsText, L"Serial Number ID = %s\r\n", pwsSerialNumberID);
OutputDebugString(wsText);
if (!pWmiAllData->WnodeHeader.Linkage)
break;
pAllDataBuffer += pWmiAllData->WnodeHeader.Linkage;
}
free(pAllDataBuffer);
}
}
WmiCloseBlock(hWmiHandle);
}
}
There is an official example code here that also seems to be able extract monitor name from EDID:
这里有一个官方示例代码,它似乎也能够从EDID中提取监视器名称:
#include <windows.h>
#include <vector>
#include <iostream>
#include <string>
using namespace std;
int main()
{
vector<DISPLAYCONFIG_PATH_INFO> paths;
vector<DISPLAYCONFIG_MODE_INFO> modes;
UINT32 flags = QDC_ONLY_ACTIVE_PATHS | QDC_VIRTUAL_MODE_AWARE;
LONG result = ERROR_SUCCESS;
do
{
// Determine how many path and mode structures to allocate
UINT32 pathCount, modeCount;
result = GetDisplayConfigBufferSizes(flags, &pathCount, &modeCount);
if (result != ERROR_SUCCESS)
{
return HRESULT_FROM_WIN32(result);
}
// Allocate the path and mode arrays
paths.resize(pathCount);
modes.resize(modeCount);
// Get all active paths and their modes
result = QueryDisplayConfig(flags, &pathCount, paths.data(), &modeCount, modes.data(), nullptr);
// The function may have returned fewer paths/modes than estimated
paths.resize(pathCount);
modes.resize(modeCount);
// It's possible that between the call to GetDisplayConfigBufferSizes and QueryDisplayConfig
// that the display state changed, so loop on the case of ERROR_INSUFFICIENT_BUFFER.
} while (result == ERROR_INSUFFICIENT_BUFFER);
if (result != ERROR_SUCCESS)
{
return HRESULT_FROM_WIN32(result);
}
// For each active path
for (auto& path : paths)
{
// Find the target (monitor) friendly name
DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = {};
targetName.header.adapterId = path.targetInfo.adapterId;
targetName.header.id = path.targetInfo.id;
targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
targetName.header.size = sizeof(targetName);
result = DisplayConfigGetDeviceInfo(&targetName.header);
if (result != ERROR_SUCCESS)
{
return HRESULT_FROM_WIN32(result);
}
// Find the adapter device name
DISPLAYCONFIG_ADAPTER_NAME adapterName = {};
adapterName.header.adapterId = path.targetInfo.adapterId;
adapterName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME;
adapterName.header.size = sizeof(adapterName);
result = DisplayConfigGetDeviceInfo(&adapterName.header);
if (result != ERROR_SUCCESS)
{
return HRESULT_FROM_WIN32(result);
}
wcout
<< L"Monitor with name "
<< (targetName.flags.friendlyNameFromEdid ? targetName.monitorFriendlyDeviceName : L"Unknown")
<< L" is connected to adapter "
<< adapterName.adapterDevicePath
<< L" on target "
<< path.targetInfo.id
<< L"\n";
}
}
On my Windows 11(10.0.22621) PC, copying it to main.cpp, compiling by running:
在我的Windows 11(10.0.22621)PC上,将其复制到main.cpp,运行以下命令进行编译:
"Path\to\Visual\Studio\vcvars32.bat" // activate Visual Studio Command Prompt
"Path\to\Visual\Studio\cl.exe /EHsc User32.lib OneCoreUAP.lib main.cpp /link /subsystem:console
and then running the generated main.exe
from command line will give:
然后从命令行运行生成的main.exe将显示:
Monitor with name GCU321HXA is connected to adapter \\?\PCI#VEN_10DE&DEV_24DC&SUBSYS_3F8417AA&REV_A1#4&12b1023e&0&0008#{5b45201d-f2f2-4f3b-85bb-30ff1f953599} on target 4356
Monitor with name Cintiq Pro_16 is connected to adapter \\?\PCI#VEN_10DE&DEV_24DC&SUBSYS_3F8417AA&REV_A1#4&12b1023e&0&0008#{5b45201d-f2f2-4f3b-85bb-30ff1f953599} on target 4352
which includes device names same as those shown in "Advanced display settings".
其中包括与“高级显示设置”中显示的设备名称相同的设备名称。
It's been quite some time and the solution from @Castorix still works. I would like to extend his answer, give some perspectives and propose another solution for future readers. There are essentially three different ideas to access the user friendly name of a display.
已经有一段时间了,@Castorix的解决方案仍然有效。我想延伸他的回答,给出一些观点,并为未来的读者提出另一种解决方案。访问显示的用户友好名称本质上有三种不同的想法。
Use Advapi32 WMI to access the processed EDID and extract the name. The approach clearly works like @Castorix showed but uses an undocumented interface to access the data. This is also the reason you have to load the functions yourself.
Use SetupAPI to find the raw EDID registry entry for all displays and extract the display name descriptor. This approach was the first I used and it's quite cumbersome. EDID has multiple versions with different guaranties, so you have to carefully check if the descriptor is supported (since EDID 1.3) and where it is located.
Use User32 QueryDisplayConfig to access the display device info including the friendly name. I like this solution the most because it uses documented functions and provides useful properties like the monitor handle (HMONITOR), the monitor index and device path of the display. This allows for easy access to a lot of additional information (e.g via GetMonitorInfoW
for virtual monitor bounds).
The third solution produces the following results on my dual monitor setup (two ASUS VC239):
第三个解决方案在我的双显示器设置(两个ASU VC239)上产生以下结果:
MONITOR[1]: ASUS VC239
Handle: 65537
DevicePath: \\?\DISPLAY#ACI23C4#7&26221e0f&2&UID256#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
MONITOR[2]: ASUS VC239
Handle: 65539
DevicePath: \\?\DISPLAY#ACI23C4#7&26221e0f&2&UID260#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
The implementation does ignore some return values, but never behaves faulty even if the api calls would fail. Depending on your requirements additional error handling could be introduced:
该实现确实忽略了一些返回值,但即使API调用失败,它也从不表现出错误行为。根据您的要求,可能会引入其他错误处理:
#include <cstdint>
#include <cstdio>
#include <vector>
#include <Windows.h>
struct MonitorQuery {
const WCHAR* device_name;
HMONITOR monitor;
uint32_t index;
};
void enumerate_display_devices() {
UINT32 query_flags = QDC_ONLY_ACTIVE_PATHS;
UINT32 path_count = 0, mode_count = 0;
GetDisplayConfigBufferSizes(query_flags, &path_count, &mode_count);
std::vector<DISPLAYCONFIG_PATH_INFO> paths(path_count);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(mode_count);
QueryDisplayConfig(query_flags, &path_count, paths.data(), &mode_count, modes.data(), nullptr);
for (UINT32 i = 0; i < path_count; ++i) {
const DISPLAYCONFIG_PATH_INFO& path = paths[i];
DISPLAYCONFIG_TARGET_DEVICE_NAME target_device = {};
target_device.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
target_device.header.size = sizeof(target_device);
target_device.header.adapterId = path.targetInfo.adapterId;
target_device.header.id = path.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&target_device.header) != ERROR_SUCCESS) {
continue;
}
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_device = {};
source_device.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_device.header.size = sizeof(source_device);
source_device.header.adapterId = path.targetInfo.adapterId;
source_device.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&source_device.header) != ERROR_SUCCESS) {
continue;
}
MonitorQuery query = {};
query.device_name = source_device.viewGdiDeviceName;
query.monitor = nullptr;
query.index = 0;
EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR monitor, HDC, LPRECT, LPARAM lparam) -> BOOL {
MonitorQuery& query = *reinterpret_cast<MonitorQuery*>(lparam);
MONITORINFOEXW monitor_info = {};
monitor_info.cbSize = sizeof(monitor_info);
GetMonitorInfoW(monitor, &monitor_info);
if (memcmp(monitor_info.szDevice, query.device_name, CCHDEVICENAME * sizeof(WCHAR)) != 0) {
++query.index;
return TRUE;
}
query.monitor = monitor;
return FALSE;
}, reinterpret_cast<LPARAM>(&query));
if (query.monitor) {
printf("MONITOR[%u]: %.32ws\n", query.index + 1, target_device.monitorFriendlyDeviceName);
printf("Handle: %llu\n", reinterpret_cast<uint64_t>(query.monitor));
printf("DevicePath: %.128ws\n", target_device.monitorDevicePath);
puts("");
}
}
}
If you just want the display names without any additional information you don't even need the DISPLAYCONFIG_SOURCE_DEVICE_NAME
and EnumDisplayMonitors
query.
如果您只需要显示名称,而不需要任何其他信息,则甚至不需要DISPLAYCONFIG_SOURCE_DEVICE_NAME和EnumDisplayMonants查询。
Appendix: Some old and cheap monitors do not support the required EDID feature to return a friendly device name. This case can be detected by checking if the monitorFriendlyDeviceName
string is of length zero.
附录:一些旧的廉价显示器不支持返回友好设备名称所需的EDID功能。可以通过检查monitor orFriendlyDeviceName字符串的长度是否为零来检测这种情况。
更多回答
This is right - "BenQ PJ" is apparently the user friendly name.
这是正确的-“明基PJ”显然是用户友好的名称。
Be aware that a shifted pointer passed into free()
is undefined behavior, see here. This code also produces a memory leak since free()
is not called if WmiQueryAllData()
fails.
请注意,传递到Free()中的移位指针是未定义的行为,请参见此处。此代码还会产生内存泄漏,因为如果WmiQueryAllData()失败,则不会调用Free()。
我是一名优秀的程序员,十分优秀!