• The Old New Thing

    Low-level hooks have thread affinity, so make sure you keep an eye on the thread

    • 4 Comments

    A customer was having a problem with their automated testing tool.

    We have an automation testing tool that, among other things, installs a low-level mouse hook. Sometimes, the hook takes too long to process an action, and it gets unhooked. We have a watchdog thread that tries to detect when this has happened, and in response, it kicks off a task on the thread pool to re-register the low-level hook. The call to register the low-level hook succeeds, but the hook apparently didn't get installed correctly because it never fires. What are we doing wrong?

    Recall that low-level hooks have thread affinity. This is spelled out in the documentation.

    This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

    So there are two mistakes here.

    First, the hook is installed from a thread pool task, which means that the hook is associated with the thread pool thread. One of the characteristics of the thread pool is that threads come and go based on demand. If there is no thread pool activity for a while, the thread pool will probably start trimming threads, and it it decides to get rid of the thread that installed the hook, and the hook disappears with it.

    The second mistake is that the hook is installed from a thread pool task. Sure, the hook registers successfully, but then when you return back to the thread pool, there's no guarantee that anybody on that thread is going to pump messages any time soon.

    Indeed, odds are that it won't.

    Tasks queued up on the thread pool tend not to be UI tasks, because, well, they're on the thread pool, not the UI thread. Therefore, there is no expectation that they will pump messages. Furthermore, if the thread goes idle, the thread pool is probably not going to pump messages; it's just going to put the thread to sleep until the next task is queued up.

    The customer thanked us for the explanation. I'm not sure what they are going to do about it, but I hope they're going to solve their problem not by patching up their watchdog thread but rather by fixing their low-level mouse hook so it doesn't exceeed the low-level hook timeout. For example, they could have the low-level hook post its events to another thread, then return immediately. That other thread can then do the expensive processing asynchronously. (This assumes that they are using the low-level hook only for monitoring the mouse rather than trying to intercept and block it.)

  • The Old New Thing

    Why don't you forward WM_GETMINMAXINFO and clamp the results?

    • 4 Comments

    In my illustration of how to make a window resizable in only one direction, commenter Josua asks, "Why don't you forward WM_GET­MIN­MAX­INFO and clamp the results?"

    I'm going to assume the question is really "Why don't you forward WM_GET­MIN­MAX­INFO before clamping the results?" rather than "Why did you bother writing all this code in the first place? Why not simply forward WM_GET­MIN­MAX­INFO and clamp the results?"¹

    The answer is that forwarding WM_GET­MIN­MAX­INFO doesn't do anything. As noted in the documentation, the incoming MIN­MAX­INFO structure already has the default values on entry. The default handler for the WM_GET­MIN­MAX­INFO message returns without doing anything, since all the default handler does is accept the defaults.

    So sure, you could forward the message, and then clamp the results, but the forwarding doesn't accomplish anything.

    ¹ In case the question really was "Why did you bother writing all this code in the first place...": Go ahead and delete all the changes aside from the initial version of the On­Get­Min­Max­Info handler. You'll see the problems called out in the text: The resize arrows appear when the mouse hovers over the corners and the left and right edges. And if you maximize the window onto a secondary monitor, and that monitor's height is different from the height of the primary monitor, it maximizes to the wrong height.

  • The Old New Thing

    How does Task Manager compute Up Time, and why doesn't it agree with GetTickCount?

    • 25 Comments

    Task Manager shows a piece of information called "Up time". How is this value calculated, and why doesn't it agree with the value reported by Get­Tick­Count/Get­Tick­Count64?

    Task Manager calculates "up time" by subtracting the system boot time from the current time. In other words, it is misnamed; it really should be called time since system was started. It doesn't subtract out the time when the computer was in sleep or hibernation.

    The tick count, on the other hand, counts only time that elapses while the computer is on.

  • The Old New Thing

    Can I run a service executable from a network location?

    • 10 Comments

    A customer liaison wanted to know whether it is possible to run a service executable from a network location. The customer was doing so and running into problems, and they wanted to know whether it is a legal scenario.

    Running a service from a network location is a bad idea. If the computer lose network connectivity momentarily, the service may die with STATUS_IN_PAGE_ERROR. (You can try to mitigiate this with /SWAPRUN:NET.) Plus there is a security issue here, because your computer is now trusting code that is coming from another computer. If somebody attacks your network and masquerades as the other computer, they can inject code into your computer with system privileges. If that other computer becomes compromised, then the attacker can inject code onto any computer that is running a service from the compromised computer.

    But even though it's a bad idea, it is nevertheless technically legal, in the same way that it is technically legal to use strcpy or to park your car and leave the windows open and the key on the front seat.

    But if you run into problems, all everybody is going to tell you is, "Don't do that."

    (The MSDN documentation advises against putting the service executable on another computer, which is already a step up from the traditional MSDN approach of merely providing facts and leaving you to develop your own guidance. But it can't bring itself to say that doing so is a bad idea; it merely says that "It is best to use a local file.")

  • The Old New Thing

    Documentation creates contract, which is why you need to be very careful what you document

    • 40 Comments

    A person with a rude name asks, "Why does MS not document the system metrics used by classic/pre-uxtheme windows and common controls? This image is really useful and I wish all of this was actually documented."

    Actually, that picture explains why it isn't documented.

    Suppose such a picture existed in the Windows 2000 documentation. I don't know what it would say exactly, so suppose, for the purpose of discussion, that it said that the caption buttons are exactly SM_CX­FRAME pixels from the right-hand edge of the window, and that the buttons are exactly SM_CX­SIZE pixels wide, with exactly SM_CX­EDGE pixels of padding between the buttons, and the buttons are exactly SM_CY­SIZE pixels tall, with SM_CY­EDGE pixels between the top of the button and the top of the window.

    Once that picture existed in the documentation, the picture you linked to could never exist.

    The picture from Windows 2000 doesn't include the SM_CX­PADDED­BORDER or the the SM_CY­PADDED­BORDER. It can't, because those metrics didn't exist in Windows 2000. Since the diagram is part of the documentation, it is contractual, and it would not be possible to alter the layout of the window caption (say, by incorporating a new metric like SM_CX­PADDED­BORDER), because that would break existing code.

    For example, a program may have looked at the diagram and concluded, "Okay, so if I want to programmatically click the Close button, I can go to the upper right corner of the window, move down SM_CY­FRAME + 1 pixels, move left move down SM_CX­FRAME + 1 pixels, and click there, and it will hit the button."

    And then Windows Vista shows up, adds some SM_CX­PADDED­BORDER between the Close button and the right edge, and the program stops working.

    Publishing the redlines would force the visual layout to be locked in stone. Windows 95 could not have added the Close button. Windows Vista could not have added extra padding around the buttons.

    Note that changing the visual layout of the caption does not break programs which draw their own caption bar. They will continue to draw the caption bar their own custom way. If they tried to mimic the Windows 2000 caption bar, then they will continue to mimic the Windows 2000 caption bar, even on Windows Vista. But nobody gets hurt, because the application is doing both the drawing and the hit-testing, so it remains in sync with itself.

  • The Old New Thing

    Hidden message in a T-shirt, it's been done before

    • 14 Comments

    While everybody is trying to figure out the hidden message in Joe Belfiore's T-shirt,¹ I figure I'd give you an easier puzzle.

    Here is the pattern of 0's and 1's printed on the T-shirt handed given out at the Windows 8 kick-off meeting. Because you don't have a project until you have a T-shirt.

                  001 01101110 0110
             01110011 00100000 01010111
          10 01100100 01101111 01110111 011
        0000 01001100 01101001 01110110 01100
       00000 01001001 01101110 01110100 011001
      110010 01101110           1110100 0100010
     1111000 0111000             101111 0111001
     1100101 011100               01001 0110111
      100100 0110111             110011 0010000
      010111 01101001           1100100 011011
       10111 01110011 00     0 01001100 01101
         110 01100101 00100000 01001001 011
           0 01100101 01110010 01101110 0
               000101 01111000 011100
           1 01110010 01100101 0111001001
        1001 01101110 01100100 01101111 0111
      110011 00100000 01    11 01101001 011011
     1100100 0110111            1110011 0010000
    01001100 01101                00101 00100000
    01001001 0110                  0101 01110010
    01101110 0110                  0101 01111000
    01110000 01101                10010 01100101
    0111001001010111             101110 01100100
     1101111 01110111 01110011 00100000 0101011
      101001 01101110 01100100 01101111 011101
        0011 00100000 01001100 01101001 0111
           1 00100000 01001001 01101110 0
                10010 01101110 0110
    

    The actual shirt clipped many of the digits to make the shape come out smoother. I've filled in the partial digits.

    There are at least two typos in the shirt.

    It didn't take a room full of developers long to decode the message.

    Click here to reveal the answer.

    The digits are merely the binary encoding of ASCII characters.

                  001 01101110 0110              // Windo
             01110011 00100000 01010111          // ws Wi
          10 01100100 01101111 01110111 011      // ndows
        0000 01001100 01101001 01110110 01100    //  Live
       00000 01001001 01101110 01110100 011001   //  Inte
      110010 01101110           1110100 0100010  // rnetE
     1111000 0111000             101111 0111001  // xplor
     1100101 011100               01001 0110111  // er In
      100100 0110111             110011 0010000  // dows 
      010111 01101001           1100100 011011   // Windo
       10111 01110011 00     0 01001100 01101    // ws Li
         110 01100101 00100000 01001001 011      // ve In
           0 01100101 01110010 01101110 0        // terne
               000101 01111000 011100            // tExpl
           1 01110010 01100101 0111001001        // orer  typo
        1001 01101110 01100100 01101111 0111     // indow
      110011 00100000 01    11 01101001 011011   // s Win
     1100100 0110111            1110011 0010000  // dows 
    01001100 01101                00101 00100000 // Live 
    01001001 0110                  0101 01110010 // Inter
    01101110 0110                  0101 01111000 // netEx
    01110000 01101                10010 01100101 // plore
    0111001001010111             101110 01100100 // r[?nd typo
     1101111 01110111 01110011 00100000 0101011  // ows L
      101001 01101110 01100100 01101111 011101   // indow
        0011 00100000 01001100 01101001 0111     // s Liv
           1 00100000 01001001 01101110 0        // e Int
                10010 01101110 0110              // ernet
    

    ¹ Looks like they figured it out.

  • The Old New Thing

    Why is there an invisible U+202A at the start of my file name?

    • 19 Comments

    There's something strange about this property sheet page:

    IMG31415 Properties ×
    General
    Security
    Details
     
    Object name:       C:\Users\Bob\Desktop\IMG31415.jpg
    Group or user names:
    👤  👤 SYSTEM
     👤 Bob
    👤  👤 Administrators
     
     
     
    To change permissions, click Edit. Edit...
    Permissions for SYSTEM Allow Deny
    Full control  
    Modify  
    Read & execute  
    Read  
    Write  
    Special permissions    
         
    Advanced... For special permissions or advanced settings, click Advanced.
    Apply Cancel OK

    Okay, that was a trick question, because the thing that's strange is not visible to the eye.

    Use the mouse to click in the object name field (the thing with the file path), then press Home, followed by Shift+End to select the entire text, then Ctrl+C to copy it to the clipboard.

    Now things get interesting.

    Fire up Notepad, paste the path into the Notepad document, and save it to the desktop with the name tricky.txt.

    Huh? Notepad says, "This file contains characters in Unicode format which will be lost if you save this as an ANSI encoded text file."

    What Unicode characters are we talking about? There are no accented letters here. All the characters in the file name fit in the ASCII repertoire.

    Go to a command prompt and type

    C:\Users\Bob> copy "
    

    and then paste the path from the clipboard, then close the quotation mark, and hit Enter.

    C:\Users\Bob> copy "?C:\Users\Bob\Desktop\IMG31415.jpg"
    The filename, directory name, or volume label syntax is incorrect.
    

    Wait, what? Where did that rogue question mark come from?

    The answers to the two questions are the same: The mysterious Unicode character, which is invisible in Notepad, and which appears as a question mark on the command line, is U+202A (LEFT-TO-RIGHT EMBEDDING).

    We saw some time ago that you can, as a last resort, insert the character U+202B (RIGHT-TO-LEFT EMBEDDING) to force text to be interpreted as right-to-left. The converse character is U+202A (LEFT-TO-RIGHT EMBEDDING), which forces text to be interpreted as left-to-right.

    The Security dialog box inserts that control character in the file name field in order to ensure that the path components are interpreted in the expected manner. Unfortunately, it also means that if you try to copy the text out of the dialog box, the Unicode formatting control character comes along for a ride. Since the character is normally invisible, it can create all sorts of silent confusion.

    (We're lucky that the confusion was quickly detected by Notepad and the command prompt. But imagine if you had pasted the path into the source code to a C program!)

  • The Old New Thing

    What does it mean when the Advanced Security Settings dialog says that an ACE was inherited from "Parent Object" without naming the specific parent?

    • 8 Comments

    The Advanced Security Settings dialog shows the ACEs in an object's ACL, and one of the pieces of information is a column labeled Inherited from which identifies whether the ACE was inherited, and if so, from where. A customer observed that when they opened the Advanced Security Settings dialog, one of their objects had an ACE that showed Parent Object as the Inherited from.

    Name: C:\dir1\dir2\dir3\dir4\file
    Type Principal Access Inherited from Applies to
    Allow Administrators Full control None This folder only
    Allow Administrators Full control C:\dir1\dir2\ This folder, subfolders and files
    Allow SYSTEM Full control C:\dir1\dir2\ This folder, subfolders and files
    Allow CREATOR OWNER Full control C:\dir1\dir2\ Subfolders and files only
    Allow Users Read & execute C:\dir1\dir2\ This folder, subfolders and files
    Allow Users Special C:\dir1\dir2\ This folder and subfolders
    Allow Authenticated Users Full control Parent Object This folder, subfolders and files

    However, when they went to the parent object C:\dir1\dir2\dir3\dir4, that ACE is nowhere to be found.

    Name: C:\dir1\dir2\dir3\dir4
    Type Principal Access Inherited from Applies to
    Allow Administrators Full control None This folder only
    Allow Administrators Full control C:\dir1\dir2\ This folder, subfolders and files
    Allow SYSTEM Full control C:\dir1\dir2\ This folder, subfolders and files
    Allow CREATOR OWNER Full control C:\dir1\dir2\ Subfolders and files only
    Allow Users Read & execute C:\dir1\dir2\ This folder, subfolders and files
    Allow Users Special C:\dir1\dir2\ This folder and subfolders
    Allow Everyone Full control C:\dir1\dir2\ This folder, subfolders and files

    How can an ACE be inherited from its parent, when it doesn't exist in the parent?

    The Advanced Security Settings dialog is trying to be helpful, but in doing so, it implies a greater level of confidence than it actually offers.

    ACEs do not specify where they were inherited from. There is merely a bit in the ACE called INHERITED_ACE which means, "This ACE was created via inheritance." Not only does this bit not tell you where the ACE was inherited from, but the bit might even be wrong! Anybody can go in and toggle the bit, and bingo, you now have forged the "I was created via inheritance" flag. Another way this flag could be out of sync is if the user started an ACL update operation and then canceled it partway through.

    The Advanced Security Settings dialog uses the Get­Inheritance­Source function to determine the source of each ACE. That function walks up the parent chain looking for matching inheritable ACEs. If a match is found, then the Advanced Security Settings dialog shows that parent as the Inherited from. Otherwise, it shrugs its shoulders and says Parent Object.

    The string Parent Object means "This ACE claims to have been inherited from somewhere, but I can't figure out where, so I'm just going to be vague and say that it came from some parent object somewhere." Perhaps a less confusing string would have been Ancestor Object or even simply Unknown.

    The Advanced Security Settings dialog figured that it would go the extra mile and instead of merely saying Inherited = Yes, it would try to find a parent object that was the most likely source of the inheritance. But by doing that, you came to expect it, and then you got upset when it wasn't able to come through for you. No good deed goes unpunished.

  • The Old New Thing

    Creating a window that can be resized in only one direction

    • 13 Comments

    Today's Little Program shows a window that can be resized in only one direction, let's say vertically but not horizontally.

    Start with the scratch program and make these changes:

    UINT OnNcHitTest(HWND hwnd, int x, int y)
    {
     UINT ht = FORWARD_WM_NCHITTEST(hwnd, x, y, DefWindowProc);
     switch (ht) {
     case HTBOTTOMLEFT:  ht = HTBOTTOM; break;
     case HTBOTTOMRIGHT: ht = HTBOTTOM; break;
     case HTTOPLEFT:     ht = HTTOP;    break;
     case HTTOPRIGHT:    ht = HTTOP;    break;
     case HTLEFT:        ht = HTBORDER; break;
     case HTRIGHT:       ht = HTBORDER; break;
     }
     return ht;
    }
    
    HANDLE_MSG(hwnd, WM_NCHITTEST, OnNcHitTest);
    

    We accomplish this by removing horizontal resize behavior from the left and right edges and corners. For the corners, we remove the horizontal resizing, but leave the vertical resizing. For the edges, we remove resizing entirely by reporting that the left and right edges should act like an inert border.

    Wait, we're not done yet. This handles resizing by grabbing the edges with the mouse, but it doesn't stop the user from hitting Alt+Space, followed by S (for Size), and then hitting the left or right arrow keys.

    For that, we need to handle WM_GET­MIN­MAX­INFO.

    void OnGetMinMaxInfo(HWND hwnd, LPMINMAXINFO lpmmi)
    {
     RECT rc = { 0, 0, 500, 0 };
     AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), FALSE,
                        GetWindowExStyle(hwnd));
    
     // Adjust the width
     lpmmi->ptMaxSize.x =
     lpmmi->ptMinTrackSize.x =
     lpmmi->ptMaxTrackSize.x = rc.right - rc.left;
    }
    
    HANDLE_MSG(hwnd, WM_GETMINMAXINFO, OnGetMinMaxInfo);
    

    This works out great, except for the case of being maximized onto a secondary monitor, because we run into the mixed case of being small than the monitor in the horizontal direction, but larger than the monitor in the vertical direction.

    void OnGetMinMaxInfo(HWND hwnd, LPMINMAXINFO lpmmi)
    {
     RECT rc = { 0, 0, 500, 0 };
     AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), FALSE,
                        GetWindowExStyle(hwnd));
    
     // Adjust the width
     lpmmi->ptMaxSize.x =
     lpmmi->ptMinTrackSize.x =
     lpmmi->ptMaxTrackSize.x = rc.right - rc.left;
    
     // Adjust the height
     MONITORINFO mi = { sizeof(mi) };
     GetMonitorInfo(MonitorFromWindow(hwnd,
                        MONITOR_DEFAULTTOPRIMARY), &mi);
     lpmmi->ptMaxSize.y = mi.rcWork.bottom - mi.rcWork.top
                        - lpmmi->ptMaxPosition.y + rc.bottom;
    }
    

    The math here is a little tricky. We want the window height to be the height of the work area of the window monitor, plus some extra goop in order to let the borders hang over the edge.

    The first two terms are easy to explain: mi.rcWork.bottom - mi.rcWork.top is the height of the work area.

    Next, we want to add the height consumed by the borders that hang off the top of the monitor. Fortunately, the window manager told us exactly how much the window is going to hang off the top of the monitor: It's in lpmmi->ptMaxPosition.y, but as a negative value since it is a coordinate that is off the top of the screen. We therefore have to negate it before adding it in.

    Finally, we add the borders that hang off the bottom of the work area.

    Yes, handling this mixed case (where the window is partly constrained and partly unconstrained) is annoying. Sorry.

  • The Old New Thing

    That's not how you start a boat

    • 9 Comments

    Tomorrow is Opening Day of the Seattle boating season. (Which, as I noted some time ago, is purely a social occasion with no legal significance.)

    One of my colleagues is not much of a boat person, but his wife is. (In fact, she's a commercial fisherman.) They were on board some fancy boat or other as it sat docked. He was up on the top deck—this being a boat so fancy that it had an upstairs and a downstairs—and as the preparations were made for heading out, his wife called out to him to start the boat, since he's up there already.

    Now, as I mentioned, my colleague isn't much of a boat person. But he figured, "These modern boats, how hard can it be? It's probably pushbutton nowadays." So he looked at the control panel and saw a bright red button. "Red button, that's probably the power button, right?"

    The button looked like a letter V with a dot in the middle, or at least that's how it was described to me.

    Immediately upon pressing the button, alarms rang on board the boat, and the Coast Guard called them on the radio.

    It turns out that the V with a dot is not the power button. It is the "man overboard" button. The dot is the person's head, and the letter V represents the two flailing arms.

Page 2 of 450 (4,498 items) 12345»