Blog. Just Blog

Обработка сообщений от клавиатуры в DialogBox

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
При разработке софта я столкнулся с таким неприятным явлением, что диалоговые окна, созданные функциями типа DialogBoxParam, не обрабатывают сообщения от клавиатуры. К таким сообщениям относятся, например, WM_KEYDOWN, WM_CHAR и WM_SYSKEYDOWN. При этом, если создать диалоговое окно функцией типа CreateWindowEx, то к нему сообщения проходят нормально. Это странное поведение связано с тем, что клавиатурные сообщения передаются напрямую элементам управления, находящимся в диалоговом окне, но не передаются самому окну. Чтобы использовать горячие клавиши, можно регистрировать их через RegisterHotKey, а затем обрабатывать сообщение WM_HOTKEY, но это очень плохое решение. Во-первых, комбинация клавиш уже может быть зарегистрирована другой программой, а во-вторых, использовать глобальные горячие клавиши для нужд локального окна - дурной тон.

Есть более гибкое и универсальное решение, основанное на установке в своем процессе хука на сообщения (функция SetWindowsHookEx с параметром WH_GETMESSAGE), который будет дублировать все сообщения от клавиатуры на диалоговое окно. Перехватчик можно устанавливать как до открытия диалогового окна, так и при его инициализации, все зависит от поставленной задачи. Мне больше нравится второй вариант.
  1. ; Обработчик сообщений диалогового окна
  2.         cmp     [msg],WM_INITDIALOG
  3.         je      .wminitdialog
  4.         cmp     [msg],WM_CLOSE
  5.         je      .wmclose
  6.         ...
  7. .wminitdialog:
  8.         ...
  9.         ; Сохранить хэндл диалогового окна
  10.         mov     eax,[hwnddlg]
  11.         mov     [hwmain],eax
  12.  
  13.         ; Установить хук на обработку сообщений
  14.         invoke  GetCurrentThreadId
  15.         invoke  SetWindowsHookEx,WH_GETMESSAGE,GetMessageProc,NULL,eax
  16.         ; Сохранить хэндл хука
  17.         mov     [hook],eax
  18.         ...
  19. .wmclose:
  20.         ...
  21.         ; Снять хук с обработки сообщений
  22.         invoke  UnhookWindowsHookEx,[hook]
  23.         ...
Хук ставится при инициализации диалогового окна и снимается при его закрытии, весь код остается внутри процедуры обработки. На мой взгляд, так более правильно.

Осталось дописать процедуру обработки хука. У нас есть хэндл диалогового окна hwmain, который мы сохранили при инициализации, ему и будут ретранслироваться все поступающие сообщения от клавиатуры. Все прочие сообщения будут обрабатываться системой в обычном порядке.
  1. ;------------------------------------------------------------
  2. ; Ретранслятор сообщений диалоговому окну
  3. ;------------------------------------------------------------
  4. proc GetMessageProc nCode:DWORD,wparam:DWORD,lparam:DWORD
  5.         pusha
  6.         ; В lParam находится указатель на MSG
  7.         mov     eax,[lparam]
  8.         ; Получить сообщение
  9.         mov     ebx,[eax+MSG.message]
  10.         ; Это клавиатурное сообщение?
  11.         cmp     ebx,WM_KEYDOWN
  12.         je      @f
  13.         cmp     ebx,WM_CHAR
  14.         je      @f
  15.         cmp     ebx,WM_SYSKEYDOWN
  16.         je      @f
  17.         ; Нет, просто пропустить сообщение дальше по цепочке
  18.  
  19.         popa
  20.         invoke  CallNextHookEx,[hook],[nCode],[wparam],[lparam]
  21.         ret
  22. @@:
  23.         ; Ретранслировать сообщение главному окну
  24.         invoke  SendMessage,[hwmain],[eax+MSG.message],\
  25.                 [eax+MSG.wParam],[eax+MSG.lParam]
  26.  
  27.         popa
  28.         xor     eax,eax
  29.         ret
  30. endp
Все, теперь в диалоговом окне можно обрабатывать клавиатурные сообщения наряду с другими. По такому же принципу можно ретранслировать в диалоговое окно и другие необрабатываемые сообщения, если таковые вдруг появятся.

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

Примеры программ с исходными текстами (FASM)Примеры программ с исходными текстами (FASM)

Keyboard.Messages.Demo.zip (5,293 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 5489 | Комментариев: 11

Метки: Assembler
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (16.08.2017 в 09:07):
Немного поправил GetMessageProc, чтобы работало на всех системах.
user (17.01.2016 в 01:50):
Еще такой вопрос возник по этой переключалке клавиатуры и хукам.
Выяснилось, что работает она нормально со всеми программами,
КРОМЕ ПРОНРАММ, входящих в MSOFFICE.

Причём если хучить WM_GETMESSAGE, то HOOK.DLL в памяти WINWORD.EXE присутствует, но сообщения в HOOK.DLL.MsgHookProc не попадают.
А если хучить WM_KEYBOARD_LL, тогда наоборот - в HOOK.DLL.KeyEventProc приходят все сообщения о нажатых кнопках, включая и искомые комбинации для переключения раскоадок, но HOOK.DLL отсутствует в памяти WINWORD.EXE.
Соответственно, в WINWORDe никакого переключения раскладок не происходит.

Не мудрствуя, заказал хучение И WM_GETMESSAGE И WM_KEYBOARD_LL в одной и той же HOOK.DLL - тогда она успешно инжектируется в WINWORD.EXE и до процедуры HOOK.DLL.KeyEventProc доходят все нажатия кнопок в WINWORD'е (пишется лог-файл) - но снова-таки переключения раскладок не происходит..

Лайоуты переключать пытаюсь вызовом функции
LoadKeyboardLayout("00000419",KLF_ACTIVATE); (и т.д.)
Это работает абсолютно везде, кроме MSOFFICE (любой версии от 97 до 2007 года).

И тем не менее, PUNTOSWITCHER справляется с этой задачей.
Склоняюсь к мысли, что нужно лайоуты переключать как-то по-другому, чтобы работало и с MSOFFICE'ом.

Может, у тебя есть какие-нибудь соображения по этому поводу?
А то, конечно, придётся ковырять PUNTOSWITCHER..
user (22.12.2015 в 13:43):
[offtop]
Вообще, в порядке оффтопа, скажу, что программы, занимающиеся клавиатурой, да и вообще имеющие интерфейс для человека, должны быть очень эргономичны (вроде бы очевидная вещь..). Больше всего мне не нравятся программы, которые дрессируют юзера на выполнение каких-то ненужных и мудрёных действий.
Работу с клавиатурой в Windows при установленных трёх и более языках ввода иначе как дрессурой не назовёшь - сейчас, когда вроде уже всё нормально, иногда машинально пытаюсь переключить ENG<->RUS двумя нажатиями Ctrl+Shift, по устоявшейся многолетней привычке. Это посчитать, сколько было сделано ненужных дополнительных нажатий - выйдет внушительная цифра. А сколько было опечаток из-за того, что клавиатура всёже нормально не переключилась.. - тут в одном из моих недавних комментов была такая лажа - вместо "Ы" везде набрано "i", как в украинской раскладке. Учитывая, что в данном случае комментарии поредактировать нельзя - то эта лажа так и остаётся висеть.
[/offtop]
ManHunter (22.12.2015 в 09:51):
Мудрено как-то, но раз надо, значит надо.
user (22.12.2015 в 02:01):
) получилась нормальная переключалка для трёх раскладок клавиатуры винды, замена стандартному механизму Windows. Переключестся ENG<->RUS по Ctrl+Shift, при необходимости переключить на Украинский жмём Shift+Ctrl, и тогда снова используем Ctrl+Shift, но уже для переключения между ENG<->UKR.  Обратно на пару ENG<->RUS снова переключем по Shift+Ctrl и так по кругу)).
А то стандартно для переключения ENG<->RUS приходилось жать на комбинацию лишний раз (т.е. дважды), чтобы "перескочить" установленный лайоут дополнительного третьего языка (украинского).
Такой штуки сильно не хватало. Доволен.
Спасибо за идею с хуками. То, что надо.
old-dos.ru/dl.php?id=12947
user (20.12.2015 в 13:17):
Да, с глобальным хуком всё работает, спасибо.
ManHunter (17.12.2015 в 22:54):
Ставишь глобальный системный хук на функцию в своей dll и она сама инжектится во все процессы без лишних движений с твоей стороны.
user (17.12.2015 в 22:29):
.. в принципе, подумаю насчёт инжекта.
Заинжектировать DLL во все процессы не проблема - но тогда нужно по мере запуска новых процессов повторять эту процедуру для каждого вновь запущенного..
user (17.12.2015 в 12:05):
ЦитатаПунто это делает через инжект своей dll во все процессы.

Это не выход. Слишком громоздко.
Жаль.
ManHunter (17.12.2015 в 08:30):
Цитатаа нужно, чтобы работало для текущего процесса в системе

Пунто это делает через инжект своей dll во все процессы.
user (17.12.2015 в 00:09):
C помощью хука клавиатуры можно сделать неплохой кейлоггер. Тут рабочий вариант, проверял:
replace.org.ua/topic/4351/

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

---------------------------------
Тут возникла такая проблема - переключал программно раскладки клавиатуры в одной программе (year.exe, задавал давеча вопрос по поводу глючения копирования кириллицы в клипбоард, если помнишь) - в общем, там нормально всё решилось, только по ходу дела задумал сделать удобную переключалку раскладок клавиатуры, давно такую хотел - но упирается всё в то, что С помощью ??Layout??(..) можно работать только со своим процессом, а нужно, чтобы работало для текущего процесса в системе. В принципе, пунто-свитчер это ведь как-то делает?
Никаких соображений нет по поводу реализации этой вещи? Конечно, можно помучить пунто-свитчер, но мало ли..
Готовых решений не нашёл, хотя народ интересуется этой темой.
Вот в общих чертах задавали похожий вопрос:
sql.ru/forum/447968/perekluchenie-raskladki-klaviatury

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0045 сек.) / Память: 4.5 Mb
Наверх