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

发布于 2024-08-03 12:37:57 字数 1001 浏览 12 评论 0原文

快速健全性检查:是否可以使用函子对窗口进行子类化?我遇到了一种情况,我想在 win 进程中获得一些可用数据,但 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”,显然不是一个好兆头。删除对上面代码的调用,生活又恢复了快乐。 :( 那么这可能吗,还是我的做法完全错误?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

御弟哥哥 2024-08-10 12:37:57

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

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;
};
我们的影子 2024-08-10 12:37:57

使用函子的问题在于调用约定: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()

......或者如果运算符是 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]

这些都不可能,因为操作系统不知道非静态 C++ 方法的调用约定,特别是包括:

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

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
萌梦深 2024-08-10 12:37:57

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

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

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

青衫负雪 2024-08-10 12:37:57

GWLP_USERDATA 已被使用

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

我遇到了一种情况,我想在 win 过程中获得一些可用的数据

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

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

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.

习惯成性 2024-08-10 12:37:57

您仍然可以使用存储在 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;
};

You can still use the value stored in 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;
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文