When you subclass a window, it’s the original window procedure of the window you subclass you have to call when you want to call the original window procedure


When you subclass a window, you set the window procedure to a function of your choosing, and you remember the original window procedure so you can pass it to the CallWindowProc function when your subclass function wants to pass the message to the original window procedure. For example, if you subclass a window like this:

SubclassWidgetDialog(HWND hdlgWidget)
{
  HWND hwndButton = GetDlgItem(hdlgWidget, ...);
  wndProcOrig = (WNDPROC)
     SetWindowLongPtr(hwndButton, GWLP_WNDPROC, (LONG_PTR)SubclassWndProc);
  // -or-
  // wndprocOrig = SubclassWindow(hwndButton, SubclassWndProc);
  ...
}

then your subclass function should go something like this:

LRESULT CALLBACK SubclassWndProc(
    HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam)
{
  switch (wm) {
  ...
  default:
    return CallWindowProc(wndprocOrig, hwnd, wm, wParam, lParam);
  }
}

The window procedure you pass to CallWindowProc is the one which was the window procedure of that window before you subclassed it, not the window procedure from some other window. In the same way that when you create a derived C++ class, you pass method calls along to your base class, not to somebody else's base class:

class DerivedClass : public BaseClass {
 ...
 // override base class: do some extra stuff
 int Method(int param)
 {
  ExtraDerivedStuff();
  return BaseClass::Method(param);
 }
};

When you are finished with your customization in DerivedClass::Method, you let the base class do what normally would have happened if you hadn't overridden the method in the first place by calling BaseClass::Method and not by calling SomeOtherClass:Method.

This sounds blatantly obvious, but you'd be surprised how often people mess this up. For example, if you subclass multiple widget dialogs, you have to save the old window procedure in a different place for each one, because each button may have had a different window procedure by the time you got around to subclassing it. For example, one of them might be a plain button, whereas another of them was subclassed in order to provide a tooltip. If you make wndprocOrig a global or static variable, then you're assuming that every widget button has the same window procedure. You are subclassing a window and forgetting to handle the case where the window is already subclassed! You forgot that somebody else could have done exactly what you're doing.

There is a popular commercial program that comes preinstalled on many computers which creates a common file open dialog box and subclasses both the file name combo box and the file type combo box, and figures that, well, since they're both combo boxes, they must have the same window procedure, right? Unfortunately, there's no guarantee that they do, because the common file dialog is free to subclass them in order to provide custom behavior like autocomplete. It so happens that the program grabs the window procedure from the subclassed combo box window and uses it for all combo boxes. (These are probably the same folks who would have called the BOZOSLIVEHERE function if given the chance.)

This makes for very exciting crashes when they take the original window procedure from the subclassed combo box and use it for the other, unsubclassed, combo box. The subclass window procedure finds itself handed a window that it never subclassed. As a result, it not only can't perform its own subclass behavior, but can't even just fall back and say "Well, I can't do my custom stuff, so I'll just forward to the original window procedure" since it can't figure out what the original window procedure was either. It's the window manager version of writing this strange C++ code:

class SiblingClass : public BaseClass { ... };
class DerivedClass : public BaseClass {
 ...
 // override base class: do some extra stuff
 // then pass method to the WRONG base class
 int Method(int param)
 {
  ExtraDerivedStuff();
  return ((SiblingClass*)this)->Method(param);
 }
};

When you subclass a window, and you want to call the original window procedure, make sure you call the correct original window procedure: The one that was the window procedure of that window before you subclassed it.

Comments (23)
  1. Criffer says:

    > SetWindowLongPtr(hwndButton, (LONG_PTR)SubclassWndProc);

    Should be

    SetWindowLongPtr(hwndButton, GWLP_WNDPROC, (LONG_PTR)SubclassWndProc);

    [Fixed, thanks. -Raymond]
  2. bahbar says:

    That’s why you don’t do C casts in c++.

    C++ makes it harder (here) to do the wrong thing (not quite that hard in a lot of cases). the WIN32 API doesn’t. There’s nothing telling me that I screwed up which method I called.

    Yes, users should know better. But then again, they don’t.

  3. Of course, it’s extra fun with runtime manipulation of the inheritance chain of anything.

    It’s possible to do similar stuff in PHP with the runkit extension that happens to be described as "For all those things you…. probably shouldn’t have been doing anyway….".

    I am not saying that it’s bad to be able to subclass windows. Just that it’s the kind of stuff that takes a certain amount of insanity to pull of properly.

    Also, there is the fun when people screw up the chain in other ways, such as pulling out the middle link without updating the other links. If the window procedure have been resubclassed, you can no longer unsubclass the window. But you where not going to do that anyway, right? Right?

  4. Alexandre Grigoriev says:

    MS lawyers made you too shy. It’s time to drag those bogosity champions to the hall of shame. World would be a better place if Windows had an automatically updated database of buggy or just plain bad software and drivers, and when it sees one, it would shout to the user: "Hey! You don’t want to run this crap. If you want to know, here is why: "blablbabla". Contact the vendor for a fix." That would keep ISVs in line. And shift the blame!

  5. John says:

    Except when 80% (or more) of the programs users use annoy the user with that prompt. That’s a great way to lose a customer base. Operating systems just need to deal somehow, in the interest of the user, and Windows does a pretty good job.

    Pre-emptive snarky response: UAC prompts are a great way to lose a customer base.

  6. Alexandre Grigoriev says:

    John,

    Or a greate way to support the Windows customers. Suppose he starts his obscure (or not) software, and gets the prompt: Hey, there is an updated version on the ISV website now, update it or you can get hit by JScript exploit in a well known document reader.

  7. Vilx- says:

    Btw – why do you have to call CallWindowProc() to call the previous function? Why can’t you just call it directly? Last I checked, function pointers worked just fine in C and C++.

    [Because it’s not actually a function pointer. -Raymond]
  8. Matt says:

    Vilx,

    Take a look at the MSDN article: CallWindowProc deals with various WndProc-related issues that would be tricky or tedious to do yourself.  For instance, it will convert message arguments between Unicode and OEM for you.

  9. blah says:

    It’s sad that the ‘official’ subclassing mechanism is nothing more than hooking into it the h4x0r way. If it’s not done in stack order, all bets are off.

  10. Liam says:

    Criffer

    "> SetWindowLongPtr(hwndButton, (LONG_PTR)SubclassWndProc);

    Should be

    SetWindowLongPtr(hwndButton, GWLP_WNDPROC, (LONG_PTR)SubclassWndProc);"

    Unfortunately that will cause a warning on a 32 bit build

    "warning C4244: ‘argument’ : conversion from ‘LONG_PTR’ to ‘LONG’, possible loss of data"

    Instead it requires a little hack on 32 bit builds of (LONG)(LONG_PTR)ptr.

    Also when subclassing a window and using teh WM_NCCREATE message there is another error you should avoid. http://www.liamdevine.co.uk/blog/2009/03/win32-window-title/

  11. MadQ says:

    Bah, subclassing is way too complicated. Just set up a trampoline at InternalCallWinProc, which will give you the correct window procedure to call. *grinning, ducking, and running like hell!*

  12. Leo Davidson says:

    I’m guessing that’s the same popular, commercial software that’s preinstalled on a lot of PCs which ignores the results of QueryInterface calls to its container and blindly calls whatever (null) pointer it gets back. (Because IE is the only ActiveX host in the world and future versions of IE will never change which interfaces they offers, so why should any ActiveX control waste time and bytes checking an HRESULT!?)

    The one that’s made by a company that, when you tell them about this bug in their software (which you’ve already worked around yourself but figure they might want to fix for other people), tells you to pay for a support contract before they’ll lift a finger.

    Sometimes I wonder if there’s anything this app *doesn’t* do wrong at some point in its miserable existence in the memory of our machines. :-D

    Still, it’s gotta be difficult for them to devote much time to getting these things right when they are still desperately, day after day, month after month, trying to find the "64-bit" switch in whichever IDE/compiler they use.

    Ahem.

  13. Naveen says:

    I think every one should use the SetWindowSubclass() function instead of the above technique.

  14. Miral says:

    @Naveen:

    That only works if you’re targeting XP or above.  Some of us still try to let programs run on older versions of Windows (they’re not totally dead yet!).

  15. Leo Davidson says:

    @Liam:

    Doing it that way removes the warning but breaks 64-bit builds, or requires you to have a load of #ifdefs and separate code for 32-bit and 64-bit. I think it’s better to put something on the previous line to suppress the warning (for the next line only). e.g.

    #pragma warning(suppress:4244) // spurious warning due to stupid Win32 SDK header definition.

    SetWindowLongPtr(hwndDlg, DWLP_USER, reinterpret_cast<LONG_PTR>(pcd));

    Alternatively, you could include a header in all files which (in one place only) uses an #ifdef to redefine SetWindowLongPtrW/A on Win32 to something that doesn’t cause the error. I don’t have the proper code to hand but it should be easy to find in a search if you don’t like the #pragma method above. I think this came up in a previous ONT comments thread, or maybe even a post by Raymond.

    (I’m surprised the SDK still hasn’t been updated to fix the problem. Or maybe it has as my SDK is still an early Vista version.)

  16. Shog9 says:

    Liam: I’d wager that it’s a bad plan to eat (fail to forward) *any* messages when you’re subclassing a window, UNLESS you know for certain that your handling of the message should completely override the default.

  17. Liam says:

    Leo I was not aware suppress worked for this warning number, as it does not work for all less than six thousand. Yes it would brake a 64 bit build but I use a macro to cast which looks something like

    #ifdef _WIN64

    # define CAST_TYPE (LONG_PTR)

    #else

    # define CAST_TYPE (LONG)(LONG_PTR)

    #endif

    I think it also comes with a comment not to dissimilar to your example :)

  18. Neil says:

    Btw – why do you have to call CallWindowProc() to call the previous function? Why can’t you just call it directly? Last I checked, function pointers worked just fine in C and C++.

    [Because it’s not actually a function pointer. -Raymond]

    Although it used to be ;-) I had some code that called the class window proc as a function pointer. (I had previously ensured that the windows that I subclassed had not already been subclassed.) To port my code to run under Windows 95 it was only necessary to correct my function pointer into a call to CallWindowProc, although it would still occasionally crash in some common dialogs for no apparent reason.

  19. Ryan says:

    What about SetWindowSubclass and DefSubclassProc?

    I call this to subclass an EDIT control:

    SetWindowSubclass(hWndStatus, StatusEdit, 0, 0);

    And then call this at the end of my subclass proc:

    return DefSubclassProc(hWnd, message, wParam, lParam);

    I am specifically using Common Controls 6, is this ok? I got the info from MSDN section on Subclassing Controls:

    http://msdn.microsoft.com/en-us/library/bb773183(VS.85).aspx

  20. Keithius says:

    > This sounds blatantly obvious, but you’d be surprised how often people mess this up.

    No, sadly I wouldn’t. I’ve been reading your blog for too long now – now I *expect* people to mess up the blatantly obvious.

  21. Clovis says:

    Did someone have to write some sort of nasty compatibility shim for Popular Commercial Program, or does it still crash to this day?

  22. ulric says:

    hum.. I thought that SetWindowLongPtr warning was fixed in a newer platform SDK.

    It’s simply an error in that’s macro definition that triggers a false w64 compatibility warning, you should redefine that macro rather than sprinkle casts or #ifdef all over your code.

  23. Yuhong Bao says:

    Ryan: You don’t have to worry about this if you use SetWindowSubclass.

Comments are closed.