September, 2010

  • The Old New Thing

    You must flush GDI operations when switching between direct access and GDI access, and direct access includes other parts of GDI

    • 8 Comments

    A customer was running into problems when accessing the pixels of a DIB section. They used the HANDLE parameter to Create­DIB­Section and created two bitmaps from the same underlying memory. Those two bitmaps were then selected into corresponding DCs, and the customer found that changes to the pixels performed by writing via one DC were not visible when read from the other DC.

    The customer pointed out this clause in MSDN:

    You need to guarantee that the GDI subsystem has completed any drawing to a bitmap created by Create­DIB­Section before you draw to the bitmap yourself. Access to the bitmap must be synchronized. Do this by calling the Gdi­Flush function. This applies to any use of the pointer to the bitmap bit values, including passing the pointer in calls to functions such as Set­DIBits.

    The customer said, "The description says that it applies to cases where you modify the bits yourself through the direct memory pointer. But all of our access is performed through HDCs; I would think GDI is smart enough to handle that, but we've found that we still need to call Gdi­Flush to get the two DCs back in sync."

    What you ask GDI to do you have done yourself. That's why the documentation say any use of the pointer. Sort of like in law, where in many causes you can be punished for "doing X or causing X to be done." If you induce somebody else to do X, you're in violation as much as if you had done X yourself.

    I doubt that every call to GDI ends with a big loop that checks whether the bits it just modified also belong to some other GDI bitmap in the system.

    GDIFinishAPI(HDC hdc)
    {
     if (IsDIBSection(GetCurrentObject(hdc, OBJ_BITMAP), &ds)) {
      EnumGdiObjects(FlushIfOverlap, &ds));
     }
    }
    
    FlushIfOverlap(HGDIOBJ h, DIBSECTION *pds)
    {
     if (IsDIBSection(h, &ds) &&
         DIBSectionsReferToSameUnderlyingBits(pds, &ds)) {
      GdiFlush();
     }
    }
    

    That would seriously slow down all DIB section operations to cover a rare scenario that most people don't realize is even possible to create. Not the best engineering tradeoff.

    The point of the documentation is is that if you ask GDI to mess with the bits in the bitmap via the HDC, you must call Gdi­Flush before anybody else tries to access those bits, even if that "somebody else" is another part of GDI. The example of Set­DIBits is an attempt to capture the sense of this requirement.

    Translating into this specific scenario: You must flush the pending changes whenever you switch between "GDI accesses bits through the DIB section created by this handle" and "the bits are accessed by anybody else." And "anybody else" could be "GDI accesses bits through the DIB section created by a different handle."

    Bonus chatter: What's the deal with Gdi­Flush anyway?

    As a performance optimization, GDI performs "batching" of operations. When you ask GDI to perform an operation, it doesn't always do it right away. Instead, it may choose to store the action in a buffer, and when the buffer gets full, it "flushes the batch" and sends the commands that it had been saving up into kernel mode for execution. (This idea of buffering up operations and submitting them as a batch is hardly new to GDI. The C stdio library does it, and in networking, a variation of it goes by the name Nagle's Algorithm.)

    GDI also flushes the batch when necessary in order to preserve semantics; for example, if you call Gradient­Fill and follow it with a call to Get­Pixel, GDI needs to flush out the Gradient­Fill before issuing the Get­Pixel so that the pixels that get read match the pixels that were written. (A much more common case of just-in-time flushing is where you Bit­Blt the results out of the bitmap into another device context.)

    This behind-the-scenes optimization works great with one exception: DIB sections. Since the memory for DIB sections is directly visible, GDI doesn't get a chance to sneak a call to Gdi­Flush before you issue your "mov eax, [esi]" instruction. Hence the clause in MSDN explaining that when you switch between GDI access and direct access, you need to call Gdi­Flush to get all pending operations out of the buffer so that the bits in memory match the operations you performed.

    Many years ago, we saw another case where we had to compensate for GDI batching.

  • The Old New Thing

    Happy Mid-Autumn festival 2010

    • 19 Comments

    The other day, I told my niece that I would be eating a moon cake on Wednesday.

    She asked, "Why? Is it your birthday?"

    For the record, my favorite flavor is red bean paste.

    Bonus chatter: The underground economy of moon cakes, moon cake vouchers, and how moon cakes are like fruitcake.

  • The Old New Thing

    What is the effect of the /LARGEADDRESSAWARE switch on a DLL?

    • 27 Comments

    Nothing. Large-address-awareness is a process property, which comes from the EXE.

  • The Old New Thing

    Fact check: The first major Microsoft product launched via Webcast

    • 14 Comments

    In 2009, while hosting the Webcast launch of Office Communications Server 2007 R2 (what a mouthful; no wonder they renamed it Lync), Stephen Elop claimed that this was the first time Microsoft had launched a major product via Webcast.

    Elop's crack team of marketing researchers apparently forgot about the Webcast launch, just three months earlier, of Windows Small Business Server 2008.

    Maybe somebody can find an even earlier Webcast launch of a major Microsoft product.

    (Perhaps Elop is claiming that Small Business Server is not a major product, but by that standard, neither is Office Communications Server, which is arguably even more of a niche product than SBS.)

  • The Old New Thing

    How do I get the dropped height of a combo box?

    • 3 Comments

    Via the Suggestion Box, commenter Twisted Combo responds to an old blog entry on why the size of a combo box includes the height of the drop-down by asking, But how do I *get* the dropped down height?"

    By using the deviously-named CB_GETDROPPEDCONTROLRECT message, which the windowsx.h header file wraps inside the ComboBox_GetDroppedControlRect macro.

    Start with the scratch program and make these changes:

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      g_hwndChild = CreateWindow(
          TEXT("combobox"), NULL, WS_CHILD | WS_VISIBLE |
          WS_TABSTOP | CBS_DROPDOWN,
          0, 0, 500, 500, hwnd, (HMENU)1, g_hinst, 0);
    
      ComboBox_AddString(g_hwndChild, TEXT("First"));
      ComboBox_AddString(g_hwndChild, TEXT("Second"));
      ComboBox_AddString(g_hwndChild, TEXT("Third"));
      ComboBox_AddString(g_hwndChild, TEXT("Fourth"));
    
      TCHAR szBuf[200];
      RECT rcWindow;
      GetWindowRect(g_hwndChild, &rcWindow);
      RECT rcDrop;
      ComboBox_GetDroppedControlRect(g_hwndChild, &rcDrop);
      wsprintf(szBuf, TEXT("window height %d, dropdown height %d"),
        rcWindow.bottom - rcWindow.top, rcDrop.bottom - rcDrop.top);
      SetWindowText(hwnd, szBuf);
    
      return TRUE;
    }
    

    The actual results will naturally vary depending on your system configuration, but when I ran this program, the window caption said "24 / 500".

  • The Old New Thing

    It's amazing how many business meetings there are in Munich in late September

    • 20 Comments

    During my emergency vacation, we stopped at a German supermarket, and my friend loaded up on all sorts of odd and fascinating products. This is something he does every time he travels abroad. At the register, my friend did the work of unloading the cart onto the conveyor belt while I went ahead to bag and to deal with any questions from the cashier, since I was the only German-speaking person in our little group. The woman behind my friend looked at what he was buying and made some remark that implied that he did not make the most price-efficient choices.

    My friend replied, "Oh, we're from the United States, and I'm just buying things that we don't have in the States."

    The woman's demeanor changed. She was no longer upset that my friend failed to purchase the sale items. Instead, she said with concern, "But these are not typical German items."

    My friend explained, "I just like buying different things."

    I was out of earshot for this conversation. Otherwise, I would have quipped, "Keine Sorgen. Letztes Mal haben wir typische deutsche Sachen gekauft." ("Don't worry. Last time, we bought typical German things.")

    Bonus chatter: As we loaded the groceries into the car, a gentleman noticed that we were speaking English and pegged us for foreigners. He asked us, "What brings you to Munich?"

    My friend explained, "I'm here for a business meeting."

    The gentleman replied with a twinkle in his eye, "It's amazing how many business meetings there are in Munich in late September."

    By popular demand: A few people wanted to know what sort of odds and ends my friend bought. To be honest, I don't remember because, after all, it was seven years ago. But it included novelty candies (for example, in the United States, candy cigarettes are difficult if not impossible to find), food packaged in squeezable tubes, and cans of fruit cocktail. I don't know why he got the fruit cocktail.

  • The Old New Thing

    What's up with the strange treatment of quotation marks and backslashes by CommandLineToArgvW

    • 28 Comments

    The way the CommandLineToArgvW function treats quotation marks and backslashes has raised eyebrows at times. Let's look at the problem space, and then see what algorithm would work.

    Here are some sample command lines and what you presumably want them to be parsed as:

    Command line Result
    program.exe "hello there.txt" program.exe
    hello there.txt
    program.exe "C:\Hello there.txt" program.exe
    C:\Hello there.txt

    In the first example, we want quotation marks to protect spaces.

    In the second example, we want to be able to enclose a path in quotation marks to protect the spaces. Backslashes inside the path have no special meaning; they are copied as any other normal character.

    So far, the rule is simple: Inside quotation marks, just copy until you see the matching quotation marks. Now here's another wrinkle:

    Command line Result
    program.exe "hello\"there" program.exe
    hello"there

    In the third example, we want to embed a quotation mark inside a quotated string by protecting it with a backslash.

    Okay, to handle this case, we say that a backslash which precedes a quotation mark protects the quotation mark. The backslash itself should disappear; its job is to protect the quotation mark and not to be part of the string itself. (If we kept the backslash, then it would not be possible to put a quotation mark into the command line parameter without a preceding backslash.)

    But what if you wanted a backslash at the end of the string? Then you protect the backslash with a backslash, leaving the quotation mark unprotected.

    Command line Result
    program.exe "hello\\" program.exe
    hello\

    Okay, so what did we come up with?

    We want a backslash before a quotation mark to protect the quotation mark, and we want a backslash before a backslash to protect the backslash (so you can end a string with a backslash). Otherwise, we want the backslash to be given no special treatment.

    The CommandLineToArgvW function therefore works like this:

    • A string of backslashes not followed by a quotation mark has no special meaning.
    • An even number of backslashes followed by a quotation mark is treated as pairs of protected backslashes, followed by a word terminator.
    • An odd number of backslashes followed by a quotation mark is treated as pairs of protected backslashes, followed by a protected quotation mark.

    The backslash rule is confusing, but it's necessary to permit the very important second example, where you can just put quotation marks around a path without having to go in and double all the internal path separators.

    Personally, I would have chosen a different backslash rule:

    Warning - these are not the actual backslash rules. These are Raymond's hypothetical "If I ran the world" backslash rules.

    • A backslash followed by another backslash produces a backslash.
    • A backslash followed by a quotation mark produces a quotation mark.
    • A backslash followed by anything else is just a backslash followed by that other character.

    I prefer these rules because they can be implemented by a state machine. On the other hand, it makes quoting regular expressions a total nightmare. It also breaks "\\server\share\path with spaces", which is pretty much a deal-breaker. Hm, perhaps a better set of rules would be

    Warning - these are not the actual backslash rules. These are Raymond's second attempt at hypothetical "If I ran the world" backslash rules.

    • Backslashes have no special meaning at all.
    • If you are outside quotation marks, then a " takes you inside quotation marks but generates no output.
    • If you are inside quotation marks, then a sequence of 2N quotation marks represents N quotation marks in the output.
    • If you are inside quotation marks, then a sequence of 2N+1 quotation marks represents N quotation marks in the output, and then you exit quotation marks.

    This can also be implemented by a state machine, and quoting an existing string is very simple: Stick a quotation mark in front, a quotation mark at the end, and double all the internal quotation marks.

    But what's done is done, and the first set of backslash rules is what CommandLineToArgvW implements. And since the behavior has been shipped and documented, it can't change.

    If you don't like these parsing rules, then feel free to write your own parser that follows whatever rules you like.

    Bonus chatter: Quotation marks are even more screwed up.

  • The Old New Thing

    How is the CommandLineToArgvW function intended to be used?

    • 18 Comments

    The CommandLineToArgvW function does some basic command line parsing. A customer reported that it was producing strange results when you passed an empty string as the first parameter:

    LPWSTR argv = CommandLineToArgvW(L"", &argc);
    

    Well, okay, yeah, but huh?

    The first parameter to CommandLineToArgvW is supposed to be the value returned by GetCommandLineW. That's the command line, and that's what CommandLineToArgvW was designed to parse. If you pass something else, then CommandLineToArgvW will try to cope, but it's not really doing what it was designed for.

    It turns out that the customer was mistakenly passing the lpCmdLine parameter that was passed to the wWinMain function:

    int WINAPI wWinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPWSTR lpCmdLine,
        int nCmdShow)
    {
        int argc;
        LPWSTR argv = CommandLineToArgvW(lpCmdLine, &argc);
        ...
    }
    

    That command line is not in the format that CommandLineToArgvW expects. The CommandLineToArgvW function wants the full, unexpurgated command line as returned by the GetCommandLineW function, and it breaks it up on the assumption that the first word on the command line is the program name. If you hand it an empty string, the CommandLineToArgvW function says, "Whoa, whoever generated this command line totally screwed up. I'll try to muddle through as best I can."

    Next time, we'll look at the strange status of quotation marks and backslashes in CommandLineToArgvW.

  • The Old New Thing

    Follow-up: The impact of overwhelmingly talented competitors on the rest of the field

    • 14 Comments

    A while back, I wrote on the impact of hardworking employees on their less diligent colleagues. Slate uncovered a study that demonstrated the reverse effect: How Tiger Woods makes everyone else on the course play worse.

    The magic ingredient is the incentive structure. If you have an incentive structure which rewards the best-performing person, and there is somebody who pretty much blows the rest of the field out of the water, then the incentive structure effectively slips down one notch. Everybody is now fighting for second place (since they've written off first place to Tiger Woods), and since the second place prize is far, far below the first place prize, people don't have as much incentive to play well as they did when Tiger wasn't in the mix.

    The effect weakens the further down the ladder you go, for although the difference between first and second place is huge, the difference between 314th place and 315th place is pretty negligible.

  • The Old New Thing

    How do I create a UNC to an IPv6 address?

    • 18 Comments

    Windows UNC notation permits you to use a raw IPv4 address in dotted notation as a server name: For example, net view \\127.0.0.1 will show you the shared resources on the computer whose IP address is 127.0.0.1. But what about IPv6 addresses? IPv6 notation contains colons, which tend to mess up file name parsing since a colon is not a valid character in a path component.

    Enter the ipv6-literal.net domain.

    Take your IPv6 address, replace the colons with dashes, replace percent signs with the letter "s", and append .ipv6-literal.net. This magic host resolves back to the original IPv6 address, but it avoids characters which give parsers the heebie-jeebies.

    Note that this magic host is resolved internally by Windows and never hits the network. It's sort of a magic escape sequence.

Page 2 of 4 (31 items) 1234