What’s the story with the parameters to the WM_INPUT_DEVICE_CHANGE message?


A customer found these strange macros in winuser.h:

#if (_WIN32_WINNT >= 0x0601)
#define GET_DEVICE_CHANGE_WPARAM(wParam)  (LOWORD(wParam))
#elif (_WIN32_WINNT >= 0x0501)
#define GET_DEVICE_CHANGE_LPARAM(lParam)  (LOWORD(lParam))
#endif /* (_WIN32_WINNT >= 0x0601) */

According to the documentation for the WM_INPUT_DEVICE_CHANGE message, the wParam is the operation code and the lParam is a handle to the device that changed. Given that definition, the correct macro would be GET_DEVICE_CHANGE_WPARAM. What's up with the bogus GET_DEVICE_CHANGE_LPARAM macro?

The macro was incorrectly defined in Windows Vista. In the Windows 7 version of the Platform SDK, the correct macro was added, but in order to avoid introducing a breaking change to existing code, the old broken macro remains in place in order to retain bug-for-bug compatibility with existing code.

Even though the macro didn't work, there is a good possibility that code exists which relied on it anyway. For example, people may have read the documentation, read the macro, realized that the macro was wrong, and worked around the bug like this:

case WM_INPUT_DEVICE_CHANGE:
 return OnInputDeviceChange(GET_DEVICE_CHANGE_LPARAM(wParam),
                            (HANDLE)lParam);

To avoid breaking this code, the old broken definition remains in the header file. But at least it's defined only if you say that you want the Windows Vista version of the header file, so at least people won't use the bad macro going forward.

Comments (13)
  1. Mc says:

    So should the winuser.h header file have a comment in there explaining what that was all for?  If it was my code, it would have an explanation alongside "the hack".  Or even just a  "see KB-99999" or something.

  2. Vilx- says:

    OK, so my C/C++ is a bit rusty, but I'm pretty sure that the "wParam"/"lParam" in this case is the parameter to the macro itself. So what's the difference how it is named, and why should changing it break any code?

  3. Vilx- says:

    Oh, wait, should learn to read first. :P The name of the macro itself changed too.

  4. John says:

    Seriously?  I'm all for backward compatibility (run-time more than compile-time), but this is taking it a bit extreme for my tastes.  I mean really, how many times could this bogus macro possibly be used?  Also, why invent it in the first place?  Why not just use LOWORD directly?  The documentation doesn't even talk about splitting the wParam.  Perhaps it was from a beta release of longhorn or something.

    On a related topic, how stringent is Microsoft in surrounding functionality with #ifdefs?  I've been burned a few times where some function was not implemented on the target I was compiling against.  Lo and behold, there were no #ifdefs to be found.

    Also, please harass somebody to fix this ridiculous bug where your comment gets lost by the blogging system if you don't post it in a certain amount of time.

  5. configurator says:

    I'm not a C++ expert, but wouldn't this definition do the same thing? Why not use this simpler version? I'm sure there's a really good reason that I'm completely missing.

    #define GET_DEVICE_CHANGE_WPARAM LOWORD

  6. David Walker says:

    Here is a case where we REALLY need that time machine.

  7. voo says:

    @Evan Nice to learn something new. The only thing I'm now worried about is that we basically started fighting over the preprocessor which I'm somehow sure will end in tears earlier or later ;)

  8. Bob says:

    @EvanED: You can also do (min)(a, b) to prevent function macro expansion. I guess MACRO_EXPANSION_BLOCKER is to show your intent to maintenance programmers.

    I wonder how GET_DEVICE_CHANGE_LPARAM could come about. Did the API developer not even write a testcase and notice this instantly?

    [Perhaps the parameter was originally in the lParam (so the testcase looked just fine), then later a design change moved it to wParam. Just speculating as to how this could've happened. (You folks need to be more creative.) -Raymond]
  9. EvanED says:

    @configurator:

    I'm not sure this is the reason (I'd argue that the actual version is more readable, and that's probably closer to the reason; Haskell folks may disagree), but those two aren't *exactly* the same

    If GET_DEVICE_CHANGE_WPARAM appears in a context where the next token is NOT a (, the actual version will not undergo macro replacement but your version will.

    I know of one place where this can arguably be used in a productive manner. Suppose you are writing code that should work in a context where "someone" impolitely defines 'min' and 'max' as a macro. [Yes, I know about NOMINMAX or whatever it's called, but you don't always have that control.] The following will allow you to use those functions anyway:

     #define MACRO_EXPANSION_BLOCKER

     …

     min MACRO_EXPANSION_BLOCKER (1, 2)

    The MACRO_EXPANSION_BLOCKER goes away by the time the compiler sees things, so it just sees "min (1, 2)", but it inhibits macro expansion of the min.

  10. Evan says:

    BTW, credit where it's due; I learned about the MACRO_EXPANSION_BLOCKER from Boost. (They call it BOOST_PREVENT_MACRO_SUBSTITUTION.)

    And it's actually got a bit wider applicability, e.g. http://www.boost.org/…/cmath.hpp uses it to inhibit expansion on "functions" from <cmath> that may actually be macros. (Boost defines it http://www.boost.org/…/suffix.hpp here. In my defense, the comment in that file *also* ignores the <cmath> macro problem. :-))

  11. denis bider says:

    I haven't been able to find a proper way to submit this bug report for the Windows 7 console subsystem. None of the categories available through Microsoft Connect seems suitable.

    Perhaps you will care about this problem and forward it to the right person.

    In Windows 7, console support was moved from CSRSS.EXE to CONHOST.EXE. In the process of implementing this change, a bug was introduced which causes premature closing of the active console screen buffer. This causes applications that use screen buffers, such as less.exe or telnet.exe, to fail when running under some types of console-based terminal servers.

    I described the bug in detail here:

    fogbugz.bitvise.com/default.asp

    The page also provides source code to a set of minimal test programs that reliably reproduce this problem in Windows 7.

    I hope the Windows console subsystem is currently maintained by someone, and that you can forward this issue to them.

  12. TC says:

    +1 Mc. The MS codebase must contain thousands of fixes etc. whose purpose is not clear just from reading the code. I've often wondered how all of those are documented. Raymond sometimes says, "we added a note describing why" (for some particular fix). Is there a fixed policy for how to document fixes? Do you do always do it inine (in the source), or by reference to a big black book, or at the coder's discretion, or what?

  13. Joshua says:

    @denis bider: To file a bug in core Windows components, you have to open a support request (spend $$). If they agree it's a bug, you get your money back.

Comments are closed.