locked
Out of memory with too many CDC/CBitmaps in custom controls? RRS feed

  • Question

  • I started replacing the standard MFC controls by some owner drawn or completely new written controls to get a more fancy look in my application (Buttons, checkboxes, radio buttons and text boxes so far).

    For example I made an owner drawn checkbox. So I created a class CMyCheckBox derived from CButton and set it in PreSubClass to be owner drawn. In the OnDraw routine I do my painting. To speed things up, the first time the OnDraw is called I create a new CDC and CBitmap where the whole control except the check of the checkbox is drawn. A pointer to this CDC and CBitmap is stored as a member variable of the control's object so that in future calls of OnDraw I do not have to redraw the whole thing (with gradients, rounded borders, etc). So after this "background" is created I create a new memDC (local to the function) BltBlt the stored "background"-CDC to it, draw only the check onto the box and finally copy the memDC to the real painting DC of the control.

    This works charming, is fast and looks great.

    But I now came into trouble with this attempt. If many of my controls are created the last ones don't get painted. I would say about after 50 of my custom controls (buttons, check boxes, etc) are created in my wizard style dialog. Further investigation showed that the CreateCompatibleBitmap function for the memDC fails with the "out of memory" error code.

    Could it be that this is because of the background CDC and CBitmap I store as a member with every control? All the CBitmaps have only the dimension of the controls, is it still possible that I run out of memory so fast?
    I do not have any memory leaks. I always watch out to delete/destroy DC's and Bitmap's where needed. I also used AppVerifier to confirm this.

    So my main question is: Is that technique I'm using not recommendable because storing CBitmaps with every control increases memory usage too much, or is this a common technique used to get flicker free and fast drawing and my problem must be somewhere else, because this does not use so much memory that I could run into out of memory after this "few" controls have been created?

    Would highly appreciate your opinion on this.

    Wednesday, February 3, 2010 12:03 PM

Answers

  • You need to modify all your functions that are calling "CreateCompatibleBitmap or CreateBitmap" to create a DIB section instead ( CreateDIBSection ).
    Since your're using MFC, you will need to inherit from the CDC and/or CBitmap class(es) and overwrite the accordant methods.
    Some time ago I also ran into this problem and I solved it with the DIB section thing.
    It seems that memory which is allocated by calls to CreateCompatibleBitmap and CreateBitmap is very limited by the GDI.
    CreateDIBSection seems to be not affected by it.
    • Marked as answer by MSWiege Friday, February 5, 2010 9:14 AM
    Wednesday, February 3, 2010 1:11 PM

All replies

  • You need to modify all your functions that are calling "CreateCompatibleBitmap or CreateBitmap" to create a DIB section instead ( CreateDIBSection ).
    Since your're using MFC, you will need to inherit from the CDC and/or CBitmap class(es) and overwrite the accordant methods.
    Some time ago I also ran into this problem and I solved it with the DIB section thing.
    It seems that memory which is allocated by calls to CreateCompatibleBitmap and CreateBitmap is very limited by the GDI.
    CreateDIBSection seems to be not affected by it.
    • Marked as answer by MSWiege Friday, February 5, 2010 9:14 AM
    Wednesday, February 3, 2010 1:11 PM
  • Sounds very promissing - thank you very much for your answer!

    To clarify:You mean in case of my check box I should do the following:
    At first call of OnDraw I create a CBitmap with CreatCompatibleBitmap and do my initial background drawing of the entire box as usual.
    After I'm done, I copy the DDB to a DIB wich I created with CreateDIBSection and destroy the CBitmap/DDB to free up some resources and only store the DIB as a member variable.
    On any other OnDraw call I copy the DIB to a new memDC with a CreatCompatibleBitmap()-Bitmap and draw my check mark on the box.
    And finally copy my memDC to my painting DC.

    Is that what you meant? Or can I skip the copying between DIB and DDB and directly use a DC to paint on the DIB (I'm new to DIBs - always used the CreateCompatibleBitmap by now)?
    Wednesday, February 3, 2010 3:24 PM
  • I still wasn't able to get it to work with a DIB. Could you - s.l.i - or someone else have a look at my code and tell me what is wrong with it? I just get a black box drawn.

    class CMyCheckBox : public CButton
    {
    ...
    	CDC* m_pSurfaceDC;
    	CBitmap* m_pSurfaceBmp;
    	CBitmap* m_pOldbmp;
    ...
    }
    
    void CMyCheckbox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
    	CRect rect;
    	this->GetClientRect(&rect);
    	CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    	if (m_pSurfaceDC == NULL)
    	{
    		// create DC
    		m_pSurfaceDC = new CDC();
    		m_pSurfaceBmp = new CBitmap();
    		m_pSurfaceDC->CreateCompatibleDC(pDC);
    		
    		// create DIB bitmap
    		HBITMAP bmpDIB = CreateCompatibleDIBBitmap(m_pSurfaceDC, rect); 
    		m_pSurfaceBmp->FromHandle(bmpDIB);
    		m_pOldbmp = m_pSurfaceDC->SelectObject(m_pSurfaceBmp);
    
    		// draw background
    		...
    
    		// write text
    		CString strText;
    		this->GetWindowText(strText);
    		if (!strText.IsEmpty())
    		{
    			m_pSurfaceDC->DrawText(strText, rectText, DT_LEFT | DT_VCENTER);
    		}
    
    		// draw box
    		...
    	}
    
    	// copy prepared DIB bitmap to the screen
    	pDC->BitBlt(0,0, rect.Width(), rect.Height(), m_pSurfaceDC, 0,0, SRCCOPY);
    	
    	// draw check on pDC
    	if (this->GetCheck() == BST_CHECKED)
    	{
    		...	
    	}
    }
    
    CMyCheckbox::~CMyCheckbox()
    {
    	if (m_pSurfaceDC != NULL)
    	{
    		m_pSurfaceDC->SelectObject(m_pOldbmp);
    		delete m_pSurfaceBmp;
    		delete m_pSurfaceDC;
    	}
    }

    The CreateCompatibleDIBBitmap() function I implemented this way:

    inline HBITMAP CreateCompatibleDIBBitmap(CDC* pDC, CRect rect)
    {
    	HBITMAP bmp = 0;
    
    	BITMAPINFO info;
    	info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    	info.bmiHeader.biWidth = rect.Width();
    	info.bmiHeader.biHeight = rect.Height();
    	info.bmiHeader.biPlanes = 1;
    	info.bmiHeader.biBitCount = 16; // I'd like to get this from the pDC - is this possible? Tried 24 and 32 too, but still just a black box
    	info.bmiHeader.biCompression = BI_RGB;
    	info.bmiHeader.biSizeImage = 0;
    	info.bmiHeader.biXPelsPerMeter = 0;
    	info.bmiHeader.biYPelsPerMeter = 0;
    	info.bmiHeader.biClrUsed = 0;
    	info.bmiHeader.biClrImportant = 0;
    
    	char *buf=NULL;
    	bmp = CreateDIBSection(pDC->GetSafeHdc(), &info, DIB_RGB_COLORS, (void **)&buf, NULL, 0);
    
    	return bmp;
    }


    Wednesday, February 3, 2010 8:15 PM
  • Use the following function for creating a DIB on WINCE devices.
    It is hard coded to 16 bits/pixel displays with pixel format 565. It should work on nearly every WinMo device.

    HBITMAP CreateDIB_16_565(int iWidth, int iHeight, void** ppPixels)
    {
    	struct BITMAPINFO_16_565
    	{
    	    BITMAPINFOHEADER bmiHeader;
    	    DWORD rMask, gMask, bMask, aMask;
    	};
    
    	BITMAPINFO_16_565 bmi = {0};
    	HBITMAP hBitmap = NULL;
    	HDC hDC = GetDC(NULL);
    	
    	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
     	bmi.bmiHeader.biBitCount = 16; 
    	bmi.bmiHeader.biWidth = iWidth;
     	bmi.bmiHeader.biHeight = iHeight * -1;
     	bmi.bmiHeader.biPlanes = 1;
     	bmi.bmiHeader.biCompression = BI_BITFIELDS;
     	bmi.bmiHeader.biSizeImage = iWidth * iHeight * 2;
     	bmi.rMask = 63488;
            bmi.gMask = 2016; 
    	bmi.bMask = 31; 
     	hBitmap = CreateDIBSection(hDC, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, ppPixels, NULL, 0); 
    	
      	ReleaseDC(NULL, hDC);
    	
      	return hBitmap; 
    }
    

    The best is, you have direct pixel access for fast and custom drawing via ppPixels :-)


    Thursday, February 4, 2010 10:23 AM
  • Sorry to bother you again, but I still couldn't couldn't get it to work. At my class I added BYTE* m_pBitmapBits; to hold the bits of the bitmap and called your function passing a refference to that variable:
    			HBITMAP bmpDIB = CreateDIB_16_565(w, h, (void **)&m_pBitmapBits); 
    			m_pSurfaceBmp->FromHandle(bmpDIB);
    			m_pOldbmp = m_pSurfaceDC->SelectObject(m_pSurfaceBmp);
    
    But the result of it is just some random noise. Did I declare the variable wrong or do I need to reserve memory for it before passing it? Tried it on different ways by now, but had no luck. Always got the random noise image.

    Hope you can help me out again.

    Thanks
    Thursday, February 4, 2010 7:19 PM
  • As I wrote above, I am not using MFC. So I can't tell you more about these lines of your code:
    m_pSurfaceBmp->FromHandle(bmpDIB);
    m_pOldbmp = m_pSurfaceDC->SelectObject(m_pSurfaceBmp);
    

    But to get an impression how it would work, please create a test project (Win32-Project, not MFC or ATL).
    This should create some code for a basic Win32 window with a WindowProc.
    In the WindowProc replace the WM_PAINT handling with:
    ...
    
    case WM_PAINT:
    
    
    {
    	hdc = BeginPaint(hWnd, &ps);
    	RECT rcClient;
    	GetClientRect(hWnd, &rcClient);
    
            void *pPixels = NULL;
    
    	int w = rcClient.right-rcClient.left;
    	int h = rcClient.bottom-rcClient.top;
    
    // create the memory dc
    
    	HDC hMemDC = CreateCompatibleDC(NULL);
    
    
    // create the DIB
    
    	HBITMAP hMyDibSection = CreateDIB_16_565(w, h, &pPixels);
    
    // select it into our memory dc
    
    	HBITMAP hBmpPrev = (HBITMAP)SelectObject(hMemDC, hMyDibSection);
    
    // do some painting
    
    	FillRect(hMemDC, &rcClient, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
    	Ellipse(hMemDC, 10, 10, w-10, h-10);
    	DrawText(hMemDC, L"Hello DIB section!", -1, &rcClient, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
    
    // bring it to the screen
    
    	BitBlt(hdc, 0, 0, w, h, hMemDC, 0, 0, SRCCOPY);
    
    // clean up
    
    	SelectObject(hMemDC, hBmpPrev);
    	DeleteDC(hMemDC);
    	DeleteObject(hMyDibSection);
    
    	EndPaint(hWnd, &ps);
    
    }
    break;
    
    ...
    
    
    • Edited by s.l.i Thursday, February 4, 2010 7:58 PM code formatting
    Thursday, February 4, 2010 7:56 PM
  • Thanks s.l.i I just made a stupid mistake. I used this to attach the handle to my CBitmap:
    m_pSurfaceBmp->FromHandle(bmpDIB);
    
    This should have been
    m_pSurfaceBmp = CBitmap.FromHandle(bmpDIB);
    
    or just
    m_pSurfaceBmp->Attach(bmpDIB);
    

    It now works perfect. I will have to do some test about performance and the memory usage (if it now really works if I have many of my controls loaded).

    Thanks again
    Friday, February 5, 2010 9:14 AM
  • MSWiege: I was wondering if the CreateDIBSection method actually improved anything? How did you end up solving this.

    Sorry to bring up such an old thread, but I have the same problem (not using MFC though, but that shouldn't really matter) on my device. (The emulator enver seems to be running out of memory...)

     

    Thanks in advance!

    Friday, May 14, 2010 8:42 AM
  • After some tests, I ended up going back to "normal" GDI obejcts instead of the DIBs because it didn't change anything in my case neither performance nor  memory usage. I don't remember what exactly I did, to solve my out of memory problem, but I guess it was simply some memory leak I had.
    Friday, May 14, 2010 8:52 AM
  • After some tests, I ended up going back to "normal" GDI obejcts instead of the DIBs because it didn't change anything in my case neither performance nor  memory usage. I don't remember what exactly I did, to solve my out of memory problem, but I guess it was simply some memory leak I had.
    Using CreateDIBSection instead of CreateCompatibleBitmap gets rid of the out of memory errors for me. I do have some other questions regarding CreateDIBSection but I'll open another thread for that.
    Friday, May 14, 2010 2:58 PM