Ассемблер для Windows

       

Данный раздел будет поевящен обработке команд мыши и клавиатуры



III

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

Прежде, однако, мы рассмотрим одну весьма необычную, но чрезвычайно полезную API-функцию. Эта функция wsprintfA. Я подчеркиваю, что это именно API-функция, которая предоставляется системой приложению. Эта функция является неким аналогом библиотечной Си-функции - sprintf. Первым параметром функции является указатель на буфер, куда помещается результат форматирования. Второй - указатель на форматную строку, например: "Числа: %lu, %lu". Далее идут указатели на параметры (либо сами параметры, если это числа, см. ниже), число которых определено только содержимым форматной строки. А теперь - самое главное. Поскольку количество параметров не определено, то стек придется освобождать нам. Пример использования этой функции будет дан ниже. Заметим также, что прототипом этой функции для библиотеки import32.lib (TASM32) будет не wsprintfA, a _wsprintfA (!). Наконец отметим, что если функция выполнена успешно, то в EAX будет возвращена длина скопированной строки.

В основе получения информации о клавиатуре и мыши в консольном режиме является функция ReadConsoleInput. Параметры этой функции:

  • 1-й, дескриптор входного буфера консоли.
  • 2-й, указатель на структуру (или массив структур), в которой содержится информация о событиях, происшедших с консолью. Ниже мы подробно рассмотрим эту структуру.
  • 3-й, количество получаемых информационных записей (структур).
  • 4-й, указатель на двойное слово, содержащее количество реально полученных записей.
  • А теперь подробно разберемся со структурой, в которой содержится информация о консольном событии. Прежде всего замечу, что в Си эта структура записывается с помощью типа данных union (о типах данных см. Гл. 6 данной части). На мой взгляд, частое использование этого слова притупляет понимание того, что же за этим стоит. И при описании этой структуры мы обойдемся без STRUCT и UNION. Замечу также, что в начале этого блока данных идет двойное слово, младшее слово которого определяет тип события. В зависимости от значения этого слова последующие байты (максимум 18) будут трактоваться так или иначе. Те, кто уже знаком с различными структурами, используемыми в Си и Макроассемблере, теперь должны понять, почему UNION здесь весьма подходит.




    Но вернемся к типу события. Всего системой зарезервировано пять типов событий:

    KEY_EVENT equ 1h ; клавиатурное событие

    MOUSE_EVENT equ 2h ; событие с мышью

    WINDOW_BUFFER_SIZE_EVENT equ 4h ; изменился размер окна



    MENU_EVENT equ 8h ; зарезервировано

    FOCUS_EVENT equ 10h; зарезервировано

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

    Событие KEY_EVENT

    СмещениеДлинаЗначение +44При нажатии клавиши значение поля больше нуля. +82Количество повторов при удержании клавиши. +102Виртуальный код клавиши. +122Скан-код клавиши. +142Для функции ReadConsoleInputA-младший байт равен ASCII-коду клавиши. Для функции ReadConsoleInputW слово содержит код клавиши в двухбайтной кодировке (Unicode). +164Содержится состояния управляющих клавиш. Может являться суммой следующих констант: RIGHT_ALT_PRESSED equ 1h

    LEFT_ALT_PRESSED equ 2h

    RIGHT_CTRL_PRESSED equ 4h

    LEFT_CTRL_PRESSED equ 8h

    SHIFT_PRESSED equ 10h

    NUMLOCK_ON equ 20h

    SCROLLLOCK_ON equ 40h

    CAPSLOCK_ON equ 80h

    ENHANCED_KEY equ 100h
    Смысл констант очевиден.

    Событие MOUSE_EVENT

    СмещениеДлинаЗначение +44Младшее слово - Х-координата курсора мыши,
    старшее слово - Y-координата мыши. +8 4Описывает состояние кнопок мыши. Первый бит - левая кнопка, второй бит - правая кнопка, третий бит - средняя кнопка. Бит установлен - кнопка нажата. +12 4Состояние управляющих клавиш. Аналогично предыдущей таблице. +164Может содержать следующие значения: MOUSE_MOV equ 1h; было движение мыши

    DOUBLE_CL equ 2h; был двойной щелчок

    Событие WINDOW_BUFFER_SIZE_EVENT

    По смещению +4 находится двойное слово, содержащее новый размер консольного окна. Младшее слово - это размер по X, старшее слово - размер по Y. Да, когда речь идет о консольном окне, все размеры и координаты даются в "символьных" единицах.

    Что касается последних двух событий, то там также значимым является двойное слово по смещению +4, Ниже на Рисунок 2.2.4 дана простая программа обработки консольных событий.



    .386P ; плоская модель .MODEL FLAT, stdcall

    ; константы STD_OUTPUT_HANDLE equ -11 STD_INPUT_HANDLE equ -10

    ; тип события KEY_EV equ 1h MOUSE_EV equ 2h

    ; константы - состояния клавиатуры RIGHT_ALT_PRESSED equ 1h LEFT_ALT_PRESSED equ 2h RIGHT_CTRL_PRESSED equ 4h LEFT_CTRL_PRESSED equ 8h SHIFT_PRESSED equ 10h NUMLOCK_ON equ 20h SCROLLLOCK_ON equ 40h CAPSLOCK_ON equ 80h ENHANCED_KEY equ 100h

    ; прототипы внешних процедур EXTERN wsprintfA:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN SetConsoleCursorPosition@8:NEAR EXTERN SetConsoleTitleA@4:NEAR EXTERN FreeConsole@0:NEAR EXTERN AllocConsole@0:NEAR EXTERN CharToOemA@8:NEAR EXTERN SetConsoleTextAttribute@8:NEAR EXTERN ReadConsoleInputA@16:NEAR EXTERN ExitProcess@4:NEAR

    ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------- ; структура для определения событий COOR STRUC Х WORD ? Y WORD ? COOR ENDS

    ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HANDL DWORD ? HANDL1 DWORD ? TITL DB "Обработка событий мыши",0 BUF DB 200 dup (?) LENS DWORD ? ; количество выведенных символов C0 DWORD ? FORM DB "Координаты: %u %u " CRD COOR <?> STR1 DB "Для выхода нажмите ESC",0 MOUS_KEY WORD 9 dup (?) _DATA ENDS

    ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; образовать консоль ; вначале освободить уже существующую CALL FreeConsole@0 CALL AllocConsole@0 ; получить HANDL1 ввода PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL1,EAX ; получить HANDL вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; задать заголовок окна консоли PUSH OFFSET TITL CALL SetConsoleTitleA@4 ;********************************** ; перекодировка строки PUSH OFFSET STR1 PUSH OFFSET STR1 CALL CharToOemA@8 ; длина строки PUSH OFFSET STR1 CALL LENSTR ; вывести строку PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET STR1 PUSH HANDL CALL WriteConsoleA@20 ; цикл ожиданий: движение мыши или двойной щелчок L00: ; координаты курсора MOV CRD.X,0 MOV CRD.Y,10 PUSH CRD PUSH HANDL CALL SetConsoleCursorPosition@8 ; прочитать одну запись о событии PUSH OFFSET C0 PUSH 1 PUSH OFFSET MOUS_KEY PUSH HANDL1 CALL ReadConsoleInputA@16 ; проверим, не с мышью ли что? CMP WORD PTR MOUS_KEY, MOUSE_EV JNE L001 ; здесь преобразуем координаты мыши в строку MOV AX, WORD PTR MOUS_KEY+6 ; Y-мышь ; копирование с обнулением старших битов MOVZX EAX,AX PUSH EAX MOV AX, WORD PTR MOUS_KEY+4 ; Х-мышь ; копирование с обнулением старших битов MOVZX EAX,AX PUSH EAX PUSH OFFSET FORM PUSH OFFSET BUF CALL wsprintfA ; восстановить стек ADD ESP,16 ; перекодировать строку для вывода PUSH OFFSET BUF PUSH OFFSET BUF CALL CharToOemA@8 ; длина строки PUSH OFFSET BUF CALL LENSTR ; вывести на экран координаты курсора PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET BUF PUSH HANDL CALL WriteConsoleA@20 JMP L00 ; к началу цикла L001: ; нет ли события от клавиатуры? CMP WORD PTR MOUS_KEY,KEY_EV JNE L00 ; есть, какое? CMP BYTE PTR MOUS_KEY+14,27 JNE L00 ;******************************** ; закрыть консоль CALL FreeConsole@0 PUSH 0 CALL ExitProcess@4 RET ; процедура определения длины строки ; строка - [EBP+08Н] ; длина в EBX LENSTR PROC ENTER 0,0 PUSH EAX CLD MOV EDI, DWORD PTR [EBP+08Н] MOV EBX, EDI MOV ECX, 100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI, EBX ; длина строки, включая 0 MOV EBX, EDI DEC EBX POP EAX LEAVE RET 4 LENSTR ENDP _TEXT ENDS END START



    Рисунок 2.2.4. Пример обработки событий от мыши и клавиатуры для консольного приложения.

    После того как вы познакомились с программой на Рисунок 2.2.4, давайте ее подробнее обсудим.

    Начнем с функции wsprintfA. Как я уже заметил, функция необычная.



    1. Она имеет переменное число параметров. Первые два параметра обязательны. Вначале идет указатель на буфер, куда будет скопирована результирующая строка. Вторым идет указатель на форматную строку. Форматная строка может содержать текст, а также формат выводимых параметров. Поля, содержащие информацию о параметре, начинаются с символа "%". Формат этих полей в точности соответствует формату полей, используемых в стандартных Си-функциях printf, sprintf и др. Исключением является отсутствие в формате для функции wsprintf вещественных чисел. Нет нужды излагать этот формат, заметим только, что каждое поле в форматной строке соответствует параметру (начиная с третьего). В нашем случае форматная строка была равна: "Координаты: %u %u". Это означало, что далее в стек будет отправлено два числовых параметра типа WORD. Конечно, в стек мы отправили два двойных слова, позаботившись лишь о том, чтобы старшие слова были обнулены. Для такой операции очень удобна команда микропроцессора MOVZX, которая копирует второй операнд в первый так, чтобы биты старшего слова были заполнены нулями. Если бы параметры были двойными словами, то вместо поля %u мы бы поставили %lu. В случае, если поле форматной строки определяет строку-параметр, например "%S", в стек следует отправлять указатель на строку (что естественно).29


    2. Поскольку функция "не знает", сколько параметров может быть в нее отправлено, разработчики не стали усложнять текст этой функции, и оставили нам проблему освобождения стека30. Это производится командой ADD ESP,N. Здесь N - это количество освобождаемых байтов.


    3. Обратимся теперь к функции ReadConsoleInputA. К уже сказанному о ней добавлю только, что если буфер событий пуст, то функция будет ждать, пока "что-то" не случится с консольным окном, и только тогда возвратит управление. Кроме того, мы можем указать, чтобы функция возвращала не одну, а несколько записей о происшедших с консолью событиях. В этом случае в буфер будет помещена не одна, а несколько информационных записей. Но мы на этом останавливаться не будем.

      По обыкновению отмечу, как откомпилировать данную программу в TASM32. Как обычно, удаляем все значки @N, указываем библиотеку import32.lib и наконец wsprintfA меняем на _wsprintfA.

      29 В этой связи не могу не отметить, что встречал в литературе по ассемблеру (!) утверждение, что все помещаемые в стек для этой функции параметры являются указателями. Как видим, вообще говоря, это не верно.

      30 Компилятор Си, естественно, делает это за нас.


      Содержание раздела