March, 2008

  • The Old New Thing

    2008 Q1 link clearance: Microsoft blogger edition

    • 12 Comments
  • The Old New Thing

    You can drag multiple virtual objects, you know

    • 4 Comments

    A customer wanted to know how they could find out the directory that the user dropped a file onto. As we already noted, users can drop files onto things other than directories, so the question itself comes with incorrect hidden assumptions. This is another one of those cases where you have to ask the customer, "What are you really trying to do?" They have a problem and solved half of it and are asking you for help with the second half, the part that makes little sense.

    In this case, what the customer really wanted to do was create additional supporting files into the directory that the user dropped the file onto. To solve the real problem, all you have to do is add virtual objects to the data object the file being dragged.

    Let's illustrate this by adding a second file to our minimal example of dragging a virtual file. Actually, let's make it more interesting. We're going to drag one real file plus one virtual file. Start by adding another file's contents to our list of clipboard formats:

      enum {
        DATA_FILEGROUPDESCRIPTOR,
        DATA_FILECONTENTS0,
        DATA_FILECONTENTS1,
        DATA_NUM,
        DATA_INVALID = -1,
      };
    

    Of course, we need to initialize the FORMATETC for the contents of our new virtual file.

    CTinyDataObject::CTinyDataObject() : _cRef(1)
    {
      SetFORMATETC(&_rgfe[DATA_FILEGROUPDESCRIPTOR],
                   RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
      SetFORMATETC(&_rgfe[DATA_FILECONTENTS0],
                   RegisterClipboardFormat(CFSTR_FILECONTENTS),
                   TYMED_ISTREAM, /* lindex */ 0);
      SetFORMATETC(&_rgfe[DATA_FILECONTENTS1],
                   RegisterClipboardFormat(CFSTR_FILECONTENTS),
                   TYMED_HGLOBAL, /* lindex */ 1);
    }
    

    We need to add this second file to our FILEGROUPDESCRIPTOR. Doing this is trickier because the FILEGROUPDESCRIPTOR is a variable-size structure, so we have to declare our own version that has room for two files.

    // Hard-coded for expository purposes
    // (I can't believe I had to write that.)
    #define FILETODRAG TEXT("C:\\windows\\clock.avi")
    
    HRESULT CreateFileGroupDescriptor(HGLOBAL *phglob)
    {
      union {
         FILEGROUPDESCRIPTOR fgd;
         BYTE buffer[FIELD_OFFSET(FILEGROUPDESCRIPTOR, fgd[2])];
      } u;
      ZeroMemory(&u, sizeof(u));
      u.fgd.cItems = 2;
    
      // item 0: the file itself
      WIN32_FILE_ATTRIBUTE_DATA wfad;
      if (!GetFileAttributesEx(FILETODRAG, GetFileExInfoStandard,
                               &wfad)) {
       return E_FAIL;
      }
      u.fgd.fgd[0].dwFlags = FD_ATTRIBUTES | FD_CREATETIME |
                     FD_ACCESSTIME | FD_WRITESTIME | FD_FILESIZE;
      u.fgd.fgd[0].dwFileAttributes = wfad.dwFileAttributes;
      u.fgd.fgd[0].ftCreationTime   = wfad.ftCreationTime;
      u.fgd.fgd[0].ftLastAccessTime = wfad.ftLastAccessTime;
      u.fgd.fgd[0].ftLastWriteTime  = wfad.ftLastWriteTime;
      u.fgd.fgd[0].nFileSizeHigh    = wfad.nFileSizeHigh;
      u.fgd.fgd[0].nFileSizeLow     = wfad.nFileSizeLow;
      StringCchCopy(u.fgd.fgd[0].cFileName,
                    ARRAYSIZE(u.fgd.fgd[0].cFileName),
                    PathFindFileName(FILETODRAG));
    
      // item 1: The virtual "tag-along" file
      StringCchCopy(u.fgd.fgd[1].cFileName,
                    ARRAYSIZE(u.fgd.fgd[0].cFileName),
                    TEXT("TagAlong"));
    
      return CreateHGlobalFromBlob(&u, sizeof(u),
                                   GMEM_MOVEABLE, phglob);
    }
    

    The ad-hoc union declares a block of memory large enough for a FILEGROUPDESCRIPTOR that holds two files. File zero is the file we are dragging, and as a courtesy (and in violation of the "doing the absolute least amount of work necessary" that has guided the series), we fill in the file attributes so that when the file is dropped onto Explorer, the resulting file has the right metadata. On the other hand, our virtual file tries to sneak by with as little as possible, providing only the mandatory file name.

    The last thing to do is hand out the FILEGROUPDESCRIPTOR and the two files when we are asked for them.

    HRESULT CTinyDataObject::GetData(FORMATETC *pfe, STGMEDIUM *pmed)
    {
      ZeroMemory(pmed, sizeof(*pmed));
    
      switch (_GetDataIndex(pfe)) {
      case DATA_FILEGROUPDESCRIPTOR:
        pmed->tymed = TYMED_HGLOBAL;
        return CreateFileGroupDescriptor(&pmed->hGlobal);
    
      case DATA_FILECONTENTS0:
        pmed->tymed = TYMED_ISTREAM;
        return SHCreateStreamOnFile(FILETODRAG, STGM_READ,
                                    &pmed->pstm);
    
      case DATA_FILECONTENTS1:
        pmed->tymed = TYMED_HGLOBAL;
        return CreateHGlobalFromBlob("Dummy", 5, GMEM_MOVEABLE,
                                      &pmed->hGlobal);
      }
    
      return DV_E_FORMATETC;
    }
    

    There you have it, a data object that consists of a file (FILETODRAG) and a virtual file. When the user drops the data object into a folder, both files are dropped into the destination directory.

  • The Old New Thing

    The most academically elite team in college basketball, in quest for their first conference win in 21 years

    • 14 Comments

    Quantum Hoops is a new documentary which follows the college basketball program at the California Institute of Technology through its 2007–2008 season. The school is regularly honored as one of the finest academic colleges in the United States. Their basketball team, on the other hand, is not as well-honored. It has won two conference games in the last 35 years. Their last victory was 240 games ago.

    The elite college basketball teams dream of winning the NCAA tournament, or at least winning their conference championship. Lesser teams dream of posting a winning record. These guys just want to win a game. Just one game.

    Only a Game reviews the film and interviews the filmmaker. I love the soaring soundtrack as former players recount one of their two "recent" victories, breaking a 99-game losing streak. That happened in 1979. "We won a game. A real game."

    The film has a limited run in selected cities. In Seattle, it plays at the Metro Cinemas March 28 through April 4. Don't spoil it for me.

  • The Old New Thing

    Microspeak: Newplacement

    • 27 Comments

    When talking about why people buy computers, there are two broad categories, "replacement computers" (those which replace an older machine being retired) and "new placement computers" (those which do not).

    Now, sure, you have to call them something, but "new placements" sounds kind of markety. Then again, I felt the same way about using "seats" to mean "users" and see what that got me.

    Pre-emptive clever comment: Verbing weirds language.

  • The Old New Thing

    Meet Deltalina, the star of Delta's new in-flight safety video

    • 11 Comments

    Who knew that in-flight safety videos could be so popular? Introduced to the world on the Delta blog, the latest Delta in-flight safety video has generated quite a buzz around the Internet. Well, more accurately, the buzz surrounds the video's star, flight attendant Katherine Lee, known to her drooling fans as Deltalina, with secondary attention paid to the cameo appearance of Exit Row Girl. The FlyerTalk forum is all over this topic of grave national importance, including appearances from Headphone Guy, Seat Belt Guy, and the aforementioned Exit Row Girl.

    I'm not sure who is in more need of a life, the person who can explain why the volume in the current video jumps up at about the halfway point, or me, the person who actually read the thread to find all these links.

  • The Old New Thing

    Why do structures get tag names even if there is a typedef?

    • 17 Comments

    As we noted last time, structure tags are different from the typedef name as a historical artifact of earlier versions of the C language. But what about just leaving out the name entirely?

    typedef struct {
     ...
    } XYZ;
    

    One problem with this approach is that it becomes impossible to make a forward reference to this structure because it has no name. For example, if you wanted to write a prototype for a function that took one of these structures, and you could not be sure that the header file defining the XYZ type definition has already been included, you can still refer to the structure by its tag name.

    // in header file A
    typedef struct tagXYZ {
     ...
    } XYZ;
    
    // in header file B
    BOOL InitializeFromXYZ(const struct tagXYZ *pxyz);
    

    The two header files can be included in either order because header file B uses a forward reference to the XYZ structure. Naturally, you would hope that people would include header file A before header file B, but there can be cases where it is not practical. (For example, header file A may contain definitions that conflict with something else that the program needs, or header file A may change its behavior based on what has already been #define'd, and you don't want to include it before the application has a chance to set up those #defines.)

    But a more important reason to avoid anonymous types is that it creates problems for MIDL.

    Okay, it doesn't actually create problems for MIDL. MIDL handles it just fine, but the way MIDL handles it creates problems for you, for when you create an anonymous type in MIDL, such as an anonymous structure above, or an anonymous enumeration like this:

    typedef enum { ... } XYZ;
    

    MIDL auto-generates a name for you. For example, the above enumeration might end up in the generated header file as

    typedef enum __MIDL___MIDL_itf_scratch_0000_0001
    {
        ...
    } XYZ;
    

    The kicker is that the auto-generated name changes if you change the IDL file. And since typedefs are just shorthand for the underlying type (rather than a type in and of themselves), the name saved in the PDB is the unwieldy __MIDL___MIDL_itf_scratch_0000_0001. Try typing that into the debugger, yuck.

    Furthermore, having the name change from build to build means that you have to make sure code libraries are all built from exactly the same header file versions, even if the changes are ostensibly compatible. For example, suppose you compile a library with a particular version of the header file, and then you add a structure to the MIDL file which has no effect on the functions and structures that the library used. But still, since you changed the MIDL file, this changes the auto-generated symbol names. Now you compile a program with the new header file and link against the library. Result: A whole bunch of errors, because the library, say, exports a function that expects its first parameter to be a __MIDL___MIDL_itf_scratch_0000_0001 (because the library was built from the older MIDL-generated header file), but your program imports a function that expects its first parameter to be a __MIDL___MIDL_itf_scratch_0001_0002 (because you compiled with the newer MIDL-generated header file).

    What's more, when you update the header file, your source control system will recognize hundreds of changes, since the MIDL compiler generated a whole different set of names which no longer match the names from the previous version of the header file, even though you didn't change the structure! This isn't fatal, but it makes digging through source code history more of an ordeal since the "real changes" are buried amidst hundreds of lines of meaningless changes.

    Now, this particular rule of thumb is not universally adhered-to in Windows header files, in large part, I believe, simple because people aren't aware of the potential for mischief. But maybe now that I wrote them up, people might start paying closer attention.

  • The Old New Thing

    We hope you enjoyed this bus tour of Charles de Gaulle International Airport

    • 20 Comments

    My trip to Lisbon entailed a connection in Paris at Charles de Gaulle International Airport. So now, I've technically been to France, but since I never left the airport, I don't think it really counts.

    (When I mentioned to one of my colleagues that I paid a brief visit to his native country, he replied, "I hope it went okay." Off my confused expression, he continued: "I try to avoid changing planes in Paris. There is about a 30% chance that somebody will be on strike.")

    When our flight landed, a series of buses arrived to take the passengers to the terminal. Or at least, that's what we thought the bus was going to do. In fact, the bus took us on what appeared to be a grand tour of all the service areas of the airport. Look, the baggage sorting machine. Here's a building shaped like a wheel of cheese. Over there, the food service trucks. Hey, didn't we pass this building already?

    After a three-hour tour of the facilities, we finally stopped in front of the terminal. I was expecting an announcement over the speaker: "We hope you enjoyed this bus tour of Charles de Gaulle International Airport."

  • The Old New Thing

    Why are structure names different from their typedef names?

    • 25 Comments

    In Windows header files, many structures are declared like this:

    typedef struct tagXYZ {
     ...
    } XYZ;
    
    typedef struct _XYZ {
     ...
    } XYZ;
    
    /* there are other variations, too */
    

    Why is the structure name different from typedef name?

    This is a holdover from very early versions of the C language where structure tags, union tags, and typedefs were kept in the same namespace. Consequently, you couldn't say typedef struct XYZ { ... } XYZ;. At the open brace, the compiler registers XYZ as a structure tag name, and then when XYZ appears a second time, you get a redeclaration error. The standard workaround for this was to make the structure tag name a minor modification of the typedef name, most typically by putting the word tag in front.

    The C language standardization process separated the structure and typename name spaces, so this workaround is no longer necessary, but it doesn't hurt either. Besides, even if new structures followed the typedef struct XYZ { ... } XYZ; pattern, you would just have people asking, "Why do some structures in winuser.h use the tagXYZ pattern and others use the XYZ pattern? Why can't it just be consistent?"

    Next time, why you also don't see the pattern typedef struct { ... } XYZ very much either.

  • The Old New Thing

    Happy Waffle Day! And other holidays named after food

    • 35 Comments

    Today is Waffle Day in Sweden, and the reason why today of all days is Waffle Day I find quite amusing.

    March 25th is the Feast of The Annunciation according to the Catholic Church calendar, the day on which the archangel Gabriel announced to Mary that she would conceive a child, the Son of God. Why March 25th? The date was arrived at by the following highly scientific calculation:

    1. Everybody knows that Jesus was born on Christmas Day, December 25th.
    2. Everybody also knows that pregnancy lasts nine months.
    3. Nine months before Christmas is March 25th.

    Ipso facto, habeas corpus delicti, we have determined the date of the Annunciation to be March 25th via incontrovertible logic.

    In England, the holiday is known as Lady Day, a name which is echoed Sweden, where the holiday goes by the name Our Lady's Day: Vårfrudagen. Now, if you say Vårfrudagen really fast and mumble it, somebody with bad hearing might misinterpret what you said as Vaffeldagen, which means Waffle Day.

    Now, Waffle Day is not to be confused with Pancake Day, a British holiday which coincides with Shrove Tuesday, the day before the beginning of Lent. Unlike Waffle Day, which is based on a pun, Pancake Day had a practical origin: In olden days, consumption of dairy products was prohibited during Lent, and pancakes were a convenient way to use up your leftover eggs, butter, and milk all at one go. (That is, on years when it isn't cancelled.)

    And this year, a Polish colleague taught me about Pączki Day, a holiday that takes place on the Thursday before Lent and is apparently celebrated by "eating as many pączki as possible." Pączki are pastries that resemble a jelly-filled doughnut, and the holiday served the same practical purpose as Pancake Day in England.

    What food-based holidays exist in your country? Here are a few more to get you started:

    • Greece: Τσικνοπέμπτη (Tsiknopempti = Barbecue Thursday). Eat lots of barbecued meat on the Thursday before Lent.
    • East Asia: 中秋節 (中秋节, zhōngqiūjié = Mid-Autumn Festival, a.k.a. the Mooncake Festival). Eat moon cakes in late September. The traditional filling is lotus seed paste, but for some reason I imprinted on red bean paste.
  • The Old New Thing

    What's the difference between int and INT, long and LONG, etc?

    • 40 Comments

    When you go through Windows header files, you'll see types with names INT, LONG, CHAR, and so on. What's the difference between these types and the uncapitalized ones?

    Well, there isn't one any more.

    What follows is an educated guess as to the story behind these types.

    The application binary interface for an operating system needs to be unambiguous. Everybody has to agree on how parameters are passed, which registers are preserved, that sort of thing. A compiler need only enforce the calling convention rules at the boundary between the application and the operating system. When a program calls another function provided by that same program, it can use whatever calling convention it likes. (Not a true statement but the details aren't important here.) Therefore, a calling convention attribute on the declarations of each operating system function is sufficient to get everybody to agree on the interface.

    However, another thing that everybody needs to agree on is the sizes of the types being passed to those functions or used in structures that cross the application/operating system boundary. The C language makes only very loose guarantees as to the sizes of each of the types, so language types like int and long would be ambiguous. One compiler might decide that a long is a 32-bit integer, and another might decide that it's a 64-bit integer. To make sure that everybody was on the same page, the Windows header files defined "platform types" like INT and LONG with prescribed semantics that everybody could agree on. Each compiler vendor could tweak the Windows header file to ensure that the type definition for these platform types resulted in the value that Windows expected. One compiler might use typedef long LONG another might use typedef __int32 LONG.

    Okay, but this doesn't explain VOID. Maybe VOID was added for the benefit of compilers which didn't yet support the then-new ANSI C standard type void? Those older compilers could typedef int VOID; and functions that were declared as "returning VOID" would be treated as if they returned an integer that was always ignored. Or maybe it was just added to complete the set, who knows.

    In the intervening years, most if not all compilers which target Windows have aligned their native types with Windows' platform types. An int is always a 32-bit signed integer, as is a long. As a result, the distinction between language types and platform types is now pretty much academic, and the two can be used interchangeably. New Windows functions tend to be introduced with language types, leaving platform types behind only for compatibility.

Page 1 of 4 (33 items) 1234