Windows下如何获取HMONITOR句柄对应的显示器友好名称?
关联HMONITOR与显示器友好名称
问题背景
需要获取HMONITOR句柄对应的显示器友好名称,目前遇到以下问题:
- WMI可列出所有已连接显示器的完整信息(包括友好名称),但无法直接关联
EnumDisplayMonitors获取的HMONITOR句柄 - 目标显示器不支持DDCCI,无法通过该方案获取信息
- 使用
GetMonitorInfoW()仅能得到Display1、Display2这类无意义的szDevice值
解决方案
核心思路:通过HMONITOR获取显示器的设备实例路径,再与WMI中WmiMonitorID类的InstanceName字段匹配(InstanceName包含设备实例路径的关键前缀)。
Python实现
需在Windows环境运行,先执行pip install wmi安装依赖:
import ctypes from ctypes.wintypes import ( DWORD, RECT, BOOL, HMONITOR, HDC, LPARAM, WCHAR, ULONG ) import wmi # 定义Windows API所需结构体 class MONITORINFOEXW(ctypes.Structure): _fields_ = [ ("cbSize", DWORD), ("rcMonitor", RECT), ("rcWork", RECT), ("dwFlags", DWORD), ("szDevice", WCHAR * 32) ] class DISPLAYCONFIG_DEVICE_INFO_HEADER(ctypes.Structure): _fields_ = [ ("type", DWORD), ("size", DWORD), ("adapterId", ctypes.c_byte * 16), ("id", DWORD) ] class DISPLAYCONFIG_TARGET_DEVICE_NAME(ctypes.Structure): _fields_ = [ ("header", DISPLAYCONFIG_DEVICE_INFO_HEADER), ("flags", ULONG), ("outputTechnology", DWORD), ("edidManufactureId", DWORD), ("edidProductCodeId", DWORD), ("connectorInstance", DWORD), ("monitorFriendlyDeviceName", WCHAR * 64), ("monitorDevicePath", WCHAR * 128) ] def get_monitor_device_path(hmonitor): # 获取显示器基本信息 monitor_info = MONITORINFOEXW() monitor_info.cbSize = ctypes.sizeof(MONITORINFOEXW) if not ctypes.windll.user32.GetMonitorInfoW(hmonitor, ctypes.byref(monitor_info)): return None # 查询显示配置路径 path_count = DWORD(0) mode_count = DWORD(0) ctypes.windll.user32.GetDisplayConfigBufferSizes( 0x00000001, # QDC_ALL_PATHS ctypes.byref(path_count), ctypes.byref(mode_count) ) path_info = (ctypes.c_void_p * path_count.value)() mode_info = (ctypes.c_void_p * mode_count.value)() if ctypes.windll.user32.QueryDisplayConfig( 0x00000001, ctypes.byref(path_count), path_info, ctypes.byref(mode_count), mode_info, None ) != 0: return None # 获取目标设备路径 target_name = DISPLAYCONFIG_TARGET_DEVICE_NAME() target_name.header.type = 4 # DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME target_name.header.size = ctypes.sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME) for i in range(path_count.value): path = ctypes.cast(path_info[i], ctypes.POINTER(ctypes.c_byte)) adapter_id = path[0:16] target_id = ctypes.cast(path[16:20], ctypes.POINTER(DWORD)).contents.value target_name.header.adapterId = (ctypes.c_byte * 16)(*adapter_id) target_name.header.id = target_id if ctypes.windll.user32.DisplayConfigGetDeviceInfo(ctypes.byref(target_name.header)) == 0: return target_name.monitorDevicePath return None def get_wmi_monitor_map(): # 构建WMI中设备路径前缀到显示器信息的映射 c = wmi.WMI(namespace='root\\wmi') monitor_map = {} for monitor in c.WmiMonitorID(): instance_name = monitor.InstanceName # 提取设备路径前缀(格式:DISPLAY\XXXX) path_prefix = '\\'.join(instance_name.split('\\')[:2]).lower() monitor_map[path_prefix] = { "friendly_name": ''.join(chr(c) for c in monitor.UserFriendlyName if c != 0), "manufacturer": ''.join(chr(c) for c in monitor.ManufacturerName if c != 0), "serial": ''.join(chr(c) for c in monitor.SerialNumberID if c != 0) } return monitor_map def get_hmonitor_mapping(): hmonitor_map = {} wmi_map = get_wmi_monitor_map() def _callback(hmonitor, hdc, lprect, lparam): device_path = get_monitor_device_path(hmonitor) if device_path: path_lower = device_path.lower() for prefix, info in wmi_map.items(): if prefix in path_lower: hmonitor_map[HMONITOR(hmonitor)] = info break return True MONITORENUMPROC = ctypes.WINFUNCTYPE(BOOL, HMONITOR, HDC, ctypes.POINTER(RECT), LPARAM) callback = MONITORENUMPROC(_callback) if not ctypes.windll.user32.EnumDisplayMonitors(0, 0, callback, 0): raise RuntimeError("EnumDisplayMonitors调用失败") return hmonitor_map # 执行测试 if __name__ == "__main__": mapping = get_hmonitor_mapping() print("HMONITOR与显示器信息映射:") for hmon, info in mapping.items(): print(f"HMONITOR: {hmon}") print(f" 友好名称: {info['friendly_name']}") print(f" 制造商: {info['manufacturer']}") print(f" 序列号: {info['serial']}")
C++实现
#include <windows.h> #include <vector> #include <map> #include <string> #include <wbemidl.h> #include <comdef.h> #pragma comment(lib, "wbemuuid.lib") #pragma comment(lib, "user32.lib") // 枚举HMONITOR的回调函数 BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { auto pMonitors = reinterpret_cast<std::vector<HMONITOR>*>(dwData); pMonitors->push_back(hMonitor); return TRUE; } // 获取HMONITOR对应的设备路径 std::wstring GetMonitorDevicePath(HMONITOR hMonitor) { MONITORINFOEXW monitorInfo = { sizeof(MONITORINFOEXW) }; if (!GetMonitorInfoW(hMonitor, &monitorInfo)) { return L""; } UINT32 pathCount = 0, modeCount = 0; if (GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &pathCount, &modeCount) != ERROR_SUCCESS) { return L""; } std::vector<DISPLAYCONFIG_PATH_INFO> pathInfos(pathCount); std::vector<DISPLAYCONFIG_MODE_INFO> modeInfos(modeCount); if (QueryDisplayConfig(QDC_ALL_PATHS, &pathCount, pathInfos.data(), &modeCount, modeInfos.data(), nullptr) != ERROR_SUCCESS) { return L""; } DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = { 0 }; targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; targetName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME); for (const auto& path : pathInfos) { targetName.header.adapterId = path.targetInfo.adapterId; targetName.header.id = path.targetInfo.id; if (DisplayConfigGetDeviceInfo(&targetName.header) == ERROR_SUCCESS) { return targetName.monitorDevicePath; } } return L""; } // 从WMI获取设备前缀到友好名称的映射 std::map<std::wstring, std::wstring> GetWmiMonitorMap() { std::map<std::wstring, std::wstring> monitorMap; HRESULT hres; // 初始化COM环境 hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(hres)) return monitorMap; hres = CoInitializeSecurity( nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE, nullptr ); if (FAILED(hres)) { CoUninitialize(); return monitorMap; } IWbemLocator* pLoc = nullptr; hres = CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); if (FAILED(hres)) { CoUninitialize(); return monitorMap; } IWbemServices* pSvc = nullptr; hres = pLoc->ConnectServer( _bstr_t(L"ROOT\\WMI"), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &pSvc ); if (FAILED(hres)) { pLoc->Release(); CoUninitialize(); return monitorMap; } hres = CoSetProxyBlanket( pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE ); if (FAILED(hres)) { pSvc->Release(); pLoc->Release(); CoUninitialize(); return monitorMap; } IEnumWbemClassObject* pEnumerator = nullptr; hres = pSvc->ExecQuery( _bstr_t(L"WQL"), _bstr_t(L"SELECT InstanceName, UserFriendlyName FROM WmiMonitorID"), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, &pEnumerator ); if (FAILED(hres)) { pSvc->Release(); pLoc->Release(); CoUninitialize(); return monitorMap; } IWbemClassObject* pclsObj = nullptr; ULONG uReturn = 0; while (pEnumerator) { HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if (uReturn == 0) break; // 读取InstanceName VARIANT vtInstanceName; VariantInit(&vtInstanceName); hr = pclsObj->Get(L"InstanceName", 0, &vtInstanceName, nullptr, nullptr); if (SUCCEEDED(hr)) { std::wstring instanceName = vtInstanceName.bstrVal; size_t secondSlash = instanceName.find(L'\\', instanceName.find(L'\\') + 1); std::wstring pathPrefix = instanceName.substr(0, secondSlash); // 读取友好名称 VARIANT vtFriendlyName; VariantInit(&vtFriendlyName); hr = pclsObj->Get(L"UserFriendlyName", 0, &vtFriendlyName, nullptr, nullptr); if (SUCCEEDED(hr)) { std::wstring friendlyName; SAFEARRAY* sa = vtFriendlyName.parray; char* pData = nullptr; SafeArrayAccessData(sa, (void**)&pData); for (long i = 0; i < sa->rgsabound[0].cElements; ++i) { if (pData[i] != 0) friendlyName += pData[i]; } SafeArrayUnaccessData(sa); monitorMap[pathPrefix] = friendlyName; VariantClear(&vtFriendlyName); } VariantClear(&vtInstanceName); } pclsObj->Release(); } pEnumerator->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return monitorMap; } int main() { // 枚举所有HMONITOR std::vector<HMONITOR> monitors; EnumDisplayMonitors(nullptr, nullptr, MonitorEnumProc, (LPARAM)&monitors); // 获取WMI映射表 auto wmiMap = GetWmiMonitorMap(); // 关联HMONITOR与友好名称 std::map<HMONITOR, std::wstring> hmonitorMapping; for (HMONITOR hMon : monitors) { std::wstring devicePath = GetMonitorDevicePath(hMon); if (!devicePath.empty()) { for (const auto& pair : wmiMap) { if (devicePath.find(pair.first) != std::wstring::npos) { hmonitorMapping[hMon] = pair.second; break; } } } } // 输出结果 for (const auto& pair : hmonitorMapping) { wprintf(L"HMONITOR: 0x%p\n", pair.first); wprintf(L" 友好名称: %s\n", pair.second.c_str()); } return 0; }
说明
- 两种方案均通过Windows原生
DisplayConfig系列API获取HMONITOR对应的设备路径,再与WMI数据匹配,无需依赖DDCCI - Python代码依赖
wmi库,C++代码需链接wbemuuid.lib和user32.lib
内容的提问来源于stack exchange,提问作者Robin
相关产品推荐
相关产品推荐

