MSDN Magazine > Issues and Downloads > 2000 > July >  Win32 Q&A: Handy Features in Windows, and Inter...
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
MSDN Magazine

Handy Features in Windows, and Interlocked Functions
Jeffrey Richter

Code for this article: Win320700.exe (79KB)

E
very once in a while I discover something about Windows that I never knew before. Just recently, I learned of two features that I'd like to share with you. First, in Windows® 2000, the contents of a standard message box may be copied to the clipboard and pasted as text. To see for yourself, open Notepad and enter some text. Then, without saving the text, choose the File | Exit menu item. This causes Notepad to display the message box shown in Figure 1.
Figure 1 Message Box in Notepad
Figure 1 Message Box in Notepad

      With this message box activated, press Ctrl+C to copy the contents of the message box to the clipboard. Now, activate any application that allows you to paste text (such as Microsoft® Word, WordPad, or even Notepad) and paste the clipboard's contents into the document. The text should appear as follows:



â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"
Notepad
â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"
The text in the Untitled file has changed.

Do you want to save the changes?
â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"
Yes   No   Cancel   
â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"â€"
      I assume that Microsoft added this feature to the standard message box so that customers can easily report error messages to technical support. This feature can also be quite handy if you use some type of automated software testing application to help you verify your code as you develop it. After all, it's much easier to compare text than bitmap images.
Q
Our team is working on interfacing patient monitoring systems to a central PC. At certain times during the application's processing, we want to prevent the user from switching to another application. Specifically, we'd like to intercept the special Ctrl+Esc, Alt+Tab, and Alt+Esc key combinations while our patient monitoring program is performing certain tasks. Can you offer any suggestions or hints to help us solve this?
Pankaj Duhan
A
In Windows, there are usually only a few ways to intercept some type of system event. The mechanism that I always consider first is a hook because hooks are fully supported by Microsoft and infrastructure exists within the system specifically so that certain events can be intercepted. If a hook won't work, I'd think about API hooking. Microsoft does not officially support API hooking, so I always try to avoid it if possible.
      Fortunately, this particular problem can be solved easily by setting a hook. Starting with Windows NT® 4.0 Service Pack 3, Microsoft added a low-level keyboard hook, WH_ KEYBOARD_LL, to the system. (This was the second new feature in Windows that I stumbled upon.) The normal, high-level keyboard hook, WH_KEYBOARD, intercepted keystrokes as they were removed from a thread's message queue. The WH_KEYBOARD hook is great for most applications. However, certain keystrokes are never directed to a thread's message queue. The Ctrl+Esc, Alt+Tab, and Alt+Esc key combinations are perfect examples. These keystrokes are handled internally by the system's raw input thread. Since application threads never receive messages for these keystrokes, there is no way that an application can intercept them and prevent the normal processing. This behavior is by design and ensures that a user can always switch to another application's window even if an application's thread enters an infinite loop or hangs.
      However, there is a small class of applications that really has a valid need to intercept these keystrokes. To meet the needs of these applications, Microsoft introduced the WH_KEYBOARD_LL hook. This low-level hook is notified of keystrokes just after the user enters them and before the system gets a chance to process them. But this hook has a serious drawback: the thread processing the hook filter function could enter an infinite loop or hang. If this happens, then the system will no longer process keystrokes properly and the user will become incredibly frustrated.
      To alleviate this situation, Microsoft places a time limit on low-level hooks. When the system sends a notification to a low-level keyboard hook's filter function, the system gives the function a fixed amount of time to execute. If the function does not return in the allotted time, the system ignores the hook filter function and processes the keystroke normally. The amount of time that is allowed (in milliseconds) is set via the LowLevelHooksTimeout value under the HKEY_CURRENT_USER\Control Panel\Desktop registry subkey.
      The DisableLowLevelKeys.cpp file shown in Figure 2 demonstrates how to install a WH_KEYBOARD_LL hook to intercept and discard the low-level Ctrl+Esc, Alt+Tab, and Alt+Esc key combinations. When you run the application, it installs the hook and displays the message box shown in Figure 3.
Figure 3 Disabling Low-level Keys
Figure 3 Disabling Low-level Keys

      While the message box is displayed, you will not be able to switch to another application using the usual keystroke combinations. When you press the OK button, the message box is closed and the hook is removed, allowing the low-level keys to be processed by the system as usual.

Q
I'm writing a multithreaded application that contains a LONG value that is going to be accessed by most of the threads. I am concerned about the performance overhead of synchronizing access to this variable. I am familiar with the interlocked functions that allow very fast access to a LONG, but these functions only allow increments, decrements, exchanges, or simple test and set operations. What I need is an interlocked function that allows me to increment the LONG, but only if its current value is greater than 0. I know that I can solve this problem with a critical section, but that seems to be overkill and I'm concerned about the critical section's additional overhead. Can you offer any suggestions?
P G Patrudu
A
I am surprised how often I receive questions similar to this one. Many people seem to think that Microsoft implements the interlocked functions and places them into the operating system. Working with this assumption, people often wonder why Microsoft doesn't create a richer set of interlocked functions that can be used in a wider range of scenarios.
      The problem is that Microsoft doesn't actually implement the interlocked functions; the CPU hardware does. For example, the InterlockedExchangeAdd function adds a value to a LONG. If you use the debugger to step through this function, you'll see that the function is very short. It just loads the ECX and EAX CPU registers with the two values and then executes the XADD CPU instruction. The XADD instruction does the addition. It differs from the more common ADD instruction because XADD is designed by Intel to be multiprocessor-safe.
      Similarly, the InterlockedIncrement and InterlockedDecrement functions also execute the XADD instruction with hardcoded values of +1 and -1, respectively. The last two interlocked functions, InterlockedExchange and InterlockedCompareExchange, both execute the CMPXCHG CPU instruction internally. Like XADD, CMPXCHG is also designed by Intel to be multiprocessor-safe.
      Intel builds intelligence into the CPU so all these instructions work correctly in a multiprocessor environment. Specifically, if one processor changes the value of a LONG, the other processors are made aware of the change so that they don't access stale data in their CPU caches. Microsoft can't just add new interlocked functions to the system because there must be CPU support for them.
      OK, so this explains why Microsoft doesn't add some additional, richer interlocked functions to the system. But it doesn't answer the reader's question. Maybe if more was known about interlocked functions, one could be synthesized that performs exactly the way you want it to without resorting to critical sections.
      Let's start off by examining the InterlockedExchange function more closely. Here is the function's prototype:


LONG InterlockedExchange(IN OUT PLONG plTarget, IN LONG lValue);
If you disassemble the function, you'll notice some interesting code:


77E838A9 MOV          ecx,dword ptr [esp+4] ;Load ECX
                                            ;with plTarget
77E838AD MOV          edx,dword ptr [esp+8] ;Load EDX
                                            ;with lValue
77E838B1 MOV          eax,dword ptr [ecx]   ;Load EAX
                                            ;with current 
                                            ;value in plTarget
77E838B3 LOCK CMPXCHG dword ptr [ecx],edx   ;if (EAX == [ECX]), 
                                            ;[ECX] = EDX
77E838B7 JNE          77E838B3              ;if (EAX != [ECX]), 
                                            ;repeat last
                                            ;instruction 
77E838B9 RET          8                     ;return to caller
      This function is different from all the other interlocked functions because it is the only one containing a loop. The CMPXCHG instruction compares the value in plTarget with the value in plTarget. If they are the same, then the value in plTarget is set to the value passed in lValue, and then the function returns. Certainly, you'd expect the value in plTarget to equal itself at all times, wouldn't you? Well, the values are not equal if another processor updates the value in plTarget while the registers are being loaded. If the values are not equal, then the JNE instruction causes the CMPXCHG instruction to execute repeatedly until the exchange is successfully performed.
      After examining the InterlockedExchange function, I gave some thought to how this approach could be used to synthesize a richer, more powerful interlocked function. The secret is to attempt to change the variable in a loop until it works. Basically, you want to execute code similar to the following:


do {
   // Save the starting value of the LONG in a temporary variable
   LONG lStartValue = g_lOriginalValue;

   // Avoid accessing g_lOriginalValue since another thread
   // could access it behind your back

   // Create a variable to use as the desired value
   LONG lNewValue = lStartValue;

   // Perform rich calculations to lNewValue

   // Update the value in *plTarget with the desired new value
} while (InterlockedExchange(&g_lOriginalValue, lNewValue, 
         lStartValue) != lStartValue);

// If you get here, the update was successful
Specifically, the code required to address your question looks like the following:


LONG InterlockedIncrementIfGreaterThanZero(PLONG plTarget) {
   do {
      LONG lStartValue = *plTarget;
      LONG lNewValue = lStartValue + ((lStartValue > 0) ? 1 : 0);
   } while (InterlockedCompareExchange(plTarget, 
                                       lNewValue, lStartValue) 
            != lStartValue);
   return(lStartValue);
}
      Now let me explain exactly what is going on here. Upon entering the loop, lStartValue is set to the value in *plTarget. From this point on, the value in plTarget cannot be accessed because other threads may alter this value without your thread knowing about it. The code must also remember this initial value of *plTarget (as you'll see later) so that lStartValue cannot be altered.
      The lNewValue variable is initialized to whatever value you want to change *plTarget to. In the previous code, I set lNewValue to lStartValue, and then I add 1 to lNewValue if lStartValue is greater than 0 (since this is what was asked for in the question). Obviously, you can use any algorithm that you'd like here to determine the new value.
      Now comes the while test. Here I call InterlockedCompareExchange. If the value in *plTarget equals the value in lStartValue, then the value in *plTarget is set to the value in lNewValue. In most applications, it is extremely unlikely that another thread will alter the value in *plTarget while the interior of your while loop executes. So the call to InterlockedCompareExchange will replace the value in *plTarget and will return the value initially in *plTarget. Since the original value in *plTarget matches the value in lStartValue, the loop terminates and the original value in *plTarget is returned to the caller.
      In those rare cases where another thread alters the value in *plTarget while the code inside the loop executes, the InterlockedCompareExchange function does not replace the value in *plTarget and InterlockedCompareExchange returns the latest value in *plTarget. At this point the value does not match the initial value in *plTarget and the loop repeats, attempting to perform the whole operation again since the other thread has invalidated your value in lNewValue.

Jeffrey Richter is the author of Programming Applications for Microsoft Windows, Fourth Edition (Microsoft Press, 1999). He is a consultant specializing in Windows application programming/design. Reach him at http://www.JeffreyRichter.com.

From the July 2000 issue of MSDN Magazine.

Page view tracker