1

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.

  • You don't seem to be doing anything with `buffer`? Also add to call to `::GetLastError()` in the appropriate places and log them (or use the debugger) – Richard Critten Sep 20 '16 at 14:21
  • A [mcve], please. This needs to be complete and **minimal**. Your `CBitmap::Attach` and `Detach` calls are a massacre in face of C++' resource management, and only serve to make your code harder to read. If you don't know, why this is wrong, you need to pick up a good book on Windows API programming (Petzold's *"Programming Windows, 5th Edition"*), followed by one for MFC (Prosise' *"Programming Windows with MFC"*)- Looking at your question history, you aren't making much progress. – IInspectable Sep 20 '16 at 14:41
  • Use gdiplus to make life easier – seccpur Sep 20 '16 at 15:15
  • "8" means there is not enough memory. – Joseph Willcoxson Sep 20 '16 at 15:17
  • If you want to know what "8" means, you could have used [FormatMessage](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680582(v=vs.85).aspx) to return to you what "8" means. – PaulMcKenzie Sep 20 '16 at 15:24
  • @seccpur: How does GDI+ make **any** of this any easier? Don't just puke out blanket statements, whenever you see a GDI question that you cannot answer. – IInspectable Sep 20 '16 at 15:44
  • Gdiplus can load BMP,PNG,etc from resource or file with few command lines. Plus gdiplus::bitmap::lockbits enable one to manipulate at bit level of bitmap very fast. – seccpur Sep 20 '16 at 15:50
  • @seccpur: And how is this applicable to the code in question? – IInspectable Sep 20 '16 at 16:22
  • Barak Shemirani's code would seem to be a good minimal example. I have to apologise because there is lots of SVG rendering stuff I chopped out that was using the Char buffer and so on. I was getting tired when I posted it. The minimal code example also fails after about 180 calls or so. (Out of memory again) – Michael Dutton Sep 20 '16 at 16:49
  • Show exactly where it fails. If `hbmp_temp` is `NULL` then record `GetLastError` immediately the next line. Just telling us that `GetLastError` returns 8 doesn't really say anything, because that error could have happen somewhere else. You can call `picControl.ModifyStyle(0, SS_BITMAP)` only once in initialization. – Barmak Shemirani Sep 20 '16 at 17:27
  • It fails at the line HBITMAP hbmp_temp = (HBITMAP).. etc... I am calling the whole procedure in a simple for loop of 0 to 200 to simulate a digital printing run. I need to sequentially load images into that picture control many times. I will call ModifyStyle oce as suggested, without that the picture will not display at all. I will research GetLastError also, but that's a bit of coding I suspect. I shall repost shortly. – Michael Dutton Sep 20 '16 at 17:45
  • See [`GetLastError`](http://stackoverflow.com/documentation/winapi/2573/error-reporting-and-handling/8521/error-reported-with-additional-information-on-failure#t=201609201748490907682) – Barmak Shemirani Sep 20 '16 at 17:49
  • Hi, GetLastError returns: "There is not enough Storage Space To Process This Command"... I removed ModifyStyle and placed in "InitDialog" and there is no difference. Therefore it looks like storage Space is not being freed up somewhere, setting this picture Control to a bitmap over and over again. (There are no memory leaks and threads exit normally). – Michael Dutton Sep 20 '16 at 18:03
  • 1
    Assuming this is a `CStatic` control, you are responsible for cleaning up the bitmap returned from the call to [CStatic::SetBitmap](https://msdn.microsoft.com/en-us/library/b7w5x74z.aspx). This is explained in the [STM_SETIMAGE](https://msdn.microsoft.com/en-us/library/windows/desktop/bb760782.aspx) message documentation. Which just goes to prove again: You cannot use MFC without **intimate** knowledge of the Windows API. – IInspectable Sep 20 '16 at 20:08
  • @IInspectable that's a good point. But it shouldn't be an issue the way OP has set this up, because the bitmap copy is immediately destroyed. Unless this is run under Windows XP which sometimes make a duplicate copy. The leak is probably somewhere else in the program. – Barmak Shemirani Sep 21 '16 at 05:36
  • I don't have a leak if I use "createBitmap". Only LoadImage does this. I need both. There are no dumps of memory leaks after close down either. This is in Windows 10 Barmak.. so does the specialcase apply for XP? – Michael Dutton Sep 21 '16 at 11:07
  • @IInspectable - Barmak answered it (see my comment). I will however continue to use MFC despite my not having a complete knowledge of windows API, partly because I have to use MFC and partly because I have a "can do" attitude and am not afraid of things I don't understand. Thanks for providing the one comment that led to Barmak being able to produce a workable solution, which I can adapt. That''s the way online communities should work and now there is a solution online which I know has been the bugbear of a lot of programmers in the past. – Michael Dutton Sep 21 '16 at 11:23
  • @MichaelDutton: MFC's leak detection can only detect **memory** leaks. It cannot detect any other leak (like GDI object leaks, as in your case). [Debugging a GDI Resource Leak](https://blogs.msdn.microsoft.com/dsui_team/2013/04/23/debugging-a-gdi-resource-leak/) provides valuable information to diagnose GDI resource leaks. – IInspectable Sep 21 '16 at 11:41

2 Answers2

3

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);
    }
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Actually it was a two line fix after all that. Merely save the bitmap and immediately destroy t.. HBITMAP oldbmp = m_picControl.SetBitmap(m_bitmap); then call DeleteObject straight away on the returned bitmap. This is because I did not need to worry about rescaling it. In fact this should work for XP too. – Michael Dutton Sep 21 '16 at 11:20
  • Your description of [CGdiObject::Detach](https://msdn.microsoft.com/en-us/library/x7ff7f4h.aspx) is all wrong. `CGdiObject` instances *own* the respective GDI objects. `Detach` disassociates the GDI resource from the C++ object, and returns a handle to the (now unowned) GDI object back to the caller. Throwing that handle away **is** a resource leak. The proposed solution keeps juggling ownership back and forth, for no apparent reason. It is unreadable, and a manifestation, that the author wasn't entirely aware of what is really going on. – IInspectable Sep 21 '16 at 11:52
  • Actually I did modify this. I do not use the Detach method. Nor do I use a global CBitmap. It's the use of the return value from SetBitmap and using the DeleteObject on that returned bitmap that did the trick. The resource was definitely being left in memory causing a GDI memory leak, as per your other helpful comment above. It is a solution, although I did modify it somewhat. – Michael Dutton Sep 21 '16 at 12:53
  • @MichaelDutton: This is not a solution. It is an implementation based on a fatal misunderstanding. That happens to appear to work, by sheer luck. I doubt this will be useful to future readers. If anything, it promotes poor resource management strategies. – IInspectable Sep 21 '16 at 13:28
  • @IInspectable, you are right, I got confused because temporary `CBitmap` has automatic cleanup. Fixed it... – Barmak Shemirani Sep 21 '16 at 17:22
  • No, not really. `Detach` doesn't destroy anything. It merely releases ownership of the owned resource. And using a class member to store an object that's only used locally isn't exactly perfect either. – IInspectable Sep 21 '16 at 17:27
  • @IInspectable what do you mean? I added `DeleteObject(hbitmap_detach)`, that should clean up. (I had typo in a second edit) – Barmak Shemirani Sep 21 '16 at 17:30
  • *"`Detach` will destroy the previous handle."* - This is not the case. It simply releases ownership (and removes it from its custom garbage collector implementation, but that is just an implementation detail). – IInspectable Sep 21 '16 at 17:32
  • I forgot to cross that out. – Barmak Shemirani Sep 21 '16 at 17:36
  • 2
    `CGdiObject`s have a [CGdiObject::DeleteObject](https://msdn.microsoft.com/en-us/library/st2k3wfa.aspx) member, so you don't have to call `Detach` followed by `::DeleteObject`. Although the code now does what it should, I cannot quite agree on the two-owner strategy. Keeping a `HBITMAP` (instead of an owning `CBitmap`) around for comparison might convey this better. – IInspectable Sep 21 '16 at 20:23
1

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:

  1. bmp_temp.Detach() releases ownership of the GDI resource.
  2. SetBitmap() passes ownership of the GDI resource to the control, and returns the previous GDI object (if any).
  3. 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).
Community
  • 1
  • 1
IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Is this for Vista+? In Windows XP `SetBitmap` may create an additional bitmap copy which this method can't account for. – Barmak Shemirani Sep 21 '16 at 17:26
  • @BarmakShemirani: Unless explicitly stated otherwise, code posted by me works on all supported versions of Windows (i.e. Windows Vista and above). – IInspectable Sep 21 '16 at 17:31