GetDIBits() 将目标指针设置为 NULL,没有错误

GetDIBits() sets destination pointer to NULL with no error

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

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

根据 GetDIBits() documentation:

If the requested format for the DIB matches its internal format, the RGB values for the bitmap are copied. If the requested format doesn't match the internal format, a color table is synthesized...

If the lpvBits parameter is a valid pointer, the first six members of the BITMAPINFOHEADER structure must be initialized to specify the size and format of the DIB. The scan lines must be aligned on a DWORD except for RLE compressed bitmaps.

A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number. The bitmap color table will be appended to the BITMAPINFO structure.

If lpvBits is NULL, GetDIBits examines the first member of the first structure pointed to by lpbi. This member must specify the size, in bytes, of a BITMAPCOREHEADER or a BITMAPINFOHEADER structure. The function uses the specified size to determine how the remaining members should be initialized.

If lpvBits is NULL and the bit count member of BITMAPINFO is initialized to zero, GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table. This technique can be used to query bitmap attributes.

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

之后的内存

我觉得你在HICONHBITMAP的过程中,没有保证GetDIBitshbm参数是兼容位图。

尝试使用以下代码进行测试:

#include <iostream>
#include <windows.h>
using namespace std;

HBITMAP getBmp(HICON hIcon)
{
    HDC hDC = GetDC(NULL);
    HDC hMemDC = CreateCompatibleDC(hDC);
    HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, 32, 32);
    HBITMAP hResultBmp = NULL;
    HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);

    DrawIconEx(hMemDC, 0, 0, hIcon, 32, 32, 0, NULL, DI_NORMAL);

    hResultBmp = hMemBmp;
    hMemBmp = NULL;

    SelectObject(hMemDC, hOrgBMP);
    DeleteDC(hMemDC);
    ReleaseDC(NULL, hDC);
    DestroyIcon(hIcon);
    return hResultBmp;
}

BYTE* getPixArray(HBITMAP hBitmap)
{
    HDC hdc, hdcMem;

    hdc = GetDC(NULL);
    hdcMem = CreateCompatibleDC(hdc);

    BITMAPINFO MyBMInfo = { 0 };
    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
    if (0 == GetDIBits(hdcMem, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS))
    {
        cout << " fail " << endl;
    }
    BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];

    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
    MyBMInfo.bmiHeader.biBitCount = 32;
    MyBMInfo.bmiHeader.biCompression = BI_RGB;
    MyBMInfo.bmiHeader.biHeight = (MyBMInfo.bmiHeader.biHeight < 0) ? (-MyBMInfo.bmiHeader.biHeight) : (MyBMInfo.bmiHeader.biHeight);

    // get the actual bitmap buffer
    if (0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS))
    {
        cout << " fail " << endl;
    }

    return lpPixels;
}
int main(int argc, const char* argv[])
{
    HICON hIcon = (HICON)LoadImage(0, L"test.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
    HBITMAP hbmp = getBmp(hIcon);
    getPixArray(hbmp);
    return 0;
}