首页 > 解决方案 > GetDIBits() 将目标指针设置为 NULL 且没有错误

问题描述

我正在尝试将应用程序图标放入 char 数组中。下面的代码将 HICON 转换为 BITMAP,然后尝试将 BITMAP 中的字节提取到 char 数组中。当我单步执行代码时,我观察到第二个 GetDIBits() 将目标指针修改为 NULL,尽管声称写入了 16 个字节。这种行为非常令人费解。我怀疑强制BITMAPINFOHEADER*转换BITMAPINFO*可能有问题,但是BITMAPINFO在退出函数时直接使用 a 会导致堆栈损坏。有谁知道为什么 GetDIBits() 会这样?

std::unique_ptr<char> getRawImg(HICON& icon)
{
    // step 1 : get a bitmap from an application icon
    ICONINFO iconInfo;
    ZeroMemory(&iconInfo, sizeof(iconInfo));
    BITMAP bitMap;
    ZeroMemory(&bitMap, sizeof(bitMap));
    HRESULT bRes = GetIconInfo(icon, &iconInfo);

    int width;  
    int height;  
    int bitsPerPixel;
    
    if (iconInfo.hbmColor)    // color icon
    {
        if (GetObject(iconInfo.hbmColor, sizeof(bitMap), &bitMap))
        {
            width = bitMap.bmWidth;
            height = bitMap.bmHeight;
            bitsPerPixel = bitMap.bmBitsPixel;
        }
    }
    else if (iconInfo.hbmMask)  // black and white icon
    {
        if (GetObject(iconInfo.hbmMask, sizeof(bitMap), &bitMap))
        {
            width = bitMap.bmWidth;
            height = bitMap.bmHeight / 2;
            bitsPerPixel = 1;
        }
    }

    // step 2 : extract bytes from the bitmap into a byte array
    HBITMAP hBitmap = CreateBitmapIndirect(&bitMap);
    int stride = (width * bitsPerPixel + 31) / 32 * 4;

    HDC hdc = GetDC(NULL);

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

    BITMAPINFO* pBitmapInfo = (BITMAPINFO*)&bi;
    if (!GetDIBits(hdc, hBitmap, 0, 0, NULL, pBitmapInfo, DIB_RGB_COLORS)) {
        // error
        std::cout << "failed to get bitmap info" << std::endl;
    }

    std::unique_ptr<char> buffer(new char[bi.biWidth * bi.biHeight * bi.biBitCount / 8]);

    // Buffer points to some address before calling GetDIBits(). After calling GetDIBits(), buffer points to NULL. bytesWritten is 16
    int bytesWritten = GetDIBits(hdc, hBitmap, 0, height, (LPVOID)buffer.get(), pBitmapInfo, DIB_RGB_COLORS);
        if (bytesWritten <= 0) {
            // error
            std::cout << "failed" << std::endl;
        }

    DeleteObject(hBitmap);
    ReleaseDC(NULL, hdc);
    if (iconInfo.hbmColor)
        DeleteObject(iconInfo.hbmColor);
    if (iconInfo.hbmMask)
        DeleteObject(iconInfo.hbmMask);
    return buffer;
}

标签: c++winapigdi

解决方案


您需要分配一个适当大小的BITMAPINFO并将其转换为BITMAPINFOHEADER*(或仅使用其bmiHeader成员)。不分配 aBITMAPINFOHEADER并将其转换为BITMAPINFO*. ABITMAPINFO由 aBITMAPINFOHEADER后跟一个包含 0 个或多个RGBQUAD颜色表元素的数组组成。ABITMAPINFOHEADER本身不包含颜色表,但它确实描述了它后面的颜色表。

根据GetDIBits()文档

如果请求的 DIB 格式与其内部格式匹配,则复制位图的 RGB 值。如果请求的格式与内部格式不匹配,则会合成一个颜色表...

如果 lpvBits 参数是有效指针,则必须初始化 BITMAPINFOHEADER 结构的前六个成员以指定 DIB 的大小和格式。扫描线必须在 DWORD 上对齐,RLE 压缩位图除外。

通过将高度设置为正数来指定自下而上的 DIB,而通过将高度设置为负数来指定自上而下的 DIB。位图颜色表将附加到 BITMAPINFO 结构中。

如果 lpvBits 为 NULL,GetDIBits 检查 lpbi 指向的第一个结构的第一个成员。此成员必须指定 BITMAPCOREHEADER 或 BITMAPINFOHEADER 结构的大小(以字节为单位)。该函数使用指定的大小来确定应如何初始化剩余成员。

如果 lpvBits 为 NULL 并且 BITMAPINFO 的位计数成员初始化为零,GetDIBits 将填充 BITMAPINFOHEADER 结构或 BITMAPCOREHEADER 而不使用颜色表。此技术可用于查询位图属性。

因此,在您的情况下,您设置lpvBits为 NULL,但该BITMAPINFOHEADER::biBitCount字段不是 0,因此GetDIBits()将尝试填写提供的颜色表BITMAPINFO,但您没有分配任何内存来接收该颜色表。所以GetDIBits()最终会破坏BITMAPINFOHEADER.


推荐阅读