Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Threads in a GUI (MFC) app the easy way

Multithreading in MFC

Threads in a GUI (MFC) app the easy way

by  PerFnurt  Posted    (Edited  )
Every once in a while questions about threading in MFC based apps gets posted.
Since thread handling can be kind of complex the answer if often like
"its too complex to teach in forums like these...".

However, given that you can follow a small set of simple rules, threading and thread's interaction with the GUI doesn't have to be all that complicated.

The rules:
Rule #1: Do not access the same data between the threads.
Rule #2: Do not pass references or pointers to variables between threads.
Rule #3: Use windows messages to interact with the GUI.

Follow these rules and you can write multithreaded MFC applications without having to be a monster thread hacker :)

So, what do they mean really?

VERY BAD CODE:

Code:
struct ThreadParam
{
	CMyDialog* mDlg;
};

UINT MyThreadProc( LPVOID pParam )
{
	ThreadParam* p = static_cast<ThreadParam*> (pParam);
	p->mDlg->callSomeMethod();
}

void CMyDialog::OnSomeButton()
{
	...
	ThreadParam param;
	param.mDlg = this;
	AfxBeginThread(MyThreadProc, &param);
	...
}
The code snipped above violates all 3 rules:

#1 Do not access the same data between the threads.
The code passes the dialog's this pointer to the thread, but accessing isn't thread safe.

#2 Do not pass references or pointers to variables.
The code passes a pointer to the local ThreadParam variable. If OnSomeButton goes out of scope before MyThreadProc tries to access the parameter things will of course go bad.

#3 Use windows messages to interact with the GUI.
Its related to rule #1. You can't do a callSomeMethod() like the code above does, simply because the access to the mDlg isn't safe.
------
So, how should it be done then?

Rule #2: Allocate with new and let the thread delete it when finished.

Rule #1 and #3: Here is a hint: The API functions ::postMessage and ::SendMessage are thread safe!
That means you can use the windows handle (ie the CWnd::m_hWnd) to interact with your MFC classes.

Here's how one could do it 'the right way':
Code:
// Defining a 'user defined' message for the call I wish to make
#define MYMESS_CALL_SOME_METHOD (WM_APP + 1)

struct ThreadParam
{
	HWND mDlg;	// Note: A handle.
};

UINT MyThreadProc( LPVOID pParam )
{
	ThreadParam* p = static_cast<ThreadParam*> (pParam);
	// Using message to call the method. Note: I could of course use the
	// WPARAM, LPARAM and returned value for something meaningful if I wished.
	::SendMessage(p->mDlg, MYMESS_CALL_SOME_METHOD, 0, 0);
	delete p;
}

void CMyDialog::OnSomeButton()
{
	...
	ThreadParam* param = new ThreadParam;
	param->mDlg = m_hWnd;  // A handle, not a dangerous 'this'
	AfxBeginThread(MyThreadProc, param);
    param = 0; // The other thread shall delete it
    ...
}

Handling the user defined message the standard way:

Declare a method in the dialog message map (in the .h file)
Code:
	//{{AFX_MSG(CMyDialog)
	...
	afx_msg LRESULT OnCallSomeMethod(WPARAM, LPARAM);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()

Map the message to the method and implement the method (in the .cpp file):
Code:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
	//{{AFX_MSG_MAP(CMyDialog)
	...
	ON_MESSAGE(MYMESS_CALL_SOME_METHOD, OnCallSomeMethod)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

LRESULT CMyDialog::OnCallSomeMethod(WPARAM wp, LPARAM lp)
{
	callSomeMethod();
	return 0;
}

With that said there's one tiny little more thing to watch out for: The CString.

CString is clever enough to use the same instance of data:
Code:
CString a ="Foo";
CString b = a;
CString c(a);
All 3 CStrings above will internally point to the same "Foo". That is normally all good and well, but if you remember:
Rule #2: Do not pass references or pointers to variables between threads
you probably realize that passing a CString across threads can violate this because of how stuff is handeled internally.

BAD CODE:
Code:
struct ThreadParam
{
	CString mSomeString;
};
...
CString foo="Foo";
ThreadParam* param = new ThreadParam;
// Looks like a copy, but internally 
// param->mSomeString points to foo's internals:
param->mSomeString = foo;  
AfxBeginThread(MyThreadProc, param);

You can simply make sure no such internal pointing occurs by hiding the fact that it's a CString you're copying from:
Code:
// Making sure mSomeString doesn't 
// 'see' the CString it's copied from
param->mSomeString = static_cast<LPCTSTR>(foo);

That's about it.

Good Luck!
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top