WNDPROC in a class... no idea

Started by
15 comments, last by Verg 19 years, 9 months ago
The problem is that a class member function has a hidden 'this' pointer that gets passed to it. However, when Windows wants to send a message to an HWND, it has no clue what CTest object to pass as the 'this' pointer. In other words, you can't use a non-static member function as a message handler. As mentioned in more detail in that article several people have pointed out, what you do is store the 'this' pointer in a memory address of your window known as the window's user data or application data. In other words, Windows is nice enough to let us give a window a pointer, so we don't have to deal with a std::map or whatever to associate additional info with windows. Once we set up that data, we use give Windows a global message handler whose only job is to call the class member function on the pointer stored in the window.
Advertisement
Quote:
I read that one a few times already (before posted this thread), I'm still searching a better solution (don't want to use MFC / ATL).

I don't know where you get the mfc and atl comment; it shows you how to do it in plain win32. Here, I'll even copy and paste the section for you. I don't know how much easier I can make.
Quote:
Per-Window Data

You can associate a block of memory with an instance of every window that is created by Windows. That block, along with variables pointed to by pointers stored in that block, is called Per-Window Data. You can specify the size of this data area by providing appropriate value in cbWndExtra member of WNDCLASS structure, read it by using GetWindowLong or GetWindowLongPtr, and write it with SetWindowLong or SetWindowLongPtr. The use of Ptr versions are recommended because they are compatible with 64-bit Windows, and you should use them if you're interested in upward compatibility.

We want to associate a given Window object with a particular window by storing pointer to the object in the window's per-window data area. In other words, at some point we will call

SetWindowLong(hwnd, GWL_USERDATA, this);

to establish the association, and at a later time we will use

Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA);

to get our Window pointer from the window. Our global WndProc will thus be:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA);
if (w)
return w->WndProc(hwnd, msg, wp, lp);
else
return DefWindowProc(hwnd, msg, wp, lp);
}

The if statement is required to handle the case when WndProc is called before the association is established; more on this later. When do we establish the association, that is, call SetWindowLong? It may be tempting to do so right after CreateWindow call, as the following code snippet shows:

Window w;
HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, 0);
if (!hwnd)
return -1;
SetWindowLong(hwnd, GWL_USERDATA, &w);

Unfortunately, this will leave many messages unprocessed, including the important WM_CREATE message, because for them WndProc will be called before CreateWindow returns. It would be nice to establish the association earlier.

WM_NCCREATE comes to the rescue. As MSDN says, it is sent before WM_CREATE, and it comes with a pointer to CREATESTRUCT that we will use to pass this pointer. Note that CreateWindow has a parameter called lpParam; this same parameter is lpCreateParams member of CREATESTRUCT. By passing a pointer to our Window object, w, in lpParam, we can retrieve it later through lpCreateParams. Having acquired the pointer, we can call SetWindowLong in WM_NCCREATE handler. When WM_CREATE comes around, the pointer will be retrieved via GetWindowLong and the member handler function will be called. See the complete source code here: windowdata.cpp

There is one disadvantage of this approach, and that is the fact that WM_NCCREATE is not the first message received by WndProc. If you are lucky and don't check whether the value retrieved by GetWindowLong was non-null and access member variables when your this pointer is NULL, your program will crash. More precisely, window procedure recieves a WM_GETMINMAXINFO message before it receives WM_NCCREATE.
Quote:Original post by Empirical
You cannot.

(I did once see an asm solution, but never in practice)

Does the ATL count as 'in practice'? I think it only works on certain systems though (that's what I remember from the #defines).
Quote:Original post by Invader X
Quote:Original post by Empirical
You cannot.

(I did once see an asm solution, but never in practice)

Does the ATL count as 'in practice'? I think it only works on certain systems though (that's what I remember from the #defines).


Well, possibly, though in reality isn't it using a methord simular to the above behind the scenes?

Truth is I know sweet FA about ATL. Win32 through and through in my narrow little world ;)

Besides I assumed he wanted to use Win32, and I was right! :P
The whole, "I don't want to use MFC/ATL" thing strikes me as yet another meme. ATL has no runtime requirements and has very small overhead.

Use WTL (which uses ATL) and don't worry about the minute details of routing messages to window instances. As a special bonus, it uses CClassname notation!
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
From my custom control:
// When creating window...m_hWnd = CreateWindowEx(0,WINDOW_CLASS_NAME,"Druink IM",WS_OVERLAPPEDWINDOW,x,y,w,h,NULL,NULL,hInstance,this);// The static WNDPROC (the one you put in your WNDCLASS structure when you register the window class with RegisterClass):LRESULT CALLBACK CWindow::StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){CWindow* pParent;   pParent = (CWindow*)GetWindowLong(hWnd,GWL_USERDATA);   if(uMsg == WM_CREATE)   {      pParent = (CWindow*)(((CREATESTRUCT*)lParam)->lpCreateParams);      SetWindowLong(hWnd,GWL_USERDATA,(LONG)pParent);      return pParent->WndProc(uMsg,wParam,lParam);   }   else if(!pParent)      return DefWindowProc(hWnd,uMsg,wParam,lParam);   assert(pParent->m_hWnd == hWnd);   return pParent->WndProc(uMsg,wParam,lParam);}// The 'real' WNDPROC (non-static, in the class):LRESULT CWindow::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam){   switch(uMsg)   {      // Code here   }   return DefWindowProc(m_hWnd,uMsg,wParam,lParam);}


The static wndproc tries to get the pointer to the class thats saved in the HWND's user data.
WM_CREATE is one of the first messages sent to the window. When the function gets that message, it extracts the pointer to the class passed as the extra parameter to CreateWindowEx() - the 'this' pointer. It then saves this pointer into the HWND's user data, and calls the non-static WndProc.
If this is a message before WM_CREATE (e.g. WM_NCCREATE), then we don't have a pointer to the class yet, and we have to just call the DefWindowProc().
For normal messages, we do a quick check to see that the HWND in the class we got from the HWND's user data is actually the same as the one we're using (just a sanity check), and then pass the message off to the non-static window proc.

The above code assumes that the CWindow class has a HWND member variable called m_hWnd.

Hope this helps.
Cheers,
Steve
Quote:Original post by psae0001
I've searching online more than weeks probably a month. I can't find any solution to tell how to obtain the address of wndproc, and I want to the callback function inside the class (if possible) because it'll give lots of advantages.


This post is one day old... right in this very forum.

As others explained, you can't do WndProc as a member function because WINDOWS itself is what calls it. Since it doesn't have an object of your Window-class type, how would it call it?

If using the class that contains your "WndProc" looks like this:

YourClass object;
object.WndProc(HWND,UINT,WPARAM,LPARAM)...

How will Windows get "object" in order to call WndProc?

It can't.

Everyone else has made good suggestions on how to get around this.
my_life:          nop          jmp my_life
[ Keep track of your TDD cycle using "The Death Star" ] [ Verge Video Editor Support Forums ] [ Principles of Verg-o-nomics ] [ "t00t-orials" ]

This topic is closed to new replies.

Advertisement