October, 2012

  • The Old New Thing

    The TEMP directory is like a public hot tub whose water hasn't been changed in over a year

    • 28 Comments

    A customer reported that they couldn't install product X. When they ran the installer, the got the error message

    setup.exe - Application Error
    The application was unable to start correctly (0xc00000ba). Click OK to close the application.

    The product X setup team weren't sure what to make of this, and they asked if anybody had any ideas.

    The error code 0xc00000ba is STATUS_FILE_IS_A_DIRECTORY, which means that something was supposed to be a file, but instead it was a directory. The path-searching algorithm is not a backtracking algorithm, so once it finds something wrong, it just stops rather than backing up and trying the next directory.

    This was enough of a clue to direct further investigation, which revealed that the customer had a directory named C:\Users\Bob\AppData\Local\Temp\version.dll\. The customer responded, "There are plenty of directories with names of DLLs in my TEMP directory, but getting rid of this one fixes the issue. Thanks!"

    (Puzzle: Why are there so many directories with the names of DLLs? Psychic answer.)

    I slipped something past you a little while back. Did you notice?

    Okay, I gave it away in the subject line. The setup program is running from the TEMP directory. That should already set off alarm bells.

    The TEMP directory is a dumping ground of random junk. A downloader may have put a DLL there and forgotten to delete it. (Or worse, expected it to stay there forever.) And that DLL might be from an incompatible version of some DLL your setup program uses. (I have seen applications ship their own custom versions of system DLLs! Yeah, because the x86 version of shlwapi.dll from Windows 2000 is drop-in compatible with the version of shlwapi.dll that comes with Windows 7.) Who knows what other yucky things have been lying around in that directory. Since the application directory is the first directory searched when the system looks for a DLL, a rogue DLL in the TEMP directory is a trap waiting to be sprung. (A similar issue applies to a shared Downloads directory.)

    It's like the horror movie trope where the frightened pretty girl runs into a room, slams the door shut, then breathes a sigh of relief, believing herself to be safe. But she didn't check that the room was empty! (In other words, she created her airtight hatchway around an insecure room.)

    The Program X setup team decided to change their installer so that it created a subdirectory of TEMP and extracted the main setup program there. That way, it got a fresh hot tub with clean water.

    Remember, the directory is the application bundle. If you drop your application into a random directory, you've just added everything in that directory to your bundle. And if you don't secure your application directory, you're allowing anybody to add components to your bundle. That's one of the reasons why the Logo program encourages (requires?) applications to install into the Program Files directory: The ACLs on the Program Files directory allow write access only to administrators and installers. This makes the application bundle secure by default. If you want to make your application bundle insecure, you have to go out of your way.

  • The Old New Thing

    The wisdom of seve^H^H^H^Hsixth graders: Living without electronics for a week

    • 11 Comments

    Sixth grade students (ages 11 to 12, roughly) were instructed to imagine that they have no television, computer, or telephone for a week and write an essay (in the form of a letter to their parents) on what they would do with their time and why. The assignment was given under standardized test conditions: 90 minutes with nothing but pencil and paper, with an additional hour available upon request. (In practice, few students ask for the extra hour.)

    Remember, these are only the funny sentences/excerpts. Do not assume that all students write like this.

    Stage One: Denial

    • A powerman came to our house and is trying to fix the precios valubles... But don't touch the family jewels.

    Stage Two: Anger

    • Since you have forcefully restricted me from using essenchal, electronics like the TV...

    Stage Three: Bargaining

    (couldn't find an example, sorry)

    Stage Four: Depression

    • My heart is full of grief and sadness, yet I regret nothing but the loss of my precious x-box.
    • Although I am in a mood of melancholy over this gloomy oncoming event, I hope my hobbies will cheer me up. Big words sound bigger.

    Stage Five: Acceptance

    • Since you have grounded me for a week, I need to nurture my growing sense of fun.

    Other excerpts

    • [When working in the barn] 1) don't touch the electric fence 2) don't throw poop
    • Every day, I am getting closer to achieving my goal of attacking my friends. You should see what your friends' goals are.
    • ... Gliding on black ashphault ...
    • I'm going to play legos until my arms hurt. In my experience, your feet hurt first.
    • I have a life outside the digital world!
    • My grade went from a C- to an A-, and that is the second highest you can get! So why not do it for real?
    • After a long day of skating, my legs are battered and crunched, and that just means I had a good day.
    • I have noticed lately that my social level is not up to my standards. Level up?
    • I will draw elaborate scratches of lead all over the blank paper.
    • Inside used to be another word for trapt. Now it translates to party!
    • We have invented a new game. We tie one sister to a post... I'm not sure who the winner of the game is, but I'm pretty sure I know who the loser is.
    • One of my options for an activity is reading. I have noticed a big collection of books on my shelf and a few look quite interesting. How'd those books get there?
    • If there were no phones, I would die, but I would still have fun.
    • Remember, reading is a movie on pages.
    • By going to church, you don't need anything but faith.
    • I would go play behind the agresive looking houses. I prefer passive housing myself.
    • Skydiving I went once it is like diving into a pool you lose your stomache sometimes you find it sometimes you don't. Somehow the run-on sentence makes it more poetic.
    • It is a very relaxful activity.
    • One book I will read is A Purpose Driven Life.

    Any final requests?

    • The last thing I will do is tape toys together and make new toys.
    • The last thing I will do is go bike ridding.
    • The final activity I would do is Africa! Um, you might want to rephrase that.
    • Then, last but not leave, my instruments...

    I'm not resentful. Why do you ask?

    • Sincerely, Your Daughter, the Formerly Loved.

    Observe that many students talked about "the last thing" they would do. This is another consequence of adhering too close to formula. "I have three things, so I will say 'The first thing', 'The next thing', and 'the last thing'." They don't realize that when you write "The last thing I will do", it carries a somewhat different meaning.

    (Today's post is in support of the millions of people currently without electricity due to Hurricane Sandy.)

  • The Old New Thing

    Keyboard shortcut for resizing all columns in a listview control to fit

    • 17 Comments

    The keyboard shortcut for resizing all columns in a report-mode (also known as Details mode) list view control to fit the current content width is Ctrl+Num+. That's the + key on the numeric keypad. (If you're using Explorer, you can also right-click the column header and choose Size All Columns to Fit.)

    Note that this command is a verb, not a state, so it takes into account the contents of the listview at the time you press the hotkey. If the contents change and you want the columns resize based on the new contents, you'll have to press the hotkey again.

  • The Old New Thing

    In the conversion to 64-bit Windows, why were some parameters not upgraded to SIZE_T?

    • 29 Comments

    James wonders why many functions kept DWORD for parameter lengths instead of upgrading to SIZE_T or DWORD_PTR.

    When updating the interfaces for 64-bit Windows, there were a few guiding principles. Here are two of them.

    • Don't change an interface unless you really need to.
    • Do you really need to?

    Changing an interface causes all sorts of problems when porting. For example, if you change the parameters to a COM interface, then you introduce a breaking change in everybody who implements it. Consider this hypothetical interface:

    // namedobject.idl
    interface INamedObject : IUnknown
    {
        HRESULT GetName([out, string, sizeof(cchBuf)] LPWSTR pszBuf,
                        [in] DWORD cchBuf);
    };
    

    And here's a hypothetical implementation:

    // contoso.cpp
    class CContosoBasicNamedObject : public INamedObject
    {
        ...
        HRESULT GetName(LPWSTR pszBuf, DWORD cchBuf)
        {
            return StringCchPrintfW(pszBuf, cchBuf, L"Contoso");
        }
        ...
    };
    

    Okay, now it's time to 64-bit-ize this puppy. So you do the natural thing: Grow the DWORD parameter to DWORD_PTR. Since DWORD_PTR maps to DWORD on 32-bit systems, this is a backward-compatible change.

    // namedobject.idl
    interface INamedObject : IUnknown
    {
        HRESULT GetName([out, string, sizeof(cchBuf)] LPWSTR pszBuf,
                        [in] DWORD_PTR cchBuf);
    };
    

    Then you recompile the entire operating system and find that the compiler complains, "Cannot instantiate abstract class: CContosoBasicNamedObject." Oh, right, that's because the INamed­Object::Get­Name method in the implementation no longer matches the method in the base class, so the method in the base class is not overridden. Fortunately, you have access to the source code for contoso.cpp, and you can apply the appropriate fix:

    // contoso.cpp
    class CBasicNamedObject : public INamedObject
    {
        ...
        HRESULT GetName(LPWSTR pszBuf, DWORD_PTR cchBuf)
        {
            return StringCchPrintfW(pszBuf, cchBuf, L"Basic");
        }
        ...
    };
    

    Yay, everything works again. A breaking change led to a compiler error, which led you to the fix. The only consequence (so far) is that the number of "things in code being ported from 32-bit Windows to 64-bit Windows needs to watch out for" has been incremented by one. Of course, too much of this incrementing, and the list of things becomes so long that developers are going to throw up their hands and say "Porting is too much work, screw it." Don't forget, the number of breaking API changes in the conversion from 16-bit to 32-bit Windows was only 117.

    You think you fixed the problem, but you didn't. Because there's another class elsewhere in the Contoso project.

    class CSecureNamedObject : public CBasicNamedObject
    {
        ...
        HRESULT GetName(LPWSTR pszBuf, DWORD cchBuf)
        {
            if (IsAccessAllowed())
            {
                return CBasicNamedObject::GetName(pszBuf, cchBuf);
            }
            else
            {
                return E_ACCESSDENIED:
            }
        }
    }
    

    The compiler did not raise an error on CSecure­Named­Object because that class is not abstract. The INamed­Object::Get­Name method from the INamed­Object interface is implemented by CBasic­Named­Object. All abstract methods have been implemented, so no "instantiating abstract class" error.

    On the other hand, the CSecure­Named­Object method wanted to override the base method, but since its parameter list didn't match, it ended up creating a separate method rather than an override. (The override pseudo-keyword not yet having been standardized.) As a result, when somebody calls the INamed­Object::Get­Name method on your CSecure­Named­Object, they don't get the one with the security check, but rather the one from CBasic­Named­Object. Result: Security check bypassed.

    These are the worst types of breaking changes: The ones where the compiler doesn't tell you that something is wrong. Your code compiles, it even basically runs, but it doesn't run correctly. Now, sure, the example I gave would have been uncovered in security testing, but I chose that just for drama. Go ahead and substitute something much more subtle. Like say, invalidating the entire desktop when you pass NULL to Invalidate­Rect.

    Okay, so let's look back at those principles. Do we really need to change this interface? The only case where expanding to SIZE_T would make a difference is if an object had a name longer than 2 billion characters. Is that a realistic end-user scenario? Not really. Therefore, don't change it.

    Remember, you want to make it easier for people to port their program to 64-bit Windows, not harder. The goal is make customers happy, not create the world's most architecturally pure operating system. And customers aren't happy when the operating system can't run their programs (because every time the vendor try to port it, they keep stumbling over random subtle behavior changes that break their program).

  • The Old New Thing

    Whether the Unicode Bidi algorithm is intuitive depends on your definition of "intuitive"

    • 11 Comments

    In Windows, we spend a good amount of time with the pseudo-mirrored build. And one of the things that you notice is that pseudo-mirrored text comes out looking really weird. For example, the string really? (yup). comes out pseudo-mirrored as .(really? (yup. Just for fun, here's here's how your browser renders it:

    really? (yup).

    Even stranger, the IPv6 address 2001:db8:85a3::8a2e:370:7334 comes out as db8:85a3::8a2e:370:7334:2001. (The IPv6 address was the string that prompted this article.) The result of the RTL IPv6 address is even weirder if you force a line break at a particular point. If your browser follows the Unicode Bidi algorithm, you can resize the box below to see how the line break position affects the rendering.

    2001:db8:85a3::8a2e:370:7334

    If your browser doesn't follow the Unicode Bidi algorithm, or if you can't resize the window, here's what you get:

    No line break db8:85a3::8a2e:370:7334:2001
    Line break :2001
    db8:85a3::8a2e:370:7334

    "Is this a bug?"

    No.

    Well, maybe yes.

    It depends.

    But mostly yes.

    Windows is following the Unicode Bidirectional Algorithm. So the part that's not a bug is "Windows is correctly following an international standard." The weirdness you're seeing is just a consequence of following the standard.

    Let's look at what's going on.

    When you render text in RTL context, what you're saying is "Render this text in the form you would see it if it appeared in a newspaper printed in an RTL language." For illustration, we follow the convention that uppercase characters are considered to be in an RTL script, lowercase characters are considered to be in an LTR script, and non-letters stand for themselves.

    Say you want to render the string "NEXT COMES john smith." A newspaper would say, "Well, my readership expects things to be laid out right to left. The string 'john smith' is a foreign name inserted into a paragraph that otherwise is written my readers' native language. If the name were in my readers' native language, I would render it as

    .HTIMS NHOJ SEMOC TXEN

    Since the name is in a foreign language, I will treat it as an opaque 'name blob' that got inserted into my otherwise beautiful RTL sentence."

    .john smith SEMOC TXEN

    (The black outline is not part of the actual output. I am using it to highlight that the phrase john smith is being treated as a single unit.)

    This also explains why "hello." comes out as "hello.". The LTR text is treated as a blob inside an otherwise RTL sentence.

    .hello

    Things get weirder once parentheses and digits and more complex punctuation marks are thrown into the mix. For example, the Unicode Bidirectional Algorithm has to figure out that in the text "IT IS A bmw 500, OK." the "500" is attached to the LTR text "bmw", resulting in

    .KO ,bmw 500 A SI TI

    And it also needs to work out the correct text rendering order when you have RTL text embedded inside LTR text, all of which is embedded inside other RTL text, as illustrated by the brain-teaser "DID YOU SAY ’he said “car MEANS CAR”‘?"

    But maybe the standard is buggy. The problem is that the Unicode Bidirectional Algorithm is designed for text, so when you ask it to render things that aren't text (such as IPv6 addresses and URLs), the results can be nonsensical.

    At least for the IPv6 case, you can work around the problem by explicitly marking the IPv6 address as LTR, so that the Unicode Bidirectional Algorithm doesn't get involved, and the characters are rendered left-to-right in the order they were written.

    Exercise: Study the Unicode Bidirectional Algorithm and explain why really? (yup). comes out as .(really? (yup.

    Bonus reading: What you need to know about the bidi algorithm and inline markup.

  • The Old New Thing

    Keyboard layouts aren't like Beetlejuice - they won't appear just because you say their name

    • 16 Comments

    A customer reported a bug in Windows Vista Home Editions:

    We are handling a Ctrl+V keyboard event and want to interpret it in the context of a US-English keyboard.

    // This keyState represents no keys pressed except for Ctrl
    BYTE keyState[256] = {0};
    keyState[VK_CONTROL] = 0x80;
    
    // This is the handle for the US-English keyboard
    HKL hkl = (HKL) 0x04090409;
    
    // output variables
    wchar_t outChar[2];
    WORD outWord;
    
    ToUnicodeEx('V', 47, keyState, outChar, 2, 0, hkl);
    ToAsciiEx('V', 47, keyState, &outWord, 0, hkl);
    VkKeyScanEx('V', hkl);
    

    On Windows XP and versions of Windows Vista other than Home editions, the three calls all succeed, whereas on Windows Vista Home Editions, the calls fail. On the other hand, if instead of using the US-English keyboard, we use the current keyboard layout:

    HKL hkl = GetKeyboardLayout(GetCurrentThreadId());
    

    then Windows Vista Home Editions behave the same as Windows XP and non-Home editions of Vista.

    This suggests that the Home Editions of Vista supports keyboard queries only for the currently active keyboard layout, which renders useless the last parameter to those three functions.

    Notice how the customer's sample code just synthesizes a keyboard layout handle from thin air. While it is true that the format keyboard layout handles is documented, that doesn't mean that you can just make one up and start using it.

    It's like saying, "I know that Contoso uses the email address format Firstname.Lastname@contoso.com, but I just tried to send email to Bob.Smith@contoso.com, and it bounced."

    Does Bob work at Contoso?

    "No. Does that matter?"

    The customer's code blindly assumes that the US-English keyboard layout is loaded rather than calling Load­Keyboard­Layout to actually load it. As a result, if the keyboard layout is not loaded, the call will fail because you passed an invalid keyboard layout handle.

    The customer liaison asked, "Is this documented somewhere that the HKL has to be created from only from the functions and cannot be assigned a value?"

    Um, yeah, it's right there in the documentation of the hkl parameter to the To­Unicode­Ex function. (Emphasis mine.)

    dwhkl [in, optional]

    Type: HKL

    The input locale identifier used to translate the specified code. This parameter can be any input locale identifier previously returned by the Load­Keyboard­Layout function.

    Identical text appears in the documentation of the hkl parameter to the To­Ascii­Ex and Vk­Key­Scan­Ex functions as well.

    The difference observed on Windows Vista Home Editions, then, is that on those systems, in the configurations the customer happens to be using, US-English is not a preloaded keyboard layout.

  • The Old New Thing

    You can't use the WM_USER message in a dialog box

    • 18 Comments

    Today, I'm not actually going to say anything new. I'm just going to collate information I've already written under a better title to improve search engine optimization.

    A customer reported that they did the following but found that it didn't work:

    #define MDM_SETITEMCOUNT WM_USER
    
    INT_PTR CALLBACK MyDlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam)
    {
      switch (wm) {
      ...
      case MDM_SETITEMCOUNT:
        SetDlgItemInt(hwnd, IDC_ITEMCOUNT, (UINT)wParam, FALSE);
        return TRUE;
      ...
      }
      return FALSE;
    }
    

    "I send the MDM_SET­ITEM­COUNT message to my dialog, but the value doesn't stick. At random times, the value resets back to zero."

    As we saw some time ago, window messages in the WM_USER range belong to the window class. In the case of a dialog box, the window class is the dialog class, and the owner of the class is the window manager itself. An application which tries to use the WM_USER message is using window messages it does not own.

    It so happens that the dialog manager already defined the WM_USER message:

    #define DM_GETDEFID         (WM_USER+0)
    

    We saw this problem some time ago when we tried to find a message we could use for custom use in a dialog box.

    What the customer is seeing is that whenever the dialog manager sends a DM_GET­DEF­ID message to the dialog box to get the default control ID, the MyDlgProc function mistakenly thinks that it's a MDM_SET­ITEM­COUNT message and sets the item count to whatever happens to be in the wParam (which happens to be zero). On top of that, it claims to have handled the message, which means that the current value of DWL_MSG­RESULT is returned to the sender (probably zero), so the dialog manager thinks that there is no default ID on the dialog.

    The solution, as noted in that same article, is to use WM_APP instead of WM_USER. Because you don't have permission to define messages in the WM_USER range if you aren't the owner of the window class.

  • The Old New Thing

    Diversion: Generating a random color from JavaScript

    • 32 Comments

    A colleague posed a little puzzle for a fun little app he was writing in HTML: He wanted to generate a random color.

    If you search around the intertubes, you can find several possible attempts at a solution, like this collection, and an interesting example that has trouble with the pigeonhole principle.

    The original function to generate a random color went like this:

    // Pad a string of up to two characters with a leading zero
    // so the result is always exactly two characters long.
    function padZero(v) {
     return (v.length == 1) ? '0' + v : v;
    }
    
    function randomColor() {
     return "#" + padZero(Math.floor(Math.random() * 256)).toString(16) +
                  padZero(Math.floor(Math.random() * 256)).toString(16) +
                  padZero(Math.floor(Math.random() * 256)).toString(16);
    }
    

    Can you do better? (My solution after the jump.)


    That was a short jump.

    My first simplification was recognizing that three random 8-bit values is the same as one random 24-bit value.

    function padZeros6(v) {
     while (v.length < 6) v = "0" + v;
     return v;
    }
    
    function randomColor() {
     return "#" + 
        padZeros6(Math.floor(Math.random() * 16777216).toString(16));
    }
    

    Next, I got rid of the padZeros6 function by simply setting bit 25 to force a 7-digit result, then removing the leading 1.

    function randomColor() {
     return "#" + 
        (Math.floor(Math.random() * 16777216) +
                                    16777216).toString(16).substr(1);
    }
    

    Finally, I did some factoring.

    function randomColor() {
     return "#" + 
        Math.floor((1 + Math.random()) * 16777216).toString(16).substr(1);
    }
    

    That last bit was a bit dodgy due to the wonders of floating point arithmetic, but hey, it's a puzzle now.

    Finally, I realized that CSS supports #rgb as shorthand for #rrggbb, so if you don't mind that your color palette is reduced to 4096 colors (and in the case of my colleague's little app, that was not an issue), you can shorten it a bit more:

    function randomColor() {
     return "#" + 
        Math.floor((1 + Math.random()) * 4096).toString(16).substr(1);
    }
    
  • The Old New Thing

    How do you come up with new shortcut keys?

    • 17 Comments

    Anon asks, "How do you come up with new shortcut keys and how do you deal with different keyboard layouts? What is the process; is there a company-wide procedure to keep things consistent?"

    This is several questions (none of them really a suggestion, but I've given up on making the Suggestion Box about suggestions; now it's just the "Ask Raymond a question" page), so let's take them one at a time. (Note that if you ask multiple questions, you reduce the likelihood that I'll answer them, because I feel obligated either to answer all of them or none of them.)

    First question: How do you come up with new shortcut keys?

    You just make them up. Application shortcut keys are local in scope (either to a dialog box, or to a menu, or to a top-level window message loop via the accelerator table), so you can just pick something that does not cause duplication within your local sphere.

    Second question: How do you deal with different keyboard layouts?

    To deal with different keyboard layouts, you, um, deal with different keyboard layouts. Menu shortcuts, dialog box shortcuts, and accelerator tables are all localizable, so translators can assign them to whatever key they feel works best on the keyboard layouts most likely to be used by their specific audience.

    If you want your shortcut to be invariant across keyboard layouts (such as Ctrl+C for copy, regardless of language) then you (or somebody you trust) need to check all the keyboard layouts to make sure your desired shortcut key is available on all of them. For example, the keyboard shortcuts for snapping, unsnapping, and repositioning applications in Windows 8 were originally going to be Win+[ and Win+]. However, upon checking with the globalization folks, it became clear that those hotkeys were not going to work, because on many keyboards, typing [ and ] requires the use of the AltGr modifier. Users would have had to press Win+AltGr+[ to move the gutter to the left, or if they didn't have an AltGr key, Win+Ctrl+Alt+[. This was clearly far too unwieldy, so the hotkeys were changed to Win+. and Win+Shift+..

    Third question: Is there a company-wide procedure to keep things consistent?

    Since application shortcut keys are local in scope, there is no need for a company-wide procedure to keep them from conflicting with each other. Each application can assign its shortcut keys as it sees fit. And maintaining consistency across applications is done the same way gas stations and airlines maintain consistency in pricing: You look at what everybody else is doing and try to be consistent with them. This creates a first-move advantage: If the first application to create a Properties menu item assigns it the keyboard shortcut Alt+R, then that makes it likely that the second and third applications which add a Properties menu item are likely to use the same shortcut.

    Windows 95 introduced a lot of new items into context menus (such as Properties), so the Windows 95 design team got to choose a lot of important keyboard shortcuts. In Explorer, hit Shift+F10 and look at the Properties menu item. On English systems, it shows up as Properties. The shortcut is still Alt+R, over a decade later.

    Besides, imagine if there was some company-wide procedure for keeping things consistent. First, you would have to make sure everybody knew about this procedure. "What, there's a procedure I have to follow before I can assign menu and dialog shortcut keys and application accelerators?"

    And then everybody would complain, "Oh great, before I choose a shortcut, I have to fill out this application in quadruplicate and then show up at yet another meeting." Microsoft is famous for its burdensome meetings. You don't address this problem by adding more meetings. (Note that I don't share that author's views, probably because I don't go to many meetings.)

    New shortcut keys are added all the time. A single dialog box can have a dozen or more. Imagine being on the shortcut-key-consistency committee. Your day would be wall-to-wall meetings about shortcuts. "Yes, the standard shortcut for Properties in German is Alt+E (for Eigen­schaften), but we'd like to apply for an exception because our menu also has an Insert command (German: Ein­fügen) and the standard shortcut for that is also Alt+E. Since Insert is used much more often, we'd like to keep Alt+E for Ein­fügen and use Alt+G for Eigen­schaften." And when somebody came to you with the request, how would you decide whether to approve or reject it? Probably by saying, "Well, let's see how other applications handle this." So your big complicated hotkey approval process reduced to what people would have done anyway. Congratulations, you just wasted everybody's time.

  • The Old New Thing

    Using WM_COPYDATA to marshal message parameters since the window manager otherwise doesn't know how

    • 17 Comments
    Miral asks for the recommended way of passing messages across processes if they require custom marshaling.

    There is no one recommended way of doing the custom marshaling, although some are hackier than others.

    Probably the most architecturally beautiful way of doing it is to use a mechanism that does perform automatic marshaling, like COM and MIDL. Okay, it's not actually automatic, but it does allow you just give MIDL your structures and some information about how they should be interpreted, and the MIDL compiler autogenerates the marshaler. You can then pass the data back and forth by simply invoking COM methods and letting COM do the work.

    Architecturally beautiful often turns into forcing me to learn more than I really wanted to learn, so here's a more self-contained approach: Take advantage of the WM_COPY­DATA message. This is sort of the poor-man's marshaler. All it knows how to marshal is a blob of bytes. It's your responsibility to take what you want to marshal and serialize it into a blob of bytes. WM_COPY­DATA will get the bytes to the other side, and then the recipient needs to deserialize the blob of bytes back into your data. But at least WM_COPY­DATA does the tricky bit of getting the bytes from one side to the other.

    Let's start with our scratch program and have it transfer data to another copy of itself. Make the following changes:

    #include <strsafe.h>
    
    HWND g_hwndOther;
    
    #define CDSCODE_WINDOWPOS 42 // lpData -> WINDOWPOS
    
    void OnWindowPosChanged(HWND hwnd, LPWINDOWPOS pwp)
    {
     if (g_hwndOther) {
      COPYDATASTRUCT cds;
      cds.dwData = CDSCODE_WINDOWPOS;
      cds.cbData = sizeof(WINDOWPOS);
      cds.lpData = pwp;
      SendMessage(g_hwndOther, WM_COPYDATA,
               reinterpret_cast<WPARAM>(hwnd),
               reinterpret_cast<LPARAM>(&cds));
     }
     FORWARD_WM_WINDOWPOSCHANGED(hwnd, pwp, DefWindowProc);
    }
    
    void OnCopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT pcds)
    {
     switch (pcds->dwData) {
     case CDSCODE_WINDOWPOS:
      if (pcds->cbData == sizeof(WINDOWPOS)) {
       LPWINDOWPOS pwp = static_cast<LPWINDOWPOS>(pcds->lpData);
       TCHAR szMessage[256];
       StringCchPrintf(szMessage, 256,
        TEXT("From window %p: x=%d, y=%d, cx=%d, cy=%d, flags=%s %s"),
        hwndFrom, pwp->x, pwp->y, pwp->cx, pwp->cy,
        (pwp->flags & SWP_NOMOVE) ? TEXT("nomove") : TEXT("move"),
        (pwp->flags & SWP_NOSIZE) ? TEXT("nosize") : TEXT("size"));
       SetWindowText(hwnd, szMessage);
      }
      break;
     }
    }
    
    // WndProc
    
        HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGED, OnWindowPosChanged);
        HANDLE_MSG(hwnd, WM_COPYDATA, OnCopyData);
    
    // WinMain
        // If there is another window called "Scratch", then it becomes
        // our recipient.
        g_hwndOther = FindWindow(TEXT("Scratch"), TEXT("Scratch"));
    
        hwnd = CreateWindow(
            "Scratch",                      /* Class Name */
            g_hwndOther ? TEXT("Sender") : TEXT("Scratch"),
            WS_OVERLAPPEDWINDOW,            /* Style */
            CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
            CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
            NULL,                           /* Parent */
            NULL,                           /* No menu */
            hinst,                          /* Instance */
            0);                             /* No special parameters */
    
    

    Just to make it easier to tell the two windows apart, I call the one sending the message "Sender". (Note that my method for finding the other window is pretty rudimentary, because that's not the point of the example.)

    Whenever the sender window receives a WM_WINDOW­POS­CHANGED message, it sends a copy of the WINDOW­POS structure to the recipient, which then displays it in its own title bar. Things to observe:

    • The value you put into dwData can be anything you like. It's just another DWORD of data. Traditionally, it's used like a "message number", used to communicate what type of data is being sent. In our case, we choose 42 to mean "The lpData points to a WINDOW­POS structure."
    • The cbData is the number of bytes you want to send, and lpData points to the buffer. In our case, the number of bytes is always the same, but variable-sized data is also fine.
    • The lpData can point anywhere, as long as the memory is valid for the lifetime of the Send­Message call. In this case, I just point it at the data given to me by the window manager. Of course, if you allocated memory to put into lpData, then the responsibility for freeing it also belongs to you.
    • For safety's sake, I validate that when I get a CDS­CODE_WINDOW­POS request, the associated data really is the size of a WINDOW­POS structure. This helps protect against a rogue caller who tries to crash the application by sending a CDS­CODE_WINDOW­POS with a size less than sizeof(WINDOW­POS), thereby triggering a buffer overflow. (Exercise: Under what other conditions can the size be incorrect? How would you fix that?)
    • The WM_COPY­DATA copies data in only one direction. It does not provide a way to pass information back to the sender. (Exercise: How would you pass information back?)
    • The hwndFrom parameter is a courtesy parameter, like dwData. There is currently no attempt to verify that the window really is that of the sender. (In practice, all that could really be verified is that the window belongs to the thread that is doing the sending, but right now, not even that level of validation is performed.)

    The WM_COPY­DATA message is suitable for small-to-medium-sized amounts of memory. Though if the amount of memory is so small that it fits into a WPARAM and LPARAM, then even WM_COPY­DATA is overkill.

    If you're going to be passing large chunks of memory, then you may want to consider using a shared memory handle instead. The shared memory handle also has the benefit of being shared, which means that the recipient can modify the shared memory block, and the sender can see the changes. (Yes, this is one answer to the second exercise, but see if you can find another answer that tays within the spirit of the exercise.)

Page 1 of 3 (25 items) 123