Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Hello, I Am Ready to Communicate!

5.00/5 (3 votes)
20 Jun 2012CPOL4 min read 28.3K   368  
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

  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
  2. After the dialog box is open, just name new class as CCountingThread. Following is the skeleton that would be produced by Wizard:
    C++
    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()
    };
  3. Now add three variables to the class to serve our purpose:
    • UINT_PTR m_uTimerID: will keep track of SetTimer ID
    • int m_iCount: will keep track of next incremented value
    • CWnd* m_pParentWnd: will keep pointer to the main window. I know there is one more variable in CWinThread itself to this task (m_pMainWnd). However, I just want to keep it simple.
  4. 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 in CCountingThread to handle WM_TIMER message.
    C++
    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).

  5. Now to make our CCountingThread::OnTimer (…) function visible to MessageLoop for WM_TIMER message, add the following code between BEGIN_MESSAGE_MAP() and END_MESSAGE_MAP().
    C++
    ON_THREAD_MESSAGE(WM_TIMER,&CCountingThread::OnTimer)
  6. Now, write the activation code for timer in CCountingThread::InitInstance() and de-activation code in CCountingThread::ExitInstance() as follows:
    C++
    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.

  7. 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 to CCountingThread in class, before doing previous don’t forget to add CountingThread.h in your mainwindow class header file.
  8. Add the following code to your Start button handler:
    C#
    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) 
        }
    }
    1. Create our UI thread in suspended mode using AfxBeginThread API, pass runtime class of CCountingThread as the first parameter and CREATE_SUSPENDED as the fourth parameter.
    2. Provide m_pRunningThread->m_pParentWnd, our main window pointer for communication
    3. m_pRunningThread->ResumeThread(): will start our thread
  9. Now write the following code for stopping our UI Thread:
    C++
    void CUserThread1Dlg::OnBnClickedStopThread()
    {
    	if(m_pRunningThread!= NULL)
    	{
    		m_pRunningThread->PostThreadMessageW(WM_QUIT,0,0); --- (a)
    
    		m_pRunningThread = NULL; --(b)
    	}
    }
    1. The best way to close UI thread is to post WM_QUIT message to the thread, it would close down gracefully. Since WM_QUIT makes message-pump of UI thread to exit.
    2. I am making m_pRunningThread equal to NULL, this is ok with DEMO scenario, however in real world problem, you need to program for synchronization and proper exit of thread, then assign m_pRunningThread the value NULL.
  10. 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:
    C++
    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():

    C++
    ON_MESSAGE(WM_USER+2, &CUserThread1Dlg::OnCountingIncrease)
  11. 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

  1. In main dialog box, add new button “Reset Counter” and add handler in your code.
  2. Now, add the following code into the OnClick handler to “Reset Counter” button:
    C++
    void CUserThread1Dlg::OnBnClickedResetThreadcounter()
    {
    	if(m_pRunningThread!= NULL) –(a)
    	{
    		m_pRunningThread->PostThreadMessageW(WM_USER+1,0,0); -- (b)
    	}
    }
    1. m_pRunningThread is object of class CCountingThread, created at the time of Start Thread button click.
    2. using PostThreadMessageW we will post message to UI thread.
  3. Now, add function in class CCountingThread to handle WM_USER+1 user message sent by Main Dialog box:
    C++
    void CCountingThread::ResetCounter(WPARAM wParam, LPARAM lParam){
    	m_iCount =0; -- reset the counter
    }
  4. Also add the following message listener in BEGIN_MESSAGE_MAP() and END_MESSAGE_MAP():
    C++
    ON_THREAD_MESSAGE(WM_USER+1,&CCountingThread::ResetCounter)
  5. Compile and run the application.

History

  • 20-Jun-2012: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)