| Tenouk C & C++ | MFC Home | Bitmaps 5 | Windows Message Processing & Multithreaded Programming 2 | Download | Site Index |


 

 

 

 

 

 

 

Module 22:

Windows Message Processing and Multithreaded Programming 1

 

 

 

 

 

 

Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below. For Win32 process, thread and synchronization story can be found starting from Module R. or Win32 and Unicode program examples.

  1. Windows Message Processing and Multithreaded Programming

  2. Windows Message Processing

  3. How a Single-Threaded Program Processes Messages

  4. Yielding Control

  5. Timers

  6. The MYMFC27A Program

 

 

 

Windows Message Processing and Multithreaded Programming

 

With its multitasking and multithreading API, Win32 revolutionized programming for Microsoft Windows. If you've seen magazine articles and advanced programming books on these subjects, you might have been intimidated by the complexity of using multiple threads. You could stick with single-threaded programming for a long time and still write useful Win32 applications. If you learn the fundamentals of threads, however, you'll be able to write more efficient and capable programs. You'll also be on your way to a better understanding of the Win32 programming model.

 

Windows Message Processing

 

To understand threads, you must first understand how 32-bit Windows processes messages. The best starting point is a single-threaded program that shows the importance of the message translation and dispatch process. You'll improve that program by adding a second thread, which you'll control with a global variable and a simple message. Then you'll experiment with events and critical sections. For heavy-duty multithreading elements such as Win32 mutexes and semaphores, however, you'll need to refer to another book, such as Jeffrey Richter's Advanced Windows, 3d Ed. (Microsoft Press, 1997).

 

How a Single-Threaded Program Processes Messages

 

All the programs so far in this book have been single-threaded, which means that your code has only one path of execution. With ClassWizard's help, you've written handler functions for various Windows messages and you've written OnDraw() code that is called in response to the WM_PAINT message. It might seem as though Windows magically calls your handler when the message floats in, but it doesn't work that way. Deep inside the MFC code (which is linked to your program) are instructions that look something like this:

 

MSG message;

while (::GetMessage(&message, NULL, 0, 0))

{

    ::TranslateMessage(&message);

    ::DispatchMessage(&message);

}

 

Windows determines which messages belong to your program and the GetMessage() function returns when a message needs to be processed. If no messages are posted, your program is suspended and other programs can run. When a message eventually arrives, your program "wakes up." The TranslateMessage() function translates WM_KEYDOWN messages into WM_CHAR messages containing ASCII characters, and the DispatchMessage() function passes control (via the window class) to the MFC message pump, which calls your function via the message map. When your handler is finished, it returns to the MFC code, which eventually causes DispatchMessage() to return.

 

Yielding Control

 

What would happen if one of your handler functions was a pig and chewed up 10 seconds of CPU time? Back in the 16-bit days, that would have hung up the whole computer for the duration. Only cursor tracking and a few other interrupt-based tasks would have run. With Win32, multitasking got a whole lot better. Other applications can run because of preemptive multitasking; Windows simply interrupts your pig function when it needs to. However, even in Win32, your program would be locked out for 10 seconds. It couldn't process any messages because DispatchMessage() doesn't return until the pig returns. There is a way around this problem, however, which works with both Win16 and Win32. You simply train your pig function to be polite and yield control once in a while by inserting the following instructions inside the pig's main loop:

 

MSG message;

if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE))

{

    ::TranslateMessage(&message);

    ::DispatchMessage(&message);

}

 

The PeekMessage() function works like GetMessage(), except that it returns immediately even if no message has arrived for your program. In that case, the pig keeps on chewing. If there is a message, however, the pig pauses, the handler is called, and the pig starts up again after the handler exits.

 

Timers

 

A Windows timer is a useful programming element that sometimes makes multithreaded programming unnecessary. If you need to read a communication buffer, for example, you can set up a timer to retrieve the accumulated characters every 100 milliseconds. You can also use a timer to control animation because the timer is independent of CPU clock speed.

Timers are easy to use. You simply call the CWnd member function SetTimer() with an interval parameter, and then you provide, with the help of ClassWizard, a message handler function for the resulting WM_TIMER messages. Once you start the timer with a specified interval in milliseconds, WM_TIMER messages will be sent continuously to your window until you call CWnd::KillTimer or until the timer's window is destroyed. If you want to, you can use multiple timers, each identified by an integer. Because Windows isn't a real-time operating system, the interval between timer events becomes imprecise if you specify an interval much less than 100 milliseconds. Like any other Windows messages, timer messages can be blocked by other handler functions in your program. Fortunately, timer messages don't stack up. Windows won't put a timer message in the queue if a message for that timer is already present.

 

The MYMFC27A Program

 

We're going to write a single-threaded program that contains a CPU-intensive computation loop. We want to let the program process messages after the user starts the computation; otherwise, the user couldn't cancel the job. Also, we'd like to display the percent-complete status by using a progress indicator control, as shown in Figure 1. The MYMFC27A program allows message processing by yielding control in the compute loop. A timer handler updates the progress control based on compute parameters. The WM_TIMER messages could not be processed if the compute process didn't yield control.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The Compute dialog box.

 

Figure 1: The Compute dialog box.

 

Here are the steps for building the MYMFC27A application:

 

Run AppWizard to generate \mfcproject\mymfc27A. Accept all the default settings but two: select Single Document and deselect Printing And Print Preview. The options and the default class names are shown here.

 

MYMFC27A SDI project summary.

 

Figure 2: MYMFC27A SDI project summary.

 

Use the dialog editor to create the dialog resource IDD_COMPUTE. Use the resource shown here as a guide.

 

Modifying dialog properties, adding progress bar control and modifying its properties.

 

Figure 3: Modifying dialog properties, adding progress bar control and modifying its properties.

 

Keep the default control ID for the Cancel button, but use IDC_START for the Start button. For the progress indicator, accept the default ID IDC_PROGRESS1.

 

Use ClassWizard to create the CComputeDlg class. ClassWizard connects the new class to the IDD_COMPUTE resource you just created.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

--------------------------------------------------------------------------------

Adding a new class, CComputeDlg for the dialog.

 

Figure 4: Adding a new class, CComputeDlg for the dialog.

 

After the class is generated, add a WM_TIMER message handler function.

 

Adding a WM_TIMER message handler function.

 

Figure 5: Adding a WM_TIMER message handler function.

 

Also add BN_CLICKED message handlers for IDC_START and IDCANCEL. Accept the default names OnStart() and OnCancel().

 

Adding BN_CLICKED message handlers for IDC_START and IDCANCEL.

 

Figure 6: Adding BN_CLICKED message handlers for IDC_START and IDCANCEL.

 

Add three data members to the CComputeDlg class. Edit the file ComputeDlg.h. Add the following private data members (added manually):

 

int m_nTimer;

int m_nCount;

enum { nMaxCount = 10000 };

 

 

Visual C++ MFC code segment screen snapshot

 

Listing 1.

 

The m_nCount data member of class CComputeDlg is incremented during the compute process. It serves as a percent complete measurement when divided by the "constant" nMaxCount.

 

Add initialization code to the CComputeDlg constructor in the ComputeDlg.cpp file. Add the following line to the constructor to ensure that the Cancel button will work if the compute process has not been started:

 

m_nCount = 0;

 

Visual C++ MFC code segment screen snapshot

 

Listing 2.

 

Be sure to add the line outside the //{{AFX_DATA_INIT comments generated by ClassWizard.

 

Code the OnStart() function in ComputeDlg.cpp. This code is executed when the user clicks the Start button. Add the following code:

 

void CComputeDlg::OnStart()

{

    MSG message;

 

    m_nTimer = SetTimer(1, 100, NULL); // 1/10 second

    ASSERT(m_nTimer != 0);

    GetDlgItem(IDC_START)->EnableWindow(FALSE);

    volatile int nTemp;

    for (m_nCount = 0; m_nCount < nMaxCount; m_nCount++) {

        for (nTemp = 0; nTemp < 10000; nTemp++) {

        // uses up CPU cycles

        }

        if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {

            ::TranslateMessage(&message);

            ::DispatchMessage(&message);

        }

    }

    CDialog::OnOK();

}

 

Visual C++ MFC code segment screen snapshot

 

Listing 3.

 

The main for loop is controlled by the value of m_nCount. At the end of each pass through the outer loop, PeekMessage() allows other messages, including WM_TIMER, to be processed. The EnableWindow(FALSE) call disables the Start button during the computation. If we didn't take this precaution, the OnStart() function could be reentered.

Code the OnTimer() function in ComputeDlg.cpp. When the timer fires, the progress indicator's position is set according to the value of m_nCount. Add the following code:

 

void CComputeDlg::OnTimer(UINT nIDEvent)

{

    CProgressCtrl* pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS1);

    pBar->SetPos(m_nCount * 100 / nMaxCount);

}

 

Visual C++ MFC code segment screen snapshot

 

Listing 4.

 

Update the OnCancel() function in ComputeDlg.cpp. When the user clicks the Cancel button during computation, we don't destroy the dialog; instead, we set m_nCount to its maximum value, which causes OnStart() to exit the dialog. If the computation hasn't started, it's okay to exit directly. Add the following code:

 

void CControlDlg::OnCancel()

{

    TRACE("entering CComputeDlg::OnCancel\n");

    if (m_nCount == 0) {      // prior to Start button

        CDialog::OnCancel();

    }

    else {                    // computation in progress

        m_nCount = nMaxCount; // Force exit from OnStart

    }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

--------------------------------------------------------------------------------------------------------------------------------------------

Visual C++ MFC code segment screen snapshot

 

Listing 5.

 

Edit the CMymfc27AView class in mymfc27AView.cpp. First edit the virtual OnDraw() function to display a message, as shown here:

 

void CMymfc27AView::OnDraw(CDC* pDC)

{

    pDC->TextOut(30, 30, "Press the left mouse button here.");

}

 

Visual C++ MFC code segment screen snapshot

 

Listing 6.

 

Then use ClassWizard to add the OnLButtonDown() function to handle WM_LBUTTONDOWN messages.

 

Adding the OnLButtonDown() function to handle WM_LBUTTONDOWN messages.

 

Figure 7: Adding the OnLButtonDown() function to handle WM_LBUTTONDOWN messages.

 

Then, add the following code:

 

void CMymfc27AView::OnLButtonDown(UINT nFlags, CPoint point)

{

    CComputeDlg dlg;

    dlg.DoModal();

}

 

Visual C++ MFC code segment screen snapshot

 

Listing 7.

 

This code displays the modal dialog whenever the user presses the left mouse button while the mouse cursor is in the view window. While you're in mymfc27AView.cpp, add the following #include statement:

 

#include "ComputeDlg.h"

 

Visual C++ MFC code segment screen snapshot

 

Listing 8.

 

Build and run the application. Press the left mouse button while the mouse cursor is inside the view window to display the dialog. Click the Start button, and then click Cancel. The progress indicator should show the status of the computation.

 

MYMFC27A program output, showing the progress bar in action when the Start button is pressed.

 

Figure 8: MYMFC27A program output, showing the progress bar in action when the Start button is pressed.

 

 

Continue on next module...part 2.

 

 

 

 

 

 

Further reading and digging:

  1. Win32 process, thread and synchronization story can be found starting from Module R.

  2. MSDN MFC 7.0 class library online documentation.

  3. MSDN MFC 9.0 class library online documentation - latest version.

  4. MSDN Library

  5. Windows data type.

  6. Win32 programming Tutorial.

  7. The best of C/C++, MFC, Windows and other related books.

  8. Unicode and Multibyte character set: Story and program examples.

 

 

 

 

 


 

| Tenouk C & C++ | MFC Home | Bitmaps 5 | Windows Message Processing & Multithreaded Programming 2 | Download | Site Index |