views:

460

answers:

4

Quick sanity check: Is it possible to subclass a window using a functor? I'm running into a situation where I want to have some data available in the win proc, but GWLP_USERDATA is already being used. A functor seems like a good alternative, but I'm having trouble getting it to work.

Here's the basics:

class MyWinProc { // Win Proc Functor
public:
 MyWinProc(ExternalClass* obj, HWND window) :
                obj(obj), window(window) {
                oldWinProc = SubclassWindow(window, this); // Apply Subclass
            }

 virtual ~MyWinProc() {
                SubclassWindow(window, oldWinProc); // Remove Subclass
            }

 LRESULT CALLBACK operator()(HWND, UINT, WPARAM, LPARAM) {
                switch( uMsg ) {
  case WM_MOUSEMOVE: {
   obj->onMouseMove(/*etc*/);
   break;
  }
                }
                return CallWindowProc(oldWinProc, hWnd, uMsg, wParam, lParam);
            }

private:
 ExternalClass* obj;
 HWND  window;
 WNDPROC oldWinProc;
};

Seems all well and good, but when I hit DispatchMessage() in me message pump, I "Access Violation Writing Location 0x00000000", obviously not a good sign. Remove the call to the above code and life is happy again. :( So is this even possible, or am I going about it entirely the wrong way?

+6  A: 

A CALLBACK function must be a static member function or an otherwise straight C-style function. The Windows API doesn't really know anything about C++ objects.

Something along the lines of this should work:

class MyWinProc { 
public:
        MyWinProc(ExternalClass* obj, HWND window) :
                obj(obj), window(window) {
                pContext = this;

                oldWinProc = SubclassWindow(window, &MyWinProc::wndproc); // Apply Subclass
            }

        virtual ~MyWinProc() {
                SubclassWindow(window, oldWinProc); // Remove Subclass
            }


private:
        static MyWinProc* pContext;

        static
        LRESULT CALLBACK wndproc( HWND, UINT, WPARAM, LPARAM) {
            MyWndProc& me = *pContext;

            // do your WndProc work...
        }

        ExternalClass* obj;
        HWND  window;
        WNDPROC oldWinProc;
};
Michael Burr
A static pContext is OK if you're only subclassing one instance of that window.
ChrisW
@ChrisW: You're right - the class should be a singleton or there needs to be a more sophisticated way to get context. This was just a quick example on top of the presented example code.
Michael Burr
Yes, and while your code example is technically correct (and I'll upvote it to make it easier for newbies to find) I do indeed need a more robust way of getting the context, since I may be subclassing several windows. It would seem that my mistake here was assuming that a functor could take the place of any callback, which obviously isn't the case. It's really too bad, since that would open up a whole new world of possibilities!
Toji
+3  A: 

GWLP_USERDATA is already being used

I don't know what your SubclassWindow function is, but CWnd::SubclassWindow says, "The window must not already be attached to an MFC object when this function is called".

I'm running into a situation where I want to have some data available in the win proc

A usual (non-MFC) way to implement that is to have a global/static dictionary, whose key/index is the HWND value of the subclassed windows, and whose data is the data that you want to associate with that window: that data is often the this pointer of a C++ class of yours.

You subclass the window procedure with a static callback function of yours: your static callback function then, when it's invoked, uses the HWND which it's passed to look up the data in the static dictionary.

ChrisW
Sorry for the confusion, but I should point out that I'm not using MFC. SubclassWindow is a macro that I'm using defined in windowsx.h, not the MFC function. Also, outside of MFC it's OK to subclass a window multiple times, there's just some care that needs to be taken with the chaining of WinProc's and order of setup/removal.
Toji
+1  A: 

GWLP_USERDATA is not the only way to store data associated with a window, you can also use SetProp().

And at least on x86, you can do ATL style thunking (A small piece of asm code that puts your class pointer in ecx and then jumps to your wndproc) You can find some links about that in a answer I posted here

Anders
Huh. And to think that with as much as I've done in Win32 I've never heard of SetProp! That's a great idea, and I think I may use it!
Toji
+2  A: 

The problem with using a functor is the calling convention: Windows is expecting the address to be the address of a static function, and will use/invoke that address as such; whereas the 'this' which you're passing is not the address of a static function.

Windows is going to use the address like this (pseudo-coded assembly):

; push the necessary parameters
push [hWnd]
push etc...
; invoke the specified address (of the static function)
call [callback]

To invoke a functor, the Windows code would need to be like this

; push the necessary parameters
push [hWnd]
push etc...
; invoke the specified address (of the functor object)
; ... first, put the 'this' pointer as a hidden parameter into the ecx register
mov ecx,[callback]
; ... next, invoke the address (where is it?) of the class' functor method
call MyWinProc::operator()

... or instead of the last two statements, the following statements if the operator is virtual ...

; ... first, put the 'this' pointer as a hidden parameter into the ecx register
mov ecx,[callback]
; ... next, invoke the address of the operator via an (which?) entry
;     in the class' vtable
call [ecx+8]

Neither of these is possible because the O/S isn't aware of the calling conventions for non-static C++ methods, especially including:

  • The way in which the implicit 'this' parameter is passed
  • The address of the class' non-virtual methods
  • The vtable entries of the class' virtual methods
ChrisW
Thank you for the very in-depth description! I began to realize that it must be something like this after I started getting answers, nice to have the details. I wish I could mark two answers as the "right" one, since this answers the specific question I asked, but the other one I marked points towards the an eventual solution, which is more likely what searchers of similar issues will be looking for. Still, you get my upvote and I hope several more besides! Thank you again!
Toji