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

MFC捕获指定显示器截图写入剪贴板生成缩略图异常

实现目标

在对话框中展示一组显示器缩略图列表,用户点击对应缩略图即可触发预设操作。

已实现逻辑

显示器枚举

参考已有方案,通过EnumDisplayMonitors接口枚举获取所有显示器的矩形区域信息,额外扩展存储了每个显示器的设备名称,当前暂时通过CListBox控件展示显示器名称列表供用户选择,后续计划替换为显示器缩略图列表。
枚举实现代码:

struct MonitorRects
{
    std::vector<RECT>   rcMonitors;
    std::vector<CString> strMonitorNames;

    static BOOL CALLBACK MonitorEnum(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData)
    {
        MonitorRects* pThis = reinterpret_cast<MonitorRects*>(pData);
        pThis->rcMonitors.push_back(*lprcMonitor);

        MONITORINFOEX sMI{};
        sMI.cbSize = sizeof(MONITORINFOEX);
        GetMonitorInfo(hMon, &sMI);

        pThis->strMonitorNames.push_back(sMI.szDevice);

        return TRUE;
    }

    MonitorRects()
    {
        EnumDisplayMonitors(nullptr, nullptr, MonitorEnum, (LPARAM)this);
    }
};

屏幕捕获初版实现

为验证捕获逻辑,最初计划将捕获结果复制到剪贴板,再粘贴到图像编辑器中验证内容正确性,实现了如下捕获函数:

BOOL CCenterCursorOnScreenDlg::CaptureRect(const CRect& rcCapture, CImage &rImage)
{
    // 销毁当前已存储的位图,用于创建新位图
    rImage.Destroy();

    // 创建位图并关联到当前对象
    if (!rImage.Create(rcCapture.Width(), rcCapture.Height(), 32, 0))
    {
        AfxMessageBox(L"Cannot create image!", MB_ICONERROR);
        return FALSE;
    }


    // 创建虚拟屏幕设备上下文
    CDC dcScreen;
    dcScreen.CreateDC(_T("DISPLAY"), nullptr, nullptr, nullptr);

    // 从虚拟屏幕DC拷贝内容
    BOOL bRet = ::BitBlt(rImage.GetDC(), 0, 0, rcCapture.Width(), rcCapture.Height(),
        dcScreen.m_hDC, rcCapture.left, rcCapture.top,
        SRCCOPY | CAPTUREBLT);

    // 资源清理并返回
    dcScreen.DeleteDC();
    rImage.ReleaseDC();

    if (!(OpenClipboard() &&
        EmptyClipboard() &&
        ::SetClipboardData(CF_BITMAP, (HBITMAP)rImage)))
        AfxMessageBox(L"Error copying image to clipboard", MB_ICONERROR);
    CloseClipboard();

    return bRet;
}

调用代码如下:

CRect rcMonitor = m_monitors.rcMonitors.at(0);
CImage img;
CaptureRect(rcMonitor, img);

遇到的问题

运行初版代码时出现两个异常:

  • 将剪贴板内容粘贴到HyperSnap时,得到的图像为空白,未显示预期捕获的对应显示器画面
  • 执行粘贴操作后HyperSnap会卡死无响应,必须强制结束进程才能恢复

后续测试将捕获的CImage对象直接保存为本地JPG文件:

rImage.Save(L"d:\\testthumb.jpg", Gdiplus::ImageFormatJPEG);

打开保存的文件确认屏幕内容捕获完全正确,因此排除截图逻辑本身的问题,故障点定位在CImage对象写入剪贴板的环节。

故障原因

剪贴板写入逻辑存在两个核心错误:

  1. 直接将CImage强转得到的HBITMAP传给SetClipboardData不符合接口规范。SetClipboardData要求传入的位图句柄所有权在调用成功后转移给系统,且优先接收设备无关位图(DIB);而CImage持有的位图是和对象生命周期绑定的设备相关位图(DDB),当CImage对象离开作用域析构时,对应的位图资源会被自动释放,剪贴板中存储的句柄变为无效悬空指针,直接导致粘贴时内容空白、目标程序卡死。
  2. 剪贴板操作未做完整的错误分支处理:OpenClipboard传入空句柄时可能因其他程序占用剪贴板打开失败,此时直接执行后续操作会引发异常;SetClipboardData调用失败时也没有做对应资源回收。

修复方案

替换原有剪贴板操作逻辑,创建独立的DIB位图副本写入剪贴板,严格处理操作流程的资源归属和错误分支,修正后的完整捕获函数代码如下:

BOOL CCenterCursorOnScreenDlg::CaptureRect(const CRect& rcCapture, CImage &rImage)
{
    rImage.Destroy();
    if (!rImage.Create(rcCapture.Width(), rcCapture.Height(), 32, 0))
    {
        AfxMessageBox(L"Cannot create image!", MB_ICONERROR);
        return FALSE;
    }

    CDC dcScreen;
    dcScreen.CreateDC(_T("DISPLAY"), nullptr, nullptr, nullptr);
    BOOL bRet = ::BitBlt(rImage.GetDC(), 0, 0, rcCapture.Width(), rcCapture.Height(),
        dcScreen.m_hDC, rcCapture.left, rcCapture.top,
        SRCCOPY | CAPTUREBLT);
    dcScreen.DeleteDC();
    rImage.ReleaseDC();

    if (bRet)
    {
        HBITMAP hBmpSrc = (HBITMAP)rImage;
        BITMAP bmpInfo = {0};
        GetObject(hBmpSrc, sizeof(BITMAP), &bmpInfo);

        HDC hMemDC = CreateCompatibleDC(nullptr);
        BITMAPINFO bmi = {0};
        bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmi.bmiHeader.biWidth = bmpInfo.bmWidth;
        bmi.bmiHeader.biHeight = bmpInfo.bmHeight;
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = BI_RGB;

        void* pBits = nullptr;
        HBITMAP hBmpClip = CreateDIBSection(hMemDC, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
        if (hBmpClip)
        {
            HGDIOBJ hOldBmp = SelectObject(hMemDC, hBmpClip);
            BitBlt(hMemDC, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, rImage.GetDC(), 0, 0, SRCCOPY);
            rImage.ReleaseDC();
            SelectObject(hMemDC, hOldBmp);

            if (OpenClipboard(m_hWnd))
            {
                EmptyClipboard();
                if (!SetClipboardData(CF_BITMAP, hBmpClip))
                {
                    AfxMessageBox(L"Error copying image to clipboard", MB_ICONERROR);
                    DeleteObject(hBmpClip);
                }
                CloseClipboard();
            }
            else
            {
                DeleteObject(hBmpClip);
                AfxMessageBox(L"Cannot open clipboard", MB_ICONERROR);
            }
        }
        DeleteDC(hMemDC);
    }

    return bRet;
}

后续缩略图实现提示

如果仅需在对话框内展示显示器缩略图,不需要经过剪贴板中转:捕获到每个显示器的完整画面后,直接将CImage缩放到预设的缩略图尺寸,通过自绘控件或列表项展示即可,点击缩略图时关联之前存储的显示器设备名、矩形区域信息,即可触发对应预设操作。


内容的提问来源于stack exchange,提问作者Andrew Truckle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:24:11