June, 2007

  • The Old New Thing

    2007 mid-year link clearance

    • 11 Comments

    A few random links that I've collected.

    And then the obligatory plug for my column in TechNet Magazine:

  • The Old New Thing

    Don't forget to pass the current directory along with the command line to your single-instance program

    • 30 Comments

    If you make a single-instance program, and somebody runs a second copy of the program and passes a command line, the most common way of handling this is to hand the command line to the first copy of the program and let the first copy deal with it. When you do this, don't forget about the current directory.

    If somebody passes a relative path to the second copy of the program, that relative path needs to be resolved against the current directory of the second program. I've seen programs that fail to take this into account. Instead, they pass the command line to the first copy of the program, and the first copy resolves the relatives paths against its current directory.

    Allow me to give a concrete example.

    C:\Directory1> start LitWare file1.lit
    ... runs LitWare with the file C:\Directory1\file1.lit ...
    C:\Directory1> cd ..\Directory2
    C:\Directory2> start LitWare file2.lit
    

    What you expect to happen is that LitWare opens the file C:\Directory2\file2.lit, since the relative path file2.lit should be resolved against the current directory, C:\Directory2. Unfortunately, I see some programs◊ try to open the file C:\Directory1\file2.lit since they passed the command line to the first copy, and the first copy then resolved the relative path file2.lit against the current directory of the first copy, namely C:\Directory1.

    Result: "File not found error."

    Moral of the story: Be mindful of the current directory when parsing the command line. You can either have the second copy parse the command line (and resolve the relative paths against its own current directory), or you can pass the current directory to the first copy (and resolve the relative paths against that directory). Either works. What doesn't work is passing the relative paths to the first copy and having the first copy resolve it against its own current directory.

    My workaround for dealing with these programs is using the %CD% pseudo-variable.

    C:\Directory2> start LitWare %CD%\file2.lit
    

    The %CD% pseudo-variable expands to the command prompt's current directory. (Don't forget to enclose it in quotation marks if the path contains a space.)

    Nitpicker's corner

    ◊Remember that the phrase "some programs" means "some programs" and Microsoft programs fall under the category of "programs".

    Yes, I'm using strange symbols as note markers. My hope is that the people who nitpick over my choice of note markers will get so upset that their heads will explode and they therefore will be unable to nitpick further. It appears that some people, on the other hand, suffer from some medical condition that disables the "fun" center of the brain.

    [Update: Give IE6 a little font help.]

  • The Old New Thing

    If it's optional, then don't make it mandatory

    • 31 Comments

    I was filling out an online form, and it gave me the option of providing feedback on the service I had received. The button was marked "optional", but I clicked it anyway because there were one or two things I thought were worthy of mentioning, suggestions on how they could improve the user's experience with the Web site, that sort of thing.

    What came next was not something I was expecting.

    I was faced with a 43-question survey asking me to rank my level of satisfaction on scale of 1 to 5 on each of those 43 different axes. I skipped all of those questions since they aren't why I chose to give them feedback. At the bottom of the form were three free-form text boxes where you could make suggestions for improvement, complain about something that didn't go right, that sort of thing. Those were the boxes I was after.

    I wrote up my suggestions and clicked Submit.

    Error: "You must complete all the feedback sections before submitting your feedback."

    Wait a second. I thought you said that giving feedback was optional, not mandatory. Don't give me this "mandatory optional" nonsense. If it's optional, then it's optional.

    I decided not to give them any feedback at all.

  • The Old New Thing

    Overheard conversation in the cockpit that you might not want to have heard

    • 24 Comments

    A few weeks ago, I got a piece of email from a friend.

    Maintenance crew in cockpit. They just told the pilot that the airplane "double faulted."
  • The Old New Thing

    Those who do not understand the dialog manager are doomed to reimplement it, badly

    • 23 Comments

    A customer wanted to alter the behavior of a multi-line edit control so that it did not treat a press of the Tab key as a request to insert a tab character but rather treated it as a normal dialog navigation key. The approach the customer took was to subclass the edit control and intercept the Tab key:

    LRESULT CALLBACK SubclassWndProc(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      switch (uMsg) {
      case WM_KEYDOWN:
        if (wParam == VK_TAB) {
          // Pressed the TAB key - tab to next control
          SetFocus(GetNextDlgTabItem(
                         GetParent(hwnd), hwnd, FALSE));
          return 0; // message handled
        }
      }
      return CallWindowProc(...);
    }
    

    There are many things wrong with this approach. You can spend quite a lot of time nitpicking the little details, how this code fails to set focus in a dialog box properly, how it fails to take nested dialogs into account, how it fails to handle the Shift+Tab navigation key, how it blatantly assumes that the control is part of a dialog box in the first place! But all of these little details are missing the big picture: Instead of fighting against the dialog manager and reimplementing all the parts we want to keep and ignoring the parts we want to skip, we should be working with the dialog manager and expressing our intentions in the manner the dialog manager expects.

    It's the difference between ordering a hamburger without pickles and ordering a hamburger with pickles, and then carefully picking the pickles off the burger when you get it.

    In this case, we want to prevent the edit control from saying "Give me the Tab key." We saw last time that this is done either by (1) setting the DLGC_WANTTAB dialog code or by (2) responding with DLGC_WANTMESSAGE when given a Tab key message. Therefore, to tell the dialog manager to not to treat the tab key specially, just turn off those two behaviors.

    LRESULT CALLBACK SubclassWndProc(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      LRESULT lres;
      switch (uMsg) {
      case WM_GETDLGCODE:
        lres = CallWindowProc(...);
        lres &= ~DLGC_WANTTAB;
        if (lParam &&
            ((MSG *)lParam)->message == WM_KEYDOWN &&
            ((MSG *)lParam)->wParam == VK_TAB) {
          lres &= ~DLGC_WANTMESSAGE;
        }
        return lres;
      }
      return CallWindowProc(...);
    }
    

    After asking the original control what behavior it thinks it wants, we turn off the DLGC_WANTTAB flag; this takes care of part (1). Next, we check whether the message is a press of the Tab key. If so, then we turn the DLGC_WANTMESSAGE flag off; this takes care of part (2).

    This is certainly less code than would need to have been written to address all of the little concerns noted earlier, and it does it by completely sidestepping the task of trying to emulate the dialog manager and instead just cooperating with the dialog manager to get the behavior you want. This principle of "If you know how a system is meant to work, you can work with it rather than against it, and everybody will be happier for it" is something I've been trying to convey through this web site and my book. Knowing how something was intended to be used allows you to be a more effective programmer.

    Exercise: Why didn't we just write this instead?

    LRESULT CALLBACK SubclassWndProc(
        HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      LRESULT lres;
      switch (uMsg) {
      case WM_GETDLGCODE:
        lres = CallWindowProc(...);
        lres &= ~DLGC_WANTTAB;
        if (wParam == VK_TAB) {
          lres &= ~DLGC_WANTMESSAGE;
        }
        return lres;
      }
      return CallWindowProc(...);
    }
    
  • The Old New Thing

    What's all this stuff hanging from that utility pole?

    • 4 Comments

    Brain Hayes, author of Infrastructure: A Field Guide to the Industrial Landscape (another example of the "short: long" book title fad), talks us through all of the wires hanging from what we commonly call a "telephone pole".

  • The Old New Thing

    Why do DLGC_WANTALLKEYS and DLGC_WANTMESSAGE have the same value?

    • 18 Comments

    From a purely theoretical point of view, there is only one "want" code you really need: DLGC_WANTMESSAGE. All the others are just conveniences. For example, returning DLGC_WANTARROWS means "I want this message if it is an arrow key; otherwise, I don't care." It lets you write

    case WM_GETDLGCODE:
     return DLGC_WANTARROWS;
    

    instead of the more cumbersome (but equivalent)

    case WM_GETDLGCODE:
     if (lParam &&
         ((MSG*)lParam)->message == WM_KEYDOWN &&
         (wParam == VK_LEFT || wParam == VK_RIGHT ||
          wParam == VK_UP || wParam == VK_DOWN)) {
       return DLGC_WANTMESSAGE;
     }
     return 0;
    

    Similarly, DLGC_WANTTAB is equivalent to returning DLGC_WANTMESSAGE if the message is a press of the tab key, and DLGC_WANTCHARS is equivalent to returning DLGC_WANTMESSAGE if the message is WM_CHAR.

    And that leaves DLGC_WANTALLKEYS, which is just another name for DLGC_WANTMESSAGE:

    #define DLGC_WANTALLKEYS    0x0004
    #define DLGC_WANTMESSAGE    0x0004
    

    They mean the same thing but look at the situation through different perspectives. The DLGC_WANTMESSAGE value is more readable if you return it as part of some larger decision-making process, like we did with our mimicry of DLGC_WANTTAB: You do a bunch of tests and then when you decide, "I guess I want this one," you return DLGC_WANTMESSAGE. On the other hand, the DLGC_WANTALLKEYS value is more readable if you are just returning it unconditionally. "I want all keys, no matter what it is."

    It's like when you're at the grocery store, and the bagger asks you, "Would you like me to help you carry your watermelon to your car?" You can say "Yes" or "Always"; the result is the same. The only difference is one of expectation: If you expect to meet the same bagger in the future, and the bagger remembers, then "Always" implies "You don't need to ask me again." The dialog manager, on the other hand, doesn't have that good of a memory, and in fact, if you think about it, you don't want it to have a good memory.

    Suppose the dialog manager remembered whether you said "Always" and stopped asking you in the future. It sends the WM_GETDLGCODE message to a control, the control responds DLGC_WANTALLKEYS, and then later, you subclass the control and change the dialog code. Oops, that doesn't work because the dialog manager "remembered" the control's previous answer and doesn't ask any more. Naturally, you expect subclassing to work, so the dialog manager asks each time.

    Continuing the analogy, if you want the bagger to help you if it is raining but not on a dry day, you can either look out the window, confirm that it is not raining, and say, "Yes", or you can just say, "Yes, if it's raining," regardless of the weather, and let the bagger make the call. (Of course, the analogy breaks down because the bagger might have a different assessment from you as to whether or not it is raining. The decisions you leave up to the dialog manager, such as whether a key is the tab key or not, are much less ambiguous.)

    Next time, we'll look at a dialog manager problem, and the information you learned today may come in handy in solving it.

    Nitpicker's corner

    Beginning of counterfactual discussion.*

    End of counterfactual discussion.*

    *Warning: Comments complaining about my choice of footnote symbol will be misrepresented and ridiculed.

  • The Old New Thing

    Why the Fantastic 4 Human Torch ATV is the Worst Movie Tie-In Toy Ever

    • 8 Comments

    Columnist Wm. Steven Humphrey expounds on Why the Fantastic 4 Human Torch ATV (with Light-Up Headlights!) is the Worst Movie Tie-In Toy Ever. (Readers cautioned for strong language, but it's funny because it's true.)

  • The Old New Thing

    There's no point improving the implementation of a bad idea

    • 42 Comments

    IsBadXxxPtr is a bad idea and you shouldn't call it. In the comments, many people proposed changes to the function to improve the implementation. But what's the point? IsBadXxxPtr is just a bad idea. There's no point improving the implementation of a bad idea.

    On the other hand, some people suggested making it clear that IsBadXxxPtr is a bad idea by making it worse. While this is tempting in a "I'm forcing you to do the right thing" sense, it carries with it serious compatibility problems.

    There's a lot of code that uses IsBadXxxPtr even though it's a bad idea, and making IsBadXxxPtr worse would risk breaking those programs that managed to get away with it up until now. The danger of this is that people would upgrade to the next version of Windows and their program would stop working. Who do you think the blame will be placed on?

    Sure, you might tell these people, "That's because it's a bug in your program. Go contact the vendor for an update." Of course, that's assuming you can prove that the reason why the program stopped working was this IsBadXxxPtr stuff. How can you tell that that was the problem? Maybe it was caused by some other problem, possibly even a bug in Windows itself. Or is your answer just going to be "Any program that crashes must be crashing due to misuse of IsBadXxxPtr?"

    And, as I've noted before, contacting the vendor may not be enough. Most large corporations have programs that run their day-to-day operations. Some of them may have been written by a consultant ten years ago. Even if they have the source code, they may not have the expertise, resources, or simply inclination go to in and fix it. This happens more often than you think. To these customers, the behavior change is simply a regression.

    Even if you have the source code and expertise, fixing the problem may not be as simple as it looks. You may have designed your program poorly and relied on IsBadXxxPtr to cover for your failings. For example, you may have decided that "The lParam to this message is a pointer to a CUSTOMER structure, or it could just be the customer ID number. I'll use IsBadReadPtr, and if the pointer is bad, then the value must be the customer ID number." Or you may have changed the definition of a function parameter and now need to detect whether your caller is calling the "old function" or the "new one". Or it could simply be that once you remove the call to IsBadXxxPtr, your program crashes constantly because the IsBadXxxPtr was covering up for a huge number of other programming errors (such as uninitialized variables).

    "But what if I'm just using it for debugging purposes?" For debugging purposes, allow me to propose the following drop-in replacement functions:

    inline BOOL IsBadReadPtr2(CONST VOID *p, UINT_PTR cb)
    {
      memcmp(p, p, cb);
      return FALSE;
    }
    
    inline BOOL IsBadWritePtr2(LPVOID p, UINT_PTR cb)
    {
      memmove(p, p, cb);
      return FALSE;
    }
    

    It's very simple: To see if a pointer is bad for reading, just read it (and similarly writing). If the pointer is bad, the read (or write) will raise an exception, and then you can investigate the bad pointer at the point it is found. We read from the memory by comparing it to itself and write to the memory by copying it to itself. These have no effect but they do force the memory to be read or written. Of course, this trick assumes that the compiler didn't optimize out the otherwise pointless "compare memory to itself" and "copy memory to itself" operations. (Note also that the replacement IsBadWritePtr2 is not thread-safe, since another thread might be modifying the memory while we're copying it. But then again, the original IsBadWritePtr wasn't thread-safe either, so there's no loss of amenity there.)

    (As an aside: I've seen people try to write replacements for IsBadXxxPtr and end up introducing a bug along the way. There are many corner cases in this seemingly-simple family of functions.)

  • The Old New Thing

    Einstein the geographer? A hoax.

    • 6 Comments

    Perhaps you've seen this quote attributed to Albert Einstein:

    As a young man, my fondest dream was to become a geographer. However, while working in the Customs Office, I thought deeply about the matter and concluded that it was far too difficult a subject. With some reluctance, I then turned to physics as an alternative.

    Well, it's a fake. The original research and debunking came from Jerry Dobson, then was picked up (with extensive excerpts—recommended) by Sharon Burnside and then Regret the Error, which is where I found it.

Page 1 of 5 (41 items) 12345