• The Old New Thing

    When you want to copy a file into a folder, make sure you have a folder

    • 19 Comments

    This story is inspired by an actual customer problem.

    The program LitWare.exe is used for TPS management, and when you want to create a new TPS report, you have to pick a cover sheet. The program shows you the cover sheets that have been defined, which it loads from the C:\TPS Cover Sheets directory.

    The customer found that on one of the machines, the cover sheets weren't showing up, even though the standard system setup copies a sample cover sheet into the C:\TPS Cover Sheets directory. The error message they got was Cannot load cover sheets. The directory name is invalid.

    The customer did some troubleshooting and determined that "The cover sheet directory is missing, and we have a file instead."

    C:\>dir
     Volume in drive C is INITECH
     Volume Serial Number is BAAD-F00D
    
     Directory of C:\
    
    09/18/2006  02:43 PM                24 autoexec.bat
    09/18/2006  02:43 PM                10 config.sys
    03/18/2009  10:30 AM    <DIR>          Program Files
    11/21/2008  01:04 PM             1,677 TPS Cover Sheets
    02/20/2008  10:39 AM    <DIR>          Users
    05/29/2009  02:23 PM    <DIR>          Windows
                   2 File(s)          1,711 bytes
                   3 Dir(s)  229,031,751,680 bytes free
    

    One of my colleagues employed psychic powers to determine that at the time the customer tried to install the sample cover sheet on the machine, the C:\TPS Cover Sheets directory did not yet exist, and that the batch file they used to set up a new computer just does a copy \\server\TPSConfig\Sample.tps "C:\TPS Cover Sheets", which results in a file being created with the name C:\TPS Cover Sheets.

    The customer was surprised by this conclusion. "I would think that copy will fail if the C:\TPS Cover Sheets directory doesn't exist, but this might be our problem. We'll look into it." (I guess this customer never used the copy command to copy a file to a new name.)

    If the destination of a copy command exists and is a directory, then the source files are copied into that directory. If the destination of a copy command does not exists or if it exists and is a file, then the destination is treated as a file name for the destination. (If there is more than one source file, then they are concatenated as if they were text files.)

    The customer went back and checked the scripts, and the line they used was almost exactly what my colleague predicted:

    copy "\\INITECH\Defaults\Sample cover sheet.tps" "C:\TPS Cover Sheets" /Y
    

    If the C:\TPS Cover Sheets directory hasn't been created yet, then that would explain the behavior they're seeing: The copy command sees that the destination doesn't exist and assumes you are doing a file-to-file copy (as opposed to a file-to-directory copy). In this case, the problem was that copying a sample cover sheet was a step they added to their setup scripts, but they added it before the step that creates the cover sheet directory. Reordering the two steps fixed the problem.

  • The Old New Thing

    Horrifically nasty gotcha: FindResource and FindResourceEx

    • 15 Comments

    The Find­Resource­Ex function is an extension of the Find­Resource function in that it allows you to specify a particular language fork in which to search for the resource. Calilng the Find­Resource function is equivalent to calling Find­Resource­Ex and passing zero as the wLanguage.

    Except for the horrible nasty gotcha: The second and third parameters to Find­Resource­Ex are in the opposite order compared to the second and third parameters to Find­Resource!

    In other words, if you are adding custom language support to a program, you cannot just stick a wLanguage parameter on the end when you switch from Find­Resource to Find­Resource­Ex. You also have to flip the second and third parameters.

    Original code Find­Resource(hModule, MAKEINTRESOURCE(IDB_MYBITMAP), RT_BITMAP)
    You change it to Find­Resource­Ex(hModule, MAKEINTRESOURCE(IDB_MYBITMAP), RT_BITMAP, 0)
    You should have changed it to Find­Resource­Ex(hModule, RT_BITMAP, MAKEINTRESOURCE(IDB_MYBITMAP), 0)

    The nasty part of this is that since the second and third parameters are the same type, the compiler won't notice that you got them backward. The only way you find out is that your resource code suddenly stopped working.

  • The Old New Thing

    The subtleties of a Will Ferrell movie, and other observations from the in-flight entertainment on a Chinese airline

    • 20 Comments

    My flights to and from Beijing were on Hainan Airlines, a Chinese airline. One consequence of this is that Mandarin Chinese is the primary language of communication; English is a distant second. It also means that the in-flight movies are subtitled in Chinese, so if you can't read Chinese, you are restricted to movies in languages you understand.

    I wasn't interested in the English-language movies, although I did watch a little bit of "The Other Guys", a Will Ferrell vehicle. In one scene, Ferrell's character and his friend have dinner in a Chinese restaurant. Ferrell's character says to the waiter, "謝謝" which means "Thank you" in Mandarin. The waiter responds, "唔該" which means (in this context) "You're welcome" in Cantonese.¹

    Part of my brain wondered if this language mismatch was some sort of subtle commentary about the nature of Will Ferrell's character, that he's perhaps a bit of a poseur, or that he's out of place and doesn't realize it?

    And part of my brain couldn't believe that the other part of my brain used "subtle" and "Will Ferrell" in the same sentence.

    Anyway, the only other language I knew that was offered by the in-flight entertainment system was German. So I watched Willi und die Wunder dieser Welt, a movie-length version of the German children's television show Willi wills wissen. And watching the movie reminded me that Germans are obsessed with poop. During the course of the movie, you see a flying fox pooping, you see a polar bear pooping, and you investigate a Japanese toilet. I didn't stick around for the whole movie, but I wouldn't be surprised if you also saw a scorpion pooping in the final segment.

    (In the Canadian segment, somebody talks with Willi in heavily Canadian-accented German which was apparently learned phonetically. I could barely understand him. It reminded me of my high school German class and the students who couldn't shake their thick American accents.)

    Footnote

    ¹There are several phrases that roughly mean "Thank you" in Cantonese. The two primary ones are the aforementioned "唔該" and "多謝", and the rules governing proper use of each one are complicated.

  • The Old New Thing

    What's the difference between LVM_HITTEST and LVM_INSERTMARKHITTEST?

    • 3 Comments

    One customer was confused by the two list view hit-test messages LVM_HITTEST and LVM_INSERTMARKHITTEST. What is the difference between the two?

    The LVM_HITTEST message tries to find the item the point is over.

    The LVM_INSERTMARKHITTEST message tries to find the item the point is next to.

    For example, suppose you have two 32×32 items, item zero at (0, 0) and item one at (0, 32).

    Coordinates LVM_HITTEST LVM_INSERTMARKHITTEST
    (0, 34) On item 1 Next to item 1 on the top side
    (0, 99) Nowhere Next to item 1 on the bottom side

    You use LVM_HITTEST when you the user is dragging something and you want to see what the item is going to be dropped on. For example, your program might ask the user to drag an icon representing a trouble report and drop it onto the icon representing the department the report should be assigned to.

    You use LVM_INSERTMARKHITTEST when the user is dragging something and you want to see what the item is going to be dropped between. For example, your program might show a list of customers, and the user can drag a new customer into the list or rearrange the customers by dragging an existing one. The LVM_INSERTMARKHITTEST message tells you where the dropped item should go.

    The shell uses both of these hit-test messages when you drop a file into an Explorer window. It uses the Explorer uses LVM_HITTEST to determine whether you're dropping the file onto an item in the folder (hit-test reports an item) or whether you're dropping the file into the folder itself (hit-test reports nowhere). Furthermore, if you're dropping into the folder and Auto-Arrange is enabled, then Explorer uses the LVM_INSERTMARKHITTEST to determine where to place the item after it is dropped.

  • The Old New Thing

    When will the static control automatically delete the image loaded into it, and when is it the responsibility of the application?

    • 25 Comments

    If you create a static control with initial contents (for example, by creating a BITMAP or ICON control in a dialog template), then the static control will load the contents upon creation and destroy the contents upon destruction. So at least in the case where you don't touch the static control, things will work automatically.

    But once you touch it, things get confusing.

    If you send the STM_SET­IMAGE message to a static control, this does a few things (assuming your parameters are all valid):

    • The previous image is replaced by the new image you passed.
    • The message returns a handle to the previous image.
    • The static control turns off automatic image deletion.

    The third part is the tricky part. If you ever (successfully) send a static control the STM_SET­IMAGE message, then it says, "Okay, it's all your problem now." You are now responsible not only for destroying the new image, but you are also responsible for destroying the old image that was returned.

    In other words, the following operation is not a nop:

    HBITMAP hbmPrev = SendMessage(hwndStatic, STM_SETIMAGE,
                                  IMAGE_BITMAP, (LPARAM)hbmNew);
    SendMessage(hwndStatic, STM_SETIMAGE,
                IMAGE_BITMAP, (LPARAM)hbmPrev);
    

    This sounds like a nop, since all you did was change the image, and then change it back. But the side effect is also that you made the static control go into your problem mode, and the original image will no longer be automatically destroyed. If you forget to destroy it yourself, then you have a leak.

    Wait, it gets worse.

    If you are using version 6 of the common controls, then things get even more confusing if you use the STM_SET­IMAGE message to change the IMAGE_BITMAP of a SS_BITMAP static control, and the bitmap you pass is a 32-bpp bitmap, and the image has a nonzero alpha channel, then the static control will make a copy of the bitmap you passed in and act as if you had passed that copy instead.¹ This by itself is no big deal, because the responsibility for destroying the image you passed in still resides with you, the application, so the rules haven't changed there.

    The nasty bit is that the application also must assume responsibility for destroying the secret copy. That bitmap you didn't even know existed and don't have a handle to? Yeah, you're on the hook for that one too.

    How unfair.

    Even more confusing is that if you send STM_SET­IMAGE a second time, it will replace the bitmap and return a handle to the secret copy (which is a bitmap you've never seen before).

    This means that the following assertion can fire:

    HBITMAP hbmPrev = SendMessage(hwndStatic, STM_SETIMAGE,
                                  IMAGE_BITMAP, (LPARAM)hbmNew);
    HBITMAP hbmBack = SendMessage(hwndStatic, STM_SETIMAGE,
                                  IMAGE_BITMAP, (LPARAM)hbmPrev);
    assert(hbmNew == hbmBack); // ??
    

    You would think that the assertion is safe because all you did was change the bitmap to hbmNew, then change it back. And when you change it back, the "previous value" is the value hbmNew you set it to on the previous line.

    Except that if hbmNew satisfies the above magic criteria, then the value in hbmBack is not hbmNew but rather the handle to the secret copy.

    Which you have to remember to destroy.

    Yuck.

    The secret copy is not too secret. You can get a handle to it by sending the STM_GET­IMAGE message. Which you now need to do when you destroy the static control, just in case it's the secret copy. You need to compare the current image against the one that you thought you passed in, and if they are different, then you have the secret copy that needs to be destroyed as an extra step.

    Yes, this sucks. I apologize.

    (My recommendation: To detect whether a "secret copy" occurred, do a STM_GET­IMAGE after your STM_SET­IMAGE and see if the handles match.)

    ¹ The secret copy is not an exact copy. (After all, if it were an exact copy, then there would be no need to create the copy. It could just use the handle you passed in.) Instead, the secret copy is a copy of the original, followed by some additional munging so that it can be displayed on the screen while respecting the alpha channel you passed in.

  • The Old New Thing

    Email tip: If you want people to look at a screen shot, you have to tell them what they're looking at

    • 14 Comments

    Some time ago, Ry Jones decided to take something that I wrote and condense it to make it funnier:

    Don't embed pictures. ... This isn't Highlights magazine.

    Those ellipses are deceptive, because they hide a change of topic! As a result, the two unrelated sentences appeared to be connected to each other.

    The comment about Highlights magazine was not a response to "Don't embed pictures." It was a response to a different part of that message. Here's the complete message, or an approximation thereof:

    Don't embed pictures. Send a link to your pictures. And when you ask us to look at the pictures which demonstrate the change in in behavior you're talking about, you have to tell us what change we're looking for. This isn't Highlights magazine.

    In the original message, the person included two screen shots. The question was something along the lines of, "The first screen shot shows the feature behaving correctly, and the second screen shot shows it behaving incorrectly. Can somebody explain why it isn't working?"

    The problem was that the two screen shots were practically identical. It wasn't obvious what the difference was between them. Now sure, to the person asking the question, the difference was as plain as the nose on your face, but to somebody who hasn't spent the last 48 hours of their life staring at this specific screen, the difference is a bit harder to pick out.

    One of the regular features of the children's magazine Highlights is a Can you spot the difference? puzzle in which two nearly identical pictures are presented to the reader, who is invited to find the difference between them.

    When you're sending screenshots please describe what part of the screenshot the reader should be focusing on. Or even better, circle it. Windows comes with a super-advanced bitmap editing tool to help you with that.

    Sometimes, the person asking the question doesn't even include the Before part of the puzzle. All that is provided is the After picture, with the question, "Can somebody explain why this changed?"

    Creating devious puzzles and challenging other people to solve them can be fun, but there is a time and place for puzzles. Asking somebody for help is not one of those times.

  • The Old New Thing

    Microspeak: Offline (noun)

    • 20 Comments

    Sure, any noun can be verbed, and any verb can be nouned. But today, we're going to noun an adjective.

    I have no written citations of this usage; the only report was via a colleague who overheard it in a hallway conversion.

    I had some offlines with Fred about that.

    In Microspeak, offline is an adjective which means "outside this meeting." In order to keep a meeting on track, the meeting organizer may advise the people engaged in the discussion of a side topic or a topic of limited interest to take it offline, please, meaning discuss this amongst yourselves after the meeting, please. In other words, "Let's not waste valuable meeting time on this topic."

    The above citation converts the adjective offline into a noun, an offline presumably being shorthand for an offline conversation (or other type of communication). The translation would therefore be something like "I had some private conversations with Fred about that."

  • The Old New Thing

    How can I detect that a user's SID has changed and recover their old data?

    • 9 Comments

    A customer maintained a database which recorded information per user. The information in the database is keyed by the user's SID. This works out great most of the time, but there are cases in which a user's SID can change.

    "Wait, I thought SIDs don't change."

    While it's true that SIDs don't change, it is also true that the SID associated with a user can change. Since SIDs encode the domain to which they belong, a user which moves from one domain to another within an organization, will need to be assigned a new SID.

    But wait, does that mean that the user lost access to all their stuff? After all, all their stuff was marked "Owned by X\UserName" but the user's SID is now Y\UserName.

    No, the user doesn't lose access to their stuff thanks to SID history, and if you move users around a lot, the SID history can get quite large.

    A token for a user contains not only their current identity but also all of their earlier identities. That is what permits Y\UserName to continue to access things that was marked "Owned by X\UserName": The token for Y\UserName includes an entry that says, "Oh, I used to be X\UserName."

    The customer's database can take advantage of the SID history to match up users with their former selves. Our customer was lucky in that their database recorded only users who had logged into the local machine, so that list is typically pretty small. The simplest solution for this particular customer is just to go through all the users in the database, and for each one, see if the current user has that database user in their SID history. And the easy way to do that is to make the security system do the work for you: To see if the current user has user X in their SID history, create a security descriptor that grants access only to user X, then call Access­Check to see if the current user can access it. If so, then that means that the current user was at one point in the past known as X.

    (If you have a large database where iterating over all users is impractical, you can ask for the current user's SID-History attribute and walk through the previous identities manually.)

  • The Old New Thing

    What's the story behind the WM_SYNCPAINT message?

    • 11 Comments

    Danail wants to know the story behind the WM_SYNC­PAINT message.

    The documentation pretty much tells the story. When a window has been hidden, shown, moved or sized, the system may determine that it needs to send a WM_SYNC­PAINT message to the windows of other threads. This message must be passed to Def­Window­Proc, which will send the WM_NCPAINT and WM_ERASE­BKGND messages to the window as necessary.

    When you call the Set­Window­Pos function, the window manager updates the window size, position, whatever, and then it goes around repainting the windows that were affected by the operation. By default, the Set­Window­Pos function does a quick-repaint of the windows before returning. After the function returns, the normal WM_PAINT message does the real work of painting the window. The quick-repaint is done so that there is immediate feedback that the window did change its size, position, whatever.

    This quick-repaint is done by sending a WM_NCPAINT and WM_ERASE­BKGND message to the windows that were affected by the Set­Window­Pos operation. This normally happens without incident, but if one of the windows affected by the Set­Window­Pos operation belongs to another thread, the window manager needs to get into the context of that other thread to finish the job. That's where WM_SYNC­PAINT comes in. The WM_SYNC­PAINT message means, "Hey, I was going around quick-painting a bunch of windows, but I couldn't quick-paint you (or any other windows on your thread) because I was on the wrong thread. Could you finish quick-painting yourself (and all the other windows that need quick-painting)? Thanks."

    Another way of looking at this is that it is a way for the window manager to teleport itself into another thread so it can finish its work. "Lah di dah, quick-painting all the windows, oh crap, I can't quick-paint that window because it's on the wrong thread. Let me inject myself into that other process [trivial, since I'm the window manager, I'M IN YR PROCESS REEDING YR MSGS], and now I can send a message to myself [WM_SYNCPAINT], and when that other copy of me receives it, he'll finish where I left off."

    If you don't like any of this teleportation or multiple-copies-of-yourself imagery, you can say that the WM_SYNC­PAINT message means, "Quick-paint this window as part of a quick-paint operation begun on another thread."

    If you don't want this quick-paint to take place, you can follow the instructions in the documentation and pass the SWP_DEFER­ERASE flag to suppress the WM_SYNC­PAINT message.

  • The Old New Thing

    Dr. Watson and the bluescreen - a story from the trenches

    • 14 Comments

    A fellow Microsoft employee volunteered a story from his prior work at a hospital as their enterprise architect.

    I received an escalation from our Tier 1 service desk on a Dr. Watson. Why would I get a simple escalation? Strange...

    Since I hadn't seen the outside of my cubicle for a while, I decided to walk over to talk to the customer in person.

    The employee having the problem was named Dr. Watson. His computer had bluescreened.

    I still get a chuckle out of that years later. That little unexpected name collision threw twelve people in the IT service and support teams into disarray.

Page 370 of 457 (4,568 items) «368369370371372»