// threads.cpp : contains all the different GDI thread classes // // This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #include "mtgdi.h" #include "threads.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif // critical section to protect while drawing to the DC CRITICAL_SECTION CGDIThread::m_csGDILock; // m_hAnotherDead is used to signal that one or more threads have ended // (it is an auto-reset event; and starts out not signaled) HANDLE CGDIThread::m_hAnotherDead = CreateEvent(NULL, FALSE, FALSE, NULL); ///////////////////////////////////////////////////////////////////////////// // CGDIThread IMPLEMENT_DYNAMIC(CGDIThread, CWinThread) BEGIN_MESSAGE_MAP(CGDIThread, CWinThread) //{{AFX_MSG_MAP(CGDIThread) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() CGDIThread::CGDIThread(CWnd* pWnd, HDC hDC) { m_bAutoDelete = FALSE; m_pMainWnd = pWnd; m_pMainWnd->GetClientRect(&m_rectBorder); m_hDC = hDC; // kill event starts out in the signaled state m_hEventKill = CreateEvent(NULL, TRUE, FALSE, NULL); m_hEventDead = CreateEvent(NULL, TRUE, FALSE, NULL); } // The reason we don't just get a CDC from our owner and simply use it is because // MFC GDI objects can't be passed between threads. So we must instead pass a // handle and then reconvert them back to MFC objects. The reason for this is // because MFC maintains a list of all GDI objects on a per thread basis. So if // you pass a GDI object to another thread, it won't be in the correct list // and MFC will assert. BOOL CGDIThread::InitInstance() { // thread setup m_dc.Attach(m_hDC); // loop but check for kill notification while (WaitForSingleObject(m_hEventKill, 0) == WAIT_TIMEOUT) SingleStep(); // thread cleanup m_dc.Detach(); // avoid entering standard message loop by returning FALSE return FALSE; } void CGDIThread::Delete() { // calling the base here won't do anything but it is a good habit CWinThread::Delete(); // acknowledge receipt of kill notification VERIFY(SetEvent(m_hEventDead)); VERIFY(SetEvent(m_hAnotherDead)); } CGDIThread::~CGDIThread() { CloseHandle(m_hEventKill); CloseHandle(m_hEventDead); } void CGDIThread::KillThread() { // Note: this function is called in the context of other threads, // not the thread itself. // reset the m_hEventKill which signals the thread to shutdown VERIFY(SetEvent(m_hEventKill)); // allow thread to run at higher priority during kill process SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL); WaitForSingleObject(m_hEventDead, INFINITE); WaitForSingleObject(m_hThread, INFINITE); // now delete CWinThread object since no longer necessary delete this; } void CGDIThread::UpdateBorder() { // Since another thread can access this variable, we block them when we write // it. In this case, it is overkill since the other thread would just animate // in the wrong direction while reading intermediate values and finally adjust // itself when the new value is valid. It is a good idea to avoid such // anomalies, however. EnterCriticalSection(&CGDIThread::m_csGDILock); { m_pMainWnd->GetClientRect(&m_rectBorder); } LeaveCriticalSection(&CGDIThread::m_csGDILock); } ///////////////////////////////////////////////////////////////////////////// // CBallThread IMPLEMENT_DYNAMIC(CBallThread, CGDIThread) BEGIN_MESSAGE_MAP(CBallThread, CGDIThread) //{{AFX_MSG_MAP(CBallThread) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() CBallThread::CBallThread(CWnd* pWnd, HDC hDC, CPoint ptPos, CPoint ptVel, CSize Size, COLORREF color) : CGDIThread(pWnd,hDC) { m_rectPosition.SetRect(ptPos.x,ptPos.y,ptPos.x+Size.cx,ptPos.y+Size.cx); m_ptVelocity = ptVel; CBrush brush(color); m_hBrush = (HBRUSH)brush.Detach(); } // The reason we don't just store the CBrush from our owner and simply use it is // because MFC GDI objects can't be passed between threads. So we must instead // pass a handle and then reconvert them back to MFC objects. The reason for // this is because MFC maintains a list of all GDI objects on a per thread basis. // So if you pass a GDI object to another thread, it won't be in the correct // list and MFC will assert. BOOL CBallThread::InitInstance() { m_brush.Attach(m_hBrush); return CGDIThread::InitInstance(); } void CBallThread::SingleStep() { m_rectPosition.OffsetRect(m_ptVelocity); if (m_rectPosition.top0) ? m_ptVelocity.y : -m_ptVelocity.y; if (m_rectPosition.bottom>m_rectBorder.bottom) m_ptVelocity.y = (m_ptVelocity.y>0) ? -m_ptVelocity.y : m_ptVelocity.y; if (m_rectPosition.left0) ? m_ptVelocity.x : -m_ptVelocity.x; if (m_rectPosition.right>m_rectBorder.right) m_ptVelocity.x = (m_ptVelocity.x>0) ? -m_ptVelocity.x : m_ptVelocity.x; // Since all threads share the same HDC it is necessary // to block all other threads while we render in the HDC EnterCriticalSection(&CGDIThread::m_csGDILock); { CBrush* oldbrush; oldbrush = m_dc.SelectObject(&m_brush); m_dc.Ellipse(m_rectPosition); m_dc.SelectObject(oldbrush); // Win32 optimizes GDI calls by collecting them in a batch // and then thunking the whole batch at once on a per // thread basis. Since we share an HDC with multiple // threads, we must flush the batch before yielding to // other threads that will adjust the HDC. To see what // I mean, comment out the GdiFlush() function call and // watch the results. GdiFlush(); } LeaveCriticalSection(&CGDIThread::m_csGDILock); } ///////////////////////////////////////////////////////////////////////////// // // CRectThread // ///////////////////////////////////////////////////////////////////////////// IMPLEMENT_DYNAMIC(CRectThread, CGDIThread) BEGIN_MESSAGE_MAP(CRectThread, CCmdTarget) //{{AFX_MSG_MAP(CRectThread) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() CRectThread::CRectThread(CWnd* pWnd, HDC hDC, CPoint ptPos, CPoint ptVel, CSize Size, COLORREF color) : CGDIThread(pWnd, hDC) { m_rectPosition.SetRect(ptPos.x ,ptPos.y, ptPos.x+Size.cx, ptPos.y+Size.cy); m_ptVelocity = ptVel; CBrush brush(color); m_hBrush = (HBRUSH)brush.Detach(); } BOOL CRectThread::InitInstance() { m_brush.Attach(m_hBrush); return CGDIThread::InitInstance(); } void CRectThread::SingleStep() { m_rectPosition.OffsetRect(m_ptVelocity); if (m_rectPosition.top0) ? m_ptVelocity.y : -m_ptVelocity.y; if (m_rectPosition.bottom>m_rectBorder.bottom) m_ptVelocity.y = (m_ptVelocity.y>0) ? -m_ptVelocity.y : m_ptVelocity.y; if (m_rectPosition.left0) ? m_ptVelocity.x : -m_ptVelocity.x; if (m_rectPosition.right>m_rectBorder.right) m_ptVelocity.x = (m_ptVelocity.x>0) ? -m_ptVelocity.x : m_ptVelocity.x; // Since all threads share the same HDC it is necessary // to block all other threads while we render in the HDC EnterCriticalSection(&CGDIThread::m_csGDILock); { CBrush* oldbrush; oldbrush = m_dc.SelectObject(&m_brush); m_dc.Rectangle(m_rectPosition); m_dc.SelectObject(oldbrush); // Win32 optimizes GDI calls by collecting them in a batch // and then thunking the whole batch at once on a per // thread basis. Since we share an HDC with multiple // threads, we must flush the batch before yielding to // other threads that will adjust the HDC. To see what // I mean, comment out the GdiFlush() function call and // watch the results. GdiFlush(); } LeaveCriticalSection(&CGDIThread::m_csGDILock); } ///////////////////////////////////////////////////////////////////////////// // CLineThread IMPLEMENT_DYNAMIC(CLineThread, CGDIThread) BEGIN_MESSAGE_MAP(CLineThread, CCmdTarget) //{{AFX_MSG_MAP(CLineThread) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() CLineThread::CLineThread(CWnd* pWnd, HDC hDC, CPoint ptPos1, CPoint ptVel1, CPoint ptVel2, CSize Size, COLORREF color) : CGDIThread(pWnd, hDC) { m_ptPosition1 = ptPos1; m_ptPosition2 = ptPos1 + Size; m_ptVelocity1 = ptVel1; m_ptVelocity2 = ptVel2; CPen pen(PS_SOLID, 1, color); m_hPen = (HPEN)pen.Detach(); } BOOL CLineThread::InitInstance() { m_pen.Attach(m_hPen); return CGDIThread::InitInstance(); } void CLineThread::SingleStep() { m_ptPosition1.Offset(m_ptVelocity1); m_ptPosition2.Offset(m_ptVelocity2); if (m_ptPosition1.y0) ? m_ptVelocity1.y : -m_ptVelocity1.y; else if (m_ptPosition1.y>m_rectBorder.bottom) m_ptVelocity1.y = (m_ptVelocity1.y>0) ? -m_ptVelocity1.y : m_ptVelocity1.y; if (m_ptPosition2.y0) ? m_ptVelocity2.y : -m_ptVelocity2.y; else if (m_ptPosition2.y>m_rectBorder.bottom) m_ptVelocity2.y = (m_ptVelocity2.y>0) ? -m_ptVelocity2.y : m_ptVelocity2.y; if (m_ptPosition1.x0) ? m_ptVelocity1.x : -m_ptVelocity1.x; else if (m_ptPosition1.x>m_rectBorder.right) m_ptVelocity1.x = (m_ptVelocity1.x>0) ? -m_ptVelocity1.x : m_ptVelocity1.x; if (m_ptPosition2.x0) ? m_ptVelocity2.x : -m_ptVelocity2.x; else if (m_ptPosition2.x>m_rectBorder.right) m_ptVelocity2.x = (m_ptVelocity2.x>0) ? -m_ptVelocity2.x : m_ptVelocity2.x; // Since all threads share the same HDC it is necessary // to block all other threads while we render in the HDC EnterCriticalSection(&CGDIThread::m_csGDILock); { CPen* oldpen; oldpen = m_dc.SelectObject(&m_pen); m_dc.MoveTo(m_ptPosition1); m_dc.LineTo(m_ptPosition2); m_dc.SelectObject(oldpen); // Win32 optimizes GDI calls by collecting them in a batch // and then thunking the whole batch at once on a per // thread basis. Since we share an HDC with multiple // threads, we must flush the batch before yielding to // other threads that will adjust the HDC. To see what // I mean, comment out the GdiFlush() function call and // watch the results. GdiFlush(); } LeaveCriticalSection(&CGDIThread::m_csGDILock); }