使用与通过 WinApi 获取显示名称相关的其他问题的答案相同的方法(使用
EnumDisplayDevicesW
同时将设备名称作为第一个参数传递,类似于例如 this one),我已经能够实现部分成功。我遇到的问题是我得到的信息不完整。右键单击桌面,选择“显示设置”,然后选择底部的“高级显示设置”即可访问“高级显示设置”面板,显示以下内容:
DELL P2414H(DisplayPort)
AOC AG271QG
BenQ PJ
但是,通过使用
EnumDisplayDevicesW
调用,我提取了以下内容:
AOC AG271QG
DELL P2414H(DisplayPort)
Generic PnP Monitor
虽然顺序对我来说并不重要,但问题是我得到的是“Generic PnP Monitor”,而不是更有帮助的“BenQ PJ”(这不是我所希望的确切型号,但仍然在至少一些信息)。我该怎么做才能提取“BenQ PJ”而不是“Generic PnP Monitor”(最好保留在 WinApi 中)?
您可以通过EDID
获取显示器信息可以使用WMI读取EDID
例如使用WmiOpenBlock等进行测试,减少WMI的C++代码=>
我拿到了我的显示器:
Instance Name = DISPLAY\PHLC085\4&20634529&0&UID65793_0
User Friendly Name = 247ELH
Manufacturer Name = PHL
Product Code ID = C085
Serial Number ID = AU01307001613
包括并定义=>
#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;
测试代码=>
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);
}
}
已经有一段时间了,来自 @Castorix 的解决方案有效。我想扩展答案,给出一些观点,并为未来的读者提出另一种解决方案。访问显示器的用户友好名称本质上有三种不同的想法。
使用Advapi32访问处理后的EDID并提取名称。该方法显然像 @Castorix 所示的那样工作,但使用未记录的接口来访问数据。这也是你必须自己加载函数的原因。
使用SetupAPI查找所有显示器的原始EDID注册表项并提取显示名称描述符。这种方法是我第一次使用,但相当麻烦,因为 EDID 有多个版本,具有不同的保证。因此,您必须仔细检查描述符是否受支持(自 EDID 1.3 起)以及它的存储位置。
使用 User32 访问显示设备信息,包括友好名称。我最喜欢这个解决方案,因为它使用记录的函数并提供有用的属性,如监视器句柄 (HMONITOR)、监视器索引和显示器的设备路径。这样可以轻松访问大量附加信息(例如通过
GetMonitorInfoW
获取虚拟监视器边界)。
第三种解决方案在我的机器上产生以下结果:
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}
为了简单起见,实现确实忽略了一些返回值,因此请确保您能够捕获项目中可能存在的错误:
#include <cstdint>
#include <cstdio>
#include <Windows.h>
struct MonitorQuery {
WCHAR* device_name;
HMONITOR monitor;
uint32_t index;
};
void enumerate_display_devices() {
UINT32 query_flags = QDC_ONLY_ACTIVE_PATHS;
UINT32 path_count, mode_count;
GetDisplayConfigBufferSizes(query_flags, &path_count, &mode_count);
auto paths = new DISPLAYCONFIG_PATH_INFO[path_count];
auto modes = new DISPLAYCONFIG_MODE_INFO[mode_count];
QueryDisplayConfig(query_flags, &path_count, paths, &mode_count, modes, nullptr);
for (size_t i = 0; i < path_count; i++) {
DISPLAYCONFIG_PATH_INFO& path = *(paths + i);
DISPLAYCONFIG_TARGET_DEVICE_NAME target_name = {};
target_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
target_name.header.size = sizeof(target_name);
target_name.header.adapterId = path.targetInfo.adapterId;
target_name.header.id = path.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&target_name.header) != ERROR_SUCCESS) {
continue;
}
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_name.header.size = sizeof(source_name);
source_name.header.adapterId = path.targetInfo.adapterId;
source_name.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&source_name.header) != ERROR_SUCCESS) {
continue;
}
MonitorQuery query = {
.device_name = source_name.viewGdiDeviceName,
.monitor = (HMONITOR)INVALID_HANDLE_VALUE,
.index = 0,
};
EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR monitor, HDC, LPRECT, LPARAM lparam) {
MonitorQuery& query = *(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;
}, (LPARAM)&query);
if (query.monitor != INVALID_HANDLE_VALUE) {
printf("MONITOR[%u]: %.32ws\n", query.index + 1, target_name.monitorFriendlyDeviceName);
printf("Handle: %llu\n", (size_t)query.monitor);
printf("DevicePath: %.128ws\n", target_name.monitorDevicePath);
puts("");
}
}
delete[] paths;
delete[] modes;
}
如果您只想要显示名称而不需要任何附加信息,您甚至不需要
DISPLAYCONFIG_SOURCE_DEVICE_NAME
和 EnumDisplayMonitors
查询。
我希望这对某人有帮助,并且不要忘记链接到 user32.lib。