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

GDI+截图在奇数分辨率下失效,求调试解决方法

屏幕捕获类奇数分辨率截图堆损坏问题排查与修复建议

我用C++编写了ScreenCapturer类解决Python截图速度慢的问题,但生成101x101这类奇数分辨率截图时,Visual Studio会触发堆损坏(heap corruption)和访问违例(access violation)错误,调试陷入僵局,求解决提示。

类代码

#include <windows.h>
#include <gdiplus.h>
#include <iostream>
#include <opencv2/opencv.hpp>

#pragma comment (lib,"Gdiplus.lib")

class ScreenCapturer {
private:
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;

    HDC hScreenDC;
    HDC hMemoryDC;

    int lastWidth;
    int lastHeight;

    HBITMAP hBitmap;
    HBITMAP hOldBitmap;

public:
    ScreenCapturer(){
        Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

        hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
        if (hScreenDC == NULL) {
            throw std::runtime_error("Failed to create screen device context");
        }
 
        hMemoryDC = CreateCompatibleDC(hScreenDC);
        if (hMemoryDC == NULL) {
            DeleteDC(hScreenDC);
            throw std::runtime_error("Failed to create memory device context.");
        }

        lastWidth = GetDeviceCaps(hScreenDC, HORZRES);
        lastHeight = GetDeviceCaps(hScreenDC, VERTRES);

        hBitmap = CreateCompatibleBitmap(hScreenDC, lastWidth, lastHeight);
        if (!hBitmap) {
            DeleteDC(hMemoryDC);
            DeleteDC(hScreenDC);
            throw std::runtime_error("Failed to create compatible bitmap");
        }

        hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
    }

    ~ScreenCapturer() {
        SelectObject(hMemoryDC, hOldBitmap);
        DeleteDC(hMemoryDC);
        DeleteDC(hScreenDC);
        DeleteObject(hBitmap);
        Gdiplus::GdiplusShutdown(gdiplusToken);
    }

    cv::Mat captureScreen(int x, int y, int width, int height) {
        if (lastWidth != width || lastHeight != height) {
            SelectObject(hMemoryDC, hOldBitmap);
            DeleteObject(hBitmap);
            hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
            /*hOldBitmap = (HBITMAP)*/SelectObject(hMemoryDC, hBitmap);
            lastWidth = width;
            lastHeight = height;
        }

        if (BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, x, y, SRCCOPY) == NULL) {
            std::cout << "ERROR " << std::endl;
        }

        BITMAPINFOHEADER bi;
        bi.biSize = sizeof(BITMAPINFOHEADER);
        bi.biWidth = width;
        bi.biHeight = -height;
        bi.biPlanes = 1;
        bi.biBitCount = 24;
        bi.biCompression = BI_RGB;
        bi.biSizeImage = 0;
        bi.biXPelsPerMeter = 0;
        bi.biYPelsPerMeter = 0;
        bi.biClrUsed = 0;
        bi.biClrImportant = 0;

        cv::Mat areaImage(height, width, CV_8UC3);

        if (GetDIBits(hMemoryDC, hBitmap, 0, (UINT)height, areaImage.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS) == NULL) {
            std::cout << "ERROR " << std::endl;
        }

        return areaImage;
    }
};

测试代码

#include "ScreenCapturer.cpp"
int main() {
    ScreenCapturer capturer;
    auto screenshot1 = capturer.captureScreen(0, 0, 1920, 1080); // 正常工作
    cv::imwrite("test1.png", screenshot1);
    auto screenshot2 = capturer.captureScreen(0, 0, 100, 100); // 正常工作
    cv::imwrite("test2.png", screenshot2);
    auto screenshot3 = capturer.captureScreen(0, 0, 101, 101); // 失败
    cv::imwrite("test3.png", screenshot3);
    std::cout << "Done\n";
}

报错信息

A breakpoint instruction (__debugbreak() statement or a similar call) was executed in Everylay.exe.

继续运行后:

Unhandled exception at 0x00007FF9A7A4F349 (ntdll.dll) in Everylay.exe: 0xC0000374: A heap has been corrupted (parameters: 0x00007FF9A7AB97F0).

随后:

Exception thrown at 0x00007FF9A797D990 (ntdll.dll) in Everylay.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF.


问题根源

  1. GDI位图行对齐要求:Windows GDI规定位图每行字节数必须是4的整数倍(DWORD对齐)。当宽度为奇数时,CV_8UC3格式的cv::Mat行字节数为width*3,无法满足对齐要求,导致GetDIBits写入时越界破坏堆内存。
  2. 资源管理漏洞:重新创建bitmap时未更新hOldBitmap,析构函数中会使用无效句柄,引发资源错误。

修复方案

1. 处理行对齐问题

修改captureScreen方法,先将GDI对齐的数据读取到临时缓冲区,再转换到cv::Mat:

cv::Mat captureScreen(int x, int y, int width, int height) {
    if (lastWidth != width || lastHeight != height) {
        SelectObject(hMemoryDC, hOldBitmap);
        DeleteObject(hBitmap);
        hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
        // 必须更新hOldBitmap,避免析构错误
        hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap); 
        lastWidth = width;
        lastHeight = height;
    }

    if (!BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, x, y, SRCCOPY)) {
        std::cout << "BitBlt failed: " << GetLastError() << std::endl;
    }

    BITMAPINFOHEADER bi;
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = width;
    bi.biHeight = -height;
    bi.biPlanes = 1;
    bi.biBitCount = 24;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // 计算GDI要求的对齐行字节数
    int gdiStride = (width * 3 + 3) & ~3;
    std::vector<BYTE> buffer(gdiStride * height);

    if (!GetDIBits(hMemoryDC, hBitmap, 0, (UINT)height, buffer.data(), (BITMAPINFO*)&bi, DIB_RGB_COLORS)) {
        std::cout << "GetDIBits failed: " << GetLastError() << std::endl;
    }

    // 将对齐数据复制到cv::Mat
    cv::Mat areaImage(height, width, CV_8UC3);
    for (int i = 0; i < height; ++i) {
        memcpy(areaImage.ptr(i), buffer.data() + i * gdiStride, width * 3);
    }

    return areaImage;
}

2. 修复资源管理

确保每次重新创建bitmap时,更新hOldBitmap存储新的旧句柄,避免析构时出现无效操作。

调试建议

  • 启用Visual Studio堆检测:项目属性→配置属性→C/C++→代码生成→启用最小堆检查(设为"1"),更早发现堆越界。
  • 用GetLastError()获取API调用的具体错误码,替代单纯的"ERROR"输出,精准定位问题。
  • 断点调试时,查看buffer和areaImage的内存布局,确认行字节数是否符合对齐要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:57:42