August, 2003

  • The Old New Thing

    What are all these files in my C:\WINDOWS\CSC directory?

    • 12 Comments
    This is where Windows keeps the files that you have marked for being available offline.  (CSC was the working name for the feature now called Offline Files. It stands for Client-Side Caching.)
  • The Old New Thing

    Why isn't my time zone highlighted on the world map?

    • 35 Comments

    In the original release of Windows 95, you could change your time zone by clicking on the map, and the time zone you selected would highlight. Similarly, you could change your Region Settings by clicking on the world map. This was one of those little touches that made Windows 95 that much more fun to use.

    But we had to remove those features within months of release, even though we based both of the maps on the borders officially recognized by the United Nations.

    In early 1995, a border war broke out between Peru and Ecuador and the Peruvian government complained to Microsoft that the border was incorrectly placed. Of course, if we complied and moved the border northward, we'd get an equally angry letter from the Ecuadorian government demanding that we move it back. So we removed the feature altogether.

    The time zone map met a similar fate. The Indian government threatened to ban all Microsoft software from the country because we assigned a disputed region to Pakistan in the time zone map. (Any map that depicts an unfavorable border must bear a government stamp warning the end-user that the borders are incorrect. You can't stamp software.) We had to make a special version of Windows 95 for them.

    Geopolitics is a very sensitive subject.

  • The Old New Thing

    Windows brings out the Rorschach test in everyone

    • 10 Comments

    It seems that no matter what you do, somebody will get offended.

    Every Windows 95 box has an anti-piracy hologram on the side. The photographer chose his infant son as his model, since the human face is very hard to copy accurately. The baby sits next to a computer, and as you turn the hologram, his arm rises and points at the computer monitor, which bursts into a Windows 95 logo.

    How cute. And everybody loves babies.

    Until we got a complaint from a government (who shall remain nameless for obvious reasons) that was upset with Windows 95 because it depicted naked children.

    "Naked children!?" we all thought to ourselves.

    They were complaining about the hologram on the box. The baby wasn't wearing a shirt. Even though the baby was visible only from the waist up, the offended government assumed that he wasn't wearing pants either.

    We had to produce a new hologram. In the new hologram, the baby is wearing a shirt and overalls. But since this was a rush job, we didn't have time to do the arm animation.

    So if you still have your copy of Windows 95, go look at the hologram. If the baby in your hologram isn't wearing a shirt, you have a genuine collector's item. I have seen the "naked baby" hologram but unfortunately my copy of Windows 95 has a clothed baby.

    If you hunt around the web, you can find lots of other people who claim to have found subliminal messages in Windows 95. My favorite is the one who claims to have found images in the clouds bitmap. Hey, they're clouds. They're Nature's Rorschach Test.

    Windows XP had its own share of complaints. The original wallpaper for Windows XP was Red Moon Desert, until people claimed that Red Moon Desert looked like a pair of buttocks. People also thought that one of the generic people used in the User Accounts control panel people looked like Hitler. And one government claimed the cartoon character in the original Switch Users dialog looked like an obscene body part. We had to change them all. But it makes me wonder about the mental state of our beta testers...

  • The Old New Thing

    The secret life of GetWindowText

    • 3 Comments

    GetWindowText() is more complicated than you think. The documentation tries to explain its complexity with small words, which is great if you don't understand long words, but it also means that you're not getting the full story.

    Here's an attempt to give the full story.

    How windows manage their text

    There are two ways window classes can manage their text. They can either do it manually or they can let the system do it. The default is to let the system do it.

    If a window class lets the system manage its text, the system will do the following:

    • Default handling of the WM_NCCREATE message takes the lpWindowName parameter passed to CreateWindow/Ex and saves it in a "special place".
    • Default handling of the WM_GETTEXT message retrieves the string from that "special place".
    • Default handling of the WM_SETTEXT message copies the string to that "special place".

    On the other hand, if a window class manages its window text manually, the system will not do any special handling, and it is the window class's responsibility to respond to the WM_GETTEXT/WM_SETTEXT messages and return/save the strings explicitly.

    Frame windows typically let the system manage their window text. Custom controls typically manage their window text manually.

    GetWindowText

    GetWindowText has a problem: Window text needs to be readily available without hanging. FindWindow() needs to get window text in order to find a window. Task-switching applications need to get window text so they can display the window title in the switcher window. It should not be possible for a hung application to clog up other applications. This is particularly true of the task switcher scenario.

    This argues against sending WM_GETTEXT messages, because the target window of the WM_GETTEXT might be hung. Instead, GetWindowText should use the "special place" since that cannot be affected by hung applications.

    On the other hand, GetWindowText is used to retrieve text from controls on a dialog, and those controls frequently employ custom text management. This argues for sending WM_GETTEXT messages, because that is the only way to retrieve custom-managed text.

    So GetWindowText strikes a compromise.

    • If you are trying to GetWindowText() from a window in your own process, then GetWindowText() will send the WM_GETTEXT message.
    • If you are trying to GetWindowText() from a window in another process, then GetWindowText() will use the string from the "special place" and not send a message.

    According to the first rule, if you are trying to get text from a window in your own process, and the window is hung, then GetWindowText() will also hang. But since the window belongs to your process, it's your own fault and you deserve to lose. Sending the WM_GETTEXT message ensures that text from windows that do custom text management (typically, custom controls) are properly retrieved.

    According to the second rule, if you are trying to get text from a window in another process, then GetWindowText() will not send a message; it will just retrieve the string from the "special place". Since the most common reason for getting text from a window in another process is to get the title of the frame, and since frame windows typically do not do custom window text manipulation, this usually gets the right string.

    The documentation simplifies this as "GetWindowText() cannot retrieve text from a window from another application."

    What if I don't like these rules?

    If the second rule bothers you because you need to get text from a custom control in another process, then you can send the WM_GETTEXT message manually. Since you are not using GetWindowText(), you are not subject to its rules.

    Note, however, that if the target window is hung, your application will also hang since SendMessage() will not return until the target window responds.

    Note also that since WM_GETTEXT is in the system message range (0 to WM_USER-1), you do not need to do any parameter marshalling (and in fact, you shouldn't). USER will do the marshalling for you.

    Can you give an example where this makes a difference?

    Consider this control:
    SampleWndProc(...)
    {
        case WM_GETTEXT:
            lstrcpyn((LPTSTR)lParam, "Booga!", (int)wParam);
            return lstrlen((LPTSTR)lParam);
        case WM_GETTEXTLENGTH: return 7; // lstrlen("Booga!") + null
        ...
    }
    
    Now consider application A that does
    hwnd = CreateWindow("Sample", "Frappy", ...);
    
    Now consider process B that gets the handle to the window created by application A (by whatever means).
    TCHAR szBuf[80];
    GetWindowText(hwnd, szBuf, 80);
    
    This will return szBuf = "Frappy" because it is getting the window text from the "special place". However,
    SendMessage(hwnd, WM_GETTEXT, 80, (LPARAM)szBuf);
    
    will return szBuf = "Booga!"
  • The Old New Thing

    Why is a registry file called a "hive"?

    • 9 Comments

    Useless trivia day.

    Why is a registry file called a "hive"?

    Because one of the original developers of Windows NT hated bees.  So the developer who was responsible for the registry snuck in as many bee references as he could.  A registry file is called a "hive", and registry data are stored in "cells", which is what honeycombs are made of.

  • The Old New Thing

    Changing the Windows boot logo

    • 12 Comments

    This is the answer I give to IT people when they ask if it's okay to customize the Windows boot logo.

    DO NOT DO THIS ON A PRODUCTION MACHINE OR YOU WILL REGRET IT.

    If you hack the bitmap everything will seem fine until six months down the road when you decide to install the latest service pack. The service pack installer will not upgrade your ntoskrnl because it looks at the file and says "Hm, this isn't the standard uniprocessor ntoskrnl, it's not the standard multiprocessor ntoskrnl, it's not the standard advanced server ntoskrnl, I can't tell what this is, so I don't know which version of ntoskrnl to update it to. I'll just leave it alone."

    If you are lucky you will bluescreen at boot because the old ntoskrnl is incompatible with some other critical part of the service pack.

    If you are unlucky, your machine will appear to run normally when in fact it is quietly corrupting itself, and then it will keel over or generate bogus data when you least expect it.

    If you planned ahead, you will have quit your job and moved to Hawaii so the disaster falls on your replacement's head to clean up while you sit on the beach sipping a pina colada.

  • The Old New Thing

    Windows 95 doesn't boot with more than 1GB of RAM

    • 12 Comments

    Short version

    Windows 95 will fail to boot if you have more than around 480MB of memory. (This was considered an insane amount of memory back then. Remember, Windows 95's target machine was a 4MB 386SX and a powerful machine had 16MB. So according to Moore's law, that gave us seven years before we had to do something about it. One of my friends got 96MB of memory on his machine to test that we didn't tank under "insanely huge memory configurations" and we all drooled.)

    Windows 98 bumped the limit to 1GB because there existed a vendor (who shall remain nameless) who was insane enough to want to sell machines with 1GB of RAM and preinstall Windows 98 instead of the much more suitable Windows NT.

    Long version

    One of the first things that happens in the Windows 95 boot process once you have transitioned into 32-bit mode is to initialize the 32-bit memory manager. But now you have a chicken-and-egg problem: The memory manager needs to allocate some memory in order to keep track of the memory it is managing. (Keeping track of which pages are paged in and which are paged out, that sort of thing.) But it can't allocate memory until the memory manager is initialized. Eek!

    The solution is to initialize the memory manager twice.

    The first time the memory manager is initialized, it gets all its memory from a fixed block of memory preallocated in the init-data segment. It sets up this fixed block as the memory manager heap. So now there is a heap available to satisfy memory allocations.

    Next, the memory manager starts looking for the real memory in the system, and when it finds some, it allocates memory (from the initial fixed block) to keep track of the real memory.

    After the memory manager has found all the real memory in the system, it's time to initialize the memory manager a second time: It carves out a chunk of that real memory to use as the "real heap" and copies the information from the heap that it has been using so far (the fixed-sized heap) to the "real heap".

    Once everything has been copied and all the pointers fixed up, the global memory manager heap pointers are changed to point at the new ("real") heap and the original heap is abandoned.

    The memory consumed by the original heap is reclaimed when the init-data segment is discarded (which happens at the end of system initialization).

    The total RAM limitation occurs because the size of the fixed block in the init-data segment needs to be large enough to satisfy all the memory allocations performed during the memory scan. If you have too much memory, an allocation during the memory scan fails and the system halts.

    The size of the init-data segment was chosen to balance two factors. The larger you make it, the more memory you can have in the system before hitting an allocation failure during the memory scan. But you can't make it too large or machines with small amounts of memory won't even be able to load VMM into memory.

  • The Old New Thing

    What are those little overlay icons?

    • 3 Comments

    Windows XP ships with a number of icon overlays.

    • A small arrow. Everybody knows this one: It's the shortcut overlay.
    • A hand, palm up. This is the "sharing" overlay. A folder with this overlay is the root of a file share.
    • A downward-pointing blue arrow. This is the "to be written to CD" overlay.
    • A pair of blue swirling arrows. This sometimes baffles people. This means that the item is available offline. (You have to enable offline folders to get this.)
    • A black clock. This really baffles people. This means that the file has been archived to tape and will take a very long time to access.

    The black clock is particularly baffling because you sometimes see it even if your system is not equipped with Hierarchical Storage Management. When this happens, it's because some program (typically a setup program) didn't check error codes properly.

    CopyFileAttributes(LPCTSTR pszSrc, LPTSTR pszDst)
    {
        SetFileAttributes(pszDst, GetFileAttributes(pszSrc));
    }
    

    The above code fragment fails to check for an error code from GetFileAttributes. It so happens that GetFileAttributes fails by returning the value 0xFFFFFFFF. If you fail to check this error code, you end up setting every possible attribute on the destination, including FILE_ATTRIBUTE_OFFLINE. FILE_ATTRIBUTE_OFFLINE is the flag that tells Explorer that the file has been archived to tape.

  • The Old New Thing

    A day in the trenches

    • 7 Comments

    I got up at 5 this morning to spend the day at Product Support Services answering phones: today was the day the Blaster worm launched its second wave. And by a startling coincidence, the person at the station next to me was Michael Howard our Senior Security Program Manager and author of Writing Secure Code. Getting Michael Howard to help you secure your computer is like getting Lance Armstrong to help you change a flat tire on your bicycle.

    As enlightening yet humbling experiences go, for a software designer, it's hard to top (1) watching a usability session, and (2) answering product support calls. You get to observe users -- customers, the people your job it is to make more productive -- struggle with the software you helped create.

    Usability sessions are particularly frustrating since you are hidden behind a one-way mirror, watching somebody struggle to accomplish something you designed to be the most obvious thing on the planet. It's a hard lesson to learn: Not everybody is a geek like you. (Watching a usability session is a lot like being a member of the studio audience at The Price Is Right trying to help the contestant on stage guess the price of a new car.)

    Product support calls let you participate in the other end of the pipeline. The software is written, it's out there, and now you have to pay for all your mistakes and bad designs when people call in with their problems. It's software karma.
  • The Old New Thing

    Painting only when your window is visible on the screen

    • 6 Comments

    Sometimes you want to perform an activity, such as updating a status window, only as long as the window is not covered by another window.

    The easiest way to determine this is by not actually trying to determine it. For example, here's how the taskbar clock updates itself:

    1. It computes how much time will elapse before the next minute ticks over.
    2. It calls SetTimer with the amount of time it needs to wait.
    3. When the timer fires, it does an InvalidateRect of itself and the kills the timer.
    4. The WM_PAINT handler draws the current time, then returns to step 1.

    If the taskbar clock is not visible, because it got auto-hidden or because somebody covered it, Windows will not deliver a WM_PAINT message, so the taskbar clock will simply go idle and consume no CPU time at all. Here's how we can make our scratch program do the same thing:

    Our scratch program displays the current time. It also puts the time into the title bar so we can see the painting action (or lack thereof) when the window is covered or minimized, by watching the taskbar.

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
        TCHAR szTime[100];
        if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, NULL,
                          szTime, 100)) {
            SetWindowText(hwnd, szTime);
            TextOut(pps->hdc, 0, 0, szTime, lstrlen(szTime));
        }
    }
    

    Here is the timer callback that fires once we decide it's time to update. It merely kills the timer and invalidates the rectangle. The next time the window becomes uncovered, we will get a WM_PAINT message. (And if the window is uncovered right now, then we'll get one almost immediately.)

    void CALLBACK
    InvalidateAndKillTimer(HWND hwnd, UINT uMsg,
                           UINT_PTR idTimer, DWORD dwTime)
    {
        KillTimer(hwnd, idTimer);
        InvalidateRect(hwnd, NULL, TRUE);
    }
    

    Finally, we add some code to our WM_PAINT handler to restart the timer each time we paint a nonempty rectangle.

    void
    OnPaint(HWND hwnd)
    {
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        if (!IsRectEmpty(&ps.rcPaint)) {
            // compute time to next update - we update once a second
            SYSTEMTIME st;
            GetSystemTime(&st);
            DWORD dwTimeToNextTick = 1000 - st.wMilliseconds;
            SetTimer(hwnd, 1, dwTimeToNextTick, InvalidateAndKillTimer);
        }
    
        PaintContent(hwnd,&ps);
        EndPaint(hwnd, &ps);
    }
    

    Compile and run this program, and watch it update the time. When you minimize the window or cover it with another window, the time stops updating. If you take the window and drag it to the bottom of the screen so only the caption is visible, it also stops updating: The WM_PAINT message is used to paint the client area, and the client area is no longer on-screen.

    This method also stops updating the clock when you switch to another user or lock the workstation, though you can't really tell because there's no taskbar you can consult to verify. But you can use your speakers: Stick a call to MessageBeep(-1); in the PaintContent() function, so you will get an annoying beep each time the time is repainted. When you switch to another user or lock the workstation, the beeping will stop.

    This technique of invalidation can be extended to cover the case where only one section of the screen is interesting: Instead of invalidating the entire client area, invalidate only the area that you want to update, and restart the timer only if that rectangle is part of the update region. Here are the changes we need to make.

    // The magic updating rectangle
    RECT g_rcTrigger = { 50, 50, 200, 100 };
    

    When the timer fires, we invalidate only the magic rectangle instead of the entire client area. (As an optimization, I disabled background erasure for reasons you'll see later.)

    void CALLBACK
    InvalidateAndKillTimer(HWND hwnd, UINT uMsg,
                           UINT_PTR idTimer, DWORD dwTime) {
        KillTimer(hwnd, idTimer);
        InvalidateRect(hwnd, &g_rcTrigger, FALSE);
    }
    

    To make it more obvious where the magic rectangle is, we draw it in the highlight color and put the time inside it. By using the ETO_OPAQUE flag, we draw both the foreground and background simultaneously. Consequently, we don't need to have it erased for us.

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
        TCHAR szTime[100];
        if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, NULL,
                          szTime, 100)) {
            SetWindowText(hwnd, szTime);
            COLORREF clrTextPrev = SetTextColor(pps->hdc,
                                GetSysColor(COLOR_HIGHLIGHTTEXT));
            COLORREF clrBkPrev = SetBkColor(pps->hdc,
                                    GetSysColor(COLOR_HIGHLIGHT));
            ExtTextOut(pps->hdc, g_rcTrigger.left, g_rcTrigger.top,
                       ETO_CLIPPED | ETO_OPAQUE, &g_rcTrigger,
                       szTime, lstrlen(szTime), NULL);
            SetBkColor(pps->hdc, clrBkPrev);
            SetTextColor(pps->hdc, clrTextPrev);
        }
    }
    

    Finally, the code in the WM_PAINT handler needs to check the magic rectangle for visibility instead of using the entire client area.

    void
    OnPaint(HWND hwnd)
    {
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        if (RectVisible(ps.hdc, &g_rcTrigger)) {
            // compute time to next update - we update once a second
            SYSTEMTIME st;
            GetSystemTime(&st);
            DWORD dwTimeToNextTick = 1000 - st.wMilliseconds;
            SetTimer(hwnd, 1, dwTimeToNextTick, InvalidateAndKillTimer);
        }
        PaintContent(hwnd,&ps);
        EndPaint(hwnd, &ps);
    }
    

    Run this program and do various things to cover up or otherwise prevent the highlight box from painting. Observe that once you cover it up, the title stops updating.

    As I noted above, this technique is usually enough for most applications. There's an even more complicated (and more expensive) method, too, which I'll cover next week.

Page 1 of 3 (29 items) 123