Pracka
Pracka

Reputation: 159

GetDIBits() sets destination pointer to NULL with no error

I am trying to put an application icon into a char array. The code below converts a HICON into a BITMAP, then attempts to extract the bytes from the BITMAP into a char array. As I step through the code, I observed that the second GetDIBits() modifies the destination pointer to NULL despite claiming 16 bytes were written. This behavior is very puzzling. I suspect that casting BITMAPINFOHEADER* into BITMAPINFO* might be problematic, but using a BITMAPINFO directly causes stack corruption upon exiting the function. Does anyone know why GetDIBits() behaves in such a way?

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;
}

Upvotes: 0

Views: 327

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 596001

You need to allocate a suitably-sized BITMAPINFO and cast it to BITMAPINFOHEADER* (or just use its bmiHeader member). Not allocate a BITMAPINFOHEADER and cast it to BITMAPINFO*. A BITMAPINFO consists of a BITMAPINFOHEADER followed by an array of 0 or more RGBQUAD elements for a color table. A BITMAPINFOHEADER itself does not contain the color table, but it does describe the color table that follows it.

Per the 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.

So, in your case, you are setting lpvBits to NULL, but the BITMAPINFOHEADER::biBitCount field is not 0, so GetDIBits() will try to fill in the color table of the provided BITMAPINFO, but you are not allocating any memory to receive that color table. So GetDIBits() ends up corrupting the memory that follows the BITMAPINFOHEADER.

Upvotes: 2

Zeus
Zeus

Reputation: 3890

I think you did not guarantee that the hbm parameter of GetDIBits is compatible bitmap during the process of converting HICON to HBITMAP.

Try to use the following code and test :

#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;
}

Upvotes: 1

Related Questions