Подкласс окна с функтором (Win32)

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


person Toji    schedule 30.08.2009    source источник
comment
Дубликат stackoverflow.com/questions/23083/ ?   -  person Anders    schedule 30.08.2009
comment
Я бы не стал обязательно называть это дубликатом, поскольку заданный вопрос был очень конкретным, можно ли использовать функтор в этой ситуации. В конце концов, ответ такой же, как и вопрос, который вы связали, но повторяющиеся ответы не создают дублирующийся вопрос. :) (но я ценю это!)   -  person Toji    schedule 30.08.2009


Ответы (5)


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

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

person Anders    schedule 30.08.2009
comment
Хм. И подумать только, сколько бы я ни делал в Win32, я никогда не слышал о SetProp! Это отличная идея, и я думаю, что я могу использовать ее! - person Toji; 30.08.2009

Функция CALLBACK должна быть статической функцией-членом или другой прямой функцией в стиле C. API Windows на самом деле ничего не знает об объектах 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;
};
person Michael Burr    schedule 30.08.2009
comment
Статический pContext подходит, если вы создаете подкласс только для одного экземпляра этого окна. - person ChrisW; 30.08.2009
comment
@ChrisW: Вы правы - класс должен быть одноэлементным, или должен быть более сложный способ получить контекст. Это был просто быстрый пример поверх представленного примера кода. - person Michael Burr; 30.08.2009
comment
Да, и хотя ваш пример кода технически верен (и я проголосую за него, чтобы новичкам было легче его найти), мне действительно нужен более надежный способ получения контекста, поскольку я могу создавать подклассы для нескольких окон. Казалось бы, моя ошибка заключалась в предположении, что функтор может заменить любой обратный вызов, что, очевидно, не так. Это действительно очень плохо, так как это открыло бы целый новый мир возможностей! - person Toji; 30.08.2009

Проблема с использованием функтора заключается в соглашении о вызовах: Windows ожидает, что адрес будет адресом статической функции, и будет использовать/вызывать этот адрес как таковой; тогда как «это», которое вы передаете, не является адресом статической функции.

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]

Ни то, ни другое невозможно, потому что операционная система не знает о соглашениях о вызовах для нестатических методов C++, особенно включая:

  • Способ передачи неявного параметра this
  • Адрес невиртуальных методов класса
  • Записи виртуальной таблицы виртуальных методов класса
person ChrisW    schedule 30.08.2009
comment
Спасибо за очень подробное описание! Я начал понимать, что это должно быть что-то вроде этого, после того, как я начал получать ответы, приятно иметь детали. Я хотел бы пометить два ответа как правильный, поскольку это отвечает на конкретный вопрос, который я задал, но другой, который я отметил, указывает на возможное решение, которое, скорее всего, будут искать искатели подобных проблем. Тем не менее, вы получаете мой голос, и я надеюсь, что еще несколько! Еще раз спасибо! - person Toji; 31.08.2009

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

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

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

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

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

person ChrisW    schedule 30.08.2009
comment
Извините за путаницу, но я должен указать, что я не использую MFC. SubclassWindow — это макрос, который я использую в файле windowsx.h, а не в функции MFC. Кроме того, за пределами MFC можно создавать подклассы окна несколько раз, просто необходимо соблюдать некоторую осторожность с цепочкой WinProc и порядком установки/удаления. - person Toji; 30.08.2009

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