• The Old New Thing

    Experiencing the world from flight level 210

    • 8 Comments

    Here are some airline-related web logs that I follow.

    • Fly with Me, a sort-of monthly podcast from a professional airline pilot. A glimpse into the world behind that cockpit door.
    • Flight Attendant Betty, another podcast, this one from your friendly flight attendant. (Though I have to admit, in Episode 2, I skipped over the disaster story and listened just to the stories of the world's stupidest hijackers.)
    • Yu Hu Stewardess, a flight attendant who isn't afraid to talk about all the stupid passengers she has to put up with (Do you recognize yourself?) and talk about what life is like when you spend a significant fraction of your nights away from home.
    • Doing Boeing, the web log of a Boeing database administrator. From the original technology company in the Seattle area.
    • The 777 Flight Test Journal. I had been looking for something to replace Randy Baseler's blog in my Boeing blogroll because Randy's was just all marketing hot air. The Flight Test Journal still feels a bit too heavily-managed, but it's a big improvement.

    Ry Jones recommends FlightAware, a web site for all your planespotting needs.

  • The Old New Thing

    The dangers of playing focus games when handling a WM_KILLFOCUS message

    • 8 Comments

    I had noted last year that WM_KILLFOCUS is the wrong time to do field validation. Here's another example of how messing with the focus during a WM_KILLFOCUS message can create confusion.

    Consider an edit control that displays feedback via a balloon tip. For example, password edit controls often warn you if you're typing your password while CapsLock is in effect. One of the things you probably want to do is to remove the balloon tip if the user moves focus to another control, since there's no point telling the user about a problem with something they aren't using. You might be tempted to subclass the edit control and do something like this:

    LRESULT CALLBACK EditSubclass(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      switch (uMsg) {
      ...
      case WM_KILLFOCUS:
        if (hwndBalloonTip) {
          DestroyWindow(hwndBalloonTip);
          hwndBalloonTip = NULL;
        }
        break;
      ...
      }
      return CallWindowProc(prevWndProc, hwnd, uMsg, wParam, lParam);
    }
    

    When you give this code a shot, it works great... unless the user clicks on the balloon tip itself the edit control's caret (the blinking insertion point thingie) disappears. What happened?

    What happened is that you gummed up the focus change process by destroying the window that focus was going to! The focus change process goes like this:

    • Put focus on new focus window.
    • Send WM_KILLFOCUS to old focus window (if any).
    • Send WM_SETFOCUS to new focus window (if any).

    But in the second step, we destroyed the new focus window. When the focus window is destroyed, the window manager tries to find a new focus window, and it settles upon the edit control itself. This starts a recursive focus change cycle, telling the edit control that it now has focus again.

    Let's look at the flow in this nested focus change scenario when the user clicks on the tooltip window.

    • Put focus on tooltip.
    • Send WM_KILLFOCUS to edit control.
      • EditSubclass destroys the tooltip.
        • Window manager puts focus on the edit control.
        • Nobody to send WM_KILLFOCUS to.
        • Send WM_SETFOCUS to edit control.
          • EditSubclass passes WM_SETFOCUS to the original window procedure.
      • EditSubclass passes WM_KILLFOCUS to the original window procedure.
    • Send WM_SETFOCUS to tooltip - fails (tooltip was destroyed).

    Do you see the problem yet?

    Look at the message traffic as it reaches the original edit control window procedure:

    • WM_SETFOCUS (from the nested focus change)
    • WM_KILLFOCUS (from the original focus change)

    As far as the edit control is concerned, it gained focus then lost it. Therefore, no caret, since the edit control displays a caret only when it has focus, and your recursive focus changing has resulted in the edit control thinking it doesn't have focus even though it does.

    There are many ways out of this mess.

    First, notice that you don't need to subclass the edit control; you can just react to the EN_KILLFOCUS notification. Second, you can respond to the EN_KILLFOCUS by posting yourself a message and destroying the tooltip on receipt of that posted message. By doing it via a posted message, you avoid the recursive focus change since your work is now being done outside a focus change cycle.

  • The Old New Thing

    The Northwest Mahler Festival performs Mahler's Second Symphony ("Resurrection")

    • 8 Comments

    Last night I attended the Northwest Mahler Festival's performance of Mahler's Second Symphony (The Resurrection). The concert opened with Copland's El Salón México and Barber's Prayers of Kierkegaard. [Typo fixed 12:30pm]

    The Copland was kind of shaky, in a way that I couldn't quite put a finger on. The wind balance seemed a bit off, and it somehow didn't seem to "come together". By contrast, my knowledge of the Barber was zero, so they could've pulled out kazoos and I wouldn't've know that something was amiss.

    The Mahler demands quite a bit from both the woodwind and brass sections, but I was relieved to find that the tricky problem of getting them to play friendly appeared to be nonexistent. The Mahler "came together". (Well, duh, this is the Northwest Mahler Festival, after all.) I was so at ease with it that I started to ignore the occasional technical error...

    Performances of Mahler symphonies have a significant visual component. It's always a blast to see the clarinets playing "Schalltrichter auf", and for the Second, I was oddly fascinated by the rute. (I think my favorite Mahler percussion instrument is the "large hammer striking a wooden block" from the Sixth Symphony. When you see the percussionist raise that huge mallet, you know it's coming... and when the blow finally comes, it sends shock waves through your body.)

    Anyway, there's no real point to this entry. Just babbling about a symphony concert that I found very satisfying.

  • The Old New Thing

    Converting from traditional to simplified Chinese, part 2: Using the dictionary

    • 8 Comments

    Now that we have our traditional-to-simplified pseudo-dictionary, we can use it to generate simplified Chinese words in our Chinese/English dictionary.

    class StringPool
    {
    public:
     StringPool();
     ~StringPool();
     LPWSTR AllocString(const WCHAR* pszBegin, const WCHAR* pszEnd);
     LPWSTR DupString(const WCHAR* pszBegin)
     {
      return AllocString(pszBegin, pszBegin + lstrlen(pszBegin));
     }
     ...
    };
    

    The DupString method is a convenience we will use below.

    Dictionary::Dictionary()
    {
     ...
        if (de.Parse(buf, buf + cchResult, m_pool)) {
         bool fSimp = false;
         for (int i = 0; de.m_pszTrad[i]; i++) {
          if (pmap->Map(de.m_pszTrad[i])) {
           fSimp = true;
           break;
          }
         }
         if (fSimp) {
          de.m_pszSimp = m_pool.DupString(de.m_pszTrad);
          for (int i = 0; de.m_pszTrad[i]; i++) {
           if (pmap->Map(de.m_pszTrad[i])) {
            de.m_pszSimp[i] = pmap->Map(de.m_pszTrad[i]);
           }
          }
         } else {
          de.m_pszSimp = NULL;
         }
         v.push_back(de);
        }
     ...
    }
    

    After we parse each entry from the dictionary, we scan the traditional Chinese characters to see if any of them have been simplified. If so, then we copy the traditional Chinese string and use the Trad2Simp object to convert it to simplified Chinese.

    If the string is the same in both simplified and traditional Chinese, then we set m_pszSimp to NULL. This may seem a bit odd, but it'll come in handy later. Yes, it makes the m_pszSimp member difficult to use. I could have created an accessor function for it (so that it falls back to traditional Chinese if the simplified Chinese is NULL), but I'm feeling lazy right now, and this is just a one-shot program.

    void RootWindow::OnGetDispInfo(NMLVDISPINFO* pnmv)
    {
     ...
      switch (pnmv->item.iSubItem) {
       case COL_TRAD:    pszResult = de.m_pszTrad;    break;
       case COL_SIMP:    pszResult =
          de.m_pszSimp ? de.m_pszSimp : de.m_pszTrad; break;
       case COL_PINYIN:  pszResult = de.m_pszPinyin;  break;
       case COL_ENGLISH: pszResult = de.m_pszEnglish; break;
      }
     ...
    }
    

    Finally, we tell our OnGetDispInfo handler what to return when the listview asks for the text that goes into the simplified Chinese column. With these changes, we can display both the traditional and simplified Chinese for each entry in our dictionary.

    Next time, a minor tweak to our display code, which happens to illustrate custom-draw as a nice side-effect.

  • The Old New Thing

    The effect of SetCursor lasts only until the next SetCursor

    • 8 Comments

    Of course the effect of the SetCursor function for a thread lasts only until that thread changes the cursor to something else. Any moron knows that, right?

    The tricky part is that the SetCursor may come from an unexpected place.

    THe most common place people run into this is when they do something like this:

    // Put up the hourglass
    HCURSOR hcurPrev = SetCursor(hcurWait);
    ... do some processing ...
    // Restore the original cursor
    SetCursor(hcurPrev);
    

    This puts up the hourglass during the processing. But if you pump messages (or if a function you call pumps messages), then the hourglass will go away and return to the normal arrow.

    That's because when you pump messages, this opens the gates for messages like WM_NCHITTEST and WM_SETCURSOR. The latter in particular will typically result in the cursor changing, either to a cursor selected by the window itself or to the class cursor if the message makes it all the way to DefWindowProc.

    If you want to keep the hourglass up even while pumping messages, you need to let the window know that "If you are asked to set the cursor, please put up an hourglass instead of what you would normally display as the cursor." That window would then have to alter its WM_SETCURSOR handling to take this setting into account.

    case WM_SETCURSOR:
     if (ForceHourglass()) {
       SetCursor(hcurWait);
       return TRUE;
     }
     ...
    

    Note that forcing the hourglass is only the tip of the iceberg. Even though the cursor is an hourglass, the window is still active and can receive other message, such as mouse clicks and keypresses. If your program is not ready to receive new input during this phase, you need to detect this case and not go into some recursive state if the user, say, impatiently clicks the "Compute!" button while you are still computing.

  • The Old New Thing

    Adventures in product testing: Candles that catch fire

    • 8 Comments

    Not to be outdone by frying pans that explode when you use them for frying, Nature's Finest Candles has issued a product recall [pdf] because the candles catch fire when you light them.

    This is not to be confused with birthday candles that catch fire and explode.

  • The Old New Thing

    Pointers to virtual functions with adjustors

    • 8 Comments

    As a mental exercise, let's combine two mind-numbing facts about pointers to member functions, namely that all pointers to virtual functions look the same and that pointers to member functions are very strange animals. The result may make your head explode.

    Consider:

    class Class1 {
     public: virtual int f() { return 1; }
    };
    
    class Class2 {
     public: virtual int g() { return 2; }
    };
    
    class Class3 : public Class1, public Class2 {
    };
    
    int (Class3::*pfn)() = Class3::g;
    

    Here, the variable pfn consists of a code pointer and an adjustor. The code pointer gives you the virtual call stub:

     mov eax, [ecx]             ; first vtable
     jmp dword ptr [eax]        ; first function
    

    and the adjustor is sizeof(Class1) (which in our case would be 4 on a 32-bit machine). The result, then, of compiling a function call (p->*pfn)() might look something like this:

     mov ecx, p
     lea eax, pfn
     add ecx, dword ptr [eax+4] ; adjust
     call dword ptr [eax]       ; call
    -- transfers to
     mov eax, [ecx]             ; first vtable
     jmp dword ptr [eax]        ; first function
    -- transfers to
     mov eax, 2                 ; return 2
     ret
    

    Okay, I lied. It's really not all that complicated after all. But you can probably still impress your friends with this knowledge. (If you have really geeky friends.)

  • 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

    What happens when you specify RegexOptions.ECMAScript?

    • 8 Comments

    The RegexOptions.ECMAScript flag changes the behavior of .NET regular expressions. One of the changes I had discussed earlier was with respect to matching digits. For those who want to know more, a summary of the differences is documented in MSDN under the devious title "ECMAScript vs. Canonical Matching Behavior".

    Apparently some people had trouble finding that page, so I figured I'd point to it explicitly.

  • 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.]

Page 377 of 448 (4,479 items) «375376377378379»