History

  • The Old New Thing

    Where did windows minimize to before the taskbar was invented?

    • 39 Comments

    Before Explorer was introduced in Windows 95, the Windows desktop was a very different place.

    The icons on your desktop did not represent files; rather, when you minimized a program, it turned into an icon on the desktop. To open a minimized program, you had to hunt for its icon, possibly minimizing other programs to get them out of the way, and then double-click it. (You could also Alt+Tab to the program.)

    Explorer changed the desktop model so that icons on your desktop represent objects (files, folders) rather than programs. The job of managing programs fell to the new taskbar.

    But where did the windows go when you minimized them?

    Under the old model, when a window was minimized, it displayed as an icon, the icon had a particular position on the screen, and the program drew the icon in response to paint messages. (Of course, most programs deferred to DefWindowProc which just drew the icon.) In other words, the window never went away; it just changed its appearance.

    But with the taskbar, the window really does go away when you minimize it. Its only presence is in the taskbar. The subject of how to handle windows when they were minimized went through several iterations, because it seemed that no matter what we did, some program somewhere didn't like it.

    The first try was very simple: When a window was minimized, the Windows 95 window manager set it to hidden. That didn't play well with many applications, which cared about the distinction between minimized (and visible) and hidden (and not visible).

    Next, the Windows 95 window manager minimized the window just like the old days, but put the minimized window at coordinates (-32000, -32000), This didn't work because some programs freaked out if they found their coordinates were negative.

    So the Windows 95 window manager tried putting minimized windows at coordinates (32000, 32000), This still didn't work because some programs freaked out if they found their coordinates were positive and too large!

    Finally the Windows 95 window manager tried coordinates (3000, 3000), This seemed to keep everybody happy. Not negative, not too large, but large enough that it wouldn't show up on the screen (at least not at screen resolutions that were readily available in 1995).

    If you have a triple-monitor Windows 98 machine lying around, you can try this: Set the resolution of each monitor to 1024x768 and place them corner-to-corner. At the bottom right corner of the third monitor, you will see all your minimized windows parked out in the boonies.

    (Windows NT stuck with the -32000 coordinates and didn't pick up the compatibility fixes for some reason. I guess they figured that by the time Windows NT became popular, all those broken programs would have been fixed. In other words: Let Windows 95 do your dirty work!)

    [Raymond is currently on vacation; this message was pre-recorded.]

  • The Old New Thing

    Why doesn't Setup asks you if you want to keep newer versions of OS files?

    • 34 Comments

    Windows 95 Setup would notice that a file it was installing was older than the file already on the machine and would ask you whether you wanted to keep the existing (newer) file or to overwrite it with the older version.

    Asking the user this question at all turned out to have been a bad idea. It's one of those dialogs that ask the user a question they have no idea how to answer.

    Say you're installing Windows 95 and you get the file version conflict dialog box. "The file Windows is attempting to install is older than the one already on the system. Do you want to keep the newer file?" What do you do?

    Well, if you're like most people, you say, "Um, I guess I'll keep the newer one," so you click Yes.

    And then a few seconds later, you get the same prompt for some other file. And you say Yes again.

    And then a few seconds later, you get the same prompt for yet another file. Now you're getting nervous. Why is the system asking you all these questions? Is it second-guessing your previous answers? Often when this happens, it's because you're doing something bad and the computer is giving you one more chance to change your mind before something horrible happens. Like in the movies when you have to type Yes five times before it will launch the nuclear weapons.

    Maybe this is one of those times.

    Now you start saying No. Besides, it's always safer to say No, isn't it?

    After a few more dialogs (answering No this time), Setup finally completes. The system reboots, and... it bluescreens.

    Why?

    Because those five files were part of a matched set of files that together form your video driver. By saying Yes to some of them and No to others, you ended up with a mishmash of files that don't work together.

    We learned our lesson. Setup doesn't ask this question any more. It always overwrites the files with the ones that come with the operating system. Sure, you may lose functionality, but at least you will be able to boot. Afterwards, you can go to Windows Update and update that driver to the latest version.

    Note, however, that this rule does not apply to hotfixes and Service Packs.

    [Raymond is currently on vacation; this message was pre-recorded.]

  • The Old New Thing

    How does Explorer detect whether your program supports long file names?

    • 70 Comments

    When you register your program with a file association, the shell needs to decide whether your program supports long file names so it can decide whether to pass you the long name (which may contains spaces! so make sure you put quotation marks around the "%1" in your registration) or the short name.

    The rule is simple: The shell looks at your program's EXE header to see what kind of program it is.

    • If it is a 16-bit program, then the shell assumes that it supports long file names if it is marked as Windows 95-compatible. Otherwise, the shell assumes that it does not support long file anmes.
    • If it is a 32-bit program (or 64-bit program for 64-bit systems), then the shell assumes that it supports long file names.
    • If it can't find your program, then the shell plays it safe and assumes that the program doesn't support long file names.

    Note that third case. If you mess up your program registration, then the shell will be unable to determine whether your program supports long file names and assumes not. Then when your program displays the file name in, say, the title bar, you end up displaying some icky short file name alias instead of the proper long file name that the user expects to see.

    The most common way people mess up their program registration is by forgetting to quote spaces in the path to the program itself! For example, an erroneous registration might go something like this:

    HKEY_CLASSES_ROOT
        litfile
            shell
                open
                    command
                        (default) = C:\Program Files\LitWare Deluxe\litware.exe "%1"

    Observe that the spaces in the path "C:\Program Files\Litware Deluxe\litware.exe" are not quoted in the program registration. Consequently, the shell mistakenly believes that the program name is "C:\Program", which it cannot find. The shell therefore plays it safe and assumes no LFN support.

    Compatibility note: As part of other security work, the code in the shell that parses these command lines was augmented to chase down the "intended" path of the program. This presented the opportunity to fix that third case, so that the shell could find the program after all and see that it supported long file names, thereby saving the user the ignominy of seeing their wonderful file name turn into a mush of tildes.

    And after we made the change, we had to take it out.

    Because there were programs that not only registered themselves incorrectly, but were relying on the shell not being smart enough to find their real location, resulting in the program receiving the short name on the command line. Turns out these programs wanted the short name, and doing this fake-out was their way of accomplishing it.

    (And to those of you who are already shouting, "Go ahead and break them," that's all fine and good as long as the thing that's incompatible isn't something you use. But if it's your program, or a program your company relies on, I expect you're going to change your tune.)

  • The Old New Thing

    The compatibility constraints of even your internal bookkeeping

    • 51 Comments

    The Listview control when placed in report mode has a child header control which it uses to display column header titles. This header control is the property of the listview, but the listview is kind enough to let you retrieve the handle to that header control.

    And some programs abuse that kindness.

    It so happens that the original listview control did not use the lParam of the header control item for anything. So some programs said, "Well, if you're not using it, then I will!" and stashed their own private data into it.

    Then a later version of the listview decided, "Gosh, there's some data I need to keep track of for each header item. Fortunately, since this is my header control, I can stash my data in the lParam of the header item."

    And then the application compatibility team takes those two ingredients (the program that stuffs data into the header control and the listview that does the same) to their laboratory, mixes them, and an explosion occurs.

    After some forensic analysis, the listview development team figures out what happened and curses that they have to work around yet another program that grovels into internal data structures. The auxiliary data is now stored in some other less convenient place so those programs can continue to run without crashing.

    The moral of the story: Even if you change something that nobody should be relying on, there's a decent chance that somebody is relying on it.

    (I'm sure there will be the usual chorus of people who will say, "You should've just broken them." What if I told you that one of the programs that does this is a widly-used system administration tool? Eh, that probably wouldn't change your mind.)

  • The Old New Thing

    What's the atom returned by RegisterClass useful for?

    • 8 Comments

    The RegisterClass and RegisterClassEx functions return an ATOM. What is that ATOM good for?

    The names of all registered window classes is kept in an atom table internal to USER32. The value returned by the class registration functions is that atom. You can also retrieve the atom for a window class by asking a window of that class for its class atom via GetClassWord(hwnd, GCW_ATOM).

    The atom can be converted to an integer atom via the MAKEINTATOM macro, which then can be used by functions that accept class names in the form of strings or atoms. The most common case is the lpClassName parameter to the CreateWindow macro and the CreateWindowEx function. Less commonly, you can also use it as the lpClassName parameter for the GetClassInfo and GetClassInfoEx functions. (Though why you would do this I can't figure out. In order to have the atom to pass to GetClassInfo in the first place, you must have registered the class (since that's what returns the atom), in which case why are you asking for information about a class that you registered?)

    To convert a class name to a class atom, you can create a dummy window of that class and then do the aforementioned GetClassWord(hwnd, GCW_ATOM). Or you can take advantage of the fact that the return value from the GetClassInfoEx function is the atom for the class, cast to a BOOL. This lets you do the conversion without having to create a dummy window. (Beware, however, that GetClassInfoEx's return value is not the atom on Windows 95-derived operating systems.)

    But what good is the atom?

    Not much, really. Sure, it saves you from having to pass a string to functions like CreateWindow, but all it did was replace a string with with an integer you now have to save in a global variable for later use. What used to be a string that you could hard-code is now an atom that you have to keep track of. Unclear that you actually won anything there.

    I guess you could use it to check quickly whether a window belongs to a particular class. You get the atom for that class (via GetClassInfo, say) and then get the atom for the window and compare them. But you can't cache the class atom since the class might get unregistered and then re-registered (which will give it a new atom number). And you can't prefetch the class atom since the class may not yet be registered at the point you prefetch it. (And as noted above, you can't cache the prefetched value anyway.) So this case is pretty much a non-starter anyway; you may as well use the GetClassName function and compare the resulting class name against the class you're looking for.

    In other words, window class atoms are an anachronism. Like replacement dialog box classes, it's one of those generalities of the Win32 API that never really got off the ground, but which must be carried forward for backwards compatibility.

    But at least now you know what they are.

    [Typos fixed October 12.]

  • The Old New Thing

    Why is there a separate GetSystemDirectory function?

    • 18 Comments

    If the system directory is always %windir%\SYSTEM32, why is there a special function to get it?

    Because it wasn't always that.

    For 16-bit programs on Windows NT, the system directory is %windir%\SYSTEM. That's also the name of the system directory for Windows 95-based systems and all the 16-bit versions of Windows.

    But even in the 16-bit world, if it was always %windir%\SYSTEM, why have a function for it?

    Because even in the 16-bit world, it wasn't always %windir%\SYSTEM.

    Back in the old days, you could run Windows directly over the network. All the system files were kept on the network server, and only the user's files were kept on the local machine. What's more, every single computer on the network used the same system directory on the server. There was only one copy of USER.EXE, for example, which everybody shared.

    Under this network-based Windows configuration, the system directory was a directory on a server somewhere (\\server\share\somewhere) and the Windows directory was a directory on the local machine (C:\WINDOWS). Clients did not have write permission into the shared system directory, but they did have permission to write into the Windows directory.

    That's why GetSystemDirectory is a separate function.

  • The Old New Thing

    The macros for declaring and implementing COM interfaces

    • 16 Comments

    There are two ways of declaring COM interfaces, the hard way and the easy way.

    The easy way is to use an IDL file and let the MIDL compiler generate your COM interface for you. If you let MIDL do the work, then you also get __uuidof support at no extra charge, which is a very nice bonus.

    The hard way is to do it all by hand. If you choose this route, your interface will look something like this:

    #undef  INTERFACE
    #define INTERFACE   ISample2
    
    DECLARE_INTERFACE_(ISample2, ISample)
    {
        BEGIN_INTERFACE
    
        // *** IUnknown methods ***
        STDMETHOD(QueryInterface)(THIS_ REFIID riid, void **ppv) PURE;
        STDMETHOD_(ULONG,AddRef)(THIS) PURE;
        STDMETHOD_(ULONG,Release)(THIS) PURE;
    
        // ** ISample methods ***
        STDMETHOD(Method1)(THIS) PURE;
        STDMETHOD_(int, Method2)(THIS) PURE;
    
        // *** ISample2 methods ***
        STDMETHOD(Method3)(THIS_ int iParameter) PURE;
        STDMETHOD_(int, Method4)(THIS_ int iParameter) PURE;
    
        END_INTERFACE
    };
    

    What are the rules?

    • You must set the INTERFACE macro to the name of the interface being declared. Note that you need to #undef any previous value before you #define the new one.
    • You must use the DECLARE_INTERFACE and DECLARE_INTERFACE_ macros to generate the preliminary bookkeeping for an interface. Use DECLARE_INTERFACE for interfaces that have no base class and DECLARE_INTERFACE_ for interfaces that derive from some other interface. In our example, we derive the ISample2 interface from ISample. Note: In practice, you will never find the plain DECLARE_INTERFACE macro because all interfaces derive from IUnknown if nothing else.
    • You must list all the methods of the base interfaces in exactly the same order that they are listed by that base interface; the methods that you are adding in the new interface must go last.
    • You must use the STDMETHOD or STDMETHOD_ macros to declare the methods. Use STDMETHOD if the return value is HRESULT and STDMETHOD_ if the return value is some other type.
    • If your method has no parameters, then the argument list must be (THIS). Otherwise, you must insert THIS_ immediately after the open-parenthesis of the parameter list.
    • After the parameter list and before the semicolon, you must say PURE.
    • Inside the curly braces, you must say BEGIN_INTERFACE and END_INTERFACE.

    There is a reason for each of these rules. They have to do with being able to use the same header for both C and C++ declarations and with interoperability with different compilers and platforms.

    • You must set the INTERFACE macro because its value is used by the THIS and THIS_ macros later.
    • You must use one of the DECLARE_INTERFACE* macros to ensure that the correct prologue is emitted for both C and C++. For C, a vtable structure is declared, whereas for C++ the compiler handles the vtable automatically; on the other hand, since C++ has inheritance, the macros need to specify the base class so that upcasting will work.
    • You must list the base class methods in exactly the same order as in the original declarations so that the C vtable structure for your derived class matches the structure for the base class for the extent that they overlap. This is required to preserve the COM rule that a derived interface can be used as a base interface.
    • You must use the STDMETHOD and STDMETHOD_ macros to ensure that the correct calling conventions are declared for the function prototypes. For C, the macro creates a function pointer in the vtable; for C++, the macro creates a virtual function.
    • The THIS and THIS_ macros are used so that the C declaration explicitly declares the "this" parameter which in C++ is implied. Different versions are needed depending on the number of parameters so that a spurious trailing comma is not generated in the zero-parameter case.
    • The word PURE ensures that the C++ virtual function is pure, because one of the defining characteristics of COM interfaces is that all methods are pure virtual.
    • The BEGIN_INTERFACE and END_INTERFACE macros emit compiler-specific goo which the compiler vendor provides in order to ensure that the generated interface matches the COM vtable layout rules. Different compilers have historically required different goo, though the need for goo is gradually disappearing over time.

    And you wonder why I called it "the hard way".

    Similar rules apply when you are implementing an interface. Use the STDMETHODIMP and STDMETHODIMP_ macros to declare your implementations so that they get the proper calling convention attached to them. We'll see examples of this next time.

  • The Old New Thing

    Sometimes the bug isn't apparent until late in the game

    • 42 Comments

    I didn't debug it personally, but I know the people who did. During Windows XP development, a bug arrived on a computer game that crashed only after you got to one of the higher levels.

    After many saved and restored games, the problem was finally identified.

    The program does its video work in an offscreen buffer and transfers it to the screen when it's done. When it draws text with a shadow, it first draws the text in black, offset down one and right one pixel, then draws it again in the foreground color.

    So far so good.

    Except that it didn't check whether moving down and right one pixel was going to go beyond the end of the screen buffer.

    That's why it took until one of the higher levels before the bug manifested itself. Not until then did you accomplish a mission whose name contained a lowercase letter with a descender! Shifting the descender down one pixel caused the bottom row of pixels in the character to extend past the video buffer and start corrupting memory.

    Once the problem was identified, fixing it was comparatively easy. The application compatibility team has a bag of tricks, and one of them is called "HeapPadAllocation". This particular compatibility fix adds padding to every heap allocation so that when a program overruns a heap buffer, all that gets corrupted is the padding. Enable that fix for the bad program (specifying the amount of padding necessary, in this case, one row's worth of pixels), and run through the game again. No crash this time.

    What made this interesting to me was that you had to play the game for hours before the bug finally surfaced.

  • The Old New Thing

    Why is the page size on ia64 8K?

    • 31 Comments

    On x86 machines, Windows chooses a page size of 4K because that was the only page size supported by that architecture at the time the operating system was designed. (4MB pages were added to the CPU later, in the Pentium as I recall, but clearly that is too large for everyday use.)

    For the ia64, Windows chose a page size of 8K. Why 8K?

    It's a balance between two competing objectives. Large page sizes allow more efficient I/O since you are reading twice as much data at one go. However large page sizes also increase the likelihood that the extra I/O you perform is wasted because of poor locality.

    Experiments were run on the ia64 with various page sizes (even with 64K pages, which were seriously considered at one point), and 8K provided the best balance.

    Note that changing the page size creates all sorts of problems for compatibility. There are large numbers of programs out there that blindly assume that the page size is 4K. Boy are they in for a surprise.

  • The Old New Thing

    Why does Windows keep your BIOS clock on local time?

    • 44 Comments

    Even though Windows NT uses UTC internally, the BIOS clock stays on local time. Why is that?

    There are a few reasons. One is a chain of backwards compatibility.

    In the early days, people often dual-booted between Windows NT and MS-DOS/Windows 3.1. MS-DOS and Windows 3.1 operate on local time, so Windows NT followed suit so that you wouldn't have to keep changing your clock each time you changed operating systems.

    As people upgraded from Windows NT to Windows 2000 to Windows XP, this choice of time zone had to be preserved so that people could dual-boot between their previous operating system and the new operating system.

    Another reason for keeping the BIOS clock on local time is to avoid confusing people who set their time via the BIOS itself. If you hit the magic key during the power-on self-test, the BIOS will go into its configuration mode, and one of the things you can configure here is the time. Imagine how confusing it would be if you set the time to 3pm, and then when you started Windows, the clock read 11am.

    "Stupid computer. Why did it even ask me to change the time if it's going to screw it up and make me change it a second time?"

    And if you explain to them, "No, you see, that time was UTC, not local time," the response is likely to be "What kind of totally propeller-headed nonsense is that? You're telling me that when the computer asks me what time it is, I have to tell it what time it is in London? (Except during the summer in the northern hemisphere, when I have to tell it what time it is in Reykjavik!?) Why do I have to remember my time zone and manually subtract four hours? Or is it five during the summer? Or maybe I have to add. Why do I even have to think about this? Stupid Microsoft. My watch says three o'clock. I type three o'clock. End of story."

    (What's more, some BIOSes have alarm clocks built in, where you can program them to have the computer turn itself on at a particular time. Do you want to have to convert all those times to UTC each time you want to set a wake-up call?)

Page 39 of 51 (506 items) «3738394041»