WinAPI WndProc молча терпит неудачу при использовании карт

Во-первых, позвольте мне сказать, что я новичок в использовании WinAPI и пытаюсь изучить основы. Тем не менее, я пытаюсь создать несколько объектов, чтобы сделать мою будущую работу с WinAPI намного проще. Одним из них является класс окна... класс. Другой, конечно же, класс Window.

Я пытаюсь упростить обработку сообщений, например myClassInst.addHandler(WM_PAINT, PaintFunction). Для этого я решил использовать карту, которая сопоставляет uints с указателями функций. Это кажется прекрасным.

Но сейчас сообщения не обрабатываются. После некоторой отладки я обнаружил, что всякий раз, когда я пытаюсь каким-либо образом использовать карту внутри обработчика WndProc моего класса, она просто молча терпит неудачу. Я не получаю ни ошибок компиляции, ни ошибок времени выполнения, ни сбоев; функция просто немедленно заканчивается там, пока не придет следующее сообщение. Я не могу понять, почему. Может быть, кто-нибудь может мне помочь?

Ниже приведен фрагмент моего кода, все в приватном разделе моего класса WinClass. Статический dummyProc message-passer — это единственный способ, который я нашел для разрешения специфичного для класса WndProc, так что это там. Это работает, так как первая часть моего WndProc myHandler действительно работает, о чем свидетельствует вывод отладочного сообщения. Он просто останавливается, как только я пытаюсь использовать карту, даже просто для получения ее размера.

*Примечание: WindowFunction — это определение типа указателя на функцию, принимающую параметр HWND.

  std::map<unsigned int, WindowFunction> messageMap;

  static LRESULT CALLBACK dummyProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    WinClass* context=(WinClass*)GetWindowLong(hwnd, GWL_USERDATA);
    return context->myHandler(hwnd, msg, wParam, lParam);
  }

  LRESULT CALLBACK myHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    std::cout<<"Message: "<<msg<<std::endl; // This gets output
    std::cout<<"  Contains "<<messageMap.size()<<" items"<<std::endl; // This does not
    std::cout<<"  And me!"<<std::endl; // Nothing works below the map usage.

    for (std::map<unsigned int, WindowFunction>::iterator it=messageMap.begin(); it!=messageMap.end(); ++it) {
      std::cout<<"  Have: "<<it->first<<std::endl;
      if (msg==it->first) {
        (it->second)(hwnd);
        break;
      }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
  }

ИЗМЕНИТЬ Итак, после дальнейшего тестирования я обнаружил дополнительную информацию. Значение контекста всегда равно 0, и хотя я не понимаю, как это вообще позволяет вызывать myHandler, это проблема. Итак, я изменил свой код, чтобы попытаться установить GWL_USERDATA из сообщения WM_CREATE... и обнаружил, что мой dummyProc никогда не получает сообщение WM_CREATE.

Ниже представлен новый dummyProc с выводом отладочной информации:

  static LRESULT CALLBACK dummyProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    std::cout<<"Create would be "<<WM_CREATE<<std::endl;
    std::cout<<"The message was "<<msg<<std::endl;
    if (msg==WM_CREATE) {
      SetLastError(0);
      SetWindowLong(hwnd, GWL_USERDATA, lParam);
      std::cout<<"ERROR: "<<GetLastError()<<std::endl;
    }
    long thelong=GetWindowLong(hwnd, GWL_USERDATA);
    std::cout<<"Long: "<<thelong<<std::endl;
    WinClass* context=(WinClass*)thelong;
    std::cout<<"Context: "<<context<<std::endl;
    return context->myHandler(hwnd, msg, wParam, lParam);
  }

Он говорит, что WM_CREATE имеет значение 1, но сообщения приходят, начиная с 36, затем со 129 и 130. Что случилось с моим сообщением WM_CREATE?

Вот код, который создает само окно:

рука = CreateWindowEx(WS_EX_CLIENTEDGE, myClass.myName, title, WS_OVERLAPPEDWINDOW, x, y, w, h, parent, NULL, inst, &myClass);

Большинство этих параметров являются аргументами, передаваемыми конструктору класса Window (где находится этот код). Параметр myClass является экземпляром WinClass. Все экземпляры WinClass устанавливают dummyProc в качестве своего обработчика WndProc. Тестирование показывает, что &myClass действительно является допустимым указателем, отличным от NULL, когда это вызывается.

Так что же мешает сообщению WM_CREATE?


person Daniel Burnett    schedule 02.03.2014    source источник
comment
Вы проверили, что context имеет правильное значение?   -  person Ben Voigt    schedule 03.03.2014
comment
Я знаю, что так и должно быть, потому что, как я уже сказал, функция действительно вызывается, и первая часть выводит просто отлично. Все в myHandler работает до тех пор, пока я не попытаюсь использовать карту.   -  person Daniel Burnett    schedule 03.03.2014
comment
Вызов функции не доказывает ничего подобного. Это неопределенное поведение, оно может работать наполовину. В частности, функция может быть найдена без правильного указателя this, потому что это не virtual. И верхняя половина функции (часть, которая работает) еще не использовала указатель this.   -  person Ben Voigt    schedule 03.03.2014
comment
WM_CREATE отправляется, но далеко не первым. В своем ответе я упомянул WM_NCCREATE, что обычно довольно рано, хотя все же не первое. Попробуйте прочитать весь мой ответ, а также сообщение в блоге Раймонда Чена, на которое я ссылаюсь.   -  person Ben Voigt    schedule 03.03.2014
comment
Что вы подразумеваете под тем, что все экземпляры WinClass устанавливают dummyProc в качестве своего обработчика WndProc. Разве вы не устанавливаете обработчик в RegisterClass?   -  person Ben Voigt    schedule 03.03.2014
comment
Итак, я узнал ниже ›_‹ . Но теперь, когда я обнаружил, что для контекста установлено значение NULL, я провел еще несколько тестов. Как вы можете видеть из приведенного выше редактирования, я обновил dummyProc таким образом, чтобы он теперь правильно обрабатывал это... за исключением того, что он никогда не получает сообщение WM_CREATE. Любая идея, почему это может быть, глядя на новый код?   -  person Daniel Burnett    schedule 03.03.2014
comment
Ну, вы по-прежнему падаете с каждым сообщением, которое идет до WM_CREATE... Может быть, вы хотите, чтобы if (!thelong) return DefWindowProc(hwnd, msg, wParam, lParam); получил обработку по умолчанию для всех этих ранних сообщений?   -  person Ben Voigt    schedule 03.03.2014
comment
Это то, что я имел в виду. У каждого WinClass есть член WNDCLASSEX, который он использует с RegisterClass. Но независимо от того, какие аргументы передаются конструктору WinClass, он всегда использует dummyProc в качестве своего обработчика в своем члене WNDCLASSEX при регистрации. Это то, что я имел в виду.   -  person Daniel Burnett    schedule 03.03.2014
comment
О, ... обычно не регистрируют отдельный класс для каждого экземпляра. Вот что меня смутило.   -  person Ben Voigt    schedule 03.03.2014
comment
Это для каждого экземпляра WinClass, а не для каждого окна :) В любом случае, ваше предложение сработало! Возврат к DefWindowProc, если пользователь long был 0, успешно получил правильное значение контекста и предотвратил смерть myHandler при использовании карты :) ОДНАКО... Теперь вся программа падает в цикле FOR итерации карты. Дальнейшее тестирование показывает, что он падает, как только итератор увеличивается. Создание итератора и настройка его на messageMap.begin() работает, но как только я пытаюсь сделать ++это или это++, все падает. Он делает это, даже когда я подтвердил, что это не конец И только один раз увеличивается. Хм?   -  person Daniel Burnett    schedule 03.03.2014
comment
Помните, что все это происходит внутри CreateWindow, который, как вы сказали, находится в вашем конструкторе. Конец вашего конструктора окон еще не достигнут. Это вообще помогает?   -  person Ben Voigt    schedule 03.03.2014
comment
Какой тип данных myClass внутри Window? &myClass делает двойной указатель? Позже вы будете обращаться с ним как с одним указателем. Предполагать, что это твоя проблема.   -  person Ben Voigt    schedule 03.03.2014


Ответы (2)


Как говорит @Ben Voigt, вы неправильно инициализируете свой GWLP_USERDATA, а также не обрабатываете случай, когда он еще не инициализирован.

Обратите внимание, что даже ваш пересмотренный код неверен. На WM_CREATE значение lParam не является вашим указателем данных пользователя. Это указатель на CREATESTRUCT, одним из членов которого является значение ваших пользовательских данных.

Попробуйте следующее, чтобы заменить функцию dummyProc:

static LRESULT CALLBACK dummyProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (uMsg == WM_NCCREATE)
        SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>( reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams ));

    WinClass* context=(WinClass*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if (!context) return DefWindowProc(hwnd, msg, wParam, lParam);
    return context->myHandler(hwnd, msg, wParam, lParam);
  }
person Jonathan Potter    schedule 02.03.2014
comment
Если я попытаюсь это сделать, я получаю ошибку компилятора (в строке SetWindowLongPtr) недопустимое преобразование из void* в LONG... Это работает, если я просто выполняю простое приведение к (LONG), но безопасно ли это? - person Daniel Burnett; 03.03.2014
comment
Извините, забыл актерский состав. - person Jonathan Potter; 03.03.2014
comment
Ой, только что отредактировала мой комментарий, когда вы разместили свой ›_‹ . Безопасно ли предположить, что приведение всегда будет работать и не даст сбой по какой-либо причине? Я мало что знаю о LPCREATESTRUCT. - person Daniel Burnett; 03.03.2014
comment
Вы должны использовать LONG_PTR, а не LONG для 64-битной совместимости. - person Jonathan Potter; 03.03.2014
comment
Хорошее место по этому вопросу. Я не понимаю, почему Даниэль считал, что просмотр связанной скретч-программы (которая делает это правильно) был необязательным. И я все еще предсказываю проблемы, связанные с использованием GWLP_USERDATA для хранения WinClass* вместо Window*; конечно, экземпляр окна понадобится в какой-то момент для правильной обработки. - person Ben Voigt; 03.03.2014
comment
@JonathanPotter: Что ж, его код заработал. И я пропустил часть CREATESTRUCT. Меня просто расстраивает, что он так и не послушал меня. - person Ben Voigt; 03.03.2014

Вы не показали создание окна или место хранения указателя в GWL_USERDATA. Однако независимо от того, как вы это сделаете, я обещаю вам, что невозможно настроить GWL_USERDATA до получения первого сообщения, потому что прибытие первого сообщения — это первый раз, когда вы на самом деле знаете какое HWND было назначено вашему новому окну.

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

Добавьте к этому тот факт, что GWL_USERDATA недостаточно велик для хранения указателя; вы должны использовать использование SetWindowLongPtr и GetWindowLongPtr с GWLP_USERDATA.

Обычно необработанная оконная процедура в первую очередь влияет на настройку GWLP_USERDATA. Взгляните на Раймонда Чена временную программу C++. .

Обратите внимание, что WM_NCCREATE обрабатывается внутри необработанной оконной процедуры, потому что еще нет сохраненного указателя для отправки (он получен из аргументов сообщения WM_NCCREATE, а не из окна). И это даже не первое сообщение (во многих случаях вы получите WM_GETMINMAXINFO первым), хотя это первое сообщение, в котором указатель может быть получен без таких уловок, как локальное хранилище потока.

person Ben Voigt    schedule 02.03.2014
comment
Создание окна просто передает указатель на соответствующий экземпляр WinClass в GWL_USERDATA. Я не устанавливаю его ДО прихода сообщения; dummyProc срабатывает только при поступлении сообщений. Я знаю, что проблема не в этом, потому что, как я уже сказал, функция вызывается, и первая часть выводит просто отлично. Все в myHandler работает до тех пор, пока я не попытаюсь использовать карту. Если контекст не был установлен должным образом, функция никогда не будет вызвана. - person Daniel Burnett; 03.03.2014
comment
@DanielBurnett: вы основываете весь свой анализ на том, что если контекст не был установлен должным образом, функция никогда не будет вызвана. что, к сожалению, совершенно неправильно. - person Ben Voigt; 03.03.2014
comment
Упс. Извините, если это прозвучало немного высокомерно. Как оказалось, вы правы; context просто устанавливается в NULL. Тогда я не понимаю, как myHandler вообще вызывается с указателем NULL... Во всяком случае, при дальнейшем тестировании я обнаружил, что, как ни странно, сообщение WM_CREATE не отправляется. Я обновлю исходный пост кодом создания окна, может быть, это поможет? - person Daniel Burnett; 03.03.2014
comment
@DanielBurnett: Вот как. Ваша строка context->myHandler(hwnd, msg, wParam, lParam); после компиляции почти такая же, как __WinClass_myHandler(context, hwnd, msg, wParam, lParam);. Компилятор точно знает во время компиляции, какую функцию вызывать, потому что она не является виртуальной, и с радостью вызывает функцию с неверным указателем context. Для виртуальных функций он ведет себя скорее как auto fn = context->__vtbl[__vslot_myHandler]; (*fn)(context, hwnd, msg, wParam, lParam);, и это приведет к сбою, если context недействителен, потому что он не может найти v-таблицу. - person Ben Voigt; 03.03.2014
comment
Я думаю, что проблема здесь в использовании CALLBACK в вашем нестатическом методе myHandler. Пожалуйста, удалите его. CALLBACK по сути является __stdcall. - person Maf; 03.03.2014
comment
@Maf: Это действительно не имеет никакого значения так или иначе. - person Ben Voigt; 03.03.2014