Hi I am trying to create a Screen Capturing class in c++ (since python screenshots are slow as heck). I created this class but I can't create screenshots with odd resolutions like 101x101.
I get a bunch of heap corruption and access violation errors in VS and I have no clue how to debug this. Appreciate any hints/tips.
Here is the class:
#include <windows.h>
#include <gdiplus.h>
#include <iostream>
#include <opencv2/opencv.hpp>
#pragma comment (lib,"Gdiplus.lib")
class ScreenCapturer {
private:
// GDI+ token and startup input
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
// Device contexts
HDC hScreenDC;
HDC hMemoryDC;
// Screen dimensions
int lastWidth;
int lastHeight;
// Bitmap
HBITMAP hBitmap;
HBITMAP hOldBitmap; // Store the old bitmap here to use for recovery and cleanup
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); // Do we need to re-store the old bitmap?
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; // Negative for top-left origin
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;
// Allocate a cv::Mat for the area
cv::Mat areaImage(height, width, CV_8UC3);
// Copy the bits to the cv::Mat
if (GetDIBits(hMemoryDC, hBitmap, 0, (UINT)height, areaImage.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS) == NULL) {
std::cout << "ERROR " << std::endl;
}
return areaImage;
}
};
I am testing this class like this:
#include "ScreenCapturer.cpp"
int main() {
ScreenCapturer capturer;
auto screenshot1 = capturer.captureScreen(0, 0, 1920, 1080); // Works
cv::imwrite("test1.png", screenshot1);
auto screenshot2 = capturer.captureScreen(0, 0, 100, 100); // Works
cv::imwrite("test2.png", screenshot2);
auto screenshot3 = capturer.captureScreen(0, 0, 101, 101); // Doesn't work
cv::imwrite("test3.png", screenshot3);
std::cout << "Done\n";
}
I tried to simply get more info from VS. When it fails in debug mode it first comes up with a Breakpoint Instruction Executed with this message:
A breakpoint instruction (__debugbreak() statement or a similar call) was executed in Everylay.exe.
And then when I continue it follows up with messages like this:
Unhandled exception at 0x00007FF9A7A4F349 (ntdll.dll) in Everylay.exe: 0xC0000374: A heap has been corrupted (parameters: 0x00007FF9A7AB97F0).
And then this:
Exception thrown at 0x00007FF9A797D990 (ntdll.dll) in Everylay.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF.
I am really clueless how to debug this. Hope someone can help out.
Buffer pointed to by lpvBits
argument of GetDIBits
must have size sufficient to hold height * scanline_width
bytes. Where scanline_width
is img_width * bytes_per_pixel
with 4 bytes alignment. With 101 image width and 3 bytes per pixel buffer provided by cv::mat
will have insufficient size since it does not align scanlines properly. So you are getting a buffer overflow.
I should also mention that you using only plain GDI functions so GdiplusStartup
and GdiplusShutdown
are not required.