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

Windows API BitBlt在部分分辨率下截图失败问题求助

屏幕捕获程序特定分辨率下内存错误排查

问题描述

使用C++基于Windows API的BitBlt函数编写了屏幕捕获保存程序,但部分分辨率下会触发内存错误,生成的截图无法查看,直接访问字节缓冲区时会抛出内存不可访问异常。已知失效分辨率包括:

  • 所有宽高小于36×36的情况
  • 255×255的分辨率

问题根源

核心问题是Windows DIB(设备无关位图)的行对齐要求:DIB规定每一行的字节数必须是4的整数倍,即行宽需按4字节对齐。你的代码直接用width * 3计算每行字节数,完全忽略了对齐规则,导致以下问题:

  • 35×35分辨率:每行实际需要108字节(35×3=105,向上取整到最近的4的倍数),但代码按105字节计算总数据量,拷贝时要么未获取完整数据,要么访问超出自己分配的内存触发异常。
  • 255×255分辨率:255×3=765,765除以4余1,实际行宽为768字节,总数据量远大于代码计算的255×255×3,拷贝时必然出现内存越界。

另外还有个次要问题:代码中保存的文件后缀为.png,但实际写入的是BMP格式,导致系统无法正确识别文件。

修复后的代码

#include <iostream>
#include <windows.h>
#include <fstream>

// 计算4字节对齐后的行字节数
inline int GetAlignedRowWidth(int width, int bitsPerPixel) {
    return ((width * bitsPerPixel + 31) / 32) * 4;
}

BYTE* CaptureScreen(int x, int y, int width, int height, int alignedRowWidth);

bool CaptureScreenAndSaveToFile(int x, int y, int width, int height, const char* filename) {
    const int bitsPerPixel = 24;
    const int alignedRowWidth = GetAlignedRowWidth(width, bitsPerPixel);
    const int totalDataSize = alignedRowWidth * height;

    BYTE* imageData = CaptureScreen(x, y, width, height, alignedRowWidth);
    if (imageData == nullptr) {
        return false;
    }

    std::ofstream outFile(filename, std::ios::binary);
    if (!outFile.is_open()) {
        delete[] imageData;
        return false;
    }

    // 写入BMP文件头
    BITMAPFILEHEADER bmpFileHeader;
    bmpFileHeader.bfType = 0x4D42; // 'BM'标识
    bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + totalDataSize;
    bmpFileHeader.bfReserved1 = 0;
    bmpFileHeader.bfReserved2 = 0;
    bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    outFile.write(reinterpret_cast<const char*>(&bmpFileHeader), sizeof(BITMAPFILEHEADER));

    // 写入BMP信息头
    BITMAPINFOHEADER bmpInfoHeader;
    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfoHeader.biWidth = width;
    bmpInfoHeader.biHeight = -height; // 负高度表示图像自上而下
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = bitsPerPixel;
    bmpInfoHeader.biCompression = BI_RGB;
    bmpInfoHeader.biSizeImage = totalDataSize; // 明确填写对齐后的图像数据大小
    bmpInfoHeader.biXPelsPerMeter = 0;
    bmpInfoHeader.biYPelsPerMeter = 0;
    bmpInfoHeader.biClrUsed = 0;
    bmpInfoHeader.biClrImportant = 0;
    outFile.write(reinterpret_cast<const char*>(&bmpInfoHeader), sizeof(BITMAPINFOHEADER));

    // 写入对齐后的完整图像数据
    outFile.write(reinterpret_cast<const char*>(imageData), totalDataSize);

    outFile.close();
    delete[] imageData;

    return true;
}

BYTE* CaptureScreen(int x, int y, int width, int height, int alignedRowWidth) {
    BYTE* result = nullptr;
    const int totalDataSize = alignedRowWidth * height;

    HDC hdc = GetDC(HWND_DESKTOP);
    if (hdc == NULL) return result;

    HDC hdcTemp = CreateCompatibleDC(hdc);
    if (hdcTemp == NULL) {
        ReleaseDC(HWND_DESKTOP, hdc);
        return result;
    }

    BITMAPINFO bitmap;
    ZeroMemory(&bitmap, sizeof(bitmap));
    bitmap.bmiHeader.biSize = sizeof(bitmap.bmiHeader);
    bitmap.bmiHeader.biWidth = width;
    bitmap.bmiHeader.biHeight = -height;
    bitmap.bmiHeader.biPlanes = 1;
    bitmap.bmiHeader.biBitCount = 24;
    bitmap.bmiHeader.biCompression = BI_RGB;
    bitmap.bmiHeader.biSizeImage = totalDataSize;
    bitmap.bmiHeader.biClrUsed = 0;
    bitmap.bmiHeader.biClrImportant = 0;

    BYTE* bitPointer = nullptr;
    HBITMAP hBitmap = CreateDIBSection(hdcTemp, &bitmap, DIB_RGB_COLORS, (void**)&bitPointer, NULL, NULL);
    if (hBitmap != NULL && bitPointer != nullptr) {
        HBITMAP hPrevBitmap = (HBITMAP)SelectObject(hdcTemp, hBitmap);

        BitBlt(hdcTemp, 0, 0, width, height, hdc, x, y, SRCCOPY);

        // 按对齐后的总大小分配内存并拷贝数据
        result = new BYTE[totalDataSize];
        if (result != nullptr) {
            memcpy(result, bitPointer, totalDataSize);
        }

        SelectObject(hdcTemp, hPrevBitmap);
        DeleteObject(hBitmap);
    }

    DeleteDC(hdcTemp);
    ReleaseDC(HWND_DESKTOP, hdc);

    return result;
}

int main() {
    // 修正文件后缀为.bmp,确保格式匹配
    CaptureScreenAndSaveToFile(125, 125, 35, 35, "Hello.bmp");
    CaptureScreenAndSaveToFile(0, 0, 255, 255, "255x255.bmp");
    return 0;
}

修复要点说明

  1. 添加行对齐计算:GetAlignedRowWidth函数使用标准公式计算符合DIB要求的行宽,确保每行字节数为4的整数倍。
  2. 修正内存逻辑:所有涉及图像数据大小的计算、内存分配和拷贝操作,均使用对齐后的总数据量,彻底避免内存越界。
  3. 明确图像数据大小:在BITMAPINFO和BITMAPINFOHEADER中明确填写biSizeImage,让CreateDIBSection能准确分配内存。
  4. 修正文件后缀:将.png改为.bmp,保证文件格式与内容一致,可被系统正确识别。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:04:51