Steven Brown
Steven Brown

Reputation: 61

Call to GetDIBits() succeeds, but program terminates

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

Answers (2)

YangXiaoPo-MSFT
YangXiaoPo-MSFT

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

Remy Lebeau
Remy Lebeau

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 the SelectObject() function and then delete the new object by calling the DeleteObject() 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

Related Questions