WinAPI - how to organize lots of HWND objects? - c++

As I push forward my first winapi UI, I find myself creating large, uncomfortable stacks of HWND variables in my WinMain file:
HWND foo;
HWND bar;
HWND baz;
HWND etc;
int WINAPI WinMain(...) {}
Of course, these variables are used in the functions in the rest of the file - the message loop, for example - so they must be accessible.
For my relatively small UI, I'll have something like 30 HWNDs piled up so they're in a visible scope. This makes me very suspicious that I'm doing it wrong.
Is this what should be happening, or is there a more practical way to organize this?

You have a few solutions, depending what your program is.
You can put all those handles in one or many containers like std::vector.
You can map them like chris suggests.
If your program gets big, you might want to organize them into logical units. For example, if 15 of those windows are for one half of your logic and the other 15 for another half (say controls inside tabs) then you might want to group those controls in some way (file, class, whatever is the most logical fit).

You only need one HWND in your main program, and that's for a main window.
The API doesn't require a single main window, but that's most common. And even when from a user point of view the application offers several apparently independent windows, it's a good idea to have a main window in the program (it can be invisible, but provide grouping).
The other windows are then either children of the main window (within it), or windows "owned" by the main window. In general. And in particular for a first ever Windows program. :-) So you don't need separate variables for these windows. Whenever a window should react to something, that's a message to the window, which means a call of one your functions with the relevant window handle as argument.
Every child window can have a unique integer ID, and that's one way to keep track of them.
But as you get ahead you will want to associate state with each window, and the easiest way to do that is to use the Windows "subclassing" API to associate a pointer with each window. Then you can route window procedure calls to a method on an associated C++ object. Where different messages can be routed further to different message handling methods, each with access to window's state (it's just this C++ object).

Related

DeferWindowPos weird behaviour

This happens with all ActiveX controls. If I reposition an ActiveX control with DeferWindowPos
HDWP hdwp = BeginDeferWindowPos(1);
DeferWindowPos(hdwp, m_pActiveX->GetSafeHwnd(), NULL, left, top, width, height, SWP_NOZORDER);
EndDeferWindowPos(hdwp);
it goes there but then moves/resizes to its old rectangle once you click anywhere inside the control. If I use MoveWindow instead
m_pActiveX->MoveWindow(left, top, width, height);
this doesn't happen.
It doesn't happen with any other type of control, only with ActiveX controls, but it happens with all of them. I made a test to confirm this, creating a new ActiveX control project and didn't make any changes, and the problem was still there.
You never got an appropriate answer. I'll try to help out a bit here.
The issue is that MFC hides a lot of the trickiness with hosting an ActiveX control within it's framework. Specifically, if you step into the MoveWindow call, it is not simply a wrapper around the Win32 MoveWindow function. It calls into the OLE Control Container support classes. This basically says, if we have a control site interface, then call COleControlSite::MoveWindow, otherwise call the standard Win32 MoveWindow. The same occurs with several other window functions handled by CWnd etc. For example COleControlSite::SetWindowPos handles hiding/showing the control, then calls COleControlSite::MoveWindow to move it, and then finally calls ::SetWindowPos (with the move/show flags masked out) to handle the rest.
Once within COleControlSite::MoveWindow, you will notice it does several things: it calls SetExtent, updates it's internal m_rect member, and then calls SetObjectRects.
Bypassing these for ActiveX controls using the Win32 API directly (eg via DeferWindowPos) causes some of these crucial steps to be missed. Depending on how your code is layed out, usually you can handle this yourself.
What is this ActiveX control?
Apart from that consider that DeferWindowPos is meant for positioning multiple windows at the same time. The concept being you enter the begin statement, change a bunch of window positions for a new layout, then end to actually move and apply the new positions and sizes.
If you aren't updating multiple windows consider using SetWindowPos instead.
Consider also that you may be getting a message to move, resize, or change the windows position while you are deferring. To prevent this if that is what is happening pass the SWP_NOSENDCHANGING flag in each call to DeferWindowPos so that it is not sent or handle the message and clear all the bits in the WINDOWPOS struct received to prevent unwanted changes.
It is also possible for this call to fail ... are you checking the return value?

C/C++ - Windows - how to keep track of HWND context data

In Windows, suppose you have multiple windows (HWNDs) of the same window class open. How do you keep track of the context data in the window procedure so that, for example, window 1 does not get modified when the user tried to type in window 2?
CreateWindow() does not return until after the WndProc() has been called several times, so you can't simply set the resulting HWND to the context data and do a lookup in the WndProc(); you do need to set it in the WndProc().
WndProc() doesn't directly have the context information passed to it except on window creation messages, but unfortunately window creation messages aren't exactly the first messages to be passed to WndProc(). Nay, I find things such as WM_SIZE, WM_NCSIZE, and even some others are passed before I ever see WM_CREATE.
Storing the HWND in a linked list type of storage mechanism would be inefficient with large amounts of windows: each control in a window is simply another type of window and therefore another HWND of which you need to keep track; after a few hundred controls, searching the linked list for the HWND will be a major bottleneck in the program after a few dozen messages are passed to the program in a short amount of time!
From what I hear, some people use SetWindowLong() - but I also hear that some libraries like to use that too to store their own context information separate from the program and that window data collisions can sometimes occur. How can that be avoided?
if I'm understanding you correctly, you want to avoid one window to catch the messages from another. One way to avoid this is to use the solution proposed in this thread, which keeps track of the windows that is created by you and makes sure that the correct windows gets the messages associated to it by storing the pointer for the caller in the GWL_USERDATA .
// ...
m_hWnd = CreateWindowEx(0,"Classname","Title",WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
320,200,NULL,NULL,hInstance, /*the magic pointer*/ this);
// ...
if(uMsg == WM_CREATE)
{
// collected here..
pParent = (CWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams;
// .. and then stored for later lookup
SetWindowLongPtr(hWnd,GWL_USERDATA,(LONG_PTR)pParent);
}
// ...
You can also catch the WM_NCCREATE message, as proposed by Moo-Juice.
And I don't think you should worry about the messages pre-WM_CREATE, because the window isn't even fully initialized at that point. If you need to set text you do that after the call to CreateWindow(Ex), be it user input or a SendMessagecall.
Whoever creates the window owns that window 100%. If you are the one to call CreateWindow(), then you can use GetWindowLong, knowing that it's yours.
If a library creates the window, however, you can't because it's not yours.
(Aside: Nothing is stopping anyone from stepping on anyone else's toes, but the convention is pretty standard).
If you are using a library that does this, it will generally have some mechanism to associate your own data with a window. Of course, you will need to refer to the documentation for that.
Use Windows properties : SetProp( HWND ,... ) , Getprop( HWND ,... ) and RemoveProp( HWND ,... )
Can't you use WNDCLASS.cbWndExtra to declare whatever private storage your class needs and then it will be allocated by Windows whenever it creates a window of that class.

How does GetGuiThreadInfo() work?

I am trying to get the HWND and ThreadID for currently Focussed Window.
Usually GetForegroundWindow() and then then calling GetThreadProcessId() gives me the correct information.
But in Case of IE8 the HWnd the GetForegroundwindow() gives me the HWND of the IE Frame but actually the focussed window (the document object or Internet Explorer_server) is different (running in a different process). So GetForegroundWindow() approach does not work.
So I used GetGuiThreadInfo(DWORD tid) , and passed it the threadId i obtained from GetForegroundWindow.. something like this;
DWORD tid = GetWindowThreadProcessId(GetForegroundWindow(),0);
GetGuiThreadInfo(tid,&guiThreadInfoObject);
HWND focus = guiThreadInfoObject.hwndFocus;
Using the above approach I get the correct HWND for the Internet Explorer_server object which is correct.
However I did not understand , even though the GetGuiThreadInfo() is getting the ThreadId of a thread in a different process which is IE Frame , howcome it is able get me the threadinfo about Internet Explorer_server object which is on a different Process and thread?
Thanks
Getting the window with the focus is pretty easy with GetFocus(). Although I think GetGuiThreadInfo() uses internal data from the window manager. Your approach is liable to fail if IE is minimized or doesn't have the focus.
Use EnumChildWindows() instead, iteratively for each child you find, until you get a window whose GetClassName() call returns "Internet Explorer_Server".
Thanks for bringing this up btw. What IE is doing is expressly forbidden in the docs for SetParent(). I only knew of Acrobat violating this rule, but now you provided evidence of a Microsoft program doing this. That settles it, it is no longer a rule!
GetWindowThreadProcessId retrieves the thread ID that created the (in your case foreground) window.
guiThreadInfoObject.hwndFocus is the window that ha input focus.
There's some leeway here - I haven't found any place that specifically states the foreground window is the one with input focus. (maybe it retrieves already the top level parent/owner?).
Also, AttachThreadInput may slightly change the game - by setting a threads input focus to a window of a different process.
Anyway, since it's not spelled out explicitely, I wouldn't bet my life on that behavior - that means testing on many systems and treating the method as a weak spot that should be improved if possible.

How do I get the PowerBuilder graphicobject for a given HWND handle?

In my (PowerBuilder) application, I'd like to be able to determine the graphicobject object which corresponds to a given window handle.
Simply iterating over the Control[] array and comparing the value returned by the Handle() function for each of the child controls doesn't work, since not all objects in my application are children of the main window (consider of login dialogs).
Any PowerScript or C/C++ solution would be acceptable.
Is there maybe some window message I could send to window handles, and this message is only understood by PowerBuilder windows, which would the respond with their internal object name, or the like?
Is it a requirement to determine the object from the handle, or do you just want to identify an object, for example to know where the code you need to modify is? I made a tool that does the latter, but it uses object focus, rather than window handles.
(added 2010-06-21) For windows that aren't children of the main window you could explicitly check each of these window class names with isValid(). Then for each valid window, dig through looking for the handle. This should work as long as you only open one instance of the window class at a time. If you open multiple instances, I think you'll need to add a registration mechanism to the open of those windows so the application has a way to access them.

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 HWNDs.
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 (that would just be the through the CREATESTRUCT with the lpParam parameter in CreateWindow[ExW]), 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). Furthermore, all dialogs already reserve a certain amount of bytes in cbWndExtra for the dialog manager to function properly. The number of extra bytes needed is the DLGWINDOWEXTRA constant. This means your stuff needs to come after the extra bytes which are already reserved by the dialog. Offset all accesses to the extra memory by DLGWINDOWEXTRA (including the value of cbWndExtra which you specify in your window class).
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.
I am personally convinced that this is the intention of the engineers that came up with GWLP_USERDATA simply because if it is true, then the API as a whole is sound, extensible, and future-proof. But if it is not true, then the API is neither of those, and it would be redundant with cbWndExtra.
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" (according to my definition of "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 window 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:
When calling SetProp (or any other API that uses the global ATOM table), instead of a string, you can use an ATOM, which you get when you register a new string GlobalAddAtom. An ATOM is just an integer which refers to one entry in the ATOM table. This will improve performance; SetProp internally always uses ATOMs as property keys, never strings. Passing a string causes SetProp and similar functions to internally search the ATOM table for a match first. Passing an ATOM directly 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. This is because atoms are 16-bit uints ranging from 0xC000 to 0xFFFF (all values below 0xC000 are pseudo-atoms pointing to fixed strings (which are perfectly fine to use, but you cannot guarantee that nobody else is using them)). 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 (realistically not going to happen).
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 that RemoveProp is called. A good place for this is usually WM_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 to SetWindowSubclass. The data is then passed to every invocation of the replacement SUBCLASSPROC. If only every WNDPROC had this parameter, then we wouldn't be in this horrible situation!
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 allocate memory for the dwRefData right there if that's appropriate). Deallocation probably best in WM_NCDESTROY. 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 small function whose machine code is dynamically generated at run-time in memory. 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 (pointer-sized). However, that is not a big problem since your arbitrary data may be a pointer to a struct containing 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 HWNDs 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. Otherwise, SetProp.
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 (answer: 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.
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.
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.
You can use GetWindowLongPtr and SetWindowLongPtr; use GWLP_USERDATA to attach the pointer to the window. However, if you are writing a custom control I would suggest to use extra window bytes to get the job done. While registering the window class set the WNDCLASS::cbWndExtra to the size of the data like this, wc.cbWndExtra = sizeof(Ctrl*);.
You can get and set the value using GetWindowLongPtr and SetWindowLongPtr with nIndex parameter set to 0. This method can save GWLP_USERDATA for other purposes.
The disadvantage with GetProp and SetProp, there will be a string comparison to get/set a property.
With regard to SetWindowLong() / GetWindowLong() security, according to Microsoft:
The SetWindowLong function fails if
the window specified by the hWnd
parameter does not belong to the same
process as the calling thread.
Unfortunately, until the release of a Security Update on October 12, 2004, Windows would not enforce this rule, allowing an application to set any other application's GWL_USERDATA. Therefore, applications running on unpatched systems are vulnerable to attack through calls to SetWindowLong().
I recommend setting a thread_local variable just before calling CreateWindow, and reading it in your WindowProc to find out the this variable (I presume you have control over WindowProc).
This way you'll have the this/HWND association on the very first message sent to you window.
With the other approaches suggested here chances are you'll miss on some messages: those sent before WM_CREATE / WM_NCCREATE / WM_GETMINMAXINFO.
class Window
{
// ...
static thread_local Window* _windowBeingCreated;
static thread_local std::unordered_map<HWND, Window*> _hwndMap;
// ...
HWND _hwnd;
// ...
// all error checking omitted
// ...
void Create (HWND parentHWnd, UINT nID, HINSTANCE hinstance)
{
// ...
_windowBeingCreated = this;
::CreateWindow (YourWndClassName, L"", WS_CHILD | WS_VISIBLE, x, y, w, h, parentHWnd, (HMENU) nID, hinstance, NULL);
}
static LRESULT CALLBACK Window::WindowProcStatic (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
Window* _this;
if (_windowBeingCreated != nullptr)
{
_hwndMap[hwnd] = _windowBeingCreated;
_windowBeingCreated->_hwnd = hwnd;
_this = _windowBeingCreated;
windowBeingCreated = NULL;
}
else
{
auto existing = _hwndMap.find (hwnd);
_this = existing->second;
}
return _this->WindowProc (msg, wparam, lparam);
}
LRESULT Window::WindowProc (UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
// ....
ATL's thunk is the most efficent. the thunk executes once and replaces the callback function for the WINPROC to the classes own message processing member function. subsiquent messages are passed by a direct call to the classes member function by windows. it doesnt get any faster than that.
In the past I've used the lpParam parameter of CreateWindowEx:
lpParam [in, optional] Type: LPVOID
Pointer to a value to be passed to the window through the CREATESTRUCT
structure (lpCreateParams member) pointed to by the lParam param of
the WM_CREATE message. This message is sent to the created window by
this function before it returns. If an application calls CreateWindow
to create a MDI client window, lpParam should point to a
CLIENTCREATESTRUCT structure. If an MDI client window calls
CreateWindow to create an MDI child window, lpParam should point to a
MDICREATESTRUCT structure. lpParam may be NULL if no additional data
is needed.
The trick here is to have a static std::map of HWND to class instance pointers. Its possible that the std::map::find might be more performant than the SetWindowLongPtr method. Its certainly easier to write test code using this method though.
Btw if you are using a win32 dialog then you'll need to use the DialogBoxParam function.
In order to prevent the problem that occurred in the Zeus editor, simply specify the window in the GetMessage function:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd, /*A handle to the window whose messages are to be retrieved.*/
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
NOTE
The window must belong to the current thread.
Easy to read Documentation of the function