What’s the point of letting you change the GCL_CBCLSEXTRA if it has no effect?

Date:August 14, 2013 / year-entry #218
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20130814-00/?p=3503
Comments:    14
Summary:The documentation for the Set­Class­Long­Ptr function mentions GCL_CB­CLS­EXTRA: Sets the size, in bytes, of the extra memory associated with the class. Setting this value does not change the number of extra bytes already allocated. What's the point of letting the application change a value if it has no effect? The GCL_CB­CLS­EXTRA class long grants access...

The documentation for the Set­Class­Long­Ptr function mentions

GCL_CB­CLS­EXTRA: Sets the size, in bytes, of the extra memory associated with the class. Setting this value does not change the number of extra bytes already allocated.

What's the point of letting the application change a value if it has no effect?

The GCL_CB­CLS­EXTRA class long grants access to the cbCls­Extra value that was originally passed in the WND­CLASS structure when you called Register­Class, or the Ex-versions mutatus mutandis. The intent is for it to be used with Get­Class­Long so you can read the value back, in case you forgot, or if you are inspecting somebody else's class (for example, because you want to superclass it, although Get­Class­Info is probably a better choice). But since Get­Class­Long and Set­Class­Long take the same class index parameter, once it's defined for one, it's defined for the other.

Okay, well, first, let's explain why it has no effect: The class has already been created. The cbCls­Extra tells the window manager how much extra memory to allocate in the class when it is created. After the class is created, the value isn't really used any more, but Windows hangs on to the value since it needs to report the value when you call Get­Class­Info. Trying to change the value is like making changes to a blueprint after the building has finished construction. The blueprints are still on file at the planning office, but changing them has no effect on the building. (Though it will mislead the fire chief who is studying the blueprints in order to decide how to put out the fire that is raging on one of your upper floors.)

Okay, so why does Windows let you change the values if they have no effect?

Let's look at the values of those class longs:

#define GCL_MENUNAME        (-8)
#define GCL_HBRBACKGROUND   (-10)
#define GCL_HCURSOR         (-12)
#define GCL_HICON           (-14)
#define GCL_HMODULE         (-16)
#define GCL_CBWNDEXTRA      (-18)
#define GCL_CBCLSEXTRA      (-20)
#define GCL_WNDPROC         (-24)

How very strange. They're all even numbers, and negative, too. And the value -22 is skipped, which lies between GCL_CB­CLS­EXTRA and GCL_WND­PROC.

Let's look at what the values were in 16-bit Windows:

#define GCL_MENUNAME        (-8)
#define GCW_HBRBACKGROUND   (-10)
#define GCW_HCURSOR         (-12)
#define GCW_HICON           (-14)
#define GCW_HMODULE         (-16)
#define GCW_CBWNDEXTRA      (-18)
#define GCW_CBCLSEXTRA      (-20)
#define GCL_WNDPROC         (-24)
#define GCW_STYLE           (-26)

Okay, now it looks even more suspicious. All of the special class values were words (as indicated by the W in GCW), except for two longs (GCL), and the gap exactly falls right where a long would go.

You've probably figured it out by now. In 16-bit Windows, the internal CLASS structure looked like this:

typedef struct tagCLASS
{
    ... blah blah blah ...
    UINT      style;            // offset -26 from extraBytes
    WNDPROC   lpfnWndProc;      // offset -24 from extraBytes
    int       cbClsExtra;       // offset -20 from extraBytes
    int       cbWndExtra;       // offset -18 from extraBytes
    HMODULE   hModule;          // offset -16 from extraBytes
    HICON     hIcon;            // offset -14 from extraBytes
    HCURSOR   hCursor;          // offset -12 from extraBytes
    HBRUSH    hbrBackground;    // offset -10 from extraBytes
    LPSTR     lpszMenuName;     // offset -8 from extraBytes
    LPSTR     lpszClassName;    // offset -4 from extraBytes
    BYTE      extraBytes[1];    // offset 0 (extra bytes start here)
}
CLASS;

When a class was created, the class extra bytes were appended directly to the CLASS structure, which meant that you could use negative offsets to access the internal class structures.

WORD GetClassWord(HWND hwnd, int index)
{
    CLASS *pcls = GetWindowClassPointer(hwnd);
    WORD *pw = (WORD*)&pcls->cls_extraBytes[index];
    return *pw;
}

LONG GetClassLong(HWND hwnd, int index)
{
    CLASS *pcls = GetWindowClassPointer(hwnd);
    LONG *pl = (LONG*)&pcls->cls_extraBytes[index];
    return *pl;
}

WORD SetClassWord(HWND hwnd, int index, WORD wNewValue)
{
    CLASS *pcls = GetWindowClassPointer(hwnd);
    WORD *pw = (WORD*)&pcls->cls_extraBytes[index];
    WORD wPrevValue = *pw;
    *pw = wNewValue;
    return wPrevValue;
}

LONG SetClassLong(HWND hwnd, int index, LONG lNewValue)
{
    CLASS *pcls = GetWindowClassPointer(hwnd);
    LONG *pl = (LONG*)&pcls->cls_extraBytes[index];
    LONG lPrevValue = *pl;
    *pl = lNewValue;
    return lPrevValue;
}

Except of course that the original code was written in assembly language, so it was more like

FindClassExtraBytes proc
      mov  bx, [bp][2][4] ;; caller's hwnd
      mov  bx, [bx].wnd_pcls ;; get the class for the window
      add  bx, cls_extraBytes ;; move to extra bytes
      add  bx, [bp][2][4][2] ;; pointer to the requested bytes
      ret

;; use helper macros from cmacros.inc

cProc GetClassWord, <FAR, PUBLIC>
ParmW hwnd
ParmW index
cBegin
      call FindClassExtraBytes
      mov  ax, [bx]      ;; get the word
cEnd

cProc GetClassLong, <FAR, PUBLIC>
ParmW hwnd
ParmW index
cBegin
      call FindClassExtraBytes
      mov  ax, [bx]      ;; get the low word
      mov  dx, [bx][2]   ;; get the high word
cEnd

cProc SetClassWord, <FAR, PUBLIC>
ParmW hwnd
ParmW index
ParmW newValue
cBegin
      call FindClassExtraBytes
      mov  ax, newValue
      xchg ax, [bx]      ;; exchange value
cEnd

cProc SetClassLong, <FAR, PUBLIC>
ParmW hwnd
ParmW index
ParDL newValue
cBegin
      call FindClassExtraBytes
      mov  ax, newValue[0] ;; low word
      mov  dx, newValue[2] ;; high word
      xchg ax, [bx][0]     ;; exchange low word
      xchg dx, [bx][2]     ;; exchange high word
cEnd

In other words, the negative offsets were exactly the values needed to access the corresponding fixed fields in the CLASS structure as if they were extra bytes. (Again, I marvel at how 16-bit Windows managed to accomplish what it did in so little code. The actual code was even tighter than this.)

There were programs that said, "Hey, since I know I can change this value all I want, and it won't have any effect, I can use it as a secret hiding place," and instead of storing data in a more sane location, they just squirreled it away in the GCL_CB­CLS­EXTRA.

Windows blocked changes to GCL_CB­CLS­EXTRA starting in Windows 95, but a compatibility loophole was created so that 16-bit programs written for older versions of Windows could still get the old behavior where they could modify a value that had no effect, just so that they could use it as a secret hiding place.

But for all 32-bit programs and newer 16-bit programs, attempting to modify the cbCls­Extra value will fail with ERROR_INVALID_PARAMETER.

Bonus chatter: Another secret hiding place that applications discovered was storing data in the window extended style bits, dwEx­Style. "Thanks, Windows, for adding four more bytes of data to each window. I'll use it to store a pointer! (I'm sure Windows won't mind.)" There is code in the window manager to enforce the rule that you must use Set­Window­Pos to change the WS_EX_TOP­MOST style rather than calling Set­Window­Long, but there is a compatibility loophole: If your application was written for Windows 3.1 and you are setting extended styles that didn't exist in Windows 3.1, then the window manager says, "I think I know what you're up to" and suspends the rules so that the application can go ahead and use the extended window style as a secret hiding place.


Comments (14)
  1. Medinoc says:

    Which reminds me, were the "private window bytes" of the Dialog window class always "private", or was that added later?

    I ran into those when trying to implement the sadly missing BOOL HasEndDialogBeenCalled(HWND) function.

  2. 12BitSlab says:

    Raymond, I very much enjoy reading about how stuff was done in the old days and how it effects current stuff.  Thanks!

  3. The Windows compatibility code must make an absolute mockery of the modern mantra that code comments are a bad thing and should be avoided, to be replaced by self-documenting code.

  4. Roman says:

    Wait, so the documentation says that you can set the value (which has no effect), but you actually can't?

  5. Yuhong Bao says:

    I wonder why MS didn't change the values in Win32.

    [Why change them? That would be a gratuitous breaking change. -Raymond]
  6. @Roman, I am equally confused.  The history lesson was interesting but the title and the pre-amble all imply that you can continue to change this value and then BOOOM, right at the last minute comes the shattering revelation that 32-bit and newer 16-bit Windows applications can't change it at all.  But presumably older 16-bit applications and brand spanking new 64-bit applications can continue to modify this value … ?

    All just a matter of idle curiosity of course, but even so… wibble.

  7. bdell says:

    How do you tell the difference between an old 16-bit program and a new one?

  8. cheong00 says:

    @bdell: Try run "dumpbin /headers <filename.exe>" in your Visual Studio command prompt to see what is there.

  9. Yuhong Bao says:

    @cheong00: For 16-bit programs, use EXEHDR instead. MS supplied a new version of RC.EXE with the Win32 SDK and Win9x SDK with a -40 switch.

  10. Yuhong Bao says:

    "But presumably older 16-bit applications and brand spanking new 64-bit applications can continue to modify this value … ?"

    64-bit would behave the same as 32-bit.

  11. Yuhong Bao says:

    *Win9x SDK

    Win9x DDK, of course.

  12. Yuhong Bao says:

    "If your application was written for Windows 3.1 and you are setting extended styles that didn't exist in Windows 3.1, then the window manager says, "I think I know what you're up to" and suspends the rules so that the application can go ahead and use the extended window style as a secret hiding place."

    In addition, I read that WM_STYLECHANGING and WM_STYLECHANGED are only sent if the app is marked as 4.0.

  13. Neil says:

    I seem to remember looking at all of these extra bytes (can't remember whether they were class or window) of the built-in classes and wondering why some of them were odd, given that you could only get a word or a long at a time. I seem to remember that one class had a single byte for some sort of flag and then four other bytes (which would then be misaligned, although I guess fewer people cared back then).

  14. Yuhong Bao says:

    Also notice that this matched the original WNDCLASS structure.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index