• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 684
  • Last Modified:

GetDC() producing errors after delay

in my custom paint method, I grab a DC within a method of a CDialog derived class:

               CDC* pDC = GetDC();

I then proceed to create offscreen memory DC's, paint and blit and such.

For every:
            CDC memDC;
            memDC.CreateCompatibleDC(pDC);
I do:
            DeleteObject(memDC);

and the method ends with:

            ReleaseDC(pDC);
                pDC = NULL;


I have another thread which is polling a camera feed and doing something similar with it's own device context which is also CStatic derived.

The app will run smoothly for several minutes, at which point it crashes,  pDC not being null, but it looks like this when I hold my mouse over it in the debugger:
memDC = 0x08acfe04 { hdC = 0x00000000 attrib=0x00000000}

GetLastError() returns 0.

As far as I can tell, everywhere in my app that I call GetDC() I am following it with a ReleaseDC() and I'm calling DeleteObject() for every CreateCompatibleDC() I create.


Any thoughts as to what I might start looking for?

Thanks
-Paul
0
PMH4514
Asked:
PMH4514
  • 6
  • 3
1 Solution
 
AlexFMCommented:
All drawing code must be executed from main application thread which creates the window. Worker thread should not do any windows-related code. When window must be redrawn, worker thread must post message to the window, and window redraws itself in the main thread.
0
 
PMH4514Author Commented:
generally I'd agree with that. in this case however, there are two camera displays, each running at its own frame rate, and thus I have threads dedicated to drawing them.

anyway, I've turned all camera threads off, and am still seeing the failure.
0
 
PMH4514Author Commented:
maybe I'm missing something..  I have a worker thread which is constantly polling a system status and calls back to CUserInterface::CustomPaintUI()
(CUserInterface  created the window)

void CUserInterface::CustomPaintUI()
{
      PAINTSTRUCT ps;
      this->BeginPaint(&ps);

      CDC* pDC = GetDC();

      //get and store info about the clientRect
      CRect clientRect;
      GetClientRect(&clientRect);

      // Create a memory DC to do the drawing to...
      CDC memDC;
      memDC.CreateCompatibleDC(NULL);

      // double buffer bitmap
      CBitmap m_BMP;
      m_BMP.CreateCompatibleBitmap(pDC, clientRect.right, clientRect.bottom);

      // select the double buffer bitmap into the memory DC  
      memDC.SelectObject(&m_BMP);  

      // save pointer to the front buffer
      CDC* pRealDC = pDC;

      // draw into the back buffer    
      pDC = &memDC;    
      
      // clear the buffer
      pDC->BitBlt(0, 0, clientRect.right, clientRect.bottom, NULL, 0, 0, BLACKNESS);    

      // blit the background skin

      CDC memDCSkin;
      memDCSkin.CreateCompatibleDC(NULL);      
      hOldBmp = (HBITMAP)SelectObject(memDCSkin, hSkinBmp);
      pDC->BitBlt(0,0,clientRect.right, clientRect.bottom, &memDCSkin, 0,0, SRCCOPY);
      SelectObject(memDCSkin, hOldBmp);
      DeleteObject(memDCSkin);

      // clean up
      ReleaseDC(pRealDC);
      pDC = NULL;
      
      memDC.DeleteDC();
      m_BMP.DeleteObject();

      this->EndPaint(&ps);
}

So that method is being executed repeatedly as long as my thread that calls it isn't stopped. It'll run for several minutes, and then crash on this line:
      memDC.CreateCompatibleDC(NULL);

as described in my first post.  

I had the thread post WM_PAINT and put this code in OnPaint() not caling BeginPaint/EndPaint (ie. the same thing) and saw the same problems.



0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
AlexFMCommented:
BeginPaint should be used only inside of WM_PAINT message handler. To draw window from other handler use GetDC/ReleaseDC.
Again, don't draw from the worker thread, results are unpredictable.
Using Task Manager test whether number of GDI objects used by the program is constantly growing.
0
 
PMH4514Author Commented:
>> BeginPaint should be used only inside of WM_PAINT message handler.
that could be the problem, I was using that and GetDC/ReleaeDC

I have been looking at the task manager, at the Mem Usage for my app, it doesn't really fluctuate. .What process name would I be looking for to actually see the number of GDI objects in use by the program?

>>Again, don't draw from the worker thread,
I'm not really sure that I am (since I've commented this all out, this isn't directly related to this problem, but just to clarify) -  I have a CStatic derived class I call "CCameraDisplay" and I have a class called CCamera that has a member instance of CCameraDisplay.  CCamera  has a worker thread which is collecting frame data from a physical camera, packaging it up along with various other system paramaters at the time of capture, and pushing it to CCameraDisplay::DrawFrame(..) That method subsequently blits the image into a memory DC and when CCameraDisplay::OnPaint() is called, the offscreen surface is copied to the display.

That is not a case of a worker thread drawing is it? It's merely pushing data along to a CStatic derived instance which draws it OnPaint. My main application CDialog contains a graphical UI, which is what the CustomPaintUI() method (the topic of this discussion) is responsible for painting, that is, painting everything outside of the CCameraDisplay "screen real estate".  The areas of the screen that display camera feeds are not painted by the main dialog, rather, by OnPaint() in the CCameraDisplay class. Since those OnPaint() methods are getting their data from a worker thread that is not associated with the main UI, I think is why I said that I had another thread displaying the camera data.

 This is the correct approach right?

-Paul


0
 
AlexFMCommented:
In the Processes tab of Task Manager select View - Select Columns. Check GDI objects.

>> This is the correct approach right?

No, this is not correct. Any window (including static window) must be redrawn in the same thread where it was created. To do this we neet to post user-defined message from worker thread to the window. Messages are handled in the window message queue which runs in the thread which created this window.
0
 
PMH4514Author Commented:
>> In the Processes tab of Task Manager select View - Select Columns. Check GDI objects.
cool! I never realized there were other available columns..

That definitely helped..  With most of my paint block commented out, the GDI count remained the same +/- a few. I then commented back in sections at a time while the app was running until suddenly the GDI object count started shooting way up.  That pointed to an error.

>>No, this is not correct. Any window (including static window) must be redrawn in the same thread where it was created. To do this we neet to post user-defined >>message from worker thread to the window. Messages are handled in the window message queue which runs in the thread which created this window.

I guess I'm confused then.  Is posting a user defined message "different" than calling back to a static member function in so far as what will actually get executed?
0
 
PMH4514Author Commented:
ok so answer me this if you would.

I have my custom paint method in my CStatic derived class:

CUserInterface::CustomPaintUI()

I have a thread that CUserInterface launched, the purpose of which (at least my intention was) to poll the system status (other hardware controllers and such) so that the UI will repaint itself based on system state changes.  

I did it as such:

UINT CUserInterface::ThreadProc(LPVOID a_pParam)
{
  CUserInterface* pUI      = CUserInterface::Instance();
  while( FALSE == m_bKillThread )
  {
    pUI->CustomPaintUI();
  }
}


where m_bKillThread and ThreadProc are static   (the thread launches and runs just fine)

Are you saying that it is incorrect for me to call CustomPaintUI from the thread, and rather, I need to push a WM_PAINT message to CUserInterface instead?
If so, could you show me the code to use and explain what the difference is between doing that and calling it directly?

thanks!
-Paul
0
 
PMH4514Author Commented:
Anyway, Alex, don't feel obligated to get into the whole windows messaging thing here in this thread (no pun intended :) - that really is a different discussion. Lets just leave that be, as my app is very much "not a normal windows app" - it looks more like a video game and doesn't rely upon standard messaging. I do believe I need to do more research into messaging and getting threads to inform creation windows about updates etc..

That being said - it turns out watching the number of GDI objects in the task manager was the perfect solution.. As I stepped through all my drawing code, I discovered many places where I'd so something like:

      HRGN tempRegion = CreateRectRgn(a_Rect.left, a_Rect.top, a_Rect.right, a_Rect.bottom);
or
      HBRUSH br1 = CreateSolidBrush( a_Color );

etc.

and never call DeleteObject()!  I don't know what I was thinking, but these added up over several minutes to the point where I got my crash, which is what this EE question was all about, so thanks! I'm going to accept that response as the answer.

-Paul
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 6
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now