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.
问题根源
- GDI位图行对齐要求:Windows GDI规定位图每行字节数必须是4的整数倍(DWORD对齐)。当宽度为奇数时,
CV_8UC3格式的cv::Mat行字节数为width*3,无法满足对齐要求,导致GetDIBits写入时越界破坏堆内存。 - 资源管理漏洞:重新创建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
相关产品推荐
相关产品推荐

