Лучший способ хранения этого указателя для использования в WndProc - PullRequest
25 голосов
/ 23 сентября 2008

Мне интересно узнать лучший / распространенный способ хранения указателя this для использования в WndProc. Я знаю несколько подходов, но у каждого, насколько я понимаю, есть свои недостатки. Мои вопросы:

Какие существуют способы создания кода такого типа:

CWindow::WndProc(UINT msg, WPARAM wParam, LPARAM)
{
  this->DoSomething();
}

Я могу думать о Thunks, HashMaps, Thread Local Storage и структуре пользовательских данных Window.

Каковы плюсы / минусы каждого из этих подходов?

Очки начисляются за примеры кода и рекомендации.

Это ради любопытства. После использования MFC мне просто стало интересно, как это работает, а потом я начал думать об ATL и т. Д.

Редактировать: Какое самое раннее место, где я могу правильно использовать HWND в процедуре окна? Это задокументировано как WM_NCCREATE - но если вы действительно экспериментируете, это , а не первое сообщение, которое будет отправлено окну.

Редактировать: ATL использует thunk для доступа к указателю this. MFC использует хеш-таблицу поиска HWND с.

Ответы [ 9 ]

12 голосов
/ 23 сентября 2008

В вашем конструкторе вызовите CreateWindowEx с "this" в качестве аргумента lpParam.

Затем на WM_NCCREATE наберите следующий код:

SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) ((CREATESTRUCT*)lParam)->lpCreateParams);
SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);

Затем в верхней части окна вы можете сделать следующее:

MyWindowClass *wndptr = (MyWindowClass*) GetWindowLongPtr(hwnd, GWL_USERDATA);

Что позволяет вам сделать это:

wndptr->DoSomething();

Конечно, вы можете использовать ту же технику для вызова чего-то похожего на вашу функцию выше:

wndptr->WndProc(msg, wparam, lparam);

... который затем может использовать свой указатель "this", как и ожидалось.

10 голосов
/ 23 сентября 2008

Хотя использование SetWindowLongPtr и GetWindowLongPtr для доступа к GWL_USERDATA может показаться хорошей идеей, я настоятельно рекомендую , а не , используя этот подход.

Это именно тот подход, который использовался редактором Zeus , и в последние годы он не причинял ничего, кроме боли.

Я думаю, что происходит, когда сообщения сторонних окон отправляются на Zeus , для которых также установлено значение GWL_USERDATA . Одним из приложений, в частности, был инструмент Microsoft, обеспечивающий альтернативный способ ввода азиатских символов в любом приложении Windows (то есть какая-то программная утилита клавиатуры).

Проблема в том, что Зевс всегда предполагает, что данные GWL_USERDATA были установлены им, и пытается использовать данные в качестве этого указателя , что затем приводит к авария.

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

7 голосов
/ 23 сентября 2008

Вы должны использовать GetWindowLongPtr() / SetWindowLongPtr() (или устаревший GetWindowLong() / SetWindowLong()). Они быстрые и делают именно то, что вы хотите сделать. Единственная сложность - выяснить, когда позвонить SetWindowLongPtr(). Это необходимо сделать при отправке первого сообщения окна, а именно WM_NCCREATE.
. См. эту статью для примера кода и более глубокого обсуждения.

Локальное хранилище потоков - плохая идея, поскольку в одном потоке может работать несколько окон.

Также будет работать хэш-карта, но вычисление хеш-функции для каждого сообщения окна (а LOT ) может стоить дорого.

Я не уверен, как вы собираетесь использовать громоотводы; как вы проходите вокруг Thunks?

5 голосов
/ 23 сентября 2008

Я использовал SetProp / GetProp для хранения указателя на данные в самом окне. Я не уверен, как он складывается с другими предметами, которые вы упомянули.

3 голосов
/ 12 сентября 2011

Вы можете использовать GetWindowLongPtr и SetWindowLongPtr; используйте GWLP_USERDATA, чтобы прикрепить указатель к окну. Однако, если вы пишете пользовательский элемент управления, я бы предложил использовать дополнительные байты окна для выполнения работы. При регистрации класса окна установите для WNDCLASS::cbWndExtra размер данных, подобных этим, wc.cbWndExtra = sizeof(Ctrl*);.

Вы можете получить и установить значение с помощью GetWindowLongPtr и SetWindowLongPtr с параметром nIndex, установленным на 0. Этот метод может сохранить GWLP_USERDATA для других целей.

Недостаток с GetProp и SetProp, будет сравнение строк для получения / установки свойства.

2 голосов
/ 24 сентября 2008

Что касается безопасности SetWindowLong () / GetWindowLong (), согласно Microsoft:

Функция SetWindowLong завершается ошибкой, если окно, указанное hWnd параметр не принадлежит одному и тому же обрабатывать как вызывающий поток.

К сожалению, до выпуска Обновления безопасности 12 октября 2004 года Windows не будет применять это правило , позволяя приложению устанавливать GWL_USERDATA любого другого приложения. Таким образом, приложения, работающие в непатентованных системах, уязвимы для атак через вызовы SetWindowLong ().

0 голосов
/ 18 ноября 2018

Подушка ATL является наиболее эффективной. thunk выполняется один раз и заменяет функцию обратного вызова для WINPROC на собственную функцию-член обработки сообщений классов. последующие сообщения передаются прямым вызовом функции-члена классов окнами. это не становится быстрее, чем это.

0 голосов
/ 03 октября 2015

Я рекомендую установить переменную thread_local непосредственно перед вызовом CreateWindow и прочитать ее в вашем WindowProc, чтобы узнать переменную this (я предполагаю, что вы контролируете WindowProc).

Таким образом, у вас будет ассоциация this / HWND в самом первом сообщении, отправленном вам в окно.

При других подходах, предложенных здесь, скорее всего, вы пропустите некоторые сообщения: отправленные до WM_CREATE / WM_NCCREATE / WM_GETMINMAXINFO.

class Window
{
    // ...
    static thread_local Window* _windowBeingCreated;
    static thread_local std::unordered_map<HWND, Window*> _hwndMap;
    // ...
    HWND _hwnd;
    // ...
    // all error checking omitted
    // ...
    void Create (HWND parentHWnd, UINT nID, HINSTANCE hinstance)
    {
        // ...
        _windowBeingCreated = this;
        ::CreateWindow (YourWndClassName, L"", WS_CHILD | WS_VISIBLE, x, y, w, h, parentHWnd, (HMENU) nID, hinstance, NULL);
    }

    static LRESULT CALLBACK Window::WindowProcStatic (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
    {
        Window* _this;
        if (_windowBeingCreated != nullptr)
        {
            _hwndMap[hwnd] = _windowBeingCreated;
            _windowBeingCreated->_hwnd = hwnd;
            _this = _windowBeingCreated;
            windowBeingCreated = NULL;
        }
        else
        {
            auto existing = _hwndMap.find (hwnd);
            _this = existing->second;
        }

        return _this->WindowProc (msg, wparam, lparam);
    }

    LRESULT Window::WindowProc (UINT msg, WPARAM wparam, LPARAM lparam)
    {
        switch (msg)
        {
            // ....
0 голосов
/ 30 мая 2014

В прошлом я использовал параметр lpParam CreateWindowEx:

lpParam [in, необязательно] Тип: LPVOID

Указатель на значение, которое будет передано в окно через CREATESTRUCT структура (член lpCreateParams), на которую указывает параметр lParam сообщение WM_CREATE. Это сообщение отправлено в созданное окно эта функция, прежде чем она возвращается. Если приложение вызывает CreateWindow чтобы создать окно клиента MDI, lpParam должен указать на Структура CLIENTCREATESTRUCT. Если окно клиента MDI вызывает CreateWindow для создания дочернего окна MDI, lpParam должен указывать на Структура MDICREATESTRUCT. lpParam может быть NULL, если нет дополнительных данных необходимо.

Хитрость здесь в том, чтобы иметь static std::map HWND для указателей экземпляра класса. Возможно, что std::map::find может быть более производительным, чем метод SetWindowLongPtr. Конечно, с помощью этого метода написать тестовый код проще.

Кстати, если вы используете диалоговое окно win32, вам нужно использовать функцию DialogBoxParam.

...