Pretty close, but not exactly. That 3rd line...
|
SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)btnWndProc);
| |
...places within the WNDCLASSEX::cbWndExtra bytes of my main window ( the one registered in WinMain() ) the address of the internal button Window Procedure - the one I mentioned above which you don't have access to unless you perform this subclass operation. Quite a bit going on there. Let me elaborate.
The reason I did that, i.e., use SetWindowLongPtr to store this value, i.e., the address of the internal button class Window Procedure, is because my coding philosophy is to avoid using global variables at all costs. In some situations it isn't possible, but in GUI applications in particular it almost always is possible. This issue of global variables is a philosophic design thing. Some very good coders use them sparingly and in the right places (Petzold would be in that category), and others, such as myself don't.
Before I get into how the variable btnWndProc is used with Subclassing, let me first allude back to some of the statements kbw and I made at the outset of this post. It involves Object Oriented Programming and the C based Win32 Api. In C++ one creates a Class and one then has the opportunity of specifying private, protected, or public member variables as member variables of the Class. With C, while one can use a struct to create objects, the syntax and use of member functions in particular is not nearly so neat as in C++. When Microsoft created the Windows Api with 16 bit Windows back in the 1980s, C++ was not really fully developed, and while the advantages of OOP were understood, OOP had to be implemented in C - the dominent systems programming language of the time. So, the WNDCLASS::cbWndExtra Bytes (see WNDCLASS or RegisterClass in the MSDN docs) were a way Microsoft provided to allow coders to add 'instance data', i.e., member variables, to instances of a class. That would be the main window of the program we are discussing. If you look in WinMain() of my program you'll see this line....
|
wc.cbWndExtra=sizeof(void*);
| |
With that line I'm specifying to Windows that I want 4 bytes of extra storage in my Window Class for my main window if the app is built as a 32 bit app, or 8 bytes of extra storage if the app is built x64. For x86 builds sizeof(void*) will return 4 bytes and 8 bytes in x64 builds. So in other words I am going to store the address of the internal "button" Class Window Procedure within the .cbWndExtra bytes of the main window. In still other words, it will be 'persisted' there. In yet still different words, it will become a C based 'instance' variable of the instantiated window. The variable will be set there at program initialization, and persisted there 'till program termination. Why?
Two reasons. First, as previously stated, I don't like global variables, and had I not done that, I would have had to have added this....
...at global scope in the program, instead of making it a local automatic variable in fnWndProc_OnCreate(). If you check out Charles' Colors program, I'm pretty sure you'll find he specified the return from the SetWindowLong() call which created the subclass in a variable at global scope within his code.
Second, the variable is needed to make subclassing work. Recall that we're discussing child window controls whose parent will receive WM_COMMAND messages from the child during program execution. And, our subclass procedure - a new Window Procedure in our program, will receive all the messages which would have originally gone to the child window control's original Window Procedure, the address of which we just stored in the main window's .cbWndExtra bytes. With subclassing, when one intercepts a message from the control's original Window Procedure, or, better said, a message that was to originally go to the controls Window Procedure but will now be coming into ours, we need to decide what to do with the message. We can write code to take some specific action on the message, but we need to decide what to do with the message after we're done with it. Specifically, we have the power to 'eat' the message, or pass it on to the original control Window Procedure. If we decide to pass it on to the control's original Window Procedure, we need the address of that procedure, because it is the first parameter of the CallWindowProc() function, which function is provided by Microsoft as part of the subclassing design to pass on the parameters of a WNDPROC function to another WNDPROC signature function. Here is my call at the end of my fnBtnSubClass() function in the return statement....
|
return CallWindowProc((WNDPROC)GetWindowLongPtr(hParent,0), hwnd, msg, wParam, lParam);
| |
Note that the 1st parameter to CallWindowProc() is a WNDPROC typed variable, and I have the address of the control's original Window Procedure stored at offset #1 (offset #0 if using zero based counting) in the main Window's WNDCLASS::cbWndExtra bytes. So that's how that convoluted function call works. Its kind of ugly and confusing, I admit, but as Petzold mentioned in a similar use of such syntax- it works.
Note that, in that program for Malibor I wanted to show how the behavior of a control can be modified by subclassing, and in that program I stopped a specific button from behaving the way it was designed to behave by Microsoft. I 'ate' several messages in the button subclass for I believe Button #2. So for Button #2 that return statement containing a call to CallWindowProc() wouldn't execute. So Button #2 was essentially 'broken'. But if Button #2 wasn't the one clicked, the return executed, and we needed that persisted 'btnWndProc' variable stored in the WNDCLASS::cbWndExtra bytes.
None of Petziold's Win32 books were written when 64 bit Windows existed. Luckily for us, Microsoft's x64 modifications to the Api were such that most 32 bit source code was largely 64 bit compatible. Most. But not all. In 64 bit builds one needs to use SetWindowLongPtr() - not SetWindowLong(). However, if one uses SetWindowLongPtr() one can still build the source as x86. That app I posted for Malibor can be built and it correctly functions if built as x86 or x64. That's why I used sizeof(void*) as the number of bytes I wanted to specify as 'extra' within the WNDCLASS::cbWndExtra bytes. In x64 pointers need 8 bytes, and in x86 4 bytes. In most cases the values I store within the WNDCLASS::cbWndExtra bytes are pointers to various application objects.
Finally, passing addresses of objects instead of objects themselves is a better and more efficient methodology in terms of function parameters. That program of mine couldn't be built as a C program because reference parameters were an innovation of C++ over C's pointer parameters. At one time in my coding history I used the same constructs but used that WndEventArgs as a pointer parameter. When I taught my self C++, such as I understood it, I started using references more than pointer parameters. I think it better and clearer. Hope this helps.
From Microsoft's SetWindowLongPtr() docs....
Calling SetWindowLongPtr with the GWLP_WNDPROC index creates a subclass of the window class used to create the window. An application can subclass a system class, but should not subclass a window class created by another process. The SetWindowLongPtr function creates the window subclass by changing the window procedure associated with a particular window class, causing the system to call the new window procedure instead of the previous one. An application must pass any messages not processed by the new window procedure to the previous window procedure by calling CallWindowProc. This allows the application to create a chain of window procedures.
Reserve extra window memory by specifying a nonzero value in the cbWndExtra member of the WNDCLASSEX structure used with the RegisterClassEx function.