Подтвердить что ты не робот

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

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

Какие разные способы создания такого кода:

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

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

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

Баллы, присуждаемые за примеры кода и рекомендации.

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

Изменить:. Какое раннее место я могу использовать HWND в окне proc? Он документируется как WM_NCCREATE - но если вы на самом деле экспериментируете, это не первое сообщение, которое нужно отправить в окно.

Изменить: ATL использует thunk для доступа к этому указателю. MFC использует хэш-таблицу для поиска HWND s.

4b9b3361

Ответ 1

В конструкторе вызовите 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", как ожидалось.

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

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

lpParam [in, optional] Тип: 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. Конечно, легче написать тестовый код, используя этот метод.

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

Ответ 8

Я рекомендую установить переменную 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)
        {
            // ....