Win32:捕获显示监视器的句柄

Win32: capture handle to Display monitor

我目前正在开发一个应用程序,该应用程序需要 HDC 连接到系统的每个屏幕。

我目前正在使用这样的代码:

std::vector<HDC> dcs;
HDC dcMain = ::GetDC(nullptr); // <-- don't understand this

::EnumDisplayMonitors(dcMain, nullptr, MONITORENUMPROC(&DisplayMonitorCallback), LPARAM(&dcs));

我的回调如下:

BOOL DisplayMonitorCallback(const HMONITOR monitor, const HDC hdcMonitor, const LPRECT lprcMonitor, std::vector<HDC>& dcs)
{
    dcs.push_back(hdcMonitor);

    // here is where it gets weird!
    HBRUSH br = CreateSolidBrush(RGB(0, 255, 0));

    auto rst = FillRect(hdcMonitor, lprcMonitor, br);

    // Process all monitors
    return TRUE;
}

请注意,我目前正在每个屏幕上渲染一个绿色画笔。这在 THIS 上下文中(即在回调中)完美运行。

现在,问题是,我正在捕获那些 HDC 供以后使用。

几行之后,我将迭代我的 dcs 向量:

for (HDC dc : dcs)
{
    HBRUSH br = CreateSolidBrush(RGB(255, 255, 0));

    RECT x = { 100, 100, 500, 500 };        

    auto rst = FillRect(dc, &x, br);

    printf("%d", rst);
}

所以,我的问题是:

  1. 为了dcMain,我必须把这个传进来,这是获得一个的好方法吗?

  2. 为什么渲染在回调中工作,但当我捕获 HDCs 并稍后迭代它们时不工作?

  1. 是的,EnumDisplayMonitors() 文档中提到了这一点:

    To paint the entire virtual screen optimally for each display monitor, you can use code like this:

    hdc = GetDC(NULL);
    EnumDisplayMonitors(hdc, NULL, MyPaintScreenEnumProc, 0);
    ReleaseDC(NULL, hdc);
    
  2. 正如@andlabs 建议的那样,
  3. HDC 仅在回调内部有效。这是有道理的,因为必须获得一个HDC然后释放,但是只有EnumDisplayMonitors()知道如何每个HDC被获得,所以只有它知道如何正确地释放每一个。由于没有 API 函数用于释放枚举 HDC,这意味着 HDC 在枚举之外无效。

    MSDN 告诉您如何获得给定监视器的 HDC

    HMONITOR and the Device Context

    Each physical display is represented by a monitor handle of type HMONITOR. A valid HMONITOR is guaranteed to be non-NULL. A physical display has the same HMONITOR as long as it is part of the desktop. When a WM_DISPLAYCHANGE message is sent, any monitor may be removed from the desktop and thus its HMONITOR becomes invalid or has its settings changed. Therefore, an application should check whether all HMONITORS are valid when this message is sent.

    Any function that returns a display device context (DC) normally returns a DC for the primary monitor. To obtain the DC for another monitor, use the EnumDisplayMonitors function. Or, you can use the device name from the GetMonitorInfo function to create a DC with CreateDC. However, if the function, such as GetWindowDC or BeginPaint, gets a DC for a window that spans more than one display, the DC will also span the two displays.

    例如:

    typedef std::vector<HDC> hdc_vector;
    
    BOOL CALLBACK DisplayMonitorCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
    {
        MONITORINFOEX mi = {0};
        mi.cbSize = sizeof(mi);
        if (GetMonitorInfo(hMonitor, &mi))
        {
            HDC dc = CreateDC(NULL, mi.szDevice, NULL, NULL);
            if (dc)
                reinterpret_cast<hdc_vector*>(dwData)->push_back(dc);
        }
        ...
        return TRUE;
    }
    

    hdc_vector dcs;
    
    EnumDisplayMonitors(dcMain, nullptr, DisplayMonitorCallback, reinterpret_cast<LPARAM>(&dcs));
    
    ...
    
    for (HDC dc : dcs)
    {
        ...
    }
    
    ...
    
    for (HDC dc : dcs)
        DeleteDC(dc);
    

    由于您显然使用的是 C++11,我建议使用 std::unique_ptr 进行 HDC 的内存管理,这样您就不必手动调用 DeleteDC() .我会使用 lambda 进行回调,并将 std::vector 更改为 std::map(因此您可以在需要时查找任何特定监视器的 HDC):

    typedef std::unique_ptr<std::remove_pointer<HDC>::type, decltype(::DeleteDC)> device_hdc;
    typedef std::map<HMONITOR, device_hdc> device_hdc_map;
    
    device_hdc_map dcs;
    
    EnumDisplayMonitors(dcMain, nullptr,
        [](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL {
            MONITORINFOEX mi = {0};
            mi.cbSize = sizeof(mi);
            if (GetMonitorInfo(hMonitor, &mi))
            {
                HDC dc = CreateDC(NULL, mi.szDevice, NULL, NULL);
                if (dc)
                    (*reinterpret_cast<device_hdc_map*>(dwData))[hMonitor] = device_hdc(dc, &::DeleteDC);
            }
            ...
            return TRUE;
        },
        reinterpret_cast<LPARAM>(&dcs)
    );
    
    ...
    
    for (device_hdc_map::value_type &dc : dcs)
    {
        // use dc.second.get() (the actual HDC) as needed ...
    }