Reputation: 61
When I call the following function in a Windows program, the program abruptly terminates.
The purpose of ScanRect()
is to copy a rectangle at specified coordinates on the screen and load the pixel values into a memory buffer.
Every function call within ScanRect()
succeeds, including both calls to GetDIBits()
. The first call, with lpvBits
set to NULL
, causes it to fill the BITMAPINFOHEADER
of bmInfo
with information about the pixel data, reporting a value of 32 bits per pixel. The second call to GetDIBits()
copies 80 lines of the rectangle into memory buffer pMem
, returning the value 80 for the number of lines copied.
Everything seems to succeed, but then the program suddenly terminates. I inserted the line Sleep(8192)
after the second call to GetDIBits()
, and the program terminates after the 8 seconds have elapsed.
What is causing the program to terminate?
EDIT: the original code is revised per suggestions in this thread. No errors are detected when the function is run, but the program still terminates unexpectedly. I realize the memory buffer size is hard coded, but it is way bigger than needed for the rectangle used in the testing. That should not cause an error. Of course I will have the program compute the necessary buffer size after I find out why the program is terminating.
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);
}
}
Upvotes: 1
Views: 234
Reputation: 2130
The biCompression
value is returned by first GetDIBits
is BI_BITFIELDS
and before you call second GetDIBits
, you need to call bmInfo.bmiHeader.biCompression = BI_RGB;
. According to c++ read pixels with GetDIBits(), Setting it to BI_RGB
is essential in order to avoid extra 3 DWORDs to be written at the end of structure.
More details
Upvotes: 1
Reputation: 596001
Like @BenVoigt said in comments, you need to restore the old HBITMAP
that you replaced with SelectObject()
before you destroy the HDC
that owns it. You are selecting hBitmap
into hCDC
, and then destroying hCDC
before destroying hBitmap
.
https://learn.microsoft.com/en-us/windows/win32/gdi/operations-on-graphic-objects
Each of these functions returns a handle identifying a new object. After an application retrieves a handle, it must call the
SelectObject()
function to replace the default object. However, the application should save the handle identifying the default object and use this handle to replace the new object when it is no longer needed. When the application finishes drawing with the new object, it must restore the default object by calling theSelectObject()
function and then delete the new object by calling theDeleteObject()
function. Failing to delete objects causes serious performance problems.
Also, you should free the GDI objects in the reverse order that you create them.
And, don't forget error handling.
Try something more like this instead:
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);
}
}
Note the TODO on the call to HeapAlloc()
. You really should be calculating the buffer size based on the bitmap's actual width, height, pixel depth, scanline padding size, etc. Don't use a hard-coded buffer size. I will leave this as an exercise for you to figure out. Although, in this particular example, 64K should be large enough for a 64x80 32bpp bitmap, it will just waste 45K of unused memory.
Upvotes: 0