MFC Controls
MFC Controls
MFC Internals by
Executing a class member in its own thread (C/C++ Users Journal, Vol.17, No. 12, p. 57) Singleton Creation the thread-safe way
(C/C++ Users Journal, Vol.17, No. 10, p. 43)
Improving performance with threadprivate heaps (C/C++ Users Journal, Vol.17, No. 9, p. 50)
AfxBeginThread()
CWinThread* pThread = new CWinThread(pfnThreadProc, pParam); if (!pThread->CreateThread( dwCreateFlags | CREATE_SUSPENDED, nStackSize, lpSecurityAttrs)) { pThread->Delete(); return NULL; } VERIFY(pThread->SetThreadPriority(nPriority)); if (!(dwCreateFlags & CREATE_SUSPENDED)) VERIFY(pThread->ResumeThread() != (DWORD)-1);
return pThread;
CWinThread
class CWinThread : public CCmdTarget { public: CWinThread(); CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam); BOOL CreateThread(DWORD dwCreateFlags = 0, UINT nStackSize = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL); virtual virtual virtual virtual virtual virtual BOOL InitInstance(); int Run(); BOOL PreTranslateMessage(MSG* pMsg); BOOL PumpMessage(); // low level message pump BOOL OnIdle(LONG lCount); //return TRUE if more idle processing BOOL IsIdleMessage(MSG* pMsg); //checks for special messages
private: LPVOID m_pThreadParams; //generic parameters passed to starting function AFX_THREADPROC m_pfnThreadProc; HANDLE m_hThread; // this thread's HANDLE } Back to AfxBeginThread() 5
CWinThread::CreateThread()
Initialization of _AFX_THREAD_STARTUP startup structure
ResumeThread(), ::ResumeThread(m_hThread) ::WaitForSingleObject(startup.hEvent, INFINITE) _AfxThreadEntry() Cleans up after creating child thread ::SetEvent(startup.hEvent2) _AfxThreadEntry() keeps running
Pic
_AfxThreadEntry()
Copies and initializes threads specific data (a lot of them) Unblocks parent thread ::WaitForSingleObject(startup.hEvent2, INFINITE) Back to CWinThread::CreateThread()
if (pThread->m_pfnThreadProc != NULL) nResult=(*pThread->m_pfnThreadProc)(pThread->m_pThreadParams); else if (!pThread->InitInstance()) nResult=pThread->ExitInstance(); else pThread-> Run(); //clean up and shutdown thread AfxEndThread(nResult);
Pic
CWinThread::Run()
int CWinThread::Run() { ASSERT_VALID(this); // for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) { // phase1: check to see if we can do idle work while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)) { // call OnIdle while in bIdle state if (!OnIdle(lIdleCount++)) bIdle = FALSE; // assume "no idle" state } // phase2: pump messages while available do { // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; lIdleCount = 0;} } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable }
CWinThread::OnIdle()
BOOL CWinThread::OnIdle(LONG lCount){ if (lCount <= 0) { // send WM_IDLEUPDATECMDUI to the main window // send WM_IDLEUPDATECMDUI to all frame window else if (lCount >= 0) { //Call AfxLockTempMaps/AfxUnlockTempMaps //to free maps, DLLs, etc } return lCount < 0; //nothing more to do if lCount >= 0 }
10
Motivation
class Task {}; class Processor { public: void SubmitTask(Task t); private: usigned ProcessTask(void *data); std::queue<Task> m_tasks; }
Problem
WINBASEAPI HANDLE WINAPI CreateThread(LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, WDORD dwCreationFlags, LPDWORD lpThreadID);
11
Usage
12
Solution #1
template <class Object> void runInThread(Object& obj, DWORD(WINAPI Objecg::*memberFunc)(void), HANDLE* handle=0, DWORD* id=0) { using std::runtime_error; DWORD (WINAPI *threadFunc)(void*); //pass object this pointer via thread void* pointer void *thisPointer=satic_cast<void*>(&obj); //copy class member pointer to global function pointer
*reinterpret_cast<long*>(&threadFunc)=*reinterpret_cast<long*>(&memberFunc); DWORD threadId = -1; HANDLE result=CreateThread(0, 0, threadFunc, thisPointer, 0, &threadId); if (result == 0) throw runtime_error(CreateThread failed); if (handle) *handle=result; if (id) *id = threadId; }
13
Problems of solution #1
The solution relies on WINAPI calling convention (__stdcall) which is not always possible. It would not work with const member functions.
The solution assumes (incorectly) that non-static member function will always revieve their this pointer as the first argument on the call stack. Using the void* thread argument to pass the object this pointer prevents the member function from taking any parameters. Its a big limitation.
14
Solution #2
template <class Object, class MemberFunc, class Param> class ThreadableMember { private: Object& m_instance; MemberFunc m_member; struct ThrdArg { Object *object; MemberFunc memberFunc; Param param; ThrdArg(Object& o, MemberFunc m, const Param& p) : object(o), memberFunc(m), param(p) {}
};
static DWORD WINAPI ThreadFunc(LPVOID v) { std::auto_ptr<ThrdArg> arg(reinterpret_cast<ThrdArg*>(v); return ((arg->object)->*(arg->memberFunc))(arg->param); } 15
Solution #2 (cont.)
public: ThreadableMember(Object& instance, MemberFunc member) : m_instance(instance), m_member(member) {} void run(const Param& param, HANDLE* handle=0, DWORD* id=0){ DWORD thrdId=-1; ThrArg* thrdArg = new ThrArg(m_instance, m_member, param); HANDLE result = CreateThread(0, 0, &ThreadFunc, thrdArg,0,&thrdId); if (result != 0){ if (handle) *handle=result; if (id) *id = thrdId; } else { delete thrdArg; throw std::tuntime_error(CreateThread failed); } } }; 16
Usage
Issues
Take care of you data members Thread security and stack-size can be added as a parameters to Threadable::run() and runInThread() One more parameter (perhaps, with default value) can be added to create thread in a suspended state
18
Problem
Thread may be preempted by the operating system during if (!m_instance). Then another thread gets control. Since the first thread did not finished creation of the m_instance, the second thread evaluates (!m_instance) as true and creates another singleton.
20
Solution
class Singleton { public: static Singleton& instance(){ m_key.Lock(); if (!m_instance) m_instance = new Singleton; return *m_instance; m_key.Unlock(); } protected: Singleton(); private: static Singleton* m_instance; static CCriticalSection m_key; }; Singleton* Singleton::m_instance = 0; CCriticalSection Singleton::m_key; Different from the code in the article
21
Heaps
void SomeFunc(Foo bar){ Obj* pObj=new Obj; pObj->Hadler(bar); delete pObj; } void SomeFunc(Foo bar){ Obj obj; obj.Hadler(bar); }
22
Possible memory leak (can be solved with auto_ptr) Heap is a shared resource, which has to be synchronized The more interdependences between threads the less scalable application
23
When a new Win32 process starts up, OS creates a default Process heap. GetProcessHeap() returns its handle
HANDLE HeapCreate(DWORD flOptions, DWORD dwInitialSize, DWORD dwMaximumSize) creates Private heap
LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwButes); BOOL HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); BOOL HeapDestroy(HANDLE hHeap);
Serialization is turned on by default, but can be turned off by using a flag value of HEAP_NO_SERIALIZE Access to the process heap in an multithreaded application must be serialized.
24