Hello, I Am Ready to Communicate!
CWinThread with TWO-Way communication using window message
Introduction
I was working on one of my pet projects yesterday night, there was a requirement of using UI thread. As I had the liberty of using MFC, I thought of using CWinThread
. When I dived more into its concepts , I though of writing an article on this highly magestic OLD technology, far away from the .NET world.
Using the Code
So now, here is our problem statement.
Problem Statement #1
"Create a UI Thread, which posts message to Main thread every second increasing the counter by 1."
Step By Step Guide to Solve Problem #1
- These days, VS wizards are so powerful, you don’t need to write MFC Classes derived classes by hand, you just need to right click on project in Class View and Add Class derived from
CWinThread
. So just right Click on Project Name ->Add->Class - After the dialog box is open, just name new class as
CCountingThread
. Following is the skeleton that would be produced by Wizard:class CCountingThread : public CWinThread { DECLARE_DYNCREATE(CCountingThread) protected: CCountingThread(); // protected constructor used by dynamic creation virtual ~CCountingThread(); public: virtual BOOL InitInstance(); virtual int ExitInstance(); protected: DECLARE_MESSAGE_MAP() };
- Now add three variables to the class to serve our purpose:
UINT_PTR
m_uTimerID
: will keep track ofSetTimer
IDint
m_iCount
: will keep track of next incremented valueCWnd*
m_pParentWnd
: will keep pointer to the main window. I know there is one more variable inCWinThread
itself to this task (m_pMainWnd
). However, I just want to keep it simple.
- Now since the Main Dialog must be updated every second with a new value, I thought of using
WM_TIMER
message with elapsed time of 1 second and on every timer message, post message to main window with new value. So now, add new function inCCountingThread
to handleWM_TIMER
message.void CCountingThread::OnTimer(WPARAM wParam, LPARAM lParam) { m_pParentWnd->PostMessageW(WM_USER+2,0,++m_iCount); ---- (1) }
Here, I have written code for posting message to main window with new value in (1).
- Now to make our
CCountingThread::OnTimer (…)
function visible toMessageLoop
forWM_TIMER
message, add the following code betweenBEGIN_MESSAGE_MAP()
andEND_MESSAGE_MAP()
.ON_THREAD_MESSAGE(WM_TIMER,&CCountingThread::OnTimer)
- Now, write the activation code for timer in
CCountingThread::InitInstance()
and de-activation code inCCountingThread::ExitInstance()
as follows:BOOL CCountingThread::InitInstance(){ m_uTimerID = SetTimer(NULL,2001,1000,NULL); return TRUE; } int CCountingThread::ExitInstance(){ KillTimer(NULL,m_uTimerID); return CWinThread::ExitInstance(); }
After the above task for our
WinThread
derived class is complete. - Now, design main window UI with One Edit Box (for displaying value from
Thread
) and two buttons (For Starting and Stopping Thread) and add relevant handler and control variable for edit box. Also add pointer toCCountingThread
in class, before doing previous don’t forget to add CountingThread.h in yourmainwindow
class header file. - Add the following code to your Start button handler:
void CUserThread1Dlg::OnBnClickedStartThread() { if(m_pRunningThread== NULL) { m_pRunningThread = (CCountingThread*)AfxBeginThread( RUNTIME_CLASS(CCountingThread), 0, 0, CREATE_SUSPENDED, NULL); --- (a) m_pRunningThread->m_pParentWnd = this; -- (b) m_pRunningThread->ResumeThread(); --- (c) } }
- Create our UI thread in suspended mode using
AfxBeginThread
API, pass runtime class ofCCountingThread
as the first parameter andCREATE_SUSPENDED
as the fourth parameter. - Provide
m_pRunningThread->m_pParentWnd
, our main window pointer for communication m_pRunningThread->ResumeThread()
: will start our thread
- Create our UI thread in suspended mode using
- Now write the following code for stopping our UI Thread:
void CUserThread1Dlg::OnBnClickedStopThread() { if(m_pRunningThread!= NULL) { m_pRunningThread->PostThreadMessageW(WM_QUIT,0,0); --- (a) m_pRunningThread = NULL; --(b) } }
- The best way to close UI thread is to post
WM_QUIT
message to the thread, it would close down gracefully. SinceWM_QUIT
makes message-pump of UI thread to exit. - I am making
m_pRunningThread
equal toNULL
, this is ok with DEMO scenario, however in real world problem, you need to program for synchronization and proper exit of thread, then assignm_pRunningThread
the valueNULL
.
- The best way to close UI thread is to post
- Our UI thread is posting
WM_USER
+2 messages every second with updated counter value, so add function in our dialog class to handle it. So add the following code:LRESULT CUserThread1Dlg::OnCountingIncrease(WPARAM wParam, LPARAM lParam) { CString strText; strText.Format(_T("%d"),lParam); m_edtCounting.SetWindowTextW(strText); return LRESULT(0); }
And add message listener in
BEGIN_MESSAGE_MAP()
:ON_MESSAGE(WM_USER+2, &CUserThread1Dlg::OnCountingIncrease)
- Build and run your application to see it work.
… Wait a minute, I told you there would be two way communication, however here it’s just one way, means UI thread is sending the message and Main window is listening. So implement this, i.e., Two Way communication, let's extend our problem statement #1 and derive the following new statement:
Problem Statement #2
“To add, reset counter button to reset count back to Zero”.
Step by Step Guide
- In main dialog box, add new button “Reset Counter” and add handler in your code.
- Now, add the following code into the
OnClick
handler to “Reset Counter” button:void CUserThread1Dlg::OnBnClickedResetThreadcounter() { if(m_pRunningThread!= NULL) –(a) { m_pRunningThread->PostThreadMessageW(WM_USER+1,0,0); -- (b) } }
m_pRunningThread
is object of classCCountingThread
, created at the time of Start Thread button click.- using
PostThreadMessageW
we will post message to UI thread.
- Now, add function in class
CCountingThread
to handleWM_USER+1
user message sent by Main Dialog box:void CCountingThread::ResetCounter(WPARAM wParam, LPARAM lParam){ m_iCount =0; -- reset the counter }
- Also add the following message listener in
BEGIN_MESSAGE_MAP()
andEND_MESSAGE_MAP()
:ON_THREAD_MESSAGE(WM_USER+1,&CCountingThread::ResetCounter)
- Compile and run the application.
History
- 20-Jun-2012: First version