Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for

Advanced Search
MSDN Home > MSJ > March 1997
March 1997

Code for this article: Win320397.exe (12KB)
Jeffrey Richter wrote Advanced Windows, Third Edition (Microsoft Press, 1997) and Windows 95: A Developer's Guide (M&T Books, 1995). Jeff is a consultant and teaches Win32-based programming seminars. He can be reached at

Q I'm doing a lot of user-interface work with my application and I'm trying to develop a utility that will help me. Basically, I want the utility to periodically update a display that tells me which window in the system is the active window, which window has focus, and which window (if any) is capturing the mouse. I tried to implement this utility, but every time I call GetActiveWindow, GetFocus, or GetCapture, these functions return either the handle of a window created by the calling thread or NULL. They never seem to return a window that was created by another application. Why not?
Dana Armstrong
Seattle, Washington

A You are being victimized by a Windows NT® and Windows® 95 feature called local-input state. (Local-input state isolates the input for each thread. An entire chapter of my book is devoted to the gory details.) To make these operating systems more robust, every thread is given its own local-input state. Each thread thinks that either it is receiving hardware messages or no thread in the system is receiving these messages. So, when your thread calls GetActiveWindow, the function returns a valid window handle only if one of the windows that your thread created is active. If another thread has created the now-active window, your thread's call to GetActiveWindow returns NULL. The same is true for the GetFocus and GetCapture functions.
However, the Win32
® API contains a new function called AttachThreadInput that allows you to connect the local-input states of two threads. Here is AttachThreadInput's prototype:

BOOL AttachThreadInput(DWORD dwThreadIdFrom, 
                       DWORD dwThreadIdTo, 
                       BOOL fAttach);
The first and second parameters indicate the IDs of the two threads whose local-input states you want to attach or detach. If you pass TRUE for the fAttach parameter, the two threads will share a single local-input state. If you pass FALSE for this parameter, the threads will stop sharing a single local-input state.
When two threads have their local-input states attached, they will think that either a window created by either thread is active or no window in the system is active. So, if one of these threads places a call to GetActiveWindow, the return value will be a handle identifying a window created by either of the two threads or NULL. If you attached all threads together so they all share a single local-input state, then a call to GetActiveWindow will always return a valid window handle regardless of which thread calls the function.
To make this utility application work, you must first determine which window the user is interacting with. This window is called the foreground window. Then you must determine the ID of the thread that created this window. This thread is called the foreground thread. With this information, you can attach the local-input state of your thread with the local-input state of the foreground thread and then call the GetActiveWindow, GetFocus, and GetCapture functions.
Figure 2 LISWatch at work
Figure 2 LISWatch at work
Figure 1 shows the code for a utility called LISWatch that does what you are trying to do. When you run LISWatch, it displays the dialog box shown in Figure 2. When this dialog box receives a WM_INITDIALOG message, it calls SetTimer to set a timer that fires once every second. When the WM_ TIMER message is received, the contents of the dialog box update to reflect which window is active, which window has focus, and which window has captured the mouse. At the bottom of the dialog box you also see which window is the foreground window. You should experiment with the utility by simply clicking on windows created by different applications. As you move around, you'll notice that LISWatch is able to accurately report the proper window information regardless of the threads that created the various windows.
Now, let's discuss how LISWatch works. The interesting part of the utility executes when a WM_TIMER message is received. Figure 3 shows the LISWatch_OnTimer function. As I explain this function, assume that the global variable, g_dwThreadIdAttachTo, is set to zero. I'll explain the purpose of this variable later.
The first thing that LISWatch_OnTimer does is call GetForegroundWindow, which returns the handle of the window the user is interacting with. Note that GetForegroundWindow always returns a valid window handle regardless of which thread in the system created this window. This window handle is then passed to GetWindowThreadProcessId:

DWORD GetWindowThreadProcessId(HWND hWnd, 
                               LPDWORD pdwProcessId); 
This function returns the ID of the thread that created the specified window. Since I pass the handle of the foreground window for the first parameter, GetWindowThreadProcessId returns the ID of the foreground thread. If you pass the address of a DWORD to GetWindowThreadProcessId's pdwProcessId parameter, the function also fills in the ID of the process that owns this thread. LISWatch doesn't need the process ID information and therefore passes NULL for the second parameter.
Next, LISWatch attaches its own thread's local-input state to the local-input state of the foreground thread by calling AttachThreadInput. After the local-input states are attached, LISWatch's thread can call GetFocus, GetActiveWindow, and GetCapture—all of which return valid window handles. The helper function, CalcWndText, constructs a string containing each window's class name and window text. Each window's string is then updated in LISWatch's dialog box. Finally, just before LISWatch_OnTimer returns, it again calls AttachThreadInput, but this time it passes FALSE for the last parameter so that the two threads' local-input states are detached from one another.
This explains the basics of LISWatch. However, I added another feature to LISWatch. When you start LISWatch, it monitors window activation changes that occur anywhere in the system. This is what "System-wide" means at the top of the dialog box. However, LISWatch also allows you to restrict it to watching a single thread's local-input state changes. With this feature enabled, LISWatch is able to report exactly what a single thread sees as its local-input state.
To have LISWatch monitor a single thread's local-input state, all you have to do is click the left mouse button on LISWatch's window, drag the mouse cursor over a window created by another thread, and then release the mouse button. After you release the mouse button, LISWatch sets the global g_dwThreadIdAttachTo variable to the ID of the selected thread. This thread ID is also shown at the top of LISWatch's dialog box. When this global variable is not zero, LISWatch_OnTimer alters its behavior slightly. Instead of attaching its local-input state to that of the foreground thread, LISWatch attaches itself to the selected thread. This way, its calls to GetActiveWindow, GetFocus, and GetCapture reflect what the selected thread's local-input state sees.
Figure 4 LISWatch watching calculator
Figure 4 LISWatch watching calculator
Try an experiment. Run Calculator and use LISWatch to select its window. When you activate Calculator's window, LISWatch updates its display as shown in Figure 4. On my system, Calculator's thread ID is 0x0000036a. LISWatch is currently set up to monitor this one thread's local-input state changes. If I click on any of Calculator's radio buttons or check boxes, LISWatch can show you this because all of these windows were created by thread 0x0000036a.
Figure 5 LISWatch watching Notepad
Figure 5 LISWatch watching Notepad
However, if you now activate a window created by another application (Notepad in my example), LISWatch shows the dialog box in Figure 5. Here, LISWatch shows the Calculator thread's local-input state reporting that no window in the system has focus, no window is active, and no window has captured the mouse. In fact, to really understand exactly how all this local-input state stuff works, you should run multiple instances of LISWatch and set up each instance so that it's monitoring the local-input states of a different thread. Then, start clicking on various windows to see what each thread's local-input state reflects.

Q I've got an application with exactly two threads: thread A handles all the windows messages and thread B (the main program thread) runs an application we've ported that doesn't really know it's running under Windows. Thread B sometimes wants to change the cursor to an hourglass figure during lengthy operations. It calls an internal function that basically calls SetCursor(LoadCursor(IDC_WAIT)), but the cursor doesn't change. But if you send a message to the message-handling thread to ask it to change the cursor, then the cursor does change.
I'm not concerned about the small amount of code required to fix this particular bug. I'm very concerned about the idea that one of the program threads can do things that the other cannot. What are the rules here? Is there anything else the main program thread has to ask the message-handling thread to do? If so, is there a workaround or am I going to wind up with a lot of code that passes these messages around from thread to thread?

Matthew Tebbs
Seattle, Washington

A In 16-bit Windows, it is possible for an application to hang the entire system. One of the big goals for both Windows NT and Windows 95 was to prohibit a single application from having so much control over the system and other running applications. Obviously, running each application in its own address space certainly helps solve this problem. But separate address spaces does not completely solve the problem. You see, in 16-bit Windows there is a single input queue where all mouse and keyboard events get posted. If a task stops pulling its hardware events from the system queue, the queue gets backed-up and other tasks can no longer receive input. Microsoft needed to solve this problem as well.
In Windows NT and Windows 95 there is still a single hardware input queue. However, applications cannot access this queue; only operating system code can access this queue. As hardware events are posted to this queue, the operating system code extracts the events and posts them to a thread's virtualized input queue. Each thread gets its very own virtualized input queue and this input queue is part of a thread's local-input state as discussed in my answer to the previous question. If a single thread stops responding to events in its queue it has no effect on any other threads in the system—this prevents a hung thread from adversely affecting any other thread's input.
When your non-UI thread, thread B, calls SetCursor(LoadCursor(IDC_WAIT)), it is telling the system to change the cursor whenever the mouse is over a window created by thread B. In other words, you are affecting thread B's local-input state but you are having no affect on thread A's local-input state and the window that thread A created. However, when thread B sends a message to thread A's window and then thread A calls SetCursor(LoadCursor(IDC_WAIT)), thread A is telling the system to change the cursor whenever the mouse cursor is over a window created by thread A. Since your window was created by thread A, the cursor is changed.
The easy way to solve this problem is to simply call AttachThreadInput to attach thread A's local-input state to thread B's local-input state. Once this is done, thread B's call to SetCursor will function as you originally expected.

Have a question about programming in Win32? Send your questions via email to Jeffrey Richter:

From the March 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.

© 2017 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies