C++/Win32: Threads
March 2, 2008
Recall that one of the problems our app has had is that when it’s scanning, you can’t do anything. You can’t move the window, which is annoying, and you can’t scroll through the results. You have to sit there and wait for the loop to finish out. The way around that is to do the actual scanning in a separate thread. Windows is, after all, built for multitasking, so not taking advantage of it makes no sense at all.
There are just a few things you need to know about threads and Windows before beginning:
Threads get their own message queue. The application we’ve been working on up to this point is really just a thread — the thread you get automatically.
Threads are created in a manner similar to the way windows are created. You will write a function that does the thread’s work, and pass this function to CreateThread when you want to fire up your thread. You can think of the thread’s work function as its ThreadProc, since it really is very similar to the WinProc function we’ve been dealing with.
So we’ll no longer want to call our goScan() function (which until now had done all the work) from our main window’s message handler. Instead, we’ll call CreateThread to spawn a thread, and pass it a ThreadProc function that will call goScan. Our goal for now is simply to wow ourselves with multithreading. We just want to be able to move the window while the scanning is taking place, since that will convince us that we are, in fact, multithreading. There are better ways to do this, but we won’t be dealing with them here.
Here’s our ThreadProc function declaration:
static DWORD WINAPI ThreadProc(LPVOID pvoid);
And here’s the definition:
DWORD PortScannerWindow::ThreadProc(LPVOID pvoid) {
((PortScannerWindow *)pvoid)->goScan();
return (DWORD)1;
}
It shouldn’t surprise you that we had to declare the ThreadProc function as static. This is for exactly the same reason we had to declare our stWinProc function static — because of the this pointer.
The question here is, what are we doing with that pvoid variable that gets passed in to ThreadProc?
Remember that if a member function is declared static, it doesn’t have access to non-static member functions and variables. But we need to call goScan, which isn’t static. If our ThreadProc function is passed a pointer to the object (aka the this pointer that static functions don’t get) then we do have access to its members.
Fortunately, when you call CreateThread, you not only pass in a pointer to the function that should do the thread’s work, you also get to pass in a DWORD containing whatever you need to get to the ThreadProc function. This value can be a pointer to anything — for example, an object containing several variables your thread will need — but in our case, we just need this.
We create our thread in our app’s main window’s message handler, when we receive the WM_COMMAND message for our IDB_GOSCAN button. Here’s that section of the message handler:
case IDB_GOSCAN:
if (validateInput()) {
DWORD dwThreadParam = (DWORD)this;
DWORD dwThreadID;
m_hThread = CreateThread (NULL, 0, ThreadProc, (VOID *)this, 0, &dwThreadID);
}
break;
Before we moved the scanning operation to a thread, remember, this message caused our app to call goScan(). Now, it creates a thread who’s sole purpose is to call goScan(). Look at parameter 4 of the call to CreateThread. It is the this pointer of our PortScannerWindow object! We have to cast it to (VOID *) because that’s what CreateThread expects, and when it gets to our ThreadProc function, we cast it back to a PortScannerWindow pointer and call that object’s goScan function. This concept should be easier than the WinProc dilemma you’ve already encountered. If only we could have done the same with CreateWindow!!!
At any rate, we now have moved our scanning operations into their own thread. Now, while the scanning is happening, we can move our window around, scroll through the results even as they are being sent to our display area, and even close the application.
What if we want to stop the thread? There is a function called TerminateThread that will allow us to kill a thread while it’s working. When we call it, we pass in the thread handle and an exit code, like this:
TerminateThread(hThread,0);
We could create a scond button to stop the scan thread, but that button will make no sense if there is no process running, and it will result in a call to TerminateThread when there is no thread to terminate. It would be much cooler to have our ThreadProc function modify our SCAN button, changing the text to “STOP” while it is running. Then, when goScan returns, it will change the text back to “SCAN”. This is done, remember, using the SetDlgItemText function. Here’s our updated ThreadProc function:
DWORD PortScannerWindow::ThreadProc(LPVOID pvoid) {
// Change the text of the IDB_GOSCAN button to STOP
SetDlgItemText(((PortScannerWindow *)pvoid)->m_hWnd, IDB_GOSCAN,"STOP");
// Do the Scan
((PortScannerWindow *)pvoid)->goScan();
// Change the text back
SetDlgItemText(((PortScannerWindow *)pvoid)->m_hWnd, IDB_GOSCAN, "SCAN");
return (DWORD)1;
}
Of course, this doesn’t actually do anything besides make the text change. Clicking the button will just cause the program to try to relaunch the thread, since that’s what we told it to do in WinProc. What we need here is a flag that we’ll set during the scan, and unset afterwards. Our message handler will check this flag when the WM_COMMAND message is received for IDB_BUTTON. If it’s false, it will launch the thread and set the flag to true. If it is false, it will kill the thread and set the flag to false.
We need to add a BOOL m_hScanningFlag variable to our class declaration, and then update the message handler.
Here’s the updated message handler case:
case IDB_GOSCAN:
if (!m_bScanFlag) {
if (validateInput()) {
DWORD dwThreadParam = (DWORD)this;
DWORD dwThreadID;
m_hThread = CreateThread (NULL, 0, ThreadProc, (VOID *)this, 0, &dwThreadID);
}
break;
} else {
TerminateThread(m_hThread, 0);
m_bScanFlag=FALSE;
SetDlgItemText(m_hWnd, IDB_GOSCAN, "SCAN");
break;
}
Notice that if the flag is false, we will create the thread. It will be the ThreadProc function’s responsibility to set the flag to TRUE (so we can be sure we actually go there). If the flag is true, we call TerminateThread, and set the flag to false. We will have to set it false in ThreadProc also, because this thread can also die on its own. If we don’t set it to false at the end of ThreadProc, and the process dies naturally (upon finishing its work), the button text will remain “STOP” and the flag will remain TRUE, causing the message handler to keep trying to terminate the process, even though it doesn’t exist anymore. Of course, this will eventually lead to the flag being set to false, since that’s what the message handler does after terminating the thread, but do you really want to make your user click the button twice for no good reason? I didn’t think so. So make sure to set the flag whereever it could possibly need to be set when doing these kinds of things.
Here is ThreadProc after accounting for the m_bScanFlag flag:
DWORD PortScannerWindow::ThreadProc(LPVOID pvoid) {
// Change the text of the IDB_GOSCAN button to STOP
SetDlgItemText(((PortScannerWindow *)pvoid)->m_hWnd, IDB_GOSCAN,"STOP");
// Set the flag to true
((PortScannerWindow *)pvoid)->m_bScanFlag = TRUE;
// Do the Scan
((PortScannerWindow *)pvoid)->goScan();
// Set the flag false
((PortScannerWindow *)pvoid)->m_bScanFlag = FALSE;
// Change the text back
SetDlgItemText(((PortScannerWindow *)pvoid)->m_hWnd, IDB_GOSCAN, "SCAN");
return (DWORD)1;
}
I also added a line to PortScannerWindow’s constructor, initializing m_bScanFlag to FALSE, since when we start this app up we won’t be doing any scanning right away.
The reason its nice to have our ThreadProc function as a member method of the PortScannerWindow class is that ThreadProc consequently has access to its objects members. So if something changes in the settings of your app, ThreadProc can deal with it immediately. You’ll see this work more clearly when we add some menu options and buttons — things that can actually change while ThreadProc is doing it’s thing.
All clear? Shouldn’t have been too difficult. Hopefully you have seen threads before, and if not there are tons of resources available since pretty much every OS you’ll be dealing with uses them. Oddly enough, I recommend reading up on how Java handles threads, since, like a lot of aspects of Java, threads are ridiculously clear and straightforward.
Next: Window Dressing.
Source Code:
win32tut_part8.zip [4.59MB zipped]
Additional Information:
· Encapsulating Win32 threads in C++
Further Reading:
· Nitty Gritty Windows Programming with C++ by Henning Hansen.
· Thinking in C++ by Bruce Eckel.




Comments
Got something to say?