LoadBitmap fails after multiple calls - c++

In this function, after about 90 calls (it's called in a loop and the idea is to load in a separate image each time, but I have kept it to one image for simplicity).The global variables now changed to local ones.
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile)
{
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbmp_temp)
{
//hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
ActionList.UpdateWindow();
if (!hbmp_temp)
return;
}
CBitmap bmp_temp;
bmp_temp.Attach(hbmp_temp);
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
return;
}
I hope someone can come up with an Idea whats wrong. GetLastError returns "8" which means nothing to me.

Detach will destroy the previous handle.
Note that if you call Detach after calling SetBitmap, the picture control's bitmap is destroyed. The result is that the picture control is painted once, but it won't be repainted. For example picture control goes blank if dialog is resized.
EDIT
To destroy the old bitmap, call Detach followed by DestroyObject. Example
HGDIOBJ hbitmap_detach = m_bitmap.Detach();
if (hbitmap_detach)
DeleteObject(hbitmap_detach);
m_bitmap.Attach(hbitmap);
If it's a temporary CBitmap then DeleteObject is not necessary, because DeleteObject is called automatically when CBitmap goes out of scope.
Note that if you destroy the bitmap after calling SetBitmap, the picture control's bitmap is destroyed. The result is that the picture control is painted once, but it won't be repainted. For example picture control goes blank if dialog is resized.
It's the same problem if you declare a temporary CBitmap on stack and attach the bitmap handle. That bitmap handle will be destroyed and picture control can't repaint itself.
In addition, Windows XP sometimes makes duplicate bitmap which needs to be destroyed as well. SetBitmap returns a handle to previous bitmap. In Vista+ the returned bitmap is the same one which was save in m_bitmap, we already destroy that with Detach. But in XP we need to destroy this copy if it is a different handle.
void CMyDialog::foo()
{
HBITMAP save = m_bitmap;
HBITMAP hbitmap = (HBITMAP)::LoadImage(0, filename,
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (hbitmap)
{
HGDIOBJ hbitmap_detach = m_bitmap.Detach();
//Edit ****************************************
//Delete old handle, otherwise program crashes after 10,000 calls
if (hbitmap_detach)
DeleteObject(hbitmap_detach);
//*********************************************
m_bitmap.Attach(hbitmap);
HBITMAP oldbmp = m_picControl.SetBitmap(m_bitmap);
//for Windows XP special case where there might be 2 copies:
if (oldbmp && (oldbmp != save))
DeleteObject(oldbmp);
}
}
Also, SetBitmap takes HBITMAP parameter and returns HBITMAP, so you can avoid using CBitmap altogether. The following example works in Vista+
void foo()
{
HBITMAP temp = (HBITMAP)::LoadImage(0,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if (temp)
{
HBITMAP oldbmp = m_picControl.SetBitmap(temp);
if (oldbmp)
DeleteObject(oldbmp);
DeleteObject(temp);
}
}

Your code has several issues, some minor, others are fatal (and the implementation really only appears to work, because the OS is prepared to deal with those common bugs). Here is an annotated listing of the original code:
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
// ^ should be const CString&
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
if (!hbmp_temp) {
//hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
ActionList.UpdateWindow();
if (!hbmp_temp)
// You already know, that the condition is true (unless your commented out code
// is supposed to run).
return;
}
CBitmap bmp_temp;
bmp_temp.Attach(hbmp_temp);
// ^ This should immediately follow the LoadImage call, to benefit from automatic
// resource management. (What's the point of using MFC when you decide to implement
// manual resource management on top of it?)
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
// ^ Use named constants. No one is going to
// look up the documentation just to find
// out, what you are trying to do.
mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
// The GDI object (hbmp_temp) now has two owners, the CBitmap instance bmp_temp, and
// the picture control. At the same time, you are throwing away the handle previously
// owned by the control. This is your GDI resource leak.
return;
// ^ Superfluous. This is merely confusing readers. Remove it.
}
// This is where things go fatal: The bmp_temp d'tor runs, destroying the GDI resource
// hbmp_temp, that's also owned by the control. This should really blow up in your face
// but the OS knows that developers cannot be trusted anymore, and covers your ass.
The two major issues are:
Failure to delete GDI resources owned by your code (the return value of SetBitmap). This eventually causes any attempt to create additional GDI resources to fail.
The dangling pointer (HBITMAP) caused by assigning two owners to the same resource (hbmp_temp).
There's really another issue here as well. Since you assigned two owners to the bitmap resource, this would lead to a double-delete. It doesn't, because you opted against resource cleanup. (The fact that you don't know what you're doing saved you here. Keep that in mind the next time you celebrate your "can do" attitude.)
Following is a version with fixed-up resource management. This won't make any sense to you, since you don't know MFC (or C++ for that matter) well enough to understand, how either one aids in automatic resource management. Anyhow:
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
// Immediately attach a C++ object, so that resources will get cleaned up
// regardless how the function is exited.
CBitmap bmp_temp;
if (!bmp_temp.Attach(hbmp_temp)) {
// Log error/load placeholder image
return;
}
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
// Swap the owned resource of bmp_temp with that of the control:
bmp_temp.Attach(mProjectorWindow.m_picControl.SetBitmap(bmp_temp.Detach()));
}
The last line is the critical part. It implements the canonical way to swap raw Windows API resources with resource management wrappers. This is the sequence of operations:
bmp_temp.Detach() releases ownership of the GDI resource.
SetBitmap() passes ownership of the GDI resource to the control, and returns the previous GDI object (if any).
bmp_temp.Attach() acquires ownership of the returned GDI resource. This ensures that the previous resource gets cleaned up, when bmp_temp goes out of scope (at the end of the function).

Related

KB5000808/KB5000802 causes bitmaps/text objects to render invisibly

We've been dealing with an issue in our software ever since Microsoft released KB5000802/808 in early March. I've seen a similar post about it here, and the code we use is virtually the same (and hasn't changed in many years):
Calling Windows SelectObject in a printer-compatible device context returns NULL
In our case, we are actually printing OK, but our objects (bitmaps and text boxes) render invisibly on the screen (only "resize handles" appear) until we de-select and re-select the object. Rolling back the security update is a workaround but it's becoming more difficult to do this. We also have tested with Microsoft's hotfix KB5001649, but the issue persists. Has anyone else encountered this?
'''
hBitmap = (HBITMAP)LoadImage(NULL,szFileName,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE|LR_VGACOLOR);
// [...]
HDC prn = GetPrinterDC(hwnd);
hdcMem = CreateCompatibleDC(prn);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hBitmap);
// hbmOld is NULL at this point, indicating SelectObject failure
'''

Is there a performance improvements over using the GetDC approach instead of the others?

Is there any benefits in using this code:
CClientDC dc(nullptr);
HFONT hFont = (HFONT)m_StatusBar.SendMessage(WM_GETFONT);
HGDIOBJ hOldFont = nullptr;
if (hFont != nullptr)
hOldFont = dc.SelectObject(hFont);
cxNewWidth = dc.GetTextExtent(strCalendar).cx + kStatusBarIconWidth;
Or this code:
CClientDC dc(nullptr);
CFont *pFont = m_StatusBar.GetFont();
if (pFont != nullptr)
dc.SelectObject(pFont);
cxNewWidth = dc.GetTextExtent(strCalendar).cx + kStatusBarIconWidth;
Or:
CDC *pDC = m_StatusBar.GetDC();
cxNewWidth = pDC->GetTextExtent(strCalendar).cx + kStatusBarIconWidth;
The first and second code samples are identical. The documentation for CWnd::GetFont explains, how it is implemented:
Sends the WM_GETFONT message to the window to retrieve the current font.
The third code snippet is different from the previous ones in that it queries the control for a device context. What CWnd::GetDC returns depends how the particular window class was registered. In the most common case, it will return the "common device context":
For common device contexts, GetDC assigns default attributes to the context each time it is retrieved.
In other words, in this case the device context you are retrieving may or may not have the desired font selected into it. As a general rule you should not rely on there being any particular graphics object selected into it.
None of the operations are inherently expensive. You should profile your code to find out, where it spends most of its time. The code posted likely isn't going to show up.
If you want to retrieve the client DC of the m_StatusBar, then
CClientDC dc(m_StatusBar);
is the correct way to do it. This way you already have the correctly sized bitmap selected into the DC and you should probably be able to save GetFont(), too.
Retrieving the DC of some top level window using ClientDC(nullptr); and then selecting the font of the m_StatusBar into it seems wrong to me.
For the performance:
The GetDC() approach allocates a new DC each time it is called. Therefore you must supply ReleaseDC() for each of the GetDC() calls.
The CClientDC object just wraps GetDC() and ReleaseDC() in a class constructor and destructor. On the one side this is more convenient, on the other side the performance of the ClientDC approach is a tiny little worse than using GetDC() and ReleaseDC() directly due to the creation and destruction of a class instance. However, this is negligible.

Copy contents of one DeviceContext to another DeviceContext

I've never done any GDI programming and despite taking several shots in the dark and searching the documentation I haven't found the correct way to do copy the contents of one DC to another DC.
The code I have at the moment is below. I don't understand why it's not working (the window remains just remains blank after creation).
SIZE srcSize;
// ... Get size of source DC
HDC destDC = ...; // from GetDC(myWindow), myWindow was
// sized before this to properly contain source
HDC sourceDC = ...;
HBITMAP buffer = CreateCompatibleBitmap(sourceDC, srcSize.cx, srcSize.cy);
HGDIOBJ oldObj = SelectObject(destDC, buffer);
BitBlt(destDC, 0, 0, srcSize.cx, srcSize.cy, sourceDC, 0, 0, SRCCOPY);
SelectObject(destDC, oldObj);
DeleteObject(buffer);
//... ReleaseDC()s here
What's the proper way this is done?
The only thing necessary to copy from one DC to another is a BitBlt. Code that works is below.
SIZE srcSize;
// ... Get size of source DC
HDC destDC = ...; // from GetDC(myWindow), myWindow was
// sized before this to properly contain source
HDC sourceDC = ...;
BitBlt(destDC, 0, 0, srcSize.cx, srcSize.cy, sourceDC, 0, 0, SRCCOPY);
//... ReleaseDC()s here
It's not very clear to me what you are trying to do. First off, why create the new bitmap and select it into the window (sorry, "client area") DC? All you want is paint/draw the window, isn't it? This is not needed then. The destDC is exactly the window's client area surface.
Does sourceDC really contain anything? For example, does it have a bitmap slected into it?
And of course, you SHOULD process WM_PAINT. If you process this message the window is validated, and you are not required to validate it explicitly. Using GetDC()/ReleaseDC() is called "drawing", as opposed to "painting". In an application I made in the past I had to use both methods, painting (processing WM_PAINT) for responding to resizing, exiting from minimized state and bringing the window to foreground (if previously obscured by another) and drawing, for making certain changes immediately visible (instead of invalidating the window and waiting for the application to nearly enter the idle state first - pls note that WM_PAINT is a low-priority message).
Hope this helps

How to use LoadImage and DeleteObject properly?

I am working on a windows application with C++. I load a bmp file to a DC using LoadImage, and it shows up properly. However, when I call DeleteObject, the memory doesn't seem to be freed. (I use windows task manager to track the memory usage)
In the WM_INITDIALOG part I do this:
static HBITMAP hBitmap = 0;
char* tempPath = "tabView.bmp";
hBitmap = (HBITMAP)LoadImage(NULL,
tempPath, // file containing bitmap
IMAGE_BITMAP, // type = bitmap
0, 0, // original size
LR_LOADFROMFILE); // get image from a file
if(hBitmap)
{
SendMessage(GetDlgItem(hwndDlg, IDC_PICTURE),
STM_SETIMAGE, // message to send
(WPARAM)IMAGE_BITMAP, // bitmap type
(LPARAM)hBitmap); // bitmap handle
}
So the picture shows up in the DC, and memory increases. And in a button I do:
int result = DeleteObject(hBitmap);
When I press the button, I checked the result and it's a non-zero value, which is success. But IDC_PICTURE will still show the picture, and memory stays the same. I am wondering if the SendMessage() may increase the ref count on the hBitmap...
So my question is: What is the proper way to clean up?
You didn't mentioned what version of Windows you are using. Anyway, if you read "Important" part of STM_SETIMAGE, you will see next:
With Windows XP, if the bitmap passed in the STM_SETIMAGE message contains pixels with nonzero alpha, the static control takes a copy of the bitmap. This copied bitmap is returned by the next STM_SETIMAGE message. The client code may independently track the bitmaps passed to the static control, but if it does not check and release the bitmaps returned from STM_SETIMAGE messages, the bitmaps are leaked.
Maybe this applies not only for Windows XP, but for later version of Windows. Hope this will help you.

Why the image disappears?

i have the following code ...
case WM_PAINT:
{
hdc = BeginPaint(hwnd,&paintSt);
temphdc = hdc;
GetClientRect(hwnd,&aRect);
if(hBitmap!=NULL)
{
HDC memDC = CreateCompatibleDC(hdc);
if(memDC!=NULL)
{
BITMAP bmp;
GetObject(hBitmap,sizeof(bmp),&bmp);
SelectObject(memDC,hBitmap);
SetStretchBltMode(hdc,HALFTONE);
StretchBlt(hdc,0,0,aRect.right,aRect.bottom,
memDC,0,0,bmp.bmWidth,bmp.bmHeight,
SRCCOPY);
DeleteObject(&bmp);
ReleaseDC(hwnd,memDC);
}
}
// the code for painting
EndPaint(hwnd,&paintSt);
}
break;
hBitmap is a global variable which is assigned at some place in the code.... Image is displayed but disappears whenever I minimize the window....
can anyone explain this ?
thanks in advance,
Your cleanup code is all wrong, you are leaking handles badly. Should be readily visible in TaskMgr.exe, Processes tab. View + Select Columns and tick GDI Objects. This code stops working when the GDI object handle count reaches 10,000. Yes, likely to happen when you resize the window since there will be a flurry of paint requests.
Don't delete the BITMAP, it is just as struct. Restore the old bitmap handle you got back from SelectObject() before you delete the memDC. Don't use ReleaseDC, DeleteDC is required. Pay attention to the return value of these functions, they tell you when you messed up but that can't work if you never check with an assert.
GDI programming is painful with these explicit cleanup rules. Consider a class library to take care of this kind of drudgery, they never get it wrong.
I guess somehow hBitmap is changing to null while minimize.
Posting the code where you are assigning and referring hBitmap will help to identify the issue I think.