使用仿函数对窗口进行子类化(Win32)

时间:2009-08-30 05:07:17

标签: c++ winapi functor subclassing

快速健全性检查:是否可以使用仿函数对窗口进行子类化?我遇到了想要在win proc中获得一些数据的情况,但是GWLP_USERDATA已经被使用了。仿函数似乎是一个很好的选择,但是我无法让它工作。

以下是基础知识:

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;
};

似乎一切都很好,但当我在消息泵中点击DispatchMessage()时,我“访问违规写入位置0x00000000”,显然不是一个好兆头。删除对上述代码的调用,生活再次开心。 :(所以这甚至是可能的,还是我完全以错误的方式去做?

5 个答案:

答案 0 :(得分:8)

CALLBACK函数必须是静态成员函数或其他直接C风格函数。 Windows API对C ++对象一无所知。

有些事情应该有效:

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;
};

答案 1 :(得分:5)

使用仿函数的问题是调用约定:Windows期望地址是静态函数的地址,并且将使用/调用该地址;而你传递的'this'不是静态函数的地址。

Windows将使用这样的地址(伪编码程序集):

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

要调用仿函数,Windows代码必须像这样

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

...或者代替最后两个语句,如果运算符是虚拟的,则使用以下语句...

; ... 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]

这些都不可能,因为O / S不知道非静态C ++方法的调用约定,尤其包括:

  • 传递隐式'this'参数的方式
  • “非虚方法”类的地址
  • “虚拟方法”类的vtable条目

答案 2 :(得分:3)

  

GWLP_USERDATA已被使用

我不知道您的SubclassWindow函数是什么,但是CWnd::SubclassWindow说,“调用此函数时,窗口不得已经附加到MFC对象。”

  

我遇到了想要在win proc

中获得一些数据的情况

通常的(非MFC)实现方法是拥有一个全局/静态字典,其键/索引是子类窗口的HWND值,其数据是您要与该窗口关联的数据:该数据通常是您的C ++类的this指针。

使用你的静态回调函数对窗口过程进行子类化:静态回调函数然后,当它被调用时,使用它传递的HWND来查找静态字典中的数据。

答案 3 :(得分:3)

GWLP_USERDATA不是存储与窗口关联的数据的唯一方法,您也可以使用SetProp()

至少在x86上,你可以进行ATL样式的thunking(一小段asm代码,将你的类指针放在ecx中然后跳转到你的wndproc)你可以在我发布的答案中找到一些关于它的链接{{ 3}}

答案 4 :(得分:-1)

您仍然可以使用存储在GWLP_USERDATA ...

中的值
class MyWinProc { // Win Proc Functor
public:
MyWinProc(ExternalClass* obj, HWND window) :
  obj(obj), window(window) {
      oldUserData = GetWindowLongPtr(GWLP_USERDATA);
      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;
                }
      }
      LONG userDataToRestore = SetWindowLongPtr(GWLP_USERDATA, oldUserData);
      LRESULT lRet = CallWindowProc(oldWinProc, hWnd, uMsg, wParam, lParam);
      SetWindowLongPtr(GWLP_USERDATA, userDataToRestore);
  }

private:
ExternalClass* obj;
HWND  window;

LONG oldUserData;
WNDPROC oldWinProc;
};