Подклассы окна с функтором (Win32) - PullRequest
2 голосов
/ 30 августа 2009

Быстрая проверка работоспособности: возможно ли разделить окно на подклассы с помощью функтора? Я сталкиваюсь с ситуацией, когда я хочу, чтобы в 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 ]

8 голосов
/ 30 августа 2009

Функция 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;
};
5 голосов
/ 30 августа 2009

Проблема с использованием функтора заключается в соглашении о вызовах: 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 виртуальных методов класса
3 голосов
/ 30 августа 2009

GWLP_USERDATA - не единственный способ хранения данных, связанных с окном, вы также можете использовать SetProp () .

И, по крайней мере, на x86 вы можете использовать thunking в стиле ATL (небольшой фрагмент кода asm, который помещает указатель вашего класса в ecx, а затем переходит на ваш wndproc). Некоторые ответы об этом вы можете найти в ответе, который я разместил здесь

3 голосов
/ 30 августа 2009

GWLP_USERDATA уже используется

Я не знаю, какова ваша функция SubclassWindow, но CWnd :: SubclassWindow говорит: «Окно не должно быть уже присоединено к объекту MFC, когда эта функция вызывается».

Я сталкиваюсь с ситуацией, когда я хочу, чтобы в win proc были доступны некоторые данные

Обычный (не MFC) способ реализации, который состоит в том, чтобы иметь глобальный / статический словарь, ключ / индекс которого является значением HWND для подклассированных окон, а данные - это данные, которые вы хотите связать с этим окном. : эти данные часто являются указателем this вашего класса C ++.

Вы подклассируете оконную процедуру своей статической функцией обратного вызова: ваша функция статического обратного вызова затем, когда она вызывается, использует HWND, который она передает, для поиска данных в статическом словаре.

0 голосов
/ 06 января 2012

Вы все еще можете использовать значение, сохраненное в 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;
};
...