views:

1152

answers:

7

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.

+1  A: 

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.

Head Geek
I think Get/SetProp is definitely a clean way of doing it. It's a tad slower than Get/SetWindowLong (which is O(1)), but as long as there's not "many" properties set for the window, I doubt that this will be a bottleneck.
Johann Gerell
+5  A: 

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?

Adam Rosenfield
As far as thunks go, you could have a separate thunk for each window (generated dynamically). You would register this thunk as the window procedure, and this thunk would already contain the object pointer. I *believe* this is the ATL/WTL approach.
Derek Park
A: 

Thunks are totally cool but also very problematic in the face of DEP.

Curt Hagenlocher
+2  A: 

In your constructor, call CreateWindowEx with "this" as the lpParam argument.

Then, on WM_NCCREATE, call the following code:

SetWindowLongPtr(hwnd, GWL_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.

+3  A: 

See this article: http://web.archive.org/web/20051125022758/www.rpi.edu/~pudeyo/articles/wndproc/

Nemanja Trifunovic
I like the hook approach detailed in that link.
Mark Ingram
+4  A: 

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.

jussij
This can be remedied by setting extra window data in the WNDCLASSEX struct and use that new offset in Get/SetWindowLongPtr instead of GWL_USERDATA.
Johann Gerell
Window *messages* do not have GWL_USERDATA. *Windows* have. Don't peek at messages for other windows, and expect to get the this pointer for your window. And note that you should own both the window and the window *class* : http://blogs.msdn.com/oldnewthing/archive/2005/03/03/384285.aspx
MSalters
The problem is there are instances when Windows sends the application a message with a handle to a window that is was not created by the application. But this approach assumes every message sent to the application will be for a window created by the application and that is just not the case.
jussij
I got inspired by this thread and as a result I re-worked the Zeus code to use a hash lookup table to do the mapping. This new approach seems to be working very well.
jussij
+1  A: 

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().