• 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

    Sometimes you can't read the text under the cursor

    • 13 Comments

    I had previously written on how you can retrieve the text under the cursor, and you may have noticed that it produces mixed results. It works great with some programs but not with others.

    It depends on the program in question. Some programs were written with greater attention to supporting screen readers than others. Internet Explorer, for example, has excellent support for ActiveAccessibility because browsing the web is a great way for people with disabilities to get involved in the world around them.

    Other programs don't do quite as good a job. For example, the program we developed to demonstrate various scrollbar techniques does not handle the WM_GETOBJECT message and is not accessible.

    So whether ActiveAccessibility works for any particular program depends heavily on how much the author of that program had accessibility in mind when they wrote it.

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

  • The Old New Thing

    The strangest way of detecting Windows NT

    • 20 Comments

    A colleague of mine nominated this code for Function of the Year. (This is the same person who was the first to report that a Windows beta used a suspect URL.) I have to admit that this code is pretty impressive. Of all the ways to check the operating system, you have to agree that sniffing at an undocumented implementation detail of memory-mapped files is certainly creative!

    // following the typographical convention that code
    // in italics is wrong
    int AreWeRunningOnWindowsNT()
    {
          HANDLE hFile, hFileMapping;
          BYTE *pbFile, *pbFile2;
          char szFile[MAX_PATH];
    
          GetSystemDirectory(szFile, MAX_PATH);
          strcat(szFile, "\\MAIN.CPL");
          hFile = CreateFile(szFile, GENERIC_READ | GENERIC_WRITE, 0,
                NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    
          hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE,
                0, 0, NULL);
    
          pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE,
                0, 0, 0);
    
          pbFile2 = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_WRITE,
                0, 65536, 0);
    
          if (pbFile + 65536 != pbFile2)
                return 1;
    
          return 0;
    }
    

    Nevermind that the function also leaves a file locked and leaks two handles and two views each time you call it!

    What's more, this function may erroneously report FALSE on a Windows NT machine if by an amazing coincidence the memory manager happens to assign the second file view to the very next 64K block of memory (which it is permitted to do since address space granularity is 64K).

    It can also erroneously report TRUE on a Windows 95 machine if the MAIN.CPL file happens to be smaller than 64K, or if you don't have write permission on the file. (Notice that the program requests read-write access to the MAIN.CPL file.)

    This particular function is from a library that is used by many popular multimedia titles.

    The quickest way to detect whether you are running on a Windows 95-series system or a Windows NT-series system is to use the hopefully-obviously-named function GetVersion.

    int AreWeRunningOnWindowsNT()
    {
        return (GetVersion() & 0x80000000) == 0;
    }
    

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

  • The Old New Thing

    Accessing the current module's HINSTANCE from a static library

    • 21 Comments

    If you're writing a static library, you may have need to access the HINSTANCE of the module that you have been linked into. You could require that the module that links you in pass the HINSTANCE to a special initialization function, but odds are that people will forget to do this.

    If you are using a Microsoft linker, you can take advantage of a pseudovariable which the linker provides.

    EXTERN_C IMAGE_DOS_HEADER __ImageBase;
    #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
    

    The pseudovariable __ImageBase represents the DOS header of the module, which happens to be what a Win32 module begins with. In other words, it's the base address of the module. And the module base address is the same as its HINSTANCE.

    So there's your HINSTANCE.

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

  • The Old New Thing

    If the Euro 2004 tournament were a video game

    • 7 Comments

    If the Euro 2004 tournament were a video game, it might have looked like this. View recreations of the tournament highlights, and since the graphics are computer-generated, you can select among multiple camera angles, embody a specific player, or see what it's like to be the ball itself!

    (Via MetaFilter.)

    [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

    Let WMI do the heavy lifting of determining system information

    • 35 Comments

    Windows Management Instrumentation is a scriptable interface to configuration information. This saves you the trouble of having to figure it out yourself.

    For example, here's a little program that enumerates all the CPUs in your system and prints some basic information about them.

    var locator = WScript.CreateObject("WbemScripting.SWbemLocator");
    var services = locator.ConnectServer();
    var cpus = new Enumerator(services.ExecQuery("SELECT * FROM Win32_Processor"));
    while (!cpus.atEnd()) {
      var cpu = cpus.item();
      WScript.StdOut.WriteLine("cpu.ProcessorType=" + cpu.ProcessorType);
      WScript.StdOut.WriteLine("cpu.CurrentClockSpeed=" + cpu.CurrentClockSpeed);
      WScript.StdOut.WriteLine("cpu.MaxClockSpeed=" + cpu.MaxClockSpeed);
      WScript.StdOut.WriteLine("cpu.Manufacturer=" + cpu.Manufacturer);
      WScript.StdOut.WriteLine();
      cpus.moveNext();
    }
    

    Save this program as cpus.js and run it via cscript cpus.js.

    There's a whole lot of other information kept inside WMI. You can get lost amidst all the classes that exist. The Scripting Guys have their own tool called WMI Scriptomatic which lets you cruise around the WMI namespace. (The Scripting Guys also wrote Tweakomatic which comes with hilarious documentation.)

    Added 11am: It appears that people have misunderstood the point of this entry. The point here is not to show how to print the results to the screen. (I just did that to prove it actually worked.) The point is that you can let WMI do the hard work of actually digging up the information instead of having to hunt it down yourself. Want BIOS information? Try Win32_BIOS. Change the query to "SELECT * FROM Win32_BIOS" and you can get the manufacturer from the Manufacturer property. Plenty more examples in MSDN.

  • 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

    Implementing higher-order clicks

    • 35 Comments

    Another question people ask is "How do I do triple-click or higher?" Once you see the algorithm for double-clicks, extending it to higher order clicks should be fairly natural. The first thing you probably should do is to remove the CS_DBLCLKS style from your class because you want to do multiple-click management manually.

    Next, you can simply reimplement the same algorithm that the window manager uses, but take it to a higher order than just two. Let's do that. Start with a clean scratch program and add the following:

    int g_cClicks = 0;
    RECT g_rcClick;
    DWORD g_tmLastClick;
    
    void OnLButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
    {
      POINT pt = { x, y };
      DWORD tmClick = GetMessageTime();
    
      if (!PtInRect(&g_rcClick, pt) ||
          tmClick - g_tmLastClick > GetDoubleClickTime()) {
        g_cClicks = 0;
      }
      g_cClicks++;
    
      g_tmLastClick = tmClick;
      SetRect(&g_rcClick, x, y, x, y);
      InflateRect(&g_rcClick,
                  GetSystemMetrics(SM_CXDOUBLECLK) / 2,
                  GetSystemMetrics(SM_CYDOUBLECLK) / 2);
    
      TCHAR sz[20];
      wnsprintf(sz, 20, TEXT("%d"), g_cClicks);
      SetWindowText(hwnd, sz);
    }
    
    void ResetClicks()
    {
      g_cClicks = 0;
      SetWindowText(hwnd, TEXT("Scratch"));
    }
    
    void OnActivate(HWND hwnd, UINT state, HWND, BOOL)
    {
      ResetClicks();
    }
    
    void OnRButtonDown(HWND hwnd, BOOL fDoubleClick,
                       int x, int y, UINT keyFlags)
    {
      ResetClicks();
    }
    
        HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
        HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);
    

    [Boundary test for double-click time corrected 10:36am.]

    (Our scratch program doesn't use the CS_DBLCLKS style, so we didn't need to remove it - it wasn't there to begin with.)

    The basic idea here is simple: When a click occurs, we see if it is in the "double-click zone" and has occurred within the double-click time. If not, then we reset the consecutive click count.

    (Note that the SM_CXDOUBLECLK and SM_CYDOUBLECLK values are the width of the entire rectangle, so we cut it in half when inflating so that the rectangle extends halfway in either direction. Yes, this means that a pixel is lost if the double-click width is odd, but Windows has been careful always to set the value to an even number.)

    Next, we record the coordinates and time of the current click so the next click can compare against it.

    Finally, we react to the click by putting the consecutive click number in the title bar.

    There are some subtleties in this code. First, notice that setting g_cClicks to zero forces the next click to be treated as the first click in a series, for regardless of whether it matches the other criteria, all that will happen is that the click count increments to 1.

    Next, notice that the way we test whether the clicks occurred within the double click time was done in a manner that is not sensitive to timer tick rollover. If we had written

          tmClick > g_tmLastClick + GetDoubleClickTime()) {
    

    then we would fail to detect multiple clicks properly near the timer tick rollover. (Make sure you understand this.)

    Third, notice that we reset the click count when the window gains or loses activation. That way, if the user clicks, then switches away, then switches back, and then clicks again, that is not treated as a double-click. We do the same if the user clicks the right mouse button in between. (You may notice that few programs bother with quite this much subtlety.)

    Exercise: Suppose your program isn't interested in anything beyond triple-clicks. How would you change this program in a manner consistent with the way the window manager stops at double-clicks?

Page 370 of 425 (4,249 items) «368369370371372»