Thread: GDI object lifetime and C++ object lifetime

  1. #1
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607

    GDI object lifetime and C++ object lifetime

    Since these two are extremely different from one another, how do you balance effective cleanup of GDI objects and effective cleanup of the C++ object using the GDI object?

    Destructor cleanup does not always work since if you call certain GDI functions when the object no longer exists (or is not an HWND) and/or cleanup GDI objects pre-maturely you get major issues (calling delete on a bitmap object when it is still selected into a device context).

    I'm having major problems with this.

    Suggestions?

  2. #2
    Registered User Dante Shamest's Avatar
    Join Date
    Apr 2003
    Posts
    970
    I think it depends on where SelectObject() is called. Then you'll have to hide the operation that calls SelectObject() somehow, so that the user of your classes never has to call SelectObject().

    I'm not sure how your classes are designed, but my Bitmap Class is not designed to be selected into any external HDC. It just loads bitmaps and draws them onto external HDCs.

  3. #3
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    In .NET compliers GDI objects are temp by default. So they will be cleaned up by the garbage collection system.

    In my apps every method that uses GDI is responsible for maintaining the mem DC in its default state and cleaning up GDI memory it allocates/creates.
    So GDI objects only have scope for within a single method and if I forget to clean up, the IDE will delete them for me (or thats the plan....)

    ie
    I create a mem DC and BMP within the OnInit (or constructor), save the original BMP pushed out by the created one.
    In the OnClose (or destructor) I clean them up.

    Any method that needs a GDI object must
    create a new GDI object,
    select it into the mem DC (saving the original),
    use,
    return original GDI object
    and delete new created GDI.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  4. #4
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Well I've been debugging my code forever and something is calling GetWindowRect() when the HWND is not valid.

    The line that fails inside of GetWindowRect is this:

    Code:
    ASSERT(pWnd->m_hWnd);
    What I have is this on shutdown:
    Code:
    //If project was indeed valid
    if (m_bInit)
      {
        //if document has been modified since last load
        if (m_pDoc->IsModified())
        {
          int result=AfxMessageBox(L"Do you wish to save your project?",MB_ICONQUESTION);
          if (result==IDOK)
          {
            //Save project here
          }
        }
    
        //Destroy tile sheet and associated pages
        if (m_pTileSheet)
        {
          AfxMessageBox(L"Destroying tile sheet");
          
          m_pTileSheet->Destroy();
          delete m_pTileSheet;
          m_pTileSheet=NULL;
        }
        
        
        //Destroy document
        m_pDoc->Destroy();
        
        //Delete docked frames
        for (DWORD i=0;i<static_cast<DWORD>(m_vDockWnds.size());i++)
        {
          CString text;
          text.Format(L"Deleting docked frame #%d",i);
          AfxMessageBox(text);
          
          if (m_vDockWnds[i]) 
          {
            delete m_vDockWnds[i];
          }
          AfxMessageBox(L"Success");
          
        }
         
        //Destroy view here
        m_pView->Destroy();
        
        //Project is no longer valid
        m_bInit=false;
        
        
        
        AfxMessageBox(L"Project shutdown Ok");
        
      }
    }
    Lots of debug messages in there, but you should get the point.


    The problem is occuring inside of the loop to destroy the docked windows. My original method has been to destroy the GDI objects inside of a destroy function for each derived class that represents any GDI object. Then I call delete on it which would then clean up the memory or any other C/C++ objects. Someone thought this was a dumb idea, but so far it works well. Well I got away from that design and tried the C++'y destructor cleanup thing which IS NOT working well for GDI objects.

    If I change the code to this:
    Code:
    ...   
       //Delete docked frames
        std::vector<CDockContWnd *>::iterator it=m_vDockWnds.begin();
     
        for (DWORD i=0;i<static_cast<DWORD>(m_vDockWnds.size());i++,it++)
        {
          CString text;
          text.Format(L"Deleting docked frame #%d",i);
          AfxMessageBox(text);
          
          //if (m_vDockWnds[i]) 
          //{
          //  delete m_vDockWnds[i];
          //}
          
          m_vDockWnds.erase(it);
          AfxMessageBox(L"Success");
          
        }
    ...
    The code shuts down, but leaks all windows docked to the container frame, and leaks all windows that are docked to any windows within the frame.

    My classes are:

    CDockContWnd
    A class derived from CWnd that acts as a client area where windows can be docked. Once a window is docked, this window acts as a parent to the docked window and the docked window acts as a child window. Maintains a vector of CWnds that can be docked to this window. Any number of CDockContWnd's can be docked to the main frame at any time, and any number of CDockDialog's can be docked to CDockContWnd's at any time.

    CDockDialog
    A class derived from CDialog that has the special ability to act as a child dialog window which can have a parent and be clipped by it's parent window. I derived from CDialog instead of CWnd to gain the functionality of being able to construct the dialog in the resource editor and simply change a few lines in the resulting source to fit the new class. Namely the message map and implement dynamic macros have to be changed from CDialog to CDockDialog so that messages are properly routed. The supplied interfaces in the IDE do not allow you to add your own custom classes for subclassing. You must create an object of base type like CDialog, CTabCtrl, etc, etc. and then change the source to reflect that it is actually a different class that is derived from one of the base classes.

    CTabDialog
    A CDockDialog derived class that has a pointer to a CMyTabCtrl object which acts as a tabbed dialog object inside of the dock dialog object.

    CMyTabCtrl
    Implements all the functionality of a CTabCtrl and can be used as a child control in any window. Maintains a vector of CWnds that can be attached to and displayed by this tab control.

    Now on shut down you can see what is happening. I just cannot supply the full source for the shutdown process as it covers about 10 classes each with their own shutdown process and/or destructor code. The system is very complex, but very simple. I just don't want to bore you by explaining all of it.

    Here is a picture that will show my setup.
    Last edited by VirtualAce; 03-12-2011 at 11:42 AM.

  5. #5
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    I solved my problem.

    I had several problems inside of my OnPaint(), RecalcClientSize(), and RecalcLayout().

    The problem was that I was storing the CDockContWnd's in a vector, as well as it's own CDockDialog children.

    Inside of CDockContWnd::OnPaint() I was assuming that if it was in the vector it was a window. However I was only doing a delete in the vector and not actually removing it from the vector thus causing the pointer to become invalid, yet the vector size was unchanged. I fixed it by calling vector::clear() at the end of my delete cleanup loop. I also added several protection mechanisms in OnPaint() and any other layout/drawing function that required window or client rects.

    Code:
    if (m_vChildren[i] && IsWindow(m_vChildren[i]->m_hWnd))
    {  
      //Proceed with whatever we need to do
    }
    I am re-designing this whole thing now to shutdown using destructors instead of destroy since I know how to fix the entire problem.

    I also added a RecalcClientSize() function to CMainFrame. This takes into account all docked windows and resizes the client window based on what and where other windows are docked. Now each CDockContWnd has a dock state which tells me where it has been docked so I can compare coordinates like this:

    Code:
    void CMainFrame::RecalcClientSize(void)
    {
      CRect rect;
      if (m_pView) m_pView->GetClientRect(&rect);
      
      if (m_vDockWnds.empty()) return;
      
      for (DWORD i=0;i<static_cast<DWORD>(m_vDockWnds.size());i++)
      {
        if (IsWindow(m_vDockWnds[i]->m_hWnd))
        {
          CRect rectDock;
          m_vDockWnds[i]->GetWindowRect(&rectDock);
          ScreenToClient(&rectDock);
       
          //CString text;
          //text.Format(L"Dock: %d %d %d %d\n"
          //            L"Client: %d %d %d %d",
          //             rectDock.left,rectDock.top,rectDock.right,rectDock.bottom,
          //             rect.left,rect.top,rect.right,rect.bottom);
          //AfxMessageBox(text);
          int mode=m_vDockWnds[i]->GetDockState();
        
         
          //Window is on right side of client
          if (rectDock.left<=rect.right && mode==DLG_DOCK_RIGHT) rect.right=rectDock.left;
        
          //Window is on far left side of client - use right edge for left side of client
          if (rectDock.right>=rect.left && mode==DLG_DOCK_LEFT) rect.left=rectDock.right;
        
          //Window is on bottom of client - use top edge for bottom of client
          //if (rectDock.top>=rect.bottom && rectDock.bottom>=rect.bottom) rect.bottom=rectDock.top;
        
          //Window is on top of client - use bottom edge for top of client
          //if (rectDock.top<=rect.top && rectDock.bottom>=rect.top) rect.top=rectDock.bottom;
        }
      }
        //CString text;
        //text.Format(L"%d %d %d %d",rect.left,rect.top,rect.right,rect.bottom);
        //AfxMessageBox(text);
      
      if (m_pView)
      {
        if (IsWindow(m_pView->m_hWnd))
        {
          CRect statusRect;
          m_wndStatusBar.GetWindowRect(&statusRect);
          ScreenToClient(&statusRect);
          rect.bottom=statusRect.top;
      
          m_pView->SetWindowPos(&wndTop,rect.left,rect.top,rect.Width(),rect.Height(),SWP_FRAMECHANGED);
        }
      }
      
    }
    As you can see it has a lot of debug messages in it which will be removed once I confirm all types of docking work correctly. I've also tested docking with a dock target - in other words docking more CDockDialog's inside of a CDockContWnd which calls CDockContWnd::RecalcLayout(). It works like a charm. Top and bottom docking has been disabled just so I can see if the left and right dock code works. Then I can use the left/right code as a template for the top/bottom code.

    Code:
    void CDockContWnd::RecalcLayout(void)
    {
      CRect dlgRect;
      CRect clientRect;
    
      GetClientRect(&clientRect);
      CRect tempRect;
     
      int iYPos=clientRect.top;
      
      for (DWORD i=0;i<m_vChildren.size();i++)
      {
        m_vChildren[i]->GetWindowRect(&dlgRect);
        tempRect.left=dlgRect.left;
        tempRect.top=iYPos;
        tempRect.bottom=tempRect.top+dlgRect.Height();
        tempRect.right=tempRect.left+dlgRect.Width();
        
        m_vChildren[i]->SetWindowPos(NULL,
                                     tempRect.left,
                                     tempRect.top,
                                     tempRect.Width(),
                                     tempRect.Height(),
                                     SWP_NOZORDER | SWP_SHOWWINDOW);
       
        //Send a WM_PAINT to the child window so it can redraw controls and any child windows
        //it may have
        m_vChildren[i]->Invalidate();
    
        //Keep track of our current Y position in this container window
        iYPos+=tempRect.Height()+1;
      }
    }
    As you can see since I'm using a vector I can insert a window at any point. This means that as long as I provide some type of visual feedback and drag and drop tracking of windows, I can allow the user to drag any CDockDialog onto any CDockContWnd at any place. Based on coordinates then I can tell where in the vector I should insert the new window. This has a lot of flexibility and I can't wait to get it all working. Since the CRect class has a lot of functionality inside of it, testing where windows need to be placed will be quite simple.

    I wish MFC would have provided better docking support, but no worries, I'm implementing my own. Perhaps I'll implement some custom UI elements next.
    Last edited by VirtualAce; 06-16-2006 at 05:29 AM.

Popular pages Recent additions subscribe to a feed