views:

267

answers:

3

Whenever my app tries to create a DIB section, either by calling CreateDIBSection(), or by calling LoadImage() with the LR_CREATEDIBSECTION flag, it seems to return successfully. The HBITMAP it returns is valid, and I can manipulate and display it just fine.

However, calls to GetLastError() will return 8: Not enough storage is available to process this command. This happens from the very first call to the last. The size of the bitmap requested seems inconsequential; 800x600 or 16x16, same result. Immediately prior to the function call, GetLastError() returns no error; additionally, calling SetLastError(0) before the function call has the same result.

I have found other people asking similar questions, but it either turns out they are using CreateCompatibleBitmap() and the problem goes away when they switch to CreateDIBSection(), or they are already using CreateDIBSection() and the result it returns is invalid and so is not working at all.

Since things seem to be working, I have thought I could just ignore it (and call SetLastError(0) after calls to either function), but there could be some subtle problem I am overlooking by doing so.

And of course, here's some of the basic code I'm using. First, the call to LoadImage(), which is part of a basic bitmap class that I use for a lot of things, and which I simplified quite a bit to show the more relevant aspects:

bool Bitmap::Load( const char* szBitmapName, /*...*/ )
{
   m_hBitmap = (HBITMAP)LoadImage( GetModuleHandle( NULL ), szBitmapName,
            IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE );
   //...
}
// ...
Bitmap::~Bitmap()
{
   if( m_hBitmap ) DeleteObject( m_hBitmap );
}
bool Bitmap::Draw( HDC hDC, int iDstX, int iDstY, int iDstWidth,
                   int iDstHeight, int iSrcX, int iSrcY, int iSrcWidth,
                   int iSrcHeight, bool bUseMask ) const
{
   HDC hdcMem = CreateCompatibleDC( hDC );
   if( hdcMem == NULL ) return false;
   HBITMAP hOld = (HBITMAP)SelectObject( hdcMem, m_hBitmap );
   BLENDFUNCTION blendFunc;
   blendFunc.BlendOp = AC_SRC_OVER;
   blendFunc.BlendFlags = 0;
   blendFunc.AlphaFormat = AC_SRC_ALPHA;
   blendFunc.SourceConstantAlpha = 255;
   AlphaBlend( hDC, iDstX, iDstY, iDstWidth, iDstHeight, hdcMem, iSrcX,
               iSrcY, iSrcWidth, iSrcHeight, blendFunc );
   SelectObject( hdcMem, hOld );
   DeleteDC( hdcMem );
}

Calls to CreateDIBSection are typically done when updating a layered window:

HDC hDCScreen( GetDC(0) );
POINT tSourcePos = { 0, 0 };
HDC hDCSource( CreateCompatibleDC( hDCScreen ) );
// iWidth and iHeight are used both for the bitmap size and window size
// to keep this example simpler
BITMAPINFO bi32 = {0};
bi32.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi32.bmiHeader.biWidth = iWidth;
bi32.bmiHeader.biHeight = iHeight;
bi32.bmiHeader.biPlanes = 1;
bi32.bmiHeader.biBitCount = 32;
bi32.bmiHeader.biCompression = BI_RGB;
void* pBits = NULL;
HBITMAP hBitmap = CreateDIBSection(NULL, &bi32, DIB_RGB_COLORS,
                  (void**)&pBits, NULL, NULL);

HBITMAP hOldBitmap = (HBITMAP)SelectObject( hDCSource, hBitmap );
POINT tWindowPos = { 0, 0 };
SIZE tWindowSize = { iWidth, iHeight };
BLENDFUNCTION blendFunction = {0};
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.SourceConstantAlpha = 255;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
DWORD iFlags( ULW_ALPHA );

// m_tBitmap is an instance of Bitmap class previously mentioned
m_tBitmap.Draw( hDCSource, 0, 0, iWidth, iHeight, 0, 0, iWidth, iHeight );
UpdateLayeredWindow( GetHandle(), hDCScreen, &tWindowPos, &tWindowSize,
                     hDCSource, &tSourcePos, 0, &blendFunction, iFlags );
SelectObject( hDCSource, hOldBitmap );
DeleteObject( hBitmap );
DeleteDC( hDCSource );
ReleaseDC( 0, hDCScreen );

Any pointers about anything I'm completely off-base about would be appreciated.

+2  A: 

I think you aren't following what the documentation says (from CreateDIBSection):

If the function succeeds, the return value is a handle to the newly created DIB, and *ppvBits points to the bitmap bit values.

If the function fails, the return value is NULL, and *ppvBits is NULL.

This function can return the following value. [...]

If the return value is not NULL, the function succeeded. Calling GetLastError won't necessarily return any reliably meaningful information on success (from GetLastError):

If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.

MSN
With code checking for NULL results, both the HBITMAP returned from CreateDIBSection() and ppvBits have always been non-NULL (I've cut out a lot of extra code like that). And I have cleared the error code before calling CreateDIBSection(); no matter what state the error code was in, it always comes out of that call with code 8.
Doug Kavendek
@Doug, Read the documentation for `GetLastError`, specifically: "some functions set the last-error code to 0 on success and others do not."
MSN
Alright, I was assuming it might be relevant, sort of a 'warning' type of error, but I see it only has relevance in the context of the caller's defined failure result. Potentially, some function called within CreateDIBSection could be flipping the error bit, and within that context it's not a problem. It's not like an unhandled exception bubbling up.
Doug Kavendek
@Doug, exactly :)
MSN
A: 

I remember that in some cases, in order to get a reliable value from GetLastError you have to call SetLastError(0) before calling any api function and then later you get the correct error with GetLastError(). Because in case that it succeeded it was not being updated.

Ismael
I have tried that, and it still comes out set to error 8 after the call.
Doug Kavendek
+1  A: 

So what? CreateDIBSection is a complicated function which makes use of many other windows APIs do its job. Some of those APIs could set the last error to reflect their internal state. CreateDIBSection isn't going to clear the error, just so that a 0 can be returned when it doesn't fail.

The contract says, when it fails, GetLastError is set to a meaningful value: it does NOT say that the last error value has any meaning to the caller when CreateDIBSection does return.

Its quite possible that, if there is a lack of memory, an exception gets thrown and/or to simplify the code the last error value simply gets preemptively set near the top of the allocations such that its correctly set with no further effort should the allocations fail or throw an exception.

Chris Becke