对 GetDIBits() 的调用成功,但程序终止



当我在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);
}
}

biCompression值由第一个返回,GetDIBitsBI_BITFIELDS,在调用第二个GetDIBits之前,需要调用bmInfo.bmiHeader.biCompression = BI_RGB;。根据 GetDIBits() 的 c++ 读取像素,将其设置为BI_RGB是必不可少的,以避免在结构末尾写入额外的 3 个 DWORD.
更多详细信息

就像@BenVoigt评论中所说的那样,在销毁拥有它的HDC之前,您需要恢复用SelectObject()替换的旧HBITMAP。 您正在选择hBitmap进入hCDC,然后在销毁hBitmap之前销毁hCDC

https://learn.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()的待办事项。 您确实应该根据位图的实际宽度、高度、像素深度、扫描线填充大小等来计算缓冲区大小。 不要使用硬编码的缓冲区大小。 我将把这留给你一个练习,让你弄清楚。 虽然在这个特定示例中,64K 对于 64x80 32bpp 位图来说应该足够大,但它只会浪费 45K 的未使用内存。

最新更新