Search code examples
c++winapigdi

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

Solution

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