• The Old New Thing

    Cleaner, more elegant, and harder to recognize

    • 116 Comments

    It appears that some people interpreted the title of one of my rants from many months ago, "Cleaner, more elegant, and wrong", to be a reference to exceptions in general. (See bibliography reference [35]; observe that the citer even changed the title of my article for me!)

    The title of the article was a reference to a specific code snippet that I copied from a book, where the book's author claimed that the code he presented was "cleaner and more elegant". I was pointing out that the code fragment was not only cleaner and more elegant, it was also wrong.

    You can write correct exception-based programming.

    Mind you, it's hard.

    On the other hand, just because something is hard doesn't mean that it shouldn't be done.

    Here's a breakdown:

    Really easy Hard Really hard
    Writing bad error-code-based code
    Writing bad exception-based code
    Writing good error-code-based code Writing good exception-based code

    It's easy to write bad code, regardless of the error model.

    It's hard to write good error-code-based code since you have to check every error code and think about what you should do when an error occurs.

    It's really hard to write good exception-based code since you have to check every single line of code (indeed, every sub-expression) and think about what exceptions it might raise and how your code will react to it. (In C++ it's not quite so bad because C++ exceptions are raised only at specific points during execution. In C#, exceptions can be raised at any time.)

    But that's okay. Like I said, just because something is hard doesn't mean it shouldn't be done. It's hard to write a device driver, but people do it, and that's a good thing.

    But here's another table:

    Really easy Hard Really hard
    Recognizing that error-code-based code is badly-written
    Recognizing the difference between bad error-code-based code and not-bad error-code-based code.
    Recognizing that error-code-base code is not badly-written
    Recognizing that exception-based code is badly-written
    Recognizing that exception-based code is not badly-written
    Recognizing the difference between bad exception-based code and not-bad exception-based code

    Here's some imaginary error-code-based code. See if you can classify it as "bad" or "not-bad":

    BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
    {
      HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
           NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
      void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
      DWORD dwHeaderSum;
      CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
               &dwHeaderSum, pdwResult);
      UnmapViewOfFile(pv);
      CloseHandle(hfm);
      CloseHandle(h);
      return TRUE;
    }
    

    This code is obviously bad. No error codes are checked. This is the sort of code you might write when in a hurry, meaning to come back to and improve later. And it's easy to spot that this code needs to be improved big time before it's ready for prime time.

    Here's another version:

    BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult)
    {
      BOOL fRc = FALSE;
      HANDLE h = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ,
           NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      if (h != INVALID_HANDLE_VALUE) {
        HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
        if (hfm) {
          void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
          if (pv) {
            DWORD dwHeaderSum;
            if (CheckSumMappedFile(pvBase, GetFileSize(h, NULL),
                                   &dwHeaderSum, pdwResult)) {
              fRc = TRUE;
            }
            UnmapViewOfFile(pv);
          }
          CloseHandle(hfm);
        }
        CloseHandle(h);
      }
      return fRc;
    }
    

    This code is still wrong, but it clearly looks like it's trying to be right. It is what I call "not-bad".

    Now here's some exception-based code you might write in a hurry:

    NotifyIcon CreateNotifyIcon()
    {
     NotifyIcon icon = new NotifyIcon();
     icon.Text = "Blah blah blah";
     icon.Visible = true;
     icon.Icon = new Icon(GetType(), "cool.ico");
     return icon;
    }
    

    (This is actual code from a real program in an article about taskbar notification icons, with minor changes in a futile attempt to disguise the source.)

    Here's what it might look like after you fix it to be correct in the face of exceptions:

    NotifyIcon CreateNotifyIcon()
    {
     NotifyIcon icon = new NotifyIcon();
     icon.Text = "Blah blah blah";
     icon.Icon = new Icon(GetType(), "cool.ico");
     icon.Visible = true;
     return icon;
    }
    

    Subtle, isn't it.

    It's easy to spot the difference between bad error-code-based code and not-bad error-code-based code: The not-bad error-code-based code checks error codes. The bad error-code-based code never does. Admittedly, it's hard to tell whether the errors were handled correctly, but at least you can tell the difference between bad code and code that isn't bad. (It might not be good, but at least it isn't bad.)

    On the other hand, it is extraordinarily difficult to see the difference between bad exception-based code and not-bad exception-based code.

    Consequently, when I write code that is exception-based, I do not have the luxury of writing bad code first and then making it not-bad later. If I did that, I wouldn't be able to find the bad code again, since it looks almost identical to not-bad code.

    My point isn't that exceptions are bad. My point is that exceptions are too hard and I'm not smart enough to handle them. (And neither, it seems, are book authors, even when they are trying to teach you how to program with exceptions!)

    (Yes, there are programming models like RAII and transactions, but rarely do you see sample code that uses either.)

  • The Old New Thing

    Why was Pinball removed from Windows Vista?

    • 115 Comments

    Windows XP was the last client version of Windows to include the Pinball game that had been part of Windows since Windows 95. There is apparently speculation that this was done for legal reasons.

    No, that's not why.

    One of the things I did in Windows XP was port several millions of lines of code from 32-bit to 64-bit Windows so that we could ship Windows XP 64-bit Edition. But one of the programs that ran into trouble was Pinball. The 64-bit version of Pinball had a pretty nasty bug where the ball would simply pass through other objects like a ghost. In particular, when you started the game, the ball would be delivered to the launcher, and then it would slowly fall towards the bottom of the screen, through the plunger, and out the bottom of the table.

    Games tended to be really short.

    Two of us tried to debug the program to figure out what was going on, but given that this was code written several years earlier by an outside company, and that nobody at Microsoft ever understood how the code worked (much less still understood it), and that most of the code was completely uncommented, we simply couldn't figure out why the collision detector was not working. Heck, we couldn't even find the collision detector!

    We had several million lines of code still to port, so we couldn't afford to spend days studying the code trying to figure out what obscure floating point rounding error was causing collision detection to fail. We just made the executive decision right there to drop Pinball from the product.

    If it makes you feel better, I am saddened by this as much as you are. I really enjoyed playing that game. It was the location of the one Windows XP feature I am most proud of.

    Update: Hey everybody asking that the source code be released: The source code was licensed from another company. If you want the source code, you have to go ask them.

  • The Old New Thing

    Windows is not a Microsoft Visual C/C++ Run-Time delivery channel

    • 113 Comments

    There's a DLL in the system directory called MSVCRT.DLL, and from its name, you might think that it is the Microsoft Visual C/C++ Run-Time library. That is a perfectly reasonable guess.

    But it would also be wrong.

    The Microsoft Visual C/C++ Run-Time libraries go by names like MSVCR71.DLL or MSVCR80.DLL or MSVCR90.DLL or MSVCR100.DLL, and the debugging versions have a D in there, too. And like MFC, these binaries might be on your machine as a side effect of the implementation of a particular Windows component, but they are not contractual. If your program requires the Visual C/C++ Run-Time library, then your program needs to install the appropriate version. (There are redistributable packages you can include with your application.)

    Okay, so what's with the DLL with the misleading name MSVCRT.DLL? The unfortunate name is a consequence of history.

    Back in Windows 95, MSVCRT.DLL was the Microsoft Visual C/C++ Run-Time library, or at least it was the runtime library for Visual C/C++ 4.2. As each new version of Visual C/C++ came out, the Windows team had to go update their copy of MSVCRT.DLL to match. And if the Windows team wanted to fix a bug in MSVCRT.DLL, they had to make sure that the Visual C/C++ team made the corresponding change in their version.

    This high degree of coördination became untenable, especially since it required the Windows team to do things like push a new version of MSVCRT.DLL to all downlevel platforms whenever a new version of Visual C/C++ came out. (Good luck doing this in the days before Windows Update!)

    And sometimes these fixes caused compatibility problems. For example, I remember there was a fix for a Y2K problem which caused one application to crash because the fix altered the stack usage in such a way that exposed an uninitialized variable bug.

    One serious problem with the MVSCRT.DLL "one runtime to rule them all" model is that multiple versions of Visual C++ would all use the same library, and keeping one DLL compatible with all versions of Visual C++ was a maintenance nightmare. For example, if a new C++ language feature required a change to the ostream class, you had to be careful to design your change so that the class was still binary-compatible with the older version of the class. This meant not changing the size of the class (because somebody may have derived from it) and not changing the offsets of any members, and being careful which virtual methods you call. This was in practice not done, and the result was that (for example) Windows 95 and Windows 98 both had DLLs called MSVCRT.DLL that were not compatible with each other.

    And of course there was the problem of some application installer unwittingly overwriting the existing copy of MSVCRT.DLL with an older one, causing the entire operating system to stop working.

    At some point, the decision was made to just give up and declare it an operating system DLL, to be used only by operating system components. All newer versions of Visual C/C++ used specifically-numbered DLLs for their runtime libraries. (Giving different names to each version of the run-time library solves the problem of trying to make one DLL service multiple versions of clients, as well as addressing the accidental downgrade problem.)

    Although MSVCRT.DLL has been an operating system DLL for a long time, and has been documented as off-limits to applications, there are still a lot of people who treat it as a C runtime delivery channel, and those programs create a lot of grief for the product team.

    I remember one change that the runtime library folks made to MSVCRT.DLL that had to be backed out and revisited because they found an application that not only linked to MSVCRT.DLL instead of the runtime library the compiler intended, but also groveled into an internal array and manipulated private members. (I was one of the people who investigated this compatibility issue, but I was not the one who solved it.)

    // Note: The issue has been simplified for expository purposes
    struct SomethingInternal
    {
        int widget;
        short widgetFlags;
        char widgetLevel;
        int needs_more_time;
    };
    
    SomethingInternal InternalArray[80];
    

    The runtime library folks added a new member to the structure:

    struct SomethingInternal
    {
        int widget;
        short widgetFlags;
        char widgetLevel;
        int needs_more_time;
        int needs_more_cowbell;
    };
    

    This change increased the size of the Something­Internal structure, which in turn meant that when the application did

    // Redeclare this internal structure in MSVCRT.DLL
    // so we can poke the needs_more_time member to get more time.
    
    struct SomethingInternal
    {
        int widget;
        short widgetFlags;
        char widgetLevel;
        int needs_more_time;
    };
    
    extern SomethingInternal InternalArray[80];
    
    ...
        InternalArray[i].needs_more_time = 1;
    ...
    

    it ended up poking the wrong byte because the structure size didn't match.

    The runtime library folks had to go back and squeeze the cowbell flag into the structure in a way that didn't alter the size of the Something­Internal structure. I don't remember exactly what the fix was, but one way they could've done it was by squeezing the flag into the one byte of padding between widgetLevel and needs_more_time.

    struct SomethingInternal
    {
        int widget;
        short widgetFlags;
        char widgetLevel;
        char needs_more_cowbell;
        int needs_more_time;
    };
    

    Bonus chatter: The application had an easy time messing with the internal array because the source code to the C runtime library is included with the compiler, So much for "All these compatibility problems would go away if you published the source code." Publishing the source code makes it easier to introduce compatibility problems, because it lays bare all the internal undocumented behaviors. Instead of trying to reverse-engineer the runtime library, you can just sit down and read it, and if you want to do something sneaky, you can just copy the declaration of the internal array and party on the needs_more_time member.

  • The Old New Thing

    Why doesn't Explorer let you create a file whose name begins with a dot?

    • 111 Comments

    Rolf Viehmann asks why Explorer doesn't let you create a file whose name begins with a dot.

    Such files are considered to have an extension but no name. If the extension is known and the user has chosen to hide known extensions, the resulting file would have no name at all!

    If you really want to create a file with a leading dot in its name, you are free to do so. You can do it from the command line or use your favorite file management tool. And then you can watch the file show up with no name and then observe the confusion that ensues. Maybe you're lucky and don't run any programs that freak out when a file has no name. If so, then more power to you.

    If it really bugs you that you can't do it from Explorer, you are free to write your own shell extension to do "Rename this file, and if I'm going to shoot myself in the foot, then let me."

  • The Old New Thing

    Why did the Win64 team choose the LLP64 model?

    • 110 Comments

    Over on Channel 9, member Beer28 wrote, "I can't imagine there are too many problems with programs that have type widths changed." I got a good chuckle out of that and made a note to write up an entry on the Win64 data model.

    The Win64 team selected the LLP64 data model, in which all integral types remain 32-bit values and only pointers expand to 64-bit values. Why?

    In addition to the reasons give on that web page, another reason is that doing so avoids breaking persistence formats. For example, part of the header data for a bitmap file is defined by the following structure:

    typedef struct tagBITMAPINFOHEADER {
            DWORD      biSize;
            LONG       biWidth;
            LONG       biHeight;
            WORD       biPlanes;
            WORD       biBitCount;
            DWORD      biCompression;
            DWORD      biSizeImage;
            LONG       biXPelsPerMeter;
            LONG       biYPelsPerMeter;
            DWORD      biClrUsed;
            DWORD      biClrImportant;
    } BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
    

    If a LONG expanded from a 32-bit value to a 64-bit value, it would not be possible for a 64-bit program to use this structure to parse a bitmap file.

    There are persistence formats other than files. In addition to the obvious things like RPC and DCOM, registry binary blobs and shared memory blocks can also be used to transfer information between processes. If the source and destination processes are different bitness, any change to the integer sizes would result in a mismatch.

    Notice that in these inter-process communication scenarios, we don't have to worry as much about the effect of a changed pointer size. Nobody in their right mind would transfer a pointer across processes: Separate address spaces mean that the pointer value is useless in any process other than the one that generated it, so why share it?

  • The Old New Thing

    Stupid Raymond talent: Screaming carrier

    • 109 Comments

    Similar to Mike, I was able to scream (not whistle: scream) a 300 baud carrier tone. This skill proved useful when I was in college and the mainframe system was down. Instead of sitting around waiting for the system to come back, I just went about my regular business around campus. Every so often, I would go to a nearby campus phone (like a free public phone but it can only make calls to other locations on campus), dial the 300 baud dial-up number, and scream the carrier tone. If I got a response, that meant that the mainframe was back online and I should wrap up what I was doing and head back to the lab.

    Mind you, this skill isn't very useful nowadays.

    What stupid computer talent do you have?

  • The Old New Thing

    Glass houses are great places to throw stones

    • 105 Comments

    Whenever I write an article explaining that programs should avoid doing X, I can confidently rely on a comment saying, "Well, Microsoft Product Q does this!" as if to say, "Gotcha, you hypocrite!"

    But they're saying "gotcha" to the wrong person. Because, and I'm sure it's a shock to many people to read this, I did not personally write every line of software Microsoft ever produced. (And even if I did write it, I may have written it as a younger developer, before I learned about said rule. Because, and I'm sure this is also a shock to many people, I was once a beginner, too.)

    If you find a Microsoft product breaking a rule, then go complain to that product team. Complaining to me won't accomplish anything. I don't have access to their source code, and even if I did, I certainly don't have permission to go in and make changes to their code, nor do I have the time to go in and learn how their product works and figure out the right place to make the fix. Furthermore, and I don't know if you all can handle three shocking revelations in one article, product teams do not send me every line of code for review.

    Indeed, one of the reasons I write here about things programs should or shouldn't do is because I myself will see a Microsoft product breaking a rule! By discussing the problem here rather than in an internal mailing list, the information gets out to everybody. And maybe, just maybe, the product team will read the entry and say, "Oops, I think we do that." Because (shocking revelation number four) not all Microsoft programmers are seasoned experts in Win32 user-interface programming.

    (Articles where I was consciously tapping my colleagues on the head include my discussion of CallMsgFilter, the long and sad story of the Shell Folders key, and reminding you to pass unhandled messages to DefWindowProc. In fact, for every "do/don't do this" article, I'd say odds are good that with enough searching, you can find a Microsoft product that breaks the rule. And when you do, complain to that product team. Even the difference between the tray and the notification area was in part a response to all the other groups that perpetuate the misuse of the terminology.)

    So when I write something like, "Applications shouldn't do this", go ahead and insert the phrase "and this means all applications, including those published by Microsoft." When I write, "some annoying programs", go ahead and insert the phrase, "which might even include programs published by Microsoft". I'm not going to insert those phrases into every sentence I write. I'm assuming you're smart enough to realize that general statements apply to everyone regardless of who signs their paychecks.

    Of course, if the consensus of my readership is that I shouldn't tell you not to do things until every last Microsoft product has been scoured to ensure that none of them violate that rule either, then I can abide by that decision. I'll just stop posting those tips here and keep them on the internal mailing lists. It's much less work for me.

  • The Old New Thing

    The administrator is an idiot

    • 104 Comments

    Nearly all computer administrators are idiots.

    That's not because the personnel department is incompetent or because it's impossible to train competent administrators. It's because, for a consumer operating system, the computer administrator didn't ask to be one. In nearly all cases, the computer administrator is dad or grandma.† They didn't ask to be to be the computer administrator. They just want to surf the web and read email from Jimmy.‡

    All this means is that you can't say, "Well, if the user is an administrator, as opposed to a normal user, then it's okay to show them all these dangerous things (such as critical operating system files) because they know what they're doing." Grandma doesn't know what she's doing.

    For a consumer operating system, a friendly user interface means protecting the administrators from themselves.

    Nitpicker's corner

    Sigh. One article without a nitpicker's corner and look what happens.

    †The words "dad" and "grandma" refer to archetypes for non-technical home users and are not intended to be interpreted as literally dad and grandma.

    ‡Not all grandchildren are named Jimmy.

  • The Old New Thing

    So what's to do in Sweden?

    • 104 Comments

    Here is where Raymond gets to abuse his power as a blogger to get some free travel advice.

    I will likely travel to Sweden in mid-March, with a whopping total of five months of Swedish under my belt. I'm sure I will embarrass myself horribly, but that's sort of the point, after all.

    The question is, "So what's to do in Sweden?" I was thinking of flying in to Stockholm, spending the first day recovering from jet lag, then spending maybe a week exploring whatever there is to see there. (Well, I'll actually be staying with a friend in Uppsala, but there appears to be regular train service to/from Stockholm.) Definitely hit Vasamuseet, Nobelmuseet, and Gamla Stan. I'm sure there's other stuff too.

    Then I figure I'd hop a train to Göteborg and spend the remaining three days there doing, um, I have no idea. But I'm told it's a nice city.

    Yes, this plan means that I miss out on Gotland, Skåne, the -Köppings, and all the northern bits. But then again, you can't see a whole country in just ten days.

    So am I nuts?

  • The Old New Thing

    Windows is not an MFC delivery channel

    • 103 Comments

    Depending on what version of Windows you're running, there may be a variety of support DLLs for things that aren't formal product components, but which are merely along for the ride. For example, Windows 95 came with MFC30.DLL because the Fax Viewer was written with the help of MFC 3.0. But if you look at Windows 98, MFC30.DLL is gone.

    What happened?

    What happened is that Windows 98 didn't have a fax viewer that used MFC 3.0. The fact that some MFC 3.0 DLLs wound up on the machine with Windows 95 was merely a side effect of the implementation and not a part of the product specification. And in fact, if you chose not to install the Fax Viewer during Windows 95 setup, you wouldn't have gotten MFC30.DLL at all either, because MFC30.DLL wouldn't have been needed.

    We had a category of Windows 98 compatibility bugs from programs that assumed that MFC30.DLL was a contractual part of Windows. If the tester did a minimal install of Windows 95 and then installed the application, the application wouldn't run there either. The application vendor simply assumed that MFC was part of Windows, even though it wasn't. In other words, the program didn't work even on Windows 95. Is it a bug in Windows 98 if the program didn't work on Windows 95?

    This problem persists today. People go scrounging around the binaries that come with Windows looking for something they can remora. And then they're surprised when those binaries change or vanish entirely. For example, one customer had reverse-engineered the Kodak image viewer in Windows 2000 and wanted to keep using it in Windows XP. But those components are not included in Windows XP; the Kodak image viewer was merely a stopgap solution until the Windows XP fax and image viewer came along. (The linked Knowledge Base article has more information on that product.) I mention this because that customer, a Fortune 500 company, was trying to copy the files from Windows 2000 and install them onto a Windows XP machine and actually came to us asking for help in what may very well have been a violation of the Windows license agreement! (And certainly a violation of Microsoft's agreement with Kodak.)

    (I now predict that everybody will comment on the last two sentences and completely ignore the point of this article.)

Page 3 of 426 (4,251 items) 12345»