Detect/identify 显示器在 Windows 7/8/10 Win32 C++ 中连接的端口(HDMI,其他)

Detect/identify the port (HDMI, other) the monitor is connected to in Windows 7/8/10 Win32 C++

我能够检测到连接到我系统的所有显示器。我可以使用以下 Windows API 调用获得很多关于它们的相关信息:

EnumDisplayMonitors

GetMonitorInfo

EnumDisplayDevices

我正在开发的系统是带有 2 个显示器的 AIO(一体机)。它有一个我可以连接的外部 HDMI 输出和外部显示器。然后,通过端口复制器,我可以连接第四台显示器。所有的显示器都在扩展我的桌面,而且它们都工作正常。

我的任务是检测通过 HDMI 端口连接的显示器。我能够使用上述 API 提取的显示器信息没有提供任何特定信息,通过这些信息我将能够检测端口类型(例如 HDMI 或 USB)。

有人知道 API 有什么用吗?谢谢!

您可以使用 EnumDisplayDevices method to get the DeviceID of the monitor and then the WmiMonitorConnectionParams WMI class (available since Windows Vista) and the VideoOutputTechnology property which returns a D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY 枚举。

尝试下一个示例代码

#include "stdafx.h"
#include <atlstr.h>
#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <comdef.h>
#include <Wbemidl.h>
# pragma comment(lib, "wbemuuid.lib")            


typedef enum _D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY {
    D3DKMDT_VOT_UNINITIALIZED = -2,
    D3DKMDT_VOT_OTHER = -1,
    D3DKMDT_VOT_HD15 = 0,
    D3DKMDT_VOT_SVIDEO = 1,
    D3DKMDT_VOT_COMPOSITE_VIDEO = 2,
    D3DKMDT_VOT_COMPONENT_VIDEO = 3,
    D3DKMDT_VOT_DVI = 4,
    D3DKMDT_VOT_HDMI = 5,
    D3DKMDT_VOT_LVDS = 6,
    D3DKMDT_VOT_D_JPN = 8,
    D3DKMDT_VOT_SDI = 9,
    D3DKMDT_VOT_DISPLAYPORT_EXTERNAL = 10,
    D3DKMDT_VOT_DISPLAYPORT_EMBEDDED = 11,
    D3DKMDT_VOT_UDI_EXTERNAL = 12,
    D3DKMDT_VOT_UDI_EMBEDDED = 13,
    D3DKMDT_VOT_SDTVDONGLE = 14,
#if (DXGKDDI_INTERFACE_VERSION >= DXGKDDI_INTERFACE_VERSION_WDDM1_3_M1)
    D3DKMDT_VOT_MIRACAST = 15,
#endif 
    D3DKMDT_VOT_INTERNAL = 0x80000000,
    D3DKMDT_VOT_SVIDEO_4PIN = D3DKMDT_VOT_SVIDEO,
    D3DKMDT_VOT_SVIDEO_7PIN = D3DKMDT_VOT_SVIDEO,
    D3DKMDT_VOT_RF = D3DKMDT_VOT_COMPOSITE_VIDEO,
    D3DKMDT_VOT_RCA_3COMPONENT = D3DKMDT_VOT_COMPONENT_VIDEO,
    D3DKMDT_VOT_BNC = D3DKMDT_VOT_COMPONENT_VIDEO
} D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY;

_D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY GetConnectorInfo(LPCWSTR ShortDeviceID)
{
    _D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY result = D3DKMDT_VOT_OTHER;

    BSTR strNetworkResource;
    strNetworkResource = L"\\.\root\WMI";

    HRESULT hres;
    hres = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (FAILED(hres))
    {
        cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        cout << "press enter to exit" << endl;
        cin.get();
        std::exit(1);
    }


    hres = CoInitializeSecurity(
        NULL,
        -1,                          // COM authentication
        NULL,                        // Authentication services
        NULL,                        // Reserved
        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
        NULL,                        // Authentication info
        EOAC_NONE,                   // Additional capabilities
        NULL                         // Reserved
        );

    if (FAILED(hres))
    {
        cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        CoUninitialize();
        cout << "press enter to exit" << endl;
        cin.get();
        std::exit(1);
    }

    IWbemLocator *pLoc = NULL;
    hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc);

    if (FAILED(hres))
    {
        cout << "Failed to create IWbemLocator object." << " Err code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        CoUninitialize();
        cout << "press enter to exit" << endl;
        cin.get();
        std::exit(1);
    }


    IWbemServices *pSvc = NULL;

    hres = pLoc->ConnectServer(
        _bstr_t(strNetworkResource),      // Object path of WMI namespace
        NULL,                    // User name. NULL = current user
        NULL,                    // User password. NULL = current
        0,                       // Locale. NULL indicates current
        NULL,                    // Security flags.
        0,                       // Authority (e.g. Kerberos)
        0,                       // Context object
        &pSvc                    // pointer to IWbemServices proxy
        );

    if (FAILED(hres))
    {
        cout << "Could not connect. Error code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        pLoc->Release();
        CoUninitialize();
        cout << "press enter to exit" << endl;
        cin.get();
        std::exit(1);
    }

    hres = CoSetProxyBlanket(
        pSvc,                        // Indicates the proxy to set
        RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
        RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
        NULL,                        // Server principal name
        RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
        NULL,                        // client identity
        EOAC_NONE                    // proxy capabilities
        );

    if (FAILED(hres))
    {
        cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        cout << "press enter to exit" << endl;
        cin.get();
        std::exit(1);
    }


    IEnumWbemClassObject* pEnumerator = NULL;
    hres = pSvc->ExecQuery(L"WQL", L"SELECT InstanceName, VideoOutputTechnology FROM WmiMonitorConnectionParams",
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);

    if (FAILED(hres))
    {
        cout << "ExecQuery failed" << " Error code = 0x" << hex << hres << endl;
        cout << _com_error(hres).ErrorMessage() << endl;
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        cout << "press enter to exit" << endl;
        cin.get();
        std::exit(1);
    }


    IWbemClassObject *pclsObj = NULL;
    ULONG uReturn = 0;
    BOOL bFound = false;
    while (pEnumerator)
    {
        HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

        if (0 == uReturn || FAILED(hr))
            break;

        VARIANT vtProp;
        CString DeviceID;
        hr = pclsObj->Get(L"InstanceName", 0, &vtProp, 0, 0);// String
        if (!FAILED(hr))
        {
            if ((vtProp.vt == VT_NULL) || (vtProp.vt == VT_EMPTY))
            { }
            else
            {
                DeviceID.Format(L"%s", vtProp.bstrVal);
                DeviceID = DeviceID.Mid(8, DeviceID.Find(L"\", 9) - 8);
                bFound = DeviceID.Compare(ShortDeviceID) == 0;
            }
        }
        VariantClear(&vtProp);

        if (bFound)
        {
            hr = pclsObj->Get(L"VideoOutputTechnology", 0, &vtProp, 0, 0);// Uint32
            if (!FAILED(hr))
            {
                if ((vtProp.vt == VT_NULL) || (vtProp.vt == VT_EMPTY))
                    result = D3DKMDT_VOT_OTHER;
                else
                {
                    result = (_D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY)vtProp.uintVal;
                }
            }
        }
        VariantClear(&vtProp);


        pclsObj->Release();
        pclsObj = NULL;

        if (bFound)
            break;
    }


    pSvc->Release();
    pLoc->Release();
    pEnumerator->Release();
    if (pclsObj != NULL)
        pclsObj->Release();

    CoUninitialize();

    return result;
}



void GetDisplayInfo()
{

    DISPLAY_DEVICE lpDisplayDevice;
    lpDisplayDevice.cb = sizeof(lpDisplayDevice);
    DWORD iDevNum = 0;

    CString DeviceID;
    while (EnumDisplayDevices(0, iDevNum, &lpDisplayDevice, 0))
    {
        DISPLAY_DEVICE lpDisplayDevice2;
        ZeroMemory(&lpDisplayDevice2, sizeof(lpDisplayDevice2));
        lpDisplayDevice2.cb = sizeof(lpDisplayDevice2);
        DWORD devMon = 0;

        while (EnumDisplayDevices(lpDisplayDevice.DeviceName, devMon, &lpDisplayDevice2, 0))
        {
            if (lpDisplayDevice2.StateFlags & DISPLAY_DEVICE_ACTIVE && !(lpDisplayDevice2.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
            {
                DeviceID.Format(L"%s", lpDisplayDevice2.DeviceID);
                wcout << DeviceID.GetString() << endl;
                DeviceID = DeviceID.Mid(8, DeviceID.Find(L"\", 9) - 8);
                _D3DKMDT_VIDEO_OUTPUT_TECHNOLOGY VideoOutputTechnology = GetConnectorInfo(DeviceID.GetString());

                switch (VideoOutputTechnology) 
                {
                case D3DKMDT_VOT_HDMI:
                    wcout << " VideoOutputTechnology : HDMI" << endl;
                    break;
                case D3DKMDT_VOT_INTERNAL:
                    wcout << " VideoOutputTechnology : Internal Monitor" << endl;
                    break;
                default:
                    wcout << " VideoOutputTechnology : " << VideoOutputTechnology << endl;
                    break;
                }

            }
            devMon++;

            ZeroMemory(&lpDisplayDevice2, sizeof(lpDisplayDevice2));
            lpDisplayDevice2.cb = sizeof(lpDisplayDevice2);
        }

        ZeroMemory(&lpDisplayDevice, sizeof(lpDisplayDevice));
        lpDisplayDevice.cb = sizeof(lpDisplayDevice);
        iDevNum++;
    }
}

int main(int argc, char* argv[])
{
    GetDisplayInfo();
    cout << "press enter to exit" << endl;
    cin.get();
    return 0;  
}

这将 return 类似于

MONITOR\CMN15BB\{4d36e96e-e325-11ce-bfc1-08002be10318}[=11=]01
   VideoOutputTechnology : Internal Monitor 
MONITOR\SNY7702\{4d36e96e-e325-11ce-bfc1-08002be10318}[=11=]00  
   VideoOutputTechnology : HDMI

很多显示器支持 DDC/CI 2B,如果它支持 VCP 功能 60h - Input Source,很容易询问显示器本身它连接的是什么,使用 Monitor Configuration API.

并非所有显示器都支持该协议,特别是大多数集成平板显示器和非常老式的 CRT,而且不能保证任何特定显示器都支持此 VCP 功能。

char* INPUT_SOURCES[] = {
    "Unknown",
    "Analog video (R/G/B) 1",
    "Analog video (R/G/B) 2",
    "Digital video (TMDS) 1 DVI",
    "Digital video (TMDS) 2 DVI",
    "Composite video 1",
    "Composite video 2",
    "S-video 1",
    "S-video 2",
    "Tuner 1",
    "Tuner 2",
    "Tuner 3",
    "Component video (YPbPr/ YCbCr) 1",
    "Component video (YPbPr/ YCbCr) 2",
    "Component video (YPbPr/ YCbCr) 3",
    "DisplayPort 1",
    "DisplayPort 2",
    "Digital Video (TMDS) 3 HDMI",
    "Digital Video (TMDS) 4 HDMI"
};

char* getInputSourceName(DWORD value)
{
    if (value > ARRAYSIZE(INPUT_SOURCES)) value = 0;
    return INPUT_SOURCES[value];
}

BOOL CALLBACK enumNextMonitor(HMONITOR monitor, HDC dc, LPRECT dRect, LPARAM param)
{
    DWORD physNum;
    if (GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, &physNum)) {
        if (physNum == 0) return 1;

        PHYSICAL_MONITOR* displays = malloc(physNum * sizeof(PHYSICAL_MONITOR));
        if (GetPhysicalMonitorsFromHMONITOR(monitor, physNum, displays)) {
            for (DWORD i = 0; i < physNum; i++) {
                DWORD value;
                if (GetVCPFeatureAndVCPFeatureReply(displays[i].hPhysicalMonitor, 0x60, NULL, &value, NULL)) {
                    printf(
                        "Display: %s, input is %s\n", 
                        displays[i].szPhysicalMonitorDescription, 
                        getInputSourceName(value)
                    );
                } else {
                    printf(
                        "Display: %s, unknown input type\n", 
                        displays[i].szPhysicalMonitorDescription
                    );
                }
            }
        }
        free(displays);
    }
}

int main()
{
    EnumDisplayMonitors(NULL, NULL, enumNextMonitor, NULL);
}

作为后备,显示器 EDID 通常包含有关所用连接类型的有限信息 – 它可能像 "Analog" 或 "Digital" 一样简单,对显示器的数字连接进行更详细的分解实施。

可用于获取显示器 EDID 的 SetupAPI,还提供了对其连接的显示适配器的参考。或许,也可以查询连接详情。