Best method for storing this pointer for use in WndProc
I'm interested to know the best / common way of storing a this
pointer for use in the WndProc
. I know of several approaches, but each as I understand it have their own drawbacks. My questions are:
What different ways are there of producing this kind of code:
CWindow::WndProc(UINT msg, WPARAM wParam, LPARAM)
{
this->DoSomething();
}
I can think of Thunks, HashMaps, Thread Local Storage and the Window User Data struct.
What are the pros / cons of each of these approaches?
Points awarded for code examples and recommendations.
This is purely for curiosities sake. After using MFC I've just been wondering how that works and then got to thinking about ATL etc.
Edit: What is the earliest place I can validly use the HWND
in the window proc? It is documented as WM_NCCREATE
- but if you actually experiment, that's not the first message to be sent to a window.
Edit: ATL uses a thunk for accessing the this pointer. MFC uses a hashtable lookup of HWND
s.
While using the SetWindowLongPtr and GetWindowLongPtr to access the GWL_USERDATA might sound like a good idea, I would strongly recommend not using this approach.
This is the exactly the approached used by the Zeus editor and in recent years it has caused nothing but pain.
I think what happens is third party windows messages are sent to Zeus that also have their GWL_USERDATA value set. One application in particular was a Microsoft tool that provied an alternative way to enter Asian characters in any windows application (i.e. some sort of software keyboard utility).
The problem is Zeus always assumes the GWL_USERDATA data was set by it and tries to use the data as a this pointer, which then results in a crash.
If I was to do it all again with, what I know now, I would go for a cached hash lookup approach where the window handle is used as the key.
In your constructor, call CreateWindowEx with "this" as the lpParam argument.
Then, on WM_NCCREATE, call the following code:
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) ((CREATESTRUCT*)lParam)->lpCreateParams);
SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
Then, at the top of your window procedure you could do the following:
MyWindowClass *wndptr = (MyWindowClass*) GetWindowLongPtr(hwnd, GWL_USERDATA);
Which allows you to do this:
wndptr->DoSomething();
Of course, you could use the same technique to call something like your function above:
wndptr->WndProc(msg, wparam, lparam);
... which can then use its "this" pointer as expected.
This question has many duplicates and almost-duplicates on SO, yet almost none of the answers I've seen explore the pitfalls of their chosen solutions.
There are several ways how to associate an arbitrary data pointer with a window, and there are 2 different situations to consider. Depending on the situation, the possibilities are different.
Situation 1 is when you are authoring the window class. This means you are implementing the WNDPROC
, and it is your intention that other people use your window class in their applications. You generally do not know who will use your window class, and for what.
Situation 2 is when you are using a window class that already exists in your own application. In general, you do not have access to the window class source code, and you cannot modify it.
I'm assuming that the problem isn't getting the data pointer into the WNDPROC
initially, but rather, how to store it for subsequent calls.
Method 1: cbWndExtra
When Windows creates an instance of a window, it internally allocates a WND
struct. This struct has a certain size, contains all sorts of window-related things, like its position, its window class, and its current WNDPROC. At the end of this struct, Windows optionally allocates a number of additional bytes that belong to the struct. The number is specified in WNDCLASSEX.cbWndExtra
, which is used in RegisterWindowClassEx
.
This implies that this method can only be used if you are the person who registers the window class, i.e. you are authoring the window class.
Applications cannot directly access the WND
struct. Instead, use GetWindowLong[Ptr]
. Non-negative indices access memory inside the extra bytes at the end of the struct. "0" will access the first extra bytes.
This is a clean, and fast way of doing it, if you are authoring the window class. Most Windows internal controls seem to use this method.
Unfortunately, this method does not play so well with dialogs (DialogBox
family). You would have a dialog window class in addition to providing the dialog template, which can become cumbersome to maintain (unless you need to do so for other reasons anyway). If you do want to use it with dialogs, you must specify the window class name in the dialog template, make sure this window class is registered before showing the dialog, and you need to implement a WNDPROC
for the dialog (or use DefDlgProc
). Offset all accesses to the extra memory by DLGWINDOWEXTRA
(including the value of cbWndExtra
-- this is because internally, dialogs already need some extra in order to function properly). See also below for an extra method exclusive to dialogs.
Method 2: GWLP_USERDATA
The aforementioned WND
struct happens to contain one pointer-sized field, which is not used by the system. It is accessed using GetWindowLongPtr
with a negative index (namely, GWLP_USERDATA
). A negative index will access fields inside the WND
structure. Note that according to this, the negative indices do not seem to represent memory offsets, but are arbitrary.
The problem with GWLP_USERDATA
is that it is not clear, and it has not been clear in the past, what exactly the purpose of this field is, and hence, who the owner of this field is. See also this question. The general consensus is that there is no consensus. It is likely that GWLP_USERDATA
was meant to be used by users of the window, and not authors of the window class. This implies that using it inside of the WNDPROC is strictly incorrect, as the WNDPROC is always provided by the window class author.
All standard windows controls that I am aware of (e.g. BUTTON
, EDIT
, etc.) adhere to this and do not use GWLP_USERDATA
internally, leaving it free for the window which uses these controls. The problem is that there are WAY too many examples, including on MSDN and on SO, which break this rule and use GWLP_USERDATA
for implementation of the window class. This effectively takes away the cleanest and simplest method for a control user to associate a context pointer with it, simply because way too many people are doing it "wrong". At worst, the user code does not know that GWLP_USERDATA
is occupied, and may overwrite it, which would likely crash the application.
Because of this longstanding dispute about the ownership of GWLP_USERDATA
, it is not generally safe to use it. If you are authoring a window class, you probably never should have used it anyway. If you are using a window, you should only do so if you are certain that it is not used by the window class.
Method 3: SetProp
The SetProp
family of functions implements access to a property table. Each window has its own, independent properties. The key of this table is a string at API surface level, but internally it is really an ATOM.
SetProp
can be used by window class authors, and window users, and it has issues too, but they are different from GWLP_USERDATA
. You must make sure that the strings used as the property keys do not collide. The winodw user may not necessarily know what strings the window class author is using internally. Even though conflicts are unlikely, you can avoid them entirely by using a GUID as string, for example. As is evident when looking at the contents of the global ATOM table, many programs use GUIDs this way.
SetProp
must be used with care. Most resources do not explain the pitfalls of this function. Internally, it uses GlobalAddAtom
. This has several implications, which need to be considered when using this function:
-
Instead of a string, you can use an
ATOM
, which you get when you register a new stringGlobalAddAtom
. An ATOM is just an integer. This will improve performance;SetProp
internally always usesATOM
s as property keys, never strings. Passing anATOM
skips searching the string in the global atom table. -
The number of possible string atoms in the global atom table is limited to 16384, system-wide. It is a bad idea to use many different property names, let alone if those names are dynamically generated at runtime. Instead, you can use a single property to store a pointer to a structure that contains all the data you need.
-
If you are using a GUID, it is safe to use the same GUID for every window you are working with, even across different software projects, since every window has its own properties. This way, all of your software will only use up at most two entries in the global atom table (you'll need at most one GUID as a window class author, and at most one GUID as a window class user). In fact, it might make sense to define two de-facto standard GUIDs everyone can use for their context pointers.
-
Because properties use
GlobalAddAtom
, you must make sure that the atoms are unregistered. Global atoms are not cleaned up when the process exists and will clog up the global atom table until the operating system is restarted. To do this, you must make sure thatRemoveProp
is called. A good place for this is usuallyWM_NCDESTROY
. -
Global atoms are reference-counted. This implies that the counter can overflow at some point. To protect against overflows, once the reference count of an atom reaches 65536, the atom will stay in the atom table forever, and no amount of
GlobalDeleteAtom
can get rid of it. The operating system must be restarted to free the atom table in this case.
Avoid having many different atom names if you want to use SetProp
. Other than that, SetProp
/GetProp
is a very clean and defensive approach. The dangers of atom leaks could be greatly mitigated if developers agreed upon using the same 2 atom names for all windows, but that is not going to happen.
Method 4: SetWindowSubclass
SetWindowSubclass
is meant to allow overriding the WNDPROC
of a specific window, so that you can handle some messages in your own callback, and delegate the rest of the messages to the original WNDPROC
. For example, this can be used to listen for specific key combinations in an EDIT
control, while leaving the rest of the messages to its original implementation.
A convenient side effect of SetWindowSubclass
is that the new, replacement WNDPROC
is not actually a WNDPROC
, but a SUBCLASSPROC
.
SUBCLASSPROC
has 2 additional parameters, one of them is DWORD_PTR dwRefData
. This is arbitrary pointer-sized data. The data comes from you, through the last parameter call to SetWindowSubclass
. The data is then passed to every invocation of the replacement SUBCLASSPROC
. If only every WNDPROC
had this parameter!
This method only helps the window class author.(1) During the initial creation of the window (e.g. WM_CREATE
), the window subclasses itself (it can use dwRefData
from lParam
for example, or allocate it right there if that's appropriate). The rest of the code that would normally go in WNDPROC
is moved to the replacement SUBCLASSPROC
instead.
It can even be used in a dialog's own WM_INITDIALOG
message. If the dialog is shown with DialogParamW
, the last parameter can be used as dwRefData
in a SetWindowSubclass
call in the WM_INITDIALOG
message. Then, all the rest of the dialog logic goes in the new SUBCLASSPROC
, which will receive this dwRefData
for every message. Note that this changes semantics slightly. You are now writing at the level of the dialog's window procedure, not the dialog procedure.
Internally, SetWindowSubclass
uses a property (using SetProp
) whose atom name is UxSubclassInfo
. Every instance of SetWindowSubclass
uses this name, so it will already be in the global atom table on practically any system. It replaces the window's original WNDPROC
with a WNDPROC
called MasterSubclassProc
. That function uses the data in the UxSubclassInfo
property to get the dwRefData
and call all registered SUBCLASSPROC
functions. This also implies that you should probably not use UxSubclassInfo
as your own property name for anything.
Method 5: Thunk
A thunk is a dynamically generated function that can be executed. Its purpose is to call another function, but with additional parameters that seem to magically come out of nowhere.
This would let you define a function which is like WNDPROC
, but it has one additional parameter. This parameter could be the equivalent of a "this" pointer. Then, when creating the window, you replace the original, stub WNDPROC
with a thunk that calls the real, pseudo-WNDPROC
with an additional parameter.
The way this works is that when the thunk is created, it generates machine code in memory for a load instruction, loading the value of the extra parameter as a constant, and then a jump instruction to the address of the function which would normally require an additional parameter. The thunk itself can then be called as if it were a regular WNDPROC
.
This method can be used by window class authors and is extremely fast. However, the implementation is not trivial. The AtlThunk
family of functions implements this, but with a quirk. It does not add an extra parameter. Instead, it replaces the HWND
parameter of WNDPROC
with your arbitrary piece of data. However, that is not a big problem since your arbitrary data may contain the HWND
of the window.
Similarly to the SetWindowSubclass
method, you would create the thunk during window creation, using an arbitrary data pointer. Then, replace the window's WNDPROC
with the thunk. All the real work goes in the new, pseudo-WNDPROC
which is targeted by the thunk.
Thunks do not mess with the global atom table at all, and there are no string uniqueness considerations either. However, like everything else that is allocated in heap memory, they must be freed, and after that, the thunk may no longer be called. Since WM_NCDESTROY
is the last message a window receives, this is the place to do that. Otherwise, you must make sure to reinstall the original WNDPROC
when freeing the thunk.
Note that this method of smuggling a "this" pointer into a callback function is practically ubiquitous in many ecosystems, including C# interop with native C functions.
Method 6: Global lookup table
No long explanation needed. In your application, implement a global table where you store HWND
s as keys and context data as values. You are responsible for cleaning up the table, and, if needed, to make it sufficiently fast.
Window class authors can use private tables for their implementations, and window users can use their own tables to store application-specific information. There are no concerns about atoms or string uniqueness.
Bottom line
These methods work if you are the Window Class Author:
cbWndExtra, (GWLP_USERDATA), SetProp, SetWindowSubclass, Thunk, Global lookup table.
Window Class Author means that you are writing the WNDPROC
function. For example, you may be implementing a custom picture box control, which allows the user to pan and zoom. You may need additional data to store pan/zoom data (e.g. as a 2D transformation matrix), so that you can implement your WM_PAINT
code correctly.
Recommendation: Avoid GWLP_USERDATA because the user code may rely on it; use cbWndExtra if possible.
These methods work if you are the Window User:
GWLP_USERDATA, SetProp, Global lookup table.
Window User means you are creating one or more of the windows and use them in your own application. For example, you may be creating a variable number of buttons dynamically, and each of them is associated with a different piece of data that is relevant when it is being clicked.
Recommendation: Use GWLP_USERDATA if it's a standard Windows control, or you are sure that the control doesn't use it internally.
Extra mention when using dialogs
Dialogs, by default, use a window class that has cbWndExtra
set to DLGWINDOWEXTRA
. It is possible to define your own window class for a dialog, where you allocate, say, DLGWINDOWEXTRA + sizeof(void*)
, and then access GetWindowLongPtrW(hDlg, DLGWINDOWEXTRA)
. But while doing so you will find yourself having to answer questions you won't like. For example, which WNDPROC
do you use (you can use DefDlgProc
), or which class styles do you use (the default dialogs happen to use CS_SAVEBITS | CS_DBLCLKS
, but good luck finding an authoritative reference).
Within the DLGWINDOEXTRA
bytes, dialogs happen to reserve a pointer-sized field, which can be accessed using GetWindowLongPtr
with index DWLP_USER
. This is kind of an additional GWLP_USERDATA
, and, in theory, has the same problems. In practice I have only ever seen this used inside the DLGPROC
which ends up being passed to DialogBox[Param]
. After all, the window user still has GWLP_USERDATA
. So it is probably safe to use for the window class implementation in practically every situation.
You should use GetWindowLongPtr()
/SetWindowLongPtr()
(or the deprecated GetWindowLong()
/SetWindowLong()
). They are fast and do exactly what you want to do. The only tricky part is figuring out when to call SetWindowLongPtr()
- You need to do this when the first window message is sent, which is WM_NCCREATE
.
See this article for sample code and a more in-depth discussion.
Thread-local storage is a bad idea, since you may have multiple windows running in one thread.
A hash map would also work, but computing the hash function for every window message (and there are a LOT) can get expensive.
I'm not sure how you mean to use thunks; how are you passing around the thunks?
I've used SetProp/GetProp to store a pointer to data with the window itself. I'm not sure how it stacks up to the other items you mentioned.