You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 01:42:06