• The Old New Thing

    Windows are not cheap objects

    • 73 Comments

    Although Windows is centered around, well, windows, a window itself is not a cheap object. What's more, the tight memory constraints of systems of 1985 forced various design decisions.

    Let's take for example the design of the list box control. In a modern design, you might design the list box control as accepting a list of child windows, each of which represents an entry in the list. A list box with 20,000 items would have 20,000 child windows.

    That would have been completely laughable in 1985.

    Recall that Windows was built around a 16-bit processor. Window handles were 16-bit values and internally were just near pointers into a 64K heap. A window object was 88 bytes (I counted), which means that you could squeeze in a maximum of 700 or so before you ran out of memory. What's more, menus hung out in this same 64K heap, so the actual limit was much lower.

    Even if the window manager internally used a heap larger than 64K (which Windows 95 did), 20,000 windows comes out to over 1.5MB. Since the 8086 had a maximum address space of 1MB, even if you devoted every single byte of memory to window objects, you'd still not have enough memory.

    Furthermore, making each list box item a window means that every list box would be a variable-height list box, which carries with it the complexity of managing a container with variable-height items. This goes against two general principles of API design: (1) simple things should be simple, and (2) "pay-for-play", that if you are doing the simple thing, you shouldn't have to pay the cost of the complex thing.

    Filling a list box with actual windows also would have made the "virtual list box" design significantly trickier. With the current design, you can say, "There are a million items" without actually having to create them.

    (This is also why the window space is divided into "client" and "non-client" areas rather than making the non-client area consist of little child windows.)

    To maintain compatibility with 16-bit Windows programs (which still run on Windows XP thanks to the WOW layer), there cannot be more than 65536 window handles in the system, because any more than that would prevent 16-bit programs from being able to talk meaningfully about windows. (Once you create your 65537'th window, there will be two windows with the same 16-bit handle value, thanks to the pigeonhole principle.)

    (And yes, 16/32-bit interoperability is still important even today.)

    With a limit of 65536 window handles, your directory with 100,000 files in it would be in serious trouble.

    The cost of a window object has grown over time, as new features get added to the window manager. Today it's even heftier than the svelte 88 bytes of yesteryear. It is to your advantage not to create more windows than necessary.

    If your application design has you creating thousands of windows for sub-objects, you should consider moving to a windowless model, like Internet Explorer, Word, list boxes, treeview, listview, and even our scrollbar sample program. By going windowless, you shed the system overhead of a full window handle, with all the baggage that comes with it. Since window handles are visible to all processes, there is a lot of overhead associated with centrally managing the window list. If you go windowless, then the only program that can access your content is you. You don't have to worry about marshalling, cross-process synchronization, Unicode/ANSI translation, external subclassing, hooks... And you can use a gigabyte of memory to keep track of your windowless data if that's what you want, since your windowless controls don't affect any other processes. The fact that window handles are accessible to other processes imposes a practical limit on how many of them can be created without impacting the system as a whole.

    I believe that WinFX uses the "everything on the screen is an element" model. It is my understanding that they've built a windowless framework so you don't have to. (I'm not sure about this, though, not being a WinFX person myself.)

  • The Old New Thing

    Dot-Con Job: How InfoSpace took its investors for a ride

    • 8 Comments

    The Seattle Times ran an excellent series last week on the rise and fall of InfoSpace and its charismatic leader, Naveen Jain, who at one point even used the phrase "cult leader" to refer to himself.

    To set the tone, and perhaps to serve as a reference while you read the series, here's a list of reported Infospace earnings per share (EPS), both pro-forma and following Generally Accepted Accounting Principles (GAAP), as reported in their SEC filings and press releases, illustrated with quotes from contemporary press releases (attributed to Naveen Jain unless otherwise noted).

      EPS    
    Period Pro-forma GAAP Quote from press release Remarks
    1999 Q1   -3¢ We executed flawlessly on our plan. Our performance this quarter is a clear demonstration of our undisputed position as a leader in providing Internet infrastructure services.  
    1999 Q2 +1¢
    −2¢
    −8¢ This demonstrates that growth and profitability are not mutually exclusive. Achieving profitability two quarters ahead of Wall Street's expectations demonstrates the strength of our business model. This press release begins the policy of not even mentioning GAAP results.
    1999 Q3 +6¢
    −2¢
    +2¢ We had a totally awesome quarter.  
    1999 Q4 +10¢
    −3¢
    ?
    −3¢
    InfoSpace is now synonymous with wireless Internet services. They did not appear to file a 10-Q with the SEC, so I couldn't find their contemporary GAAP EPS. A year later they claimed it was −3¢.
    2000 Q1 +1¢ −38¢ InfoSpace is leading the convergence of the two fastest growing industry segments in history—wireless and the Internet—creating a new industry: the wireless Internet.  
    2000 Q2 −1¢ −14¢ Today marks another historic milestone in the history of the rapid evolution of InfoSpace.  
    2000 Q3 +1¢ −30¢ This was an excellent quarter for InfoSpace, as we continued to build upon our market leadership in the globally-expanding wireless sector. Quote comes from Arun Sarin, CEO.
    2000 Q4 +4¢ −17¢ InfoSpace continues to expand its relationships and deliver value to wireless carriers proven by the significant revenue growth in our wireless business and the more than 1.5 million wireless subscribers.  
    2001 Q1 −2¢ −37¢ InfoSpace continues to demonstrate its strength and ability to generate new business and pursue favorable market opportunities.  
    2001 Q2 +1¢ −22¢ Our return to pro-forma profitability this quarter reconfirms the strength of our business model. I like how they're proud that they are profitable "once you ignore all that accounting stuff".
    2001 Q3 −3¢ −63¢ InfoSpace's performance this quarter underscores our continuing success in focusing on our high growth areas of wireless and merchant.  
    2001 Q4 −2¢ −32¢ We are feeling better about our near and long-term prospects and believe we have turned a corner in our business. In response to the Enron scandal, GAAP numbers are once again being reported.
    2002 Q1 −2¢ −77¢ Results this quarter demonstrate our ability to continue executing on our strategy.  
    2002 Q2 −2¢ −4¢ We are pleased to post another quarter of encouraging results.  
    2002 Q3 −0.2¢ −8.6¢ I'm pleased to report that our focus on profitability is paying off.  
    2002 Q4 +0.5¢ −20.7¢ Our team made significant progress this past year aligning costs with revenues. Quote comes from Jim Voelker, CEO.

    The stock underwent a 10-1 reverse split in September 2002; values have been adjusted to pre-split values for comparison purposes.

    Some boxes list two numbers. The top number is the value reported at the time of the press release. The bottom number is the value reported the following year. For example, in 1999 Q2, the press release claimed that they earned 1¢/share pro forma, but one year later, in the 2000 Q2 filing, they reported a 2¢ loss per share for 1999 Q2. [Years fixed, 10pm.] Why change the amount? Because it makes the 2000 Q2 results look better when compared to the "same period last year". I have no idea which set of numbers (if any!) is correct.

  • The Old New Thing

    A subtlety in restoring previous window position

    • 35 Comments

    A common feature for many applications is to record their screen location when they shut down and reopen at that location when relaunched. If implemented naively, a program merely restores from its previous position unconditionally.

    You run into usability problems with this naive implementation. If a user runs two copies of your program, the two windows end up in exactly the same place on the screen. Unless the user paid close attention to their taskbar, it looks like running the second copy had no effect. Now things get interesting.

    Depending on what the program does, the second copy may encounter a sharing violation, or it may merely open a second copy of the document for editing, or two copies of the song may start playing, resulting in a strange echo effect since the two copies are out of sync. Even more fun is if the user hits the Stop button and the music keeps playing! Why? Because only the second copy of the playback was stopped. The first copy is still running.

    I know one user who not infrequently gets as many as four copies of a multimedia title running, resulting in a horrific cacophany as they all play their attract music simultaneously, followed by mass confusion as the user tries to fix the problem, which usually consists of hammering the "Stop" button on the topmost copy. This stops the topmost instance, but the other three are still running...

    If a second copy of the document is opened, the user may switch away from the editor, then switch back to the first instance, and think that all the changes were lost. Or the user may fail to notice this and make a conflicting set of changes to the first instance. Then all sorts of fun things happen when the two copies of the same document are saved.

    Moral of the story: If your program saves and restores its screen position, you may want to check if a copy of the program is already running at that screen position. If so, then move your second window somewhere else so that it doesn't occupy exactly the same coordinates.

  • The Old New Thing

    VegFest 2005 this weekend - and - vegetarian is as vegetarian does

    • 35 Comments

    The weekend of March 12th and 13th, Vegetarians of Washington is hosting VegFest 2005, a festival of vegetarian food.

    This reminds me of a Time Magazine cover story from July 2002, wherein it was revealed that...

    In a survey of 11,000 individuals, 37% of those who responded "Yes, I am a vegetarian" also reported that in the previous 24 hours they had eaten red meat; 60% had eaten meat, poultry or seafood.
  • The Old New Thing

    Performance gains at the cost of other components

    • 64 Comments

    In the operating systems group, we have to take a holistic view of performance. The goal is to get the entire system running faster, balancing applications against each other for the greater good.

    Applications, on the other hand, tend to have a selfish view of performance: "I will do everything possible to make myself run faster. The impact on the rest of the system is not my concern."

    Some applications will put themselves into the Startup group so that they will load faster. This isn't really making the system run any faster; it's just shifting the accounting. By shoving some of the application startup cost into operating system startup, the amount of time between the user double-clicking the application icon and the application being ready to run has been reduced. But the total amount of time hasn't changed.

    For example, consider the following time diagram. The "*" marks the point at which the user turns on the computer, the "+" marks the point at which Explorer is ready and the user double-clicks the application icon, and the "!" marks the point at which the application is ready.

    * OS Startup + Application Startup !

    The application developers then say, "Gosh, that pink 'Application Startup' section is awfully big. What can we do to make it smaller? I know, let's break our application startup into two pieces...

    * OS Startup + Application Startup 1 Application Startup 2 !

    "... and put part of it in the Startup group.

    * OS Startup Application Startup 1 + Application Startup 2 !

    "Wow, look, the size of the pink bar (which represents how long it takes for our application to get ready after the user double-clicks the icon) is much shorter now!"

    The team then puts this new shorter value in their performance status report, everybody gets raises all around, and maybe they go for a nice dinner to celebrate.

    Of course, if you look at the big picture, from the asterisk all the way to the exclamation point, nothing has changed. It still takes the same amount of time for the application to be ready from a cold start. All this "performance" improvement did was rob Peter to pay Paul. The time spent doing "Application Startup 1" is now charged against the operating system and not against the application. You shuffled numbers around, but the end user gained nothing.

    In fact, the user lost ground. For the above diagrams assume that the user wants to run your application at all! If the user didn't want to run your application but instead just wanted to check their email, they are paying for "Application Startup 1" even though they will reap none of the benefits.

    Another example of applications having a selfish view of performance came from a company developing an icon overlay handler. The shell treats overlay computation as a low-priority item, since it is more important to get icons on the screen so the user can start doing whatever it is they wanted to be doing. The decorations can come later. This company wanted to know if there was a way they could improve their performance and get their overlay onto the screen even before the icon shows up, demonstrating a phenomenally selfish interpretation of "performance".

    Performance is about getting the user finished with their task sooner. If that task does not involve running your program, then your "performance improvement" is really a performance impediment. I'm sure your program is very nice, but it would also be rather presumptuous to expect that every user who installs your program thinks that it should take priority over everything else they do.

  • The Old New Thing

    Why does SystemParametersInfo hang when I pass the SPIF_SENDCHANGE flag?

    • 6 Comments

    If you pass the SPIF_SENDCHANGE flag to the SystemParametersInfo function, it will broadcast the WM_SETTINGCHANGE message with the wParam equal to the system parameter code you passed. For example, if you call

    SystemParametersInfo(SPI_SETDOUBLECLICKTIME,
          500, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
    

    then the system will broadcast the message

    SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
                SPI_SETDOUBLECLICKTIME, 0);
    

    If there is a window that isn't responding to messages, then this broadcast will hang until that unresponsive window finally resumes responding to messages or is killed.

    If you'd rather not be victimed by unresponsive windows, you have a few options, but it also may affect your program's expectations.

    You could issue the SystemParametersInfo call on a background thread. Then your background thread is the one that blocks instead of your UI thread.

    With this message, the background thread can notify the main thread when the broadcast finally completes, at which point your program now knows that all windows have received their notifications and are on board with the new setting.

    You could issue the SystemParametersInfo call without the SPIF_SENDCHANGE flag, then manually broadcast the change via

    DWORD dwResult;
    SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE,
                SPI_SETDOUBLECLICKTIME, 0,
                SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG,
                5000, &dwResult);
    

    This does mean that unresponsive windows will not receive the notification that a system parameter has changed. This is acceptable if you decide that your change in settings was minor enough that a program missing the notification is no big deal. In other words, when the unresponsive program finally wakes up, it will not know that the setting has changed since it missed the notification.

    You can combine the above two methods: Use a background thread and send the message with a timeout.

    Perhaps the best technique is to use the SendNotifyMessage function. As we learned earlier, the SendNotifyMessage function is like SendMessage except that it doesn't wait for a response. This lets your program get back work while not messing up programs that were momentarily unresponsive when you decided to broadcast the notification.

    SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
                SPI_SETDOUBLECLICKTIME, 0);
    

    The downside is that you don't know when all windows have finally received and processed the notification. All you know is that someday, they will eventually find out. Usually you don't care about this aspect of the broadcast, so this lack of information is not an impediment.

  • The Old New Thing

    Using SystemParametersInfo to access user interface settings

    • 54 Comments

    The SystemParametersInfo function gives you access to a whole slew of user interface settings, and it is the only supported method for changing those settings.

    I'm not going to list every single setting; go read the list yourself. Here are some highlights:

    • SPI_GETICONTITLELOGFONT lets you query the font that is used for icon labels; SPI_SETICONTITLELOGFONT lets you change it.
    • SPI_GETNONCLIENTMETRICS lets you query the fonts that are used for window captions, menus, status bars, and message boxes; SPI_SETNONCLIENTMETRICS lets you change them.

    Here are some control panel settings.

    • SPI_SETKEYBOARDDELAY and SPI_SETKEYBOARDSPEED let you set the keyboard autorepeat parameters.
    • SPI_SETDOUBLECLICKTIME lets you set the mouse double-click speed.
    • SPI_SETMENUFADE lets you enable or disable the menu fade animation. [Typo fixed, 4pm.]
    • There is a whole series of SPI_SETxxxANIMATION settings that let you control which screen elements animate.

    Notice that when using the SPI_SET* commands, you also have to choose whether the setting changes are temporary (lost at logoff) or persistent. The historically-named SPIF_UPDATEINIFILE flag causes the changes to be saved to the user profile; if you leave it off, then the changes are not saved and are lost when the user logs off. You should also set the SPIF_SENDCHANGE flag so that programs which want to refresh themselves in response to changes in the settings can do so.

    The fact that there exist both temporary and persistent changes highlights the danger of accessing the registry directly to read or write the current settings. If the current settings are temporary, then they are not saved in the registry. The SystemParametersInfo function retrieves the actual current settings, including temporary ones. For example, if you want to query whether menus are being animated, and the user has temporarily disabled animation, reading the registry will tell you that they are being animated when in fact they are not.

    Also, changes written to the registry don't take effect untll the next logon, because that is the only time the values are consulted. To make a change take effect immediately, you must use SystemParametersInfo.

    It still puzzles me why people go to the undocumented registry keys to change these settings when there is a perfectly good documented function for doing it. Especially when the documented function works and the undocumented registry key is unreliable.

    I remember one application that went straight for the undocumented registry keys (to get the icon title font, I think). Unfortunately for the application, the format of the registry key is different between Windows 95 and Windows 2000, and it ended up crashing. (It expected the Windows 95 format.) If it had used the documented method of retrieving the icon title font, it would have worked fine. In other words, this program went out of its way to go around the preferred way of doing something and got hoist by its own petard.

  • The Old New Thing

    Keep your eye on the code page

    • 47 Comments

    Remember that there are typically two 8-bit code pages active, the so-called "ANSI" code page and the so-called "OEM" code page. GUI programs usually use the ANSI code page for 8-bit files (though utf-8 is becoming more popular lately), whereas console programs usually use the OEM code page.

    This means, for example, when you open an 8-bit text file in Notepad, it assumes the ANSI code page. But if you use the TYPE command from the command prompt, it will use the OEM code page.

    This has interesting consequences if you switch between the GUI and the command line frequently.

    The two code pages typically agree on the first 128 characters, but they nearly always disagree on the characters from 128 to 255 (so-called "extended characters"). For example, on a US-English machine, character 0x80 in the OEM code page is Ç, whereas in the ANSI code page it is €.

    Consider a directory which contains a file named Ç. If you type "dir" at a command prompt, you see a happy Ç on the screen. On the other hand, if you do "dir >files.txt" and open files.txt in a GUI editor like Notepad, you will find that the Ç has changed to a €, because the 0x80 in the file is being interpreted in the ANSI character set instead of the OEM character set.

    Stranger yet, if you mark/select the file name from the console window and paste it into Notepad, you get a Ç. That's because the console window's mark/select code saves text on the clipboard as Unicode; the character saved into the clipboard is not 0x80 but rather U+00C7, the Unicode code point for "Latin Capital Letter C With Cedilla". When this is pasted into Notepad, it gets converted from Unicode to the ANSI code page, which on a US-English system encodes the Ç character as 0xC7.

    But wait, there's more. The command processor has an option (/U) to generate all piped and redirected output in Unicode rather than the OEM code page.

    (Note that the built-in documentation for the command processor says that the /A switch produces ANSI output; this is incorrect. /A produces OEM output. This is one of those bugs that you recognize instantly if you are familiar with what is going on. It's so obviously OEM that when I see the documentation say "ANSI", my mind just reads it as "OEM". In the same way native English speakers often fail to notice misspellings or doubled words.)

    If you run the command

    cmd /U /C dir ^>files.txt
    

    then the output will be in Unicode and therefore will record the Ç character as U+00C7, which Notepad will then be able to read back.

    This has serious consequences for batch files.

    Batch files are 8-bit files and are interpreted according to the OEM character set. This means that if you write a batch file with Notepad or some other program that uses the ANSI character set for 8-bit files, and your batch file contains extended characters, the results you get will not match the what you see in your editor.

    Why the discrepancy between GUI programs and console programs over how 8-bit characters should be interpreted?

    The reason is, of course, historical.

    Back in the days of MS-DOS, the code page was what today is called the OEM code page. For US-English systems, this is the code page with the box-drawing characters and the fragments of the integral signs. It contained accented letters, but not a very big set of them, just enough to cover the German, French, Spanish, and Italian languages. And Swedish. (Why Swedish yet not Danish and Norwegian I don't know.)

    When Windows came along, it decided that those box-drawing characters were wasting valuable space that could be used for adding still more accented characters, so out went the box-drawing characters and in went characters for Danish, Norwegian, Icelandic, and Canadian French. (Yes, Canadian French uses characters that European French does not.)

    Thus began the schism between console programs (MS-DOS) and GUI programs (Windows) over how 8-bit character data should be interpreted.

  • The Old New Thing

    A timed context menu

    • 25 Comments

    This is sort of in the same spirit as our previous exercise in writing a timed message box, but this is much easier. Here, we use the handy-dandy WM_CANCELMODE message to get us out of menu mode.

    void CALLBACK
    MenuTooLateProc(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime)
    {
      SendMessage(hwnd, WM_CANCELMODE, 0, 0);
    }
    
    BOOL
    TimedTrackPopupMenuEx(HMENU hMenu, UINT uFlags, int x, int y,
        HWND hwnd, LPTPMPARAMS pTpm, DWORD dwTimeout)
    {
        UINT idTimer = SetTimer(NULL, IDT_TOOLATE, dwTimeout, MenuTooLateProc);
        BOOL fResult = TrackPopupMenuEx(hMenu, uFlags, x, y, hwnd, pTpm);
        if (idTimer) KillTimer(NULL, idTimer);
        return fResult;
    }
    

    Before displaying the menu, we set a timer. (And we use a thread timer because we don't own the hwnd window and therefore don't know what timer IDs are safe to use.) If the timer fires, we send ourselves a WM_CANCELMODE message to cancel menu mode. This causes the system to act as if the user had dismissed the menu without selecting anything, either by hitting ESC or clicking outside the menu. The call to the TrackPopupMenuEx function returns once the user has selected something (or the timeout has elapsed), at which point we clean up by destroying our timer before returning.

  • The Old New Thing

    Modality, part 8: A timed MessageBox, the better version

    • 16 Comments

    A few days ago, we saw a simple version of a timed message box which had a limitation that it could be used from only one thread at a time. Today we'll work to remove that limitation.

    As you may recall, the reason why it could be used from only one thread at a time was that we kept the "Did the message box time out?" flag in a global. To fix it, we will move the flag to a per-instance location, namely a helper window.

    Start with the scratch program, add the code for the scratch window class, change the name of the scratch window class so it doesn't conflict with the class name of the scratch program (thanks to reader Adrian for pointing this out), then add the following:

    #define IDT_TOOLATE     1
    
    typedef struct TOOLATEINFO {
     BOOL fTimedOut;
     HWND hwndReenable;
    } TOOLATEINFO;
    
    void CALLBACK
    MsgBoxTooLateProc(HWND hwnd, UINT uiMsg, UINT_PTR idEvent, DWORD dwTime)
    {
      TOOLATEINFO *ptli = reinterpret_cast<TOOLATEINFO*>(
        GetWindowLongPtr(hwnd, GWLP_USERDATA));
      if (ptli) {
        ptli->fTimedOut = TRUE;
        if (ptli->hwndReenable) {
           EnableWindow(ptli->hwndReenable, TRUE);
        }
        PostQuitMessage(42);
      }
    }
    
    int TimedMessageBox(HWND hwndOwner, LPCTSTR ptszText,
        LPCTSTR ptszCaption, UINT uType, DWORD dwTimeout)
    {
      TOOLATEINFO tli;
      tli.fTimedOut = FALSE;
      BOOL fWasEnabled = hwndOwner && IsWindowEnabled(hwndOwner);
      tli.hwndReenable = fWasEnabled ? hwndOwner : NULL;
    
      HWND hwndScratch = CreateScratchWindow(hwndOwner, DefWindowProc);
      if (hwndScratch) {
          SetWindowLongPtr(hwndScratch, GWLP_USERDATA,
                           reinterpret_cast<LPARAM>(&tli));
          SetTimer(hwndScratch, IDT_TOOLATE, dwTimeout, MsgBoxTooLateProc);
      }
      int iResult = MessageBox(hwndOwner, ptszText, ptszCaption, uType);
      if (hwndScratch) {
        KillTimer(hwndScratch, IDT_TOOLATE);
        if (tli.fTimedOut) { // We timed out
          MSG msg;
          // Eat the fake WM_QUIT message we generated
          PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
          iResult = -1;
        }
        DestroyWindow(hwndScratch);
      }
      return iResult;
    }
    
    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
    {
      switch (ch) {
      case ' ':
        TimedMessageBox(hwnd, TEXT("text"), TEXT("caption"),
                        MB_OK, 2000);
        break;
      }
    }
    
    // add to WndProc
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
    
    // add to InitApp
        RegisterScratchWindowClass();
    

    This is basically the same as the previous cheap version, just with slightly different bookkeeping.

    The state of the timed message box is kept in the structure TOOLATEINFO. But how to pass this state to the timer callback? You can't pass any parameters to timer callbacks.

    Aha, but timer callbacks do get a window handle. But as we discovered a few days ago, we can't just hang the callback off the hwndOwner window because we don't know how to pick a timer ID that doesn't conflict with an existing one.

    The solution: Hang it on a window of our own window creation. That way, we get a whole new space of timer IDs to play in, separate from the timer IDs that belong to hwndOwner. The scratch window is a convenient window to use. We don't pass an interesting window procedure to CreateScratchWindow because there is no need; all we wanted was a window to own our timer.

Page 368 of 436 (4,356 items) «366367368369370»