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 SetClassLongPtr function mentions GCL_CBCLSEXTRA: 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_CBCLSEXTRA class long grants access... |
The documentation for
the
What's the point of letting the application change a value if it has no effect?
The
Okay, well, first, let's explain why it has no effect:
The class has already been created.
The 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 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
You've probably figured it out by now.
In 16-bit Windows, the internal 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 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
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
Windows blocked changes to
But for all 32-bit programs and newer 16-bit programs,
attempting to modify the
Bonus chatter:
Another secret hiding place that applications discovered
was storing data in the
window extended style bits,
|
Comments (14)
Comments are closed. |
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.
Raymond, I very much enjoy reading about how stuff was done in the old days and how it effects current stuff. Thanks!
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.
Wait, so the documentation says that you can set the value (which has no effect), but you actually can't?
I wonder why MS didn't change the values in Win32.
@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.
How do you tell the difference between an old 16-bit program and a new one?
@bdell: Try run "dumpbin /headers <filename.exe>" in your Visual Studio command prompt to see what is there.
@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.
"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.
*Win9x SDK
Win9x DDK, of course.
"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.
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).
Also notice that this matched the original WNDCLASS structure.