January, 2004

  • The Old New Thing

    The hollow brush

    • 7 Comments
    What is the hollow brush for?

    The hollow brush is a brush that doesn't do anything. You can use it when you're forced to use a brush but you don't want to.

    As one example, you can use it as your class brush. Then when your program stops responding and Windows decide to do the "white flash" (see yesterday's entry), it grabs the hollow brush and ends up not drawing anything. (At least, that's how it worked on Windows 2000. Things may be different on XP.)

    Another place you can use the hollow brush is when handling the WM_CTLCOLOR* messages. Those messages require you to return a brush, which will be used to erase the background. If you don't want to erase the background, a hollow brush does the trick.
  • The Old New Thing

    David Hasselhoff's daughters normal in every way

    • 10 Comments
    (Forwarded to me my a friend who is apparently fascinated with David Hasselhoff.)

    David Hasselhoff says his daughters hate his music and change the channel whenever Knight Rider comes on the TV.

    Okay, I defended the Germans last week, but this one baffles even me. Do the Germans not realize that David Hasselhoff is a joke? Er war ein unbeudentender Schauspieler in einem blöden Fernsehprogramm. Ihm wurde die Schau von einem Auto gestohlen!

    (And for those who still want their Knight Rider fix, you can check out this movie of some people who installed remote control hydraulics in a 1982 Toyota Camry and drove it around the desert, just like the Knight Rider title sequence.)

  • The Old New Thing

    German as RPN

    • 23 Comments
    It should be noted that "Reverse Polish Notation" is named in honor of the Polish logician Jan Lukasiewicz, who developed prefix notation, wherein the operator comes before the operands. Postfix notation proved more useful for stack-based arithmetic computations, and so the opposite of prefix notation came to be known as "Reverse Polish Notation".

    It was kind of a strange feeling when I encountered Polish notation in logic class. Finally I got to see the forwards version of what I had been doing in reverse for so many years!

    Anyway, here's a classic example of German as RPN, hidden in a web page on the subject of Dutch word order. Consider the clause "... that Frank saw Julia help Fred swim." In German, that would be expressed as

    ... daß Frank Julia Fred schwimmen helfen sah.
    ... that Frank Julia Fred swim help saw.

    You can create this sort of constructing in English too, but nobody does this unless they are trying to cause trouble: "The rat the cat the dog chased caught died."

    English also gets somewhat unpredictable if you decide to start the sentence with something other than the subject:

    What you need I cannot give you. Object first, no inversion.
    Nothing but blue skies do I see. Object first with inversion.
    Sometimes it snows. Adverb first, no inversion.
    Rarely does it snow. Adverb first with inversion.

    It's like English is struggling to decide whether it wants to hang out with its Germanic buddies and use X-V-S or strike out on its own and be an S-V language.

    I found German an easy language to learn because it is much more logical, much less capricious. "The verb goes in second position, the adjective goes in front of the noun."

    "But what if the adjective is really long?"

    "Tough. Goes in front. Because that's where adjectives go."

    Swedish (at least to my unaccustomed ears) leans more towards the capricious end of the scale. What's the difference between "från" and "ifrån" and "i från"? It's probably one of those subtleties that I will never learn.

    Okay, enough about language. Geek talk resumes on Monday.
  • The Old New Thing

    In defense of the German language

    • 24 Comments
    Some commenters deplored the inflectional complexity of the German language. I find the complexity reassuring rather than offputting, because it means that you always know where to find the functional parts of the sentence.

    The lack of inflectional complexity in English is made up for by its much more complicated structural form. English word order is nuts.

    • "I rarely go."
    • "I don't go often."
    • "I don't usually go."

    Why does the temporal adverb go in front of the verb in one case, but after it in another? And it comes in the middle of the verb in a third case!

    (Okay, technically you can put the adverb in any of those places, but it sounds stilted or changes the meaning of the sentence subtly. Try explaining that to a student of English and they will merely shake their head in frustration.)

    Or consider the placement of the verb particle in English (which corresponds to the German separable prefix):

    • "I picked it up."
    • "I picked up the ball."
    • "I picked the ball up."
    • but not "I picked up it."

    Now put these two rules together and you find that seemingly minor changes to a sentence (changing one temporal adverb for another, replacing a noun with a pronoun) has a radical effect upon sentence structure.

    • "I rarely pick up the ball."
    • "I don't pick it up often."

    The sentence structure goes from

    • <subject> <frequency> <verb> <particle> <object>

    to

    • <subject> <verb> <object> <particle> <frequency>.

    How is anybody expected to learn this?

    In German, the word order is predictable. All of these sentences would be structured as "<subject> <verb> <object> <frequency> <prefix>".

    For added fun, add "carefully" to the sentence and watch everything moves around again: "I don't often pick it up carefully."

    I find it ironic that when a Germanic language discards inflectional complexity (making it harder to see the relationship among the words in a sentence), it compounds the difficulty by adding greater structural complexity (making it even harder still to see the relationship among the words in a sentence).

    Twain complained about all the exceptions. Actually I find that German is comparatively lacking in exceptions; the rules tend to be followed fairly uniformly. Twain complains about "parentheticals", but it is the parentheticals that make English so crazy. In German, the rule is very simple: "The adjective comes before the noun". Even if the adjective happens to be complicated. "The to-its-winter-home-flying goose."

    Whereas in English, the rule is "The adjective comes before the noun, unless the adjective would sound better if it came after the noun." "The goose flying to its winter home" but "The slowly-flying goose". Try explaining that to your dad.

    English, now that's where all the crazy exceptions hang out.

    For example, the adverb can be moved to the front for emphasis

    • "Sometimes I go."
    • "Usually I don't go."
    • but not: "Rarely I don't go."
    • but not: "Always I don't go."

    "Rarely" is one of those exceptions that require inverted word order.

    • "Rarely do I go."

    And "always" is an even weirder exception: You can't start a declarative sentence with it at all! (Though you can start imperatives with it. Go figure.)

    Swedish used to be a more heavily inflected language, but it has been shedding its inflectional complexity over the centuries. (The number of genders reduced from three to two; special inflective forms for plurals have been removed; the dative case is now obsolete...) To compensate, Swedish (like English) has been making the verb forms and word order more complicated. The word "inte" ("not") goes immediately after the finite verb, except when it doesn't. And sometimes it changes to "ej" or "ikke" for reasons I have yet to determine.

  • The Old New Thing

    The white flash

    • 12 Comments
    If you had a program that didn't process messages for a while, but it needed to be painted for whatever reason (say, somebody uncovered it), Windows would eventually lose patience with you and paint your window white.

    Or at least, that's what people would claim. Actually, Windows is painting your window with your class background brush. Since most people use COLOR_WINDOW and since COLOR_WINDOW is white in most color schemes, the end result is a flash of white.

    Why paint the window white? Why not just leave it alone?

    Well, that's what it used to do, but the result was that the previous contents of the screen would be shown where the window "would be". So suppose you were looking at Explorer, and then you restored a program that stopped responding. Inside the program's main window would be... a picture of Explorer. And then people would try to double-click on what they thought was Explorer but was really a hung program.

    In Windows XP, the behavior for a window that has stopped painting is different. Now, the system captures the pixels of the unresponsive window and just redraws those pixels if the window is unable to draw anything itself. Note, however, that if the system can't capture all of the pixels - say because the window was partially covered - then the parts that it couldn't get are filled in with the class brush.

    Which is usually white.
  • The Old New Thing

    What happened to DirectX 4?

    • 19 Comments
    If you go through the history of DirectX, you'll see that there is no DirectX 4. It went from DirectX 3 straight to DirectX 5. What's up with that?

    After DirectX 3 was released, development on two successor products took place simultaneously: a shorter-term release called DirectX 4 and a more substantial longer-term release called DirectX 5.

    But based on the feedback we were getting from the game development community, they didn't really care about the small features in DirectX 4; what they were much more interested in were the features of DirectX 5. So it was decided to cancel DirectX 4 and roll all of its features into DirectX 5.

    So why wasn't DirectX 5 renamed to DirectX 4?

    Because there were already hundreds upon hundreds of documents that referred to the two projects as DirectX 4 and DirectX 5. Documents that said things like "Feature XYZ will not appear until DirectX 5". Changing the name of the projects mid-cycle was going to create even more confusion. You would end up with headlines like "Microsoft removes DirectX 5 from the table - kiss good-bye to feature XYZ" and conversations reminiscent of Who's on First:

    "I have some email from you saying that feature ABC won't be ready until DirectX 5. When do you plan on releasing DirectX 5?"

    "We haven't even started planning DirectX 5; we're completely focused on DirectX 4, which we hope to have ready by late spring."

    "But I need feature XYZ and you said that won't be ready until DirectX 5."

    "Oh, that email was written two weeks ago. Since then, DirectX 5 got renamed to DirectX 4, and DirectX 4 was cancelled."

    "So when I have a letter from you talking about DirectX 5, I should pretend it says DirectX 4, and when it says DirectX 4, I should pretend it says 'a project that has since been cancelled'?"

    "Right, but check the date at the top of the letter, because if it's newer than last week, then when it says DirectX 4, it really means the new DirectX 4."

    "And what if it says DirectX 5?"

    "Then somebody screwed up and didn't get the memo."

    "Okay, thanks. Clear as mud."

  • The Old New Thing

    Cell phones: Can't live with 'em, can't live without 'em, but maybe can ban 'em

    • 2 Comments
    Cell phones top the latest Lemelson-MIT Invention Index survey, asking people to name the invention that you hate the most yet cannot live without. [Link fixed 8:22am]
  • The Old New Thing

    Fixing security holes in other programs

    • 36 Comments
    Any crash report that involves a buffer overrun quickly escalates in priority. The last few that came my way were actually bugs in other programs that were detected by Windows.

    For example, there were a few programs that responded to the LVN_GETDISPINFO notification by overflowing the LVITEM.pszText buffer, writing more than LVITEM.cchTextMax characters.

    Another responded to IContextMenu::GetContextMenu by overflowing the pszName buffer, writing more than cchMax characters.

    Fortunately, in both cases, the overflow was only one character, so we were able to fix it by over-allocating the buffer by one and underreporting its size. That way, if the program overflows the buffer by one, it doesn't corrupt anything.

    Another one overflows one of its own stack buffers if you right-click on a file whose name is longer than MAX_PATH. (These files are legal but are hard to create or manipulate.) Not much we can do to prevent that one.

    So remember folks, watch those buffer sizes and don't overflow them. Security is everybody's job. We're all in this together.
  • The Old New Thing

    What tools should I assume everybody has?

    • 24 Comments
    My code samples assume you are using the latest header files from the Platform SDK (free download), the one that includes support for Win64. If you have an older SDK then you won't have various new data types like UINT_PTR and INT_PTR and should just use UINT and INT.

    I write code that is Win64-compliant as a matter of course since all code in Windows must be Win64-compliant. Writing noncompliant code is as foreign to me as it would be for a chess player to consider the ramifications of an illegal move. It doesn't even enter my mind.

    The question for readers: Should I assume that everybody has the latest header files? Or should I write old-style code (that won't run on Win64)?
  • The Old New Thing

    ia64 - misdeclaring near and far data

    • 22 Comments
    As I mentioned yesterday, the ia64 is a very demanding architecture. Today I'll discuss another way that lying to the compiler will come back and bite you.

    The ia64 does not have an absolute addressing mode. Instead, you access your global variables through the r1 register, nicknamed "gp" (global pointer). This register always points to your global variables. For example, if you had three global variables, one of them might be kept at [gp+0], the second at [gp+8] and the third at [gp+16].

    (I believe the Win32 MIPS calling convention also used this technique.)

    On the ia64, there is a limitation in the "addl" instruction: You can only add constants up to 22 bits, which comes out to 4MB. So you can have only 4MB of global variables.

    Well, it turns out that some people want more than 4MB of global variables. Fortunately, these people don't have one million DWORD variables. Rather, they have a few really big global arrays.

    The ia64 compiler solves this problem by splitting global variables into two categories, "small" and "large". (The boundary between "small" and "large" can be set by a compiler flag. I believe the default is to treat anything larger than 8 bytes as "large".)

    The code to access a "small" variable goes like this:

            addl    r30 = -205584, gp;; // r30 -> global variable
            ld4     r30 = [r30]         // load a DWORD from the global variable
    

    (The gp register actually points into the middle of your global variables, so that both positive and negative offsets can be used. In this case, the variable happened to live at a negative offset from gp.)

    By comparison, "large" global variables are accessed through a two-step process. First, the variable itself is allocated in a separate section of the file. Second, a pointer to the variable is placed into the "small" globals variables section of the module. As a result, accessing a "large" global variable requires an added level of indirection.

            addl    r30 = -205584, gp;; // r30 -> global variable forwarder
            ld8     r30 = [r30];;       // r30 -> global variable
            ld4     r30 = [r30]         // load a DWORD from the global variable
    

    If you leave the size of an object unspecified, like

    extern BYTE b[];
    
    then the compiler plays it safe and assumes the variable is large. If it turns out that the variable is small, the forwarder pointer will still be there, and the code will do the double-indirection to fetch something that could have been accessed with a single indirection. The code is slightly less efficient, but at least it still works.

    On the other hand, if you misdeclare the object as being small when it is actually large, then you end up in trouble. For example, if you write

        extern BYTE b;
    
    in one file, and
        extern BYTE b[256];
    
    in another, then files that include the first header will think the object is small and generate "small" code to access it, but files that include the second header will think it is large. And if the object turns out to be large after all, the code that used the first header file will fail pretty spectacularly.

    So don't do that. When you declare a variable, make sure to declare it accurately. Otherwise the ia64 will catch you in a lie, and you will pay.

Page 2 of 5 (43 items) 12345