See the question and my original answer on StackOverflow

You can use Connecting and configuring displays (CCD) API, especially the The QueryDisplayConfig function which retrieves information about all possible display paths for all display devices, or views, in the current setting.

With the following code, you'll get the correspondance between a Device Path and a Monitor (and its handle).

#include <Windows.h>
#include <stdio.h>
#include <vector>
#include <tuple>
#include <string>

int main()
    // get all paths
    UINT pathCount;
    UINT modeCount;
    if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount))
        return 0;

    std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
    std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
    if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount,, &modeCount,, nullptr))
        return 0;

    // enum all monitors => (handle, device name)>
    std::vector<std::tuple<HMONITOR, std::wstring>> monitors;
    EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hmon, HDC hdc, LPRECT rc, LPARAM lp)
        MONITORINFOEX mi = {};
        mi.cbSize = sizeof(MONITORINFOEX);
        GetMonitorInfo(hmon, &mi);
        auto monitors = (std::vector<std::tuple<HMONITOR, std::wstring>>*)lp;
        monitors->push_back({ hmon, mi.szDevice });
        return TRUE;
    }, (LPARAM)&monitors);

    // for each path, get GDI device name and compare with monitor device name
    for (UINT i = 0; i < pathCount; i++)
        deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
        deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
        deviceName.header.adapterId = paths[i].targetInfo.adapterId; = paths[i];
        if (DisplayConfigGetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&deviceName))

        wprintf(L"Monitor Friendly Name : %s\n", deviceName.monitorFriendlyDeviceName);
        wprintf(L"Monitor Device Path   : %s\n", deviceName.monitorDevicePath);

        sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
        sourceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
        sourceName.header.adapterId = paths[i].targetInfo.adapterId; = paths[i];
        if (DisplayConfigGetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&sourceName))

        wprintf(L"GDI Device Name       : %s\n", sourceName.viewGdiDeviceName);

        // find the monitor with this device name
        auto mon = std::find_if(monitors.begin(), monitors.end(), [&sourceName](std::tuple<HMONITOR, std::wstring> t)
            return !std::get<1>(t).compare(sourceName.viewGdiDeviceName);
        wprintf(L"Monitor Handle        : %p\n", std::get<0>(*mon));
    return 0;

On my PC (with 2 monitors), it will output something like this:

Monitor Friendly Name : C27HG7x
Monitor Device Path   : \\?\DISPLAY#SAM0E16#7&307b5912&0&UID1024#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
GDI Device Name       : \\.\DISPLAY2
Monitor Handle        : 0000000000160045

Monitor Friendly Name : DELL U2715H
Monitor Device Path   : \\?\DISPLAY#DELD069#7&307b5912&0&UID1028#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
GDI Device Name       : \\.\DISPLAY1
Monitor Handle        : 0000000000020083

Note you can also get the same information using WinRT's DisplayManager class => DisplayView class => Paths property, DisplayPath class, => Target property => TryGetMonitor function