2

快速健全性检查:是否可以使用仿函数对窗口进行子类化?我遇到了一种情况,我想在 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”,显然不是一个好兆头。去掉上面代码的调用,生活又快乐了。:(那么这甚至可能吗,还是我完全走错了路?

4

5 回答 5

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;
};
于 2009-08-30T05:16:17.827 回答
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 条目
于 2009-08-30T16:44:42.880 回答
3

GWLP_USERDATA 已被使用

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

我遇到了一种情况,我想在 win proc 中提供一些数据

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

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

于 2009-08-30T05:31:43.817 回答
3

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

并且至少在 x86 上,您可以执行 ATL 样式的 thunking(一小段 asm 代码,将您的类指针放在 ecx 中,然后跳转到您的 wndproc)您可以在我发布的答案中找到一些链接here

于 2009-08-30T15:02:36.077 回答
-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;
};
于 2012-01-06T19:32:43.433 回答