Говорила мне мама – сынок, проверяй результат каждой вызванной функции!
Nov 29, 2007 · CommentsОтладкаПрограммирование
На днях в очередной раз попробовал поставить Punto Switcher после того, как на глаза попалась заметка о выходе версии, совместимой с Vista. Надо сказать, что я это делаю периодически, - в смысле устанавливаю Punto Switcher, играюсь с ним какое-то время, а потом удаляю. Честно говоря, я и сам не очень понимаю, почему он у меня не приживается. СОвсем неплохо напсаная программа. Какая-то тотальная несправедливость с моей стороны. Хотя нет, в последний раз причина была в том, что он не поддерживает 64-х битные версии Windows.
Но это неважно. Причиной написания этого поста стало то, что вскоре после установки Punto Switcher я заметил, что Notepad2 начал повисать в бесконечном цикле. Отладчик сразу подтвердил догадку о причастности Punto Switcher. Стек зависшего потока выглядел вот так:
correct.dll - это имя DLL которую Punto Switcher внедряет в каждый процесс для перехвата клавиатурного ввода. Оставалось ответить на два вопроса: «Почему Punto Switcher подвешивает только Notepad2?»и «Что такого делает Notepad2, что ломает Punto Switcher?»
Покопавшись в коде, выяснилось, что correct.dll на каждой итерации цикла функцию GetMenuItemInfo, каждый раз увеличивая номер элемента меню на единицу. Т.е. похоже, что correct.dll опрашивала все элементы меню.
На каждой итерации счетчик цикла (он же номер элемента меню, он же ebx) сравнивался с переменной, которая, по идее, должна была содержать общее число элементов меню. На самом деле переменная содержала 0xffffffff (-1).
Что характерно, функция возвращающая число элементов меню GetMenuItemCount возвращает -1 в случае ошибки. Догадка быстро подтвердилась в отладчике. GetMenuItemCount завершалась с ошибкой “1401: Invalid menu handle.”
Проблема, однако, заключалась в том, что описатель меню был самый, что ни на есть, правильный, что подтверждал Spy++, а функция GetMenu, возвращающая описатель, всегда завершалась успешно.
На следующий день, я попытался разобраться, что же не так с GetMenu и GetMenuItemCount. Долго ли, коротко ли, но в один прекрасный момент я заметил, что GetMenuItemCount работает, если функция вызывается из обработчика WM_CREATE главного окна Notepad2, и возвращает ошибку, если вызвать её позже. Оказалось, что всему виной были мои шаловливые ручки. Ранее, я добавил код для работы с меню в обработчик WM_CREATE. Этот код использовал класс CMenu из WTL. Объект этого класса предполагает, что он владеет описателем меню и, соответственно, удаляет его в своём деструкторе. GetMenu, естественно, об этом ничего не знает и по-прежнему возвращает теперь уже некорректный описатель.
Мораль этой истории - проверяйте результат каждой вызванной функции. Так как даже чужие ошибки (Notepad2) могут выглядеть как ваши собственные (Punto Switcher). Это особенно верно в случае, если код должен работать в «агрессивной среде» - DLL, внедряемая в чужой процесс, API операционной системы и так далее.