【问题标题】:Call to GetDIBits() succeeds, but program terminates调用 GetDIBits() 成功,但程序终止
【发布时间】:2021-05-10 20:49:57
【问题描述】:

当我在 Windows 程序中调用以下函数时,程序突然终止。

ScanRect()的目的是在屏幕上的指定坐标处复制一个矩形,并将像素值加载到内存缓冲区中。

ScanRect() 中的每个函数调用都会成功,包括对GetDIBits() 的两次调用。第一次调用,将lpvBits 设置为NULL,使它用有关像素数据的信息填充bmInfoBITMAPINFOHEADER,报告每像素32 位的值。第二次调用GetDIBits() 将矩形的80 行复制到内存缓冲区pMem,返回值80 表示复制的行数。

一切似乎都成功了,但随后程序突然终止。我在第二次调用GetDIBits() 后插入了Sleep(8192) 行,程序在8 秒后终止。

是什么导致程序终止?

编辑:原始代码根据此线程中的建议进行了修改。函数运行时没有检测到错误,但程序仍然意外终止。我意识到内存缓冲区大小是硬编码的,但它比测试中使用的矩形所需的要大得多。这不应该导致错误。当然,在找出程序终止的原因后,我会让程序计算必要的缓冲区大小。

VOID ScanRect(int x, int y, int iWidth, int iHeight) // 992, 96, 64, 80
{   HDC hDC = GetDC(NULL);
    if (!hDC)
    {
      cout << "!hDC" << endl;        // error handling ...
    }
    else
    {   HBITMAP hBitmap = CreateCompatibleBitmap(hDC, iWidth, iHeight);
        if (!hBitmap)
        {
           cout << "!hBitmap" << endl;        // error handling ...
        }
        else
        {   HDC hCDC = CreateCompatibleDC(hDC); // compatible with screen DC
            if (!hCDC)
            {
              cout << "!hCDC" << endl;        // error handling ...
            }
            else
            {   HBITMAP hOldBitmap = (HBITMAP) SelectObject(hCDC, hBitmap);
                BitBlt(hCDC, 0, 0, iWidth, iHeight, hDC, x, y, SRCCOPY);
                BITMAPINFO bmInfo = {0};
                bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader);
                if (!GetDIBits(hCDC, hBitmap, 0, iHeight, NULL, &bmInfo, DIB_RGB_COLORS))
                {
                  cout << "!GetDIBits" << endl; // error handling ...
                }
                else
                {   HANDLE hHeap = GetProcessHeap();
                    LPVOID pMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 65536); // TODO: calculate a proper size based on bmInfo's pixel information ...
                    if (!pMem)
                    {
                      cout << "!pMem" << endl;
                    }
                    else
                    {   int i = GetDIBits(hCDC, hBitmap, 0, iHeight, pMem, &bmInfo, DIB_RGB_COLORS);
                        cout << "i returned by GetDIBits() " << i << endl;
                        HeapFree(hHeap, NULL, pMem);
                    }
                }
                SelectObject(hCDC, hOldBitmap);
                DeleteDC(hCDC);
            }
            DeleteObject(hBitmap);
        }
        ReleaseDC(NULL, hDC);
    }
}

【问题讨论】:

  • 使用调试器而不是睡眠来找出问题所在。
  • 在删除 HDC 或 HGDIOBJ 之前,您应该始终撤消 SelectObject
  • 你不知道任何函数是否成功,因为你没有做错误检查
  • David Heffernan,我使用 cout 对 AllocConsole 创建的控制台进行了错误检查,但为了简洁起见,我在代码示例中省略了这一点。相信我,我检查了所有调用函数的返回值,它们成功了。如果将函数复制并粘贴到 Windows 程序中,则可以检查返回值。在第二次调用 GetDIBits() 之后,i 的值是 80。根据 Ben Voight 的建议,我将在弄清楚如何执行此操作后尝试撤消 SelectObject()。但请注意,当我注释掉函数的最后四行时,程序仍然会终止。
  • 你没有为颜色表预留空间,所以这可能会溢出堆栈缓冲区,这是个坏消息。

标签: winapi


【解决方案1】:

biCompression 值是由第一个 GetDIBits 返回的 BI_BITFIELDS 并且在您调用第二个 GetDIBits 之前,您需要调用 bmInfo.bmiHeader.biCompression = BI_RGB;。根据c++ read pixels with GetDIBits(),将其设置为BI_RGB 是必不可少的,以避免在结构末尾写入额外的3 个DWORD。
More details

【讨论】:

  • 感谢YangXiaPo 发布该问题的最终解决方案。 BITMAPINFO结构的BITMAPINOHEADER成员的biCompression字段设置为BI_RGB后,ScanRect()函数按预期工作,程序不会意外终止。我发布的“解决方案”解决了这个问题并暗示了原因,缓冲区溢出导致堆栈溢出。
【解决方案2】:

就像@BenVoigt 在 cmets 中所说,在销毁拥有它的 HDC 之前,您需要恢复用 SelectObject() 替换的旧 HBITMAP。您将hBitmap 选择为hCDC,然后在销毁hBitmap 之前销毁hCDC

https://docs.microsoft.com/en-us/windows/win32/gdi/operations-on-graphic-objects

这些函数中的每一个都返回一个标识新对象的句柄。在应用程序检索到句柄后,它必须调用SelectObject() 函数来替换默认对象。 但是,应用程序应保存标识默认对象的句柄,并在不再需要时使用此句柄替换新对象。当应用程序使用新对象完成绘制后,必须调用SelectObject() 函数恢复默认对象,然后调用DeleteObject() 函数删除新对象。 无法删除对象会导致严重的性能问题。

此外,您应该按照创建它们的相反顺序释放 GDI 对象。

而且,不要忘记错误处理。

试试类似的方法:

VOID ScanRect(int x, int y, int iWidth, int iHeight) // 992, 96, 64, 80
{ 
    HDC hDC = GetDC(NULL);
    if (!hDC)
    {
        // error handling ...
    }
    else
    {
        HBITMAP hBitmap = CreateCompatibleBitmap(hDC, iWidth, iHeight);
        if (!hBitmap)
        {
            // error handling ...
        }
        else
        {
            HDC hCDC = CreateCompatibleDC(hDC); // compatible with screen DC
            if (!hCDC)
            {
                // error handling ...
            }
            else
            {
                HBITMAP hOldBitmap = (HBITMAP) SelectObject(hCDC, hBitmap);

                BitBlt(hCDC, 0, 0, iWidth, iHeight, hDC, x, y, SRCCOPY);

                SelectObject(hCDC, hOldBitmap);

                BITMAPINFO bmInfo = {0};
                bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader);

                if (!GetDIBits(hCDC, hBitmap, 0, iHeight, NULL, &bmInfo, DIB_RGB_COLORS))
                {
                    // error handling ...
                }
                else
                {
                    HANDLE hHeap = GetProcessHeap();
                    LPVOID pMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 65536); // TODO: calculate a proper size based on bmInfo's pixel information ...
                    if (!pMem)
                    {
                        // error handling ...
                    }
                    else
                    {
                        int i = GetDIBits(hCDC, hBitmap, 0, iHeight, pMem, &bmInfo, DIB_RGB_COLORS);
                        HeapFree(hHeap, NULL, pMem);
                    }
                }

                DeleteDC(hCDC);
            }

            DeleteObject(hBitmap);
        }

        ReleaseDC(NULL, hDC);
    }
}

注意对HeapAlloc() 的调用中的TODO。你真的应该根据位图的实际宽度、高度、像素深度、扫描线填充大小等来计算缓冲区大小。不要使用硬编码的缓冲区大小。我将把它作为一个练习让你弄清楚。虽然在这个特定的例子中,64K 对于 64x80 32bpp 位图来说应该足够大,但它只会浪费 45K 的未使用内存。

【讨论】:

  • 我将您的代码示例复制并粘贴到我的程序中。它符合但当我运行它时,程序仍然意外终止。现在我将检查返回值。
  • 我检查了返回值,没有错误。但是,程序仍然终止。
  • 那么问题很可能不在这段代码中。在调试器下运行你的代码,看看到底什么地方崩溃了。
  • 我会试一试的。你很有帮助。
  • “解决问题”是指程序停止终止,这是问题性质的线索。最终的解决方案是将 BITMAPINFOHEADER 的 biCompression 字段设置为 BI_RGB,防止 BITMPAPINFO 结构超出界限。
猜你喜欢
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 2020-12-24
  • 2021-07-07
  • 1970-01-01
  • 2015-11-19
  • 1970-01-01
  • 2011-04-19
相关资源
最近更新 更多