Export (0) Print
Expand All
13 out of 15 rated this helpful - Rate this topic

Multiple Threads in the User Interface

 

Nancy Winnick Cluts
Microsoft Developer Network Technology Group

Created: November 24, 1993

Abstract

The Microsoft® Win32® Application Programming Interface (API) has given us the ability to use multiple threads within applications. Many programmers for Microsoft Windows® are now looking for ways to add threads to their applications. Although there are some excellent reasons to add threads to an application, there are also times when threads are unnecessary and will only add to the complexity of a program. In this technical article, I will explain the ramifications of adding multiple threads to the user interface, including the following:

  • Why and where (and why not and where not) to use multiple threads
  • The cost of adding multiple threads to the user interface
  • How threads affect message routing
  • How multiple user-interface threads affect window management
  • Alternatives to multiple threads
  • How to avoid message deadlocking
  • New functions for handling multiple threads

Introduction

So you already have an application written for Microsoft® Windows® version 3.1 that you have decided to port to Windows NT®. Since Windows NT has these new things called threads that are supposed to make applications faster, you're interested in putting these in your application. The first thing you do is look up the functions that you need to create and use threads, and you jump right in, adding threads to all of the "slow" areas in your application and creating a thread for each new window that you create. After you finally get your application to build (passing NULL as a parameter every time you were asked for a security descriptor), you find to your utter horror that not only is your application not faster, but it doesn't work correctly anymore! At this point you begin to wonder if threads are such a great idea after all.

Perhaps the problem isn't with the threads, but with where you have decided to add your threads. When threads are used and placed correctly in an application, you can gain some excellent benefits in speed, but when they are placed inappropriately, you may well end up with a complex application that is no faster than it was originally and much harder to debug.

Why Use Multiple Threads in the User Interface?

When you think of multithreading, some tasks immediately come to mind, such as data calculations, database queries, and input gathering. These activities, most often thought of as "background" tasks, do not directly involve the user interface or window management. Does this mean that there are no places that you can incorporate threads in your user interface? Certainly not. In general, if there are places in your application that do not need to be serialized, you can add threads. For instance, it does not make sense to distribute input to threads because all input is implicitly serialized by USER. It also does not make sense to distribute output to threads because output devices, such as a printer, are inherently single-threaded.

Where?

Let's say you have an application that creates one window that displays current stock quotes and another window that allows you to enter requests for buying or selling stock. Within your application, you have two discrete tasks to complete that do not depend upon one another for information: (1) Displaying current stock quotes and (2) Placing a buy or sell order. These tasks do not affect each other in any way. One way to improve the performance of your application would be to create a thread for each window, thereby creating a more modular application.

Another situation in which multiple threads would benefit the user interface would be in an application in which one window displays a spinning cube and another window receives input from the user on how fast to spin the cube, the coordinates of the cube, and its angle. (Since we're just dreaming this up, let's make it three-dimensional.) You can fairly cleanly break down the tasks each window needs to complete—one window displays the spinning cube while the other window is gathering input. The big difference between this example and the previous one is that here you need to have one thread communicate some information (the new speed, coordinates, or angle) to the other thread. The first thread can continue to display the cube while the second thread is gathering new input. In other words, you use one thread for input and one for output.

Where Not?

Because I like to be as evenhanded as possible, let me give you a couple of examples of situations in which multiple threads would be a detriment to the user interface. Let's say you have an application that relies upon getting some type of input before it can continue processing, such as a dialog box that accepts logon information. Would it be a good idea to create a thread to gather that input? Probably not. If you are creating a thread to accomplish something that needs to be synchronous, you are making things needlessly complex for almost no benefit, plus there are costs to adding threads, which I will discuss in the next section.

Another poor multithreading candidate is creating a thread for a nonreentrant window or dialog box. If this window has some global data, and if another thread calls it more than once, your global data can become corrupted. You can work around this problem by ensuring that you keep your data on a per-thread basis rather than a per-process basis. For example, let's say you have a dialog box that you use to open a file, but the data you keep in the thread for the pointer to file or the filename is kept in global data, rather than per-thread data. In this case, if the dialog box is called twice, the pointer and the filename will become corrupted and, when you try to access the file via the pointer in the first thread, you will be accessing the wrong file. This is not the functionality you want. Bear in mind that this situation is not specifically a USER issue—it is a general Windows NT multithreading issue. In any Windows NT-based application, you need to take care with data that you use on a per-thread basis.

Costs of Creating Threads

Just as there is no such thing as a free lunch, there is also no such thing as a free thread. Before creating threads, you must keep the costs in mind. In many cases, you may find that the cost is low compared to the benefit. The following list enumerates some of the costs incurred when creating multiple threads in your process.

  • Memory is needed for the structures required by threads.
  • An application that has a large number of threads consumes extra CPU time in keeping track of those threads.
  • An application is responsible for synchronizing access to shared resources by multiple threads. This is true for system resources (such as communications ports or disk drives), handles to resources shared by multiple processes (such as file or pipe handles), or the resources of a single process (such as global variables accessed by multiple threads). If you don't synchronize multiple threads properly (in the same or in different processes), you can run into some nasty problems, including the dreaded deadlock and race conditions.
  • Because all threads of a process share the same address space and can access the process's global variables, an application must also synchronize access to these global variables. This means that the developer must decide what data can be process-specific and what data is thread-specific.

How Multiple Threads Affect Window Management

A Windows NT-based application can have multiple threads of execution, and each thread can create windows by calling the CreateWindow function. An application must remove and process messages posted to the message queues of its threads. A single-threaded application uses a message loop in its WinMain function to remove and send messages to the appropriate window procedures for processing.

Changes to the Message Loop

Applications with multiple threads must include a message loop in each thread that creates a window. The message loop and window procedure for a window must be processed by the thread that created the window. If the message loop does not reside in the same thread that created the window, the DispatchMessage function will not get messages for the window. As a result, the window will appear but won't show activation and won't repaint, be moved, receive mouse messages, or generally work as you expect it to.

Enumerating Your Thread's Windows

If you have a need in your application to affect all of the windows created by a particular thread, you can use the new EnumThreadWindows function to enumerate the windows created by a particular thread. For example, let's say you have an application that displays all movie times for a movie theater in a window and creates a window for each theater. The windows created are dependent on the general location the user entered, and you spin off a thread to create all of the windows for the theaters for that location. When the user refines the search to a smaller area, you may want to enumerate the windows in your thread to determine whether or not you need to close down one of the windows containing information about theaters outside of the specified area. This saves you from having to search for information you have already displayed.

The EnumThreadWindows function is much like the EnumChildWindows function in that the application developer can specify enumeration of the windows and provide a callback function to effect the necessary changes. This function passes the handle of each thread's window, in turn, to an application-defined callback function. The GetWindowThreadProcessId function returns the identifier of the thread that created a particular window.

Finding a Window; or, Am I Here Yet?

Another common window management job that is affected by multiple threads is determining whether or not a previous instance of the application is already running. A common method used by applications to determine whether or not their application has started is to use the FindWindow function specifying the class and window name. If FindWindow returns NULL, the application assumes that it has not been started already. Unfortunately, this is not an entirely reliable method. It is possible for a second instance of an application to be started and to execute the FindWindow call before the first instance has created its window.

An alternative method for determining whether an instance of your application is running is to use any named object (mutex, semaphore, event, named shared memory). That is, when you start your application, check for a particular named semaphore (the name is your choice, so be creative) and, if the semaphore does not exist, go about starting up your application as usual; if the semaphore does exist, task-switch to your application. The following example is a function that checks to see if a unique semaphore was already created. If the semaphore exists, the function calls FindWindow to find the application, and sets that window to foreground. If it does not exist, the function returns FALSE. The function uses the CreateSemaphore function to illustrate a named-object creation operation that fails if the object already exists.

BOOL DoIExist(LPSTR lpszSemName, LPSTR lpszClassName, LPSTR lpszWindowTitle)
{

    HANDLE hSem;
    HWND hWndMe;

    /* Create or open a named semaphore. */
    hSem = CreateSemaphore(
        NULL,         /* Security attributes */
        0,            /* Initial count       */
        1,            /* Maximum count       */
        lpszSemName); /* Semaphore name      */

    /* Close handle, and return NULL if existing semaphore opened. */
    if (hSem != NULL && GetLastError() == ERROR_ALREADY_EXISTS)
    {
        CloseHandle(hSem);
        hWndMe = FindWindow(lpszClassName, lpszWindowName);
        if (hWndMe)
            SetForegroundWindow(hWndMe);
        return TRUE;
    }

    /* If new semaphore was created, return FALSE. */
    return FALSE;
}

The Effects of Multiple Threads on Message Routing

Windows uses two methods to route messages to a window procedure: (1) posting messages to a first-in, first-out queue called a message queue, which is a system-defined memory object that temporarily stores messages; and (2) sending messages directly to a window procedure. Messages posted to a message queue are called queued messages. They are primarily the result of user input via the mouse or keyboard, such as WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR messages. Other queued messages include the timer, paint, and quit messages: WM_TIMER, WM_PAINT, and WM_QUIT. Most other messages are sent directly to a window procedure and are called, surprisingly enough, nonqueued messages.

Queued Messages

Windows NT maintains a single system-message queue and any number of thread-message queues, one for each thread. Whenever the user moves the mouse, clicks the mouse buttons, or types at the keyboard, the device driver for the mouse or keyboard converts the input into messages and places them in the system-message queue. Windows removes the messages, one at a time, from the system-message queue, examines them to determine the destination window, and then posts them to the message queue of the thread that created the destination window. A thread's message queue receives all mouse and keyboard messages for the windows created by the thread. The thread removes messages from its queue and directs Windows NT to send them to the appropriate window procedure for processing. The system posts a message to a thread's message queue by filling an MSG structure and then copying it to the message queue. Information in the MSG structure includes the handle of the window for which the message is intended, the message identifier, the two message parameters, the message time, and the mouse-cursor position. A thread can post a message to its own message queue or to the queue of another thread by using the PostMessage or PostThreadMessage function.

Nonqueued Messages

Nonqueued messages are sent immediately to the destination window procedure, bypassing the system-message queue and thread-message queue. Windows typically sends nonqueued messages to notify a window of events that affect it. For example, when the user activates a new application window, Windows sends the window a series of messages, including WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR. These messages notify the window that it has been activated, that keyboard input is being directed to the window, and that the mouse cursor has been moved within the borders of the window. Certain Windows function calls can prompt nonqueued messages. For example, Windows sends the WM_WINDOWPOSCHANGED message after an application uses the SetWindowPos function to move a window.

Window Focus and Activation Considerations

The addition of multiple windows owned by multiple threads in the system has brought to the forefront some changes in the concept of focus and activation in the system. These changes affect all applications regardless of whether the application is single- or multithreaded. Remember that each application runs in its own process and when that new process is created, a thread is created for that process. Each thread has its own input state that is not shared, by default, with other threads. As a result, when this new process creates its first window, that window will belong to a different thread from any other window in the system. This "separate thread" concept rears its ugly head if your application tries to set the input focus to another application. Because the windows were created in different threads, the call to SetFocus will fail. I will describe a way around this problem and, since we all know that ignorance is not bliss when programming for Windows, I will detail the three concepts of focus, activation, and foreground and background windows in Win32®.

ms810439.winthr_1(en-us,MSDN.10).gif

Figure 1. The focus window, MDI 3

The Focus Window

Windows NT posts keyboard messages to the message queue of the thread that created the window with the keyboard focus. The keyboard focus is a temporary property of a window. Windows shares the keyboard among all windows on the display by shifting the keyboard focus, at the user's direction, from one window to another. The window that has the keyboard focus receives (from the message queue of the thread that created it) all keyboard messages until the focus changes to a different window. A thread can call the GetFocus function to determine which of its windows (if any) currently has the keyboard focus. A thread can give the keyboard focus to one of its windows by calling the SetFocus function. When the keyboard focus changes from one window to another, the system sends a WM_KILLFOCUS message to the window that has lost the focus, and then sends a WM_SETFOCUS message to the window that has gained the focus. In Figure 1, the focus window is the window entitled "MDI 3."

The Active Window

The concept of an active window is probably old hat to many of you reading this article, but I am including the information in the spirit of being complete. The concept of keyboard focus is related to that of the active window. The active window is the top-level window the user is currently working with. The window with the keyboard focus is either the active window itself or a child window of the active window. So that the user can easily identify the active window, the system places it at the top of the z-order and highlights its title bar (if it has one) and border. The user can activate a top-level window by clicking it, by selecting it using the ALT+TAB or ALT+ESC key combination, or by selecting it from the Task List. A thread can activate a top-level window by using the SetActiveWindow function. It can determine whether a top-level window it created is active by using the GetActiveWindow function.

In Figure 1, the active window is the window entitled "MDI Demonstration." You might think that the "MDI 3" window was the active one, but remember, the active window is always a top-level window and the focus window can be either the active window or a child of the active window. Thus, in this case, the focus window is "MDI 3" and the active window is its parent, "MDI Demonstration."

When one window is deactivated and another activated, Windows sends the WM_ACTIVATE message first to the window being deactivated and then to the window being activated. The low-order word of the wParam parameter is zero if the window is being deactivated and nonzero if it is being activated. When the default window procedure receives the WM_ACTIVATE message, it sets the keyboard focus to the active window.

Let's say you try to set the focus to a window that resides in another thread by using SetFocus without calling AttachThreadInput. The call will fail, right? Right. But then what happens to the focus and activation states? I'm glad you asked.

One window can have the focus while another window is active

A situation can arise in Windows NT when one window has the input focus while another window is active. This mainly occurs when a console window attempts to set the focus via SetFocus to a GUI window, and the call to SetFocus fails. When this happens, the console window has lost the keyboard focus, but still remains the active window. The window that gains the focus is dependent on which window in the z-order was next to get the focus. When this happens, the user cannot figure out why the proper window is no longer getting the keyboard input he or she is typing in. Luckily, the person running the software will usually have the presence of mind to click the mouse over the window he or she wants to have the input focus, and the focus will then be set to the correct window. To avoid this problem, call SetForegroundWindow, rather than SetFocus, to reset the focus between unattached windows created in different threads.

Foreground and Background Windows

In Windows NT, each process can have multiple threads of execution, and as we saw before, each thread can create windows. The thread that created the window with which the user is currently working is called the foreground thread, and the window is called the foreground window. All other threads are background threads, and the windows created by background threads are called—you guessed it—background windows.

The foreground thread gets more CPU time

Each thread has a priority level that determines the amount of CPU time the thread receives. Although an application can set the priority level of its threads, normally the foreground thread has a slightly higher priority level than the background threads. Because it has a higher priority, the foreground thread receives more CPU time than the background threads. The foreground thread has a normal base priority of 9; a background thread has a normal base priority of 7. The user sets the foreground window by clicking a window or by using the ALT+TAB or ALT+ESC key combination. An application sets the foreground window by using the SetForegroundWindow function. If the new foreground window is a top-level window, Windows activates it; otherwise, Windows activates the associated top-level window. An application retrieves the handle of the foreground window by using the GetForegroundWindow function. One interesting point to note here is that the user can change this behavior somewhat by opening the Tasking dialog box in the System applet found in the Control Panel. This dialog box allows the user to set the relative foreground/background responsiveness to one of the following:

  • Best Foreground Application Response Time
  • Foreground Application More Responsive than Background
  • Foreground and Background Applications Equally Responsive

Don't starve your threads!

It is important to remember that the priority boost the foreground thread receives from the system can leave you open to starving your other threads or other threads in the system if you are not careful. Some developers incorrectly assume that because Windows NT has multiple threads of execution and asynchronous input queues, they no longer have to worry about their application taking over the system for long periods of time or starving other application's threads in the system. That is not true. Windows NT will not hang—you will still be able to task-switch away from the offending task, and the scheduler will time-slice the foreground thread out every so often; however, "hogging" the input queue will still cause the system to be unresponsive and is considered to be bad programming practice. If you have an unresponsive situation in your application, remember to call PeekMessage or another function that will give up the CPU time to the rest of the system. For instance, let's say that your application needs to populate a list box containing the phone numbers of all persons with the last name Jones. Querying for this will most likely take a very long time, but your application really cannot do anything else because it needs to list those names before it can go on. In this case, if you were to generate this list in response to a sent message, you would need to ensure that you call PeekMessage during your generation so you don't starve the rest of the threads in your application.

Bringing a Window to the Foreground

If you have an application that currently uses SetFocus to bring a window created by another thread to the foreground, and you are porting this application to Win32, you have already noticed that SetFocus no longer sets the focus and returns NULL. Luckily, there is no reason to panic; you can still set the focus by using SetForegroundWindow instead. In Figure 1, if you want to programmatically switch the focus from the "MDI Demonstration" window to the Notepad window, you would use SetForegroundWindow to do so rather than SetFocus because the windows were created in different threads.

By default, each thread has an independent input state (its own active window, its own focus window, and so forth). SetActiveWindow always logically sets a thread's active window state. To force a window to the foreground, however, use SetForegroundWindow. SetForegroundWindow activates a window and forces the window into the foreground. SetActiveWindow brings the active window into the foreground only if the thread is the foreground thread.

Applications can call AttachThreadInput to allow a set of threads to share the same input state. By sharing input state, the threads share their concept of the active window. By doing this, one thread can always activate another thread's window. This function is also useful for sharing focus state, mouse capture state, keyboard state, and window z-order state among windows created by different threads whose input state is shared.

Avoiding Message Deadlocking

SendMessage works differently under Windows NT than it does under Windows version 3.1. Under Windows version 3.1, SendMessage calls the window procedure corresponding to the window handle that it is passed. Under Windows NT, only the thread that created a window may process the window's messages. A thread that calls the SendMessage function to send a message to another thread cannot continue executing until the window procedure that receives the message returns. If the receiving thread yields control while processing the message, the sending thread cannot continue executing because it is waiting for SendMessage to return. This situation is called a deadlock. So, if a thread sends a message via SendMessage to a window that was created by a different thread, the first thread must wait for the second thread to be in a receiving state and handle the message for it.

ms810439.winthr_2(en-us,MSDN.10).gif

Figure 2. A deadlock condition

In the previous illustration, Mr. Thread-one cannot respond to Mr. Thread-two because Mr. Thread-two has not given Mr. Thread-one the batteries he needs. And Mr. Thread-two cannot send the batteries back to Mr. Thread-one because Mr. Thread-two doesn't know what size batteries to send.

(Please note: Any resemblance to any company's customer services is purely coincidental.)

The previous deadlock condition may be prevented by using SendNotifyMessage. SendNotifyMessage behaves much like SendMessage except that it returns immediately. In fact, if you call SendNotifyMessage to communicate between two windows created in the same thread, SendNotifyMessage acts exactly like SendMessage. This may be an advantage if it is not important that the sent message be completed before the sending thread continues. Note that because SendNotifyMessage is asynchronous, pointers to any local variables must not be passed along because they will no longer be valid by the time the receiving thread attempts to look at them. This would result in a general protection (GP) violation when the receiving thread accesses the pointer.

The following functions can be called by the receiving thread to yield control:

  • DialogBox
  • DialogBoxIndirect
  • DialogBoxIndirectParam
  • DialogBoxParam
  • GetMessage
  • MessageBox
  • PeekMessage

As mentioned above, if a window procedure yields control when processing a message sent by another thread, a message deadlock may result. To determine whether a message received was sent by another thread, call the InSendMessage function. If it returns TRUE, the window procedure must call the ReplyMessage function. By calling this ReplyMessage, the window procedure that receives the message allows the thread that called SendMessage to continue to run as though the thread receiving the message had returned control. The thread that calls the ReplyMessage function also continues to run. If the message was not sent through the SendMessage function or if the message was sent by the same thread, ReplyMessage has no effect. This is shown in the following example.

case WM_USER + 105:
    // If this was sent by another thread, call ReplyMessage to
    // avoid a deadlock when calling DialogBox.
    if (InSendMessage())
        ReplyMessage(TRUE);

    DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc);
    break;

New Functions

Windows NT includes some new functions that are designed to manage the changes in functionality and window management when including multiple threads in the user interface. These new functions will help you get around deadlocking problems and will allow you to share different information, such as key state and focus, among threads in the same process. Table 1 lists these functions and indicates whether each is supported in Win32s™. The sections following the table give descriptions of each new function, including any known problems. I have not included the new functions that don't involve window management or basic thread manipulation. For detailed information about general thread management, please refer to the "Multithreading for Rookies" technical article by Ruediger Asche in the Development Library.

Table 1. Win32s Support for New Win32 Functions Used for Window Management or Thread Manipulation

FunctionWin32s Support
SendNotifyMessageNo
PostThreadMessageYes
SendMessageTimeoutNo
SendMessageCallbackNo
SendAsyncProcNo
EnumThreadWindowsYes
EnumThreadWndProcYes
GetWindowThreadProcessIdYes
AttachThreadInputNo

SendNotifyMessage

As we just saw, the SendNotifyMessage function sends the specified message to the given window. This function is identical to SendMessage if it is used to send a message to a window that was created in the current thread—it sends the message and does not return until the window procedure has processed the message. In the case of an interthread call, the send does not wait for a reply from the receiver; it simply returns a Boolean value indicating success or failure.

PostThreadMessage

The PostThreadMessage function places (posts) a message in the message queue of the given thread and then returns without waiting for the thread to process the message.

This function replaces the PostAppMessage function. In fact, in Win32, PostAppMessage is prototyped to be PostThreadMessage. New Win32 applications should use PostThreadMessage instead. The thread to which the message is posted retrieves the message by calling the GetMessage or PeekMessage function. The hwnd member of the returned MSG structure is NULL.

SendMessageTimeout

The SendMessageTimeout function synchronously sends the specified message to the given window or windows. The function sends the message to the given window and does not return until the window procedure has processed the message or the specified time-out period has elapsed. If the window receiving the message does not belong to the same message queue as the current thread, the appropriate thread to receive the message is awakened and this function waits for a reply. If the thread does not respond or if the time-out value is exceeded, this function fails. If the window receiving the message belongs to the same message queue as the current thread, the time-out is ignored and the functionality is the same as it would be if SendMessage were called instead.

In the following example, if the child window procedure (ChildWindowProc) were to execute the SendMessageTimeout and the parent window procedure (ParentWindowProc) called SendMessage to send a message to its child, ChildWindowProc would still be able to process the message because the SMTO_NORMAL flag is specified. The SMTO_NORMAL flag indicates that the calling thread can process other requests while waiting for SendMessageTimeout to return.

ChildWindowProc(HWND hWnd, UINT msg, UINT wParam, LONG lParam)
{
      ...
      case <xxx>:
         ...
         SendMessageTimeout( hWndParent,   // Window handle
                              WM_USER+100, // Message to send
                                 wParam,   // First message parameter
                                 lParam,   // Second message parameter
                            SMTO_NORMAL,   // Flag *
                                    100,   // Time-out (in ms)
                                   &ret ); // Return value
      ...
         break;

      case WM_USER+101:
      <time-consuming procedure>
         break;
}

ParentWindowProc(HWND hWnd, UINT msg, UINT wParam, LONG lParam)
{
      ...
      case WM_USER+100:
         ...
         SendMessage( hWndChild,    // Window handle
                      WM_USER+101,  // Message to send
                         wParam,    // First message parameter
                         lParam );  // Second message parameter
         AFunction();
         ...
         break;
}

If the processing of the message sent by SendMessageTimeout is interrupted, the countdown to the time-out will be suspended. Once a message or another system event is received and processed, the time-out will be restarted. In the above example, the receipt of the message WM_USER+101 by ChildWindowProc will cause the time-out to restart after the WM_USER+101 is processed.

In the above example, if the function executed by ParentWindowProc takes longer than 100 milliseconds to return and awaken the thread that created the child window, the call to SendMessageTimeout will time-out and return. This means that any value that was to be returned to ChildWindowProc will not be returned.

SendMessageCallback

The SendMessageCallback function sends the specified message to the given window or windows. The function sends the message to the given window and returns immediately. After the window procedure processes the message, the system calls the specified callback function, passing the result of the message processing and an application-defined value to the callback function.

SendAsyncProc

A SendAsyncProc function is an application-defined callback function that the operating system calls when the SendMessageCallback function is called. The system passes the message to the callback function after passing the message to the destination window procedure. A value of type SENDASYNCPROC is a pointer to such a function. SendAsyncProc is a placeholder for an application-defined function name. A SendAsyncProc function is installed for calling by passing a SENDASYNCPROC pointer to the SendMessageCallback function. This callback function does not return a value.

EnumThreadWindows

The EnumThreadWindows function enumerates all windows associated with a thread by passing the handle of each window, in turn, to an application-defined callback function. EnumThreadWindows continues until the last window is enumerated or the callback function returns FALSE.

Note that the EnumTaskWindows function is now obsolete. It has been replaced by the EnumThreadWindows function. To maintain compatibility for older applications, EnumTaskWindows has been replaced with a macro that calls EnumThreadWindows. Older applications may continue to call EnumTaskWindows, but new applications should use EnumThreadWindows.

EnumThreadWndProc

The EnumThreadWndProc function is an application-defined callback function that receives the window handles associated with a thread as a result of a call to the EnumThreadWindows function. The callback function can carry out any desired task. An application must register this callback function by passing its address to the EnumThreadWindows function. EnumThreadWndProc is a placeholder for the application-defined function name. To continue enumeration, the callback function must return TRUE; to stop enumeration, it must return FALSE.

GetWindowThreadProcessId

The GetWindowThreadProcessId function retrieves the identifier of the thread that created the given window and, optionally, the identifier of the process that created the window. The return value is the identifier of the thread that created the window. The GetWindowTask function is now obsolete. It has been replaced by the GetWindowThreadProcessId function. To maintain compatibility for older applications, GetWindowTask has been replaced with a macro that calls GetWindowThreadProcessId. Older applications may continue to call GetWindowTask, but new applications should use GetWindowThreadProcessId. The macro that maps GetWindowTask to GetWindowThreadProcessId is:

#define GetWindowTask(hwnd) ((HANDLE) GetWindowThreadProcessId(hwnd, NULL))

AttachThreadInput

The AttachThreadInput function allows a thread to synchronize its input processing with another thread. Normally, windows created in different threads process input independently of each other, and they are not synchronized with the input processing of other threads. By using this function, a thread can attach its input processing to another thread. This allows a thread to share its input states and, for example, to call the SetFocus function to set the keyboard focus to a window of a different thread. In Windows 3.1, this capability is not possible.

Creating a window can force an implicit AttachThreadInput when the parent windows of the window being created in the second thread were created in the first thread. When windows are created or set with a parent-child relationship between threads, the mouse and keyboard queues are attached.

Because this function synchronizes input processing, you cannot use AttachThreadInput to attach to the Task Manager. The Task Manager must remain unsynchronized at all times in order to allow the user to bring an application to the foreground and to kill an application. In addition to the Task Manager, shell and system threads cannot be attached.

As mentioned above, a call to AttachThreadInput allows, among other things, the sharing of key state information between two threads. It is important to note that the key state, which can be ascertained by calls to GetKeyState or GetKeyboardState, is reset after a call to AttachThreadInput. It is also important to note that AttachThreadInput does not take over the input queue from another thread. This means that the two threads still have separate input, and a call to AttachThreadInput will not force all window messages to filter into the same queue. If you need to pass all window messages from one message loop to another message loop, you will need to pass those messages on manually by sending the message yourself.

Known Problems

Currently, if you start an application that calls AttachThreadInput to a thread in another process and reset the keyboard state via SetKeyboardState after detaching the threads, the call to SetKeyboardState will return TRUE, indicating success, but the key state is not successfully set. If the thread is in the same process, however, the call to SetKeyboardState does succeed and works as expected. This occurs because, when one thread is attached to another thread, the system creates a temporary message queue to keep a copy of the current key state information of the queue to which you are attaching. If you then set the key state, only the temporary queue key state is updated and the function call succeeds. Once you detach input processing, the temporary queue is removed and the key state change information is lost—it reverts to what it was before the attach. To work around this problem, you can either stay attached or use hooks. Microsoft is currently looking into a fix for this problem.

The Final Word

I have created Table 2 as a quick reference to summarize the different methods of sending messages between threads. The first column lists the Win32 function, the second column lists the equivalent (or closely equivalent) Windows 3.1 function, and the third column lists the differences between the Win32 function and the Windows 3.1 function. In cases where no equivalent is applicable, the item is listed as such. In all cases, if you are writing a new application for Win32, you should use the new Win32 function rather than the Windows 3.1 function.

Table 2. Win32 Messaging Functions and the Windows 3.1 Equivalents

Win32 FunctionWindows 3.1 EquivalentDifferences/Suggestions
AttachThreadInputNot applicableBecause all windows are created in the same thread under Windows 3.1, input is inherently synchronized, so this function does not have a Windows 3.1 equivalent.
EnumThreadWindowsEnumTaskWindowsTasks no longer exist under Win32. Use EnumThreadWindows for Win32 applications. The call will be mapped to EnumTaskWindows if your application is running under Windows 3.1.
GetWindowThreadProcessIdGetWindowTaskTasks no longer exist under Win32. Use GetWindowThreadProcessId for Win32 and Win32s applications. The call will be mapped to GetWindowTask if your application is running under Windows 3.1.
PostMessagePostMessageNo changes necessary.
PostThreadMessagePostAppMessageUse PostThreadMessage for Win32 and Win32s applications. The call will be mapped to PostAppMessage if your application is running under Windows 3.1.
SendMessageSendMessageNo changes necessary.
SendMessageCallbackPostMessage with the receiving window procedure posting back a reply.There is really no exact equivalent to SendMessageCallback in Windows 3.1. SendMessageCallback allows an application to asynchronously send a message and call a callback function when the message is fully processed.
SendMessageTimeoutSendMessage There is no exact equivalent to SendMessageTimeout in Windows 3.1. SendMessageTimeout allows an application to synchronously send a message and also to specify a time-out period for the message to be fully processed. This allows the application to be a bit more responsive than if it had to rely on SendMessage.
SendNotifyMessageSendMessage for windows in the same thread.

PostMessage for inter-thread messaging.

There is no exact equivalent to SendNotifyMessage in Windows 3.1. SendNotifyMessage allows the application to send a message synchronously if the receiving and sending window are in the same thread and to send the message asynchronously if the windows are in different threads. This call is used to avoid a deadlock situation.

Now that you understand the implications of threads on window management, you can make your decision on where threads are appropriate to your application. Remember to weigh the benefits versus the costs of the threads, and remember, too, that adding threads can add complexity to your application. You will find that there may be areas in your application that will really benefit from processing in a thread, while other places may benefit from the use of synchronization objects instead. You may even find that, in looking for appropriate places to use threads, you can speed up execution of your application or make it more responsive by doing a bit of tidying up in your code. (I was going to say "tying up loose threads" instead of "tidying up"—aren't you glad I showed some restraint?)

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.