【问题标题】:GetDIBits() sets destination pointer to NULL with no errorGetDIBits() 将目标指针设置为 NULL 且没有错误
【发布时间】:2021-05-08 06:53:36
【问题描述】:

我正在尝试将应用程序图标放入 char 数组中。下面的代码将 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;
}

【问题讨论】:

    标签: c++ winapi gdi


    【解决方案1】:

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

    根据GetDIBits() documentation

    如果请求的 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 之后的内存。

    【讨论】:

    • 你是对的!我没有正确分配 BITMAPINFO,这就是导致堆栈损坏的原因。感谢您指出!
    【解决方案2】:

    我认为您在将HICON转换为HBITMAP的过程中没有保证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;
    }
    

    【讨论】:

    • 这行得通!修复我的 BITMAPINFO 分配问题后,我得到了空数组。原来我创建位图的方式也不正确。谢谢!
    • 使用 hOrgBMP 并调用 SelectObject(hMemDC, hOrgBMP) 是否有原因?我尝试删除 SelectObject(hMemDC, hOrgBMP) 它似乎也有效
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-22
    • 2016-03-01
    • 1970-01-01
    • 2010-10-16
    • 1970-01-01
    • 2016-05-24
    相关资源
    最近更新 更多