Private classes, superclassing, and global subclassing


In the suggestion box, A. Skrobov asks why it's impossible to superclass WC_DIALOG, but the example that follows is not actually superclassing.

When I register my own class under this atom, and leave NULL in WNDCLASS.hInstance, Windows fills it in for me. Then I have two distinct classes registered: (0,WC_DIALOG) and (hMyInstance,WC_DIALOG), and DialogBox functions all use the first one.

This question is a bit confused, since it says that the goal is to superclass the dialog class, but registering WC_DIALOG is not superclassing.

First, I'll refer everyone to this MSDN article which describes the various ways of manipulating a window class: Subclassing, superclassing, and global subclassing.

To superclass the dialog class, you retrieve information about the class by calling GetClassInfo and then register a new class based on the original class. But you don't need to go to all that effort to superclass the dialog class, because you already know what you need to know: The number of extra bytes is DLGWINDOWEXTRA, and the dialog procedure is DefDlgProc. You can just register your superclass directly, as we saw last time.

Superclassing is done by registering your custom class under a different name, and using that class name if you want to obtain the new behavior. On the other hand, the question about talks about registering a class under the same name as the original (namely, WC_DIALOG). This isn't subclassing, nor is it superclassing, nor is it even global subclassing.

Before continuing the discussion, I'll first address the issue of leaving NULL in WNDCLASS.hInstance: The value NULL for the instance handle is not legal when registering a class. Each class is associated with a module instance, and NULL is not a module instance. The window manager autocorrects this mistake by registering the class under the module corresponding to the executable. This is the same special-case behavior you get if you call GetModuleHandle(NULL), so it's not something completely out of the blue. It looks like A. Skrobov is being confused by the window manager's attempt to do what you mean. So much for being helpful.

Okay, back to the original problem. Recall that the HINSTANCE member of the WNDCLASS structure is used to specify the class namespace. If you register a class against the handle of the current executable, then in order to create a window with that class, you need to create it with that same instance handle.

Now we can put all the pieces together: Registering the class with WNDCLASS.hInstance = NULL is autocorrected to registering it with WNDCLASS.hInstance = GetModuleHandle(NULL), which places the class in the window class namespace of the current module. This is a separate class from the system dialog class, which is registered against GetModuleHandle(TEXT("USER32")). The two are registered against different modules, so they live independent lives. They just happen to have the same name.

As we learned a few years ago, the instance handle you pass to the CreateWindow (or related) function is used to look up the window class, and as we also learned, the HINSTANCE you pass to the DialogBox (or related) function is used to look up the template as well as to create the frame window. The class name comes from the template, and if you didn't specify an explicit class in your template, then the dialog manager will use WC_DIALOG.

You now have all the pieces necessary to understand what is going on. When you register the class against your executable's instance, you need to use that same instance when creating the dialog box so that your private class is found instead of the global one.

To show how this all fits together, I've written a little program which registers a private class which happens to have the name WC_DIALOG and then uses it to create a dialog box.

// scratch.rc
#include <windows.h>

// A pointless dialog box, for illustration only
1 DIALOG 0,0,150,50
STYLE DS_MODALFRAME | DS_SHELLFONT | WS_POPUP | WS_VISIBLE |
    WS_CAPTION | WS_SYSMENU
CAPTION "Pointless"
FONT 8, "MS Shell Dlg"
BEGIN
    DEFPUSHBUTTON "Cancel",IDCANCEL,50,18,50,14
END

// scratch.cpp
#include <windows.h>

LRESULT CALLBACK
SuperDlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uiMsg) {
  case WM_ERASEBKGND:
    return DefWindowProc(hwnd, uiMsg, wParam, lParam);
  }
  return DefDlgProc(hwnd, uiMsg, wParam, lParam);
}

INT_PTR CALLBACK
DlgProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam)
{
  switch (wm) {
  case WM_INITDIALOG: return TRUE;
  case WM_CLOSE: EndDialog(hwnd, 0); return TRUE;
  }
  return FALSE;
}

int CALLBACK
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
        LPSTR pszCmdLine, int nShowCmd)
{
  WNDCLASS wc;
  wc.style = 0;
  wc.lpfnWndProc = SuperDlgProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = DLGWINDOWEXTRA;
  wc.hInstance = hinst;
  wc.hIcon = NULL;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_INFOBK + 1);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = WC_DIALOG;

  if (RegisterClass(&wc))
    DialogBox(hinst, MAKEINTRESOURCE(1), NULL, DlgProc);

  return 0;
}

The dialog template is itself entirely unremarkable; it looks like any old dialog template.

Our superclass takes the regular dialog box class and gives it a custom background color, namely COLOR_INFOBK.

The program registers this private version of WC_DIALOG and creates a dialog box based on it. Since we passed the same HINSTANCE in the WNDCLASS.hInstance as we did to DialogBox, the lookup of the WC_DIALOG class will find our private version and use it instead of the global version.

Comments (9)
  1. asf says:

    OT, but, this will give you the wrong font on NT6+

  2. Médinoc says:

    One thing that bugs me more with dialogs is that you can’t use EndDialog() in a custom message loop, because I couldn’t find any documented way to tell if EndDialog() has been called on a dialog box or not: Only DialogBoxParam’s internal message seems to have access to this data.

    And then there’s ERROR_PRIVATE_DIALOG_INDEX, that shows dialog box still get a “special” treatment from the window manager…

    [I agree that the inability to use EndDialog in a custom message loop totally sucks. As for ERROR_PRIVATE_DIALOG_INDEX: Another case of “no matter what you do someone will call you an idiot.” (1) Obviously Windows should stop applications from accessing internal data. (2) Windows is idioting for stopping me from accessing internal data. -Raymond]
  3. Apparently "superclassing" is the officially documented terminology, and there is not much to be done about that now, but it sounds really odd to me. What MSDN refers to as "subclassing" and "superclassing" both create what I (with my generic OOP understanding) would describe as a subclass. In the "superclassing" case the new subclass has its own name, whereas "subclassing" creates an anonymous subclass for a single window. And "global subclassing" is effectively a hybrid, creating a subclass that hijacks the name of its superclass.

    (Not that this has much to do with the HINSTANCE issues Raymond is speaking about. Sorry).

  4. Billy O'Neal says:

    @Henning Makholm: That’s the difference between language classes and Window Classes. They’re not related and use completely different terminology throughout MSDN.

    Do keep in mind that the way MSDN refers to classes was written long before todays age of object-oriented programming languages.

  5. Neil says:

    Ah yes, global subclassing was much easier in Windows 3.1, you could just use SetClassLong to overwrite the window procedure (from a DLL loaded as a boot driver, of course.) Obviously I learned very quickly not to let the code crash!

  6. Mike says:

    @asf:

    The question was posed in 2006, NT 6 was released in 2007.

  7. hasta la vista says:

    NT 6 was released in 2006.

  8. Bulletmagnet says:

    > Do keep in mind that the way MSDN refers to classes was written long before todays age of object-oriented programming languages.

    Duty calls.

    Windows 1.0 appeared in November 1985. By that time "The C++ Programming Language" was out, along with the first *commercial* C++ compiler.

    MSDN started in 1992.

    Not to mention that Simula had classes in 1967.

  9. Médinoc says:

    [I agree that the inability to use EndDialog in a custom message loop totally sucks. As for ERROR_PRIVATE_DIALOG_INDEX: Another case of "no matter what you do someone will call you an idiot." (1) Obviously Windows should stop applications from accessing internal data. (2) Windows is idioting for stopping me from accessing internal data. -Raymond]

    Actually, I’m from the "(1)" group, but I’m puzzled by "special treatments" of things that look like just another normal window class. I mean, if dialog boxes can hide their data, why can’t user-defined windows do it?

    …And also, the frustrating fact that caused me to try GetWindowLong() in the first place. But we’re already in agreement about it.

Comments are closed.