[Назад] [Далее]

4.4. Ввод с клавиатуры

4.4.1. Средства DOS

Как и в случае вывода на экран, DOS предоставляет набор функций для чтения данных с клавиатуры, которые используют стандартное устройство ввода STDIN, так что можно использовать в качестве источника данных файл или стандартный вывод другой программы.


Функция DOS 0Ah — Считать строку символов из STDIN в буфер

Ввод: АН = 0Ah
DS:DX = адрес буфера
Вывод: Буфер содержит введенную строку

Для вызова этой функции надо подготовить буфер, первый байт которого содержит максимальное число символов для ввода (1 – 254), а содержимое, если оно задано, может использоваться как подсказка для ввода. При наборе строки обрабатываются клавиши Esc, F3, F5, BS, Ctrl-C/Ctrl-Break и т.д., как при наборе команд DOS (то есть Esc начинает ввод сначала, F3 восстанавливает подсказку для ввода, F5 запоминает текущую строку как подсказку, Backspace стирает предыдущий символ). После нажатия клавиши Enter строка (включая последний символ CR (0Dh)) записывается в буфер, начиная с третьего байта. Во второй байт записывается длина реально введенной строки без учета последнего CR.

Рассмотрим пример программы, выполняющей преобразование десятичного числа в шестнадцатеричное.

; dosinl.asm
; Переводит десятичное число в шестнадцатеричное
;
        .model    tiny
        .code
        .286                    ; для команды shr al,4
        org       100h          ; начало СОМ-файла
start:
        mov       dx,offset message1
        mov       ah,9
        int       21h           ; вывести приглашение ко вводу message1
        mov       dx,offset buffer
        mov       ah,0Ah
        int       21h           ; считать строку символов в буфер
        mov       dx,offset crlf
        mov       ah,9
        int       21h           ; перевод строки

; перевод числа в ASCII-формате из буфера в бинарное число в АХ
        xor       di,di         ; DI = 0 - номер байта в буфере
        xor       ах,ах         ; АХ = 0 - текущее значение результата
        mov       cl,blength
        xor       ch,ch
        xor       bx,bx
        mov       si,cx         ; SI - длина буфера
        mov       cl,10         ; CL = 10, множитель для MUL
asc2hex:
        mov       bl,byte ptr bcontents[di]
        sub       bl,'0'        ; цифра = код цифры - код символа "0",
        jb        asc_error     ; если код символа был меньше, чем код "0",
        cmp       bl,9          ; или больше, чем "9",
        ja        asc_error     ; выйти из программы с сообщением об ошибке,
        mul       cx            ; иначе: умножить текущий результат на 10,
        add       ax,bx         ; добавить к нему новую цифру,
        inc       di            ; увеличить счетчик
        cmp       di,si         ; если счетчик+1 меньше числа символов -
        jb        asc2hex       ; продолжить (счетчик считается от 0)

; вывод на экран строки message2
        push      ax            ; сохранить результат преобразования
        mov       ah,9
        mov       dx,offset message2
        int       21h
        pop       ax

; вывод на экран числа из регистра АХ
        push      ax
        xchg      ah,al         ; поместить в AL старший байт
        call      print_al      ; вывести его на экран
        pop       ax            ; восстановить в AL младший байт
        call      print_al      ; вывести его на экран

        ret                     ; завершение СОМ-файла

asc_error:
        mov       dx,offset err_msg
        mov       ah,9
        int       21h           ; вывести сообщение об ошибке
        ret                     ; и завершить программу

; Процедура print_al
; выводит на экран число в регистре AL
; в шестнадцатеричном формате,
; модифицирует значения регистров АХ и DX

print_al:
        mov       dh,al
        and       dh,0Fh        ; DH - младшие 4 бита
        shr       al,4          ; AL - старшие
        call      print_nibble  ; вывести старшую цифру
        mov       al,dh         ; теперь AL содержит младшие 4 бита
print_nibble:                   ; процедура вывода 4 бит (шестнадцатеричной цифры)
        cmp       al,10         ; три команды, переводящие цифру в AL
        sbb       al,69h        ; в соответствующий ASCII-код
        das                     ; (см. описание команды DAS)
        mov       dl,al         ; код символа в DL
        mov       ah,2          ; номер функции DOS в АН
        int       21h           ; вывод символа
        ret                     ; этот RET работает два раза - один раз
                                ; для возврата из процедуры print_nibble,
                                ; вызванной для старшей цифры,
                                ; и второй раз - для возврата из print_al

messagel db       "Десятичное число: $"
message2 db       "Шестнадцатеричное число: $"
err_msg  db       "Ошибка ввода"
crlf     db       0Dh,0Ah,'$'
buffer   db       6             ; максимальный размер буфера ввода
blength  db       ?             ; размер буфера после считывания
bcontents:                      ; содержимое буфера располагается за
                                ; концом СОМ-файла
        end       start

Функция 0Ah предоставляет удобный, но ограниченный способ ввода данных. Чаще всего используют функции посимвольного ввода, позволяющие контролировать отображение символов на экране, реакцию программы на функциональные и управляющие клавиши и т.д.


Функция DOS 01h — Считать символ из STDIN с эхом, ожиданием и проверкой на Ctrl-Break

Ввод: АН = 01h
Вывод: AL = ASCII-код символа или 0. Если AL = 0, второй вызов этой функции возвратит в AL расширенный ASCII-код символа

При чтении с помощью этой функции введенный символ автоматически немедленно отображается на экране (посылается в устройство STDOUT — так что его можно перенаправить в файл). При нажатии Ctrl-C или Ctrl-Break выполняется команда INT 23h. Если нажата клавиша, не соответствующая какому-нибудь символу (стрелки, функциональные клавиши Ins, Del и т.д.), то в AL возвращается 0 и функцию надо вызвать еще один раз, чтобы получить расширенный ASCII-код (см. приложение 1).

В трех следующих вариантах этой функции код символа возвращается в AL по такому же принципу.


Функция DOS 08h — Считать символ из STDIN без эха, с ожиданием и проверкой на Ctrl-Break

Ввод: АН = 08h
Вывод: AL = код символа

Функция DOS 07h — Считать символ из STDIN без эха, с ожиданием и без проверки на Ctrl-Break

Ввод: АН = 07h
Вывод: AL = код символа

Функция DOS 06h — Считать символ из STDIN без эха, без ожидания и без проверки на Ctrl-Break

Ввод: АН = 07h
DL = 0FFh
Вывод: ZF = 1, если не была нажата клавиша, и AL = 00
ZF = 0, если клавиша была нажата. В этом случае AL = код символа

Кроме перечисленных функций могут потребоваться и некоторые служебные функции DOS для работы с клавиатурой.


Функция DOS 0Bh — Проверить состояние клавиатуры

Ввод: АН = 0Bh
Вывод: AL = 0, если не была нажата клавиша
AL = 0FFh, если была нажата клавиша

Эту функцию удобно использовать перед функциями 01, 07 и 08, чтобы не ждать нажатия клавиши. Кроме того, вызов этой функции позволяет проверить, не считывая символ с клавиатуры, была ли нажата комбинация клавиш Ctrl-Break; если это произошло, выполнится прерывание 23h.


Функция DOS 0Ch — Очистить буфер и считать символ

Ввод: АН = 0Ch
AL = Номер функции DOS (01, 06, 07, 08, 0Ah)
Вывод: Зависит от вызванной функции

Функция 0Ch очищает буфер клавиатуры, так что следующая функция чтения символа будет ждать ввода с клавиатуры, а не использовать нажатый ранее и еще не обработанный символ. Например, именно эта функция используется для считывания ответа на вопрос «Уверен ли пользователь в том, что он хочет отформатировать диск?».


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

; dosin2.asm
; Изображает пентамино F, которое можно перемещать по экрану клавишами
; управления курсором и вращать клавишами X и Z. Выход из программы - Esc.
;
line_length = 3                   ; число символов в строке изображения
number_of_lines = 3               ; число строк

        .model    tiny
        .code
        org       100h            ; начало СОМ-файла
start:
        cld                       ; будут использоваться команды
                                  ; строковой обработки
        mov       ax,0B800h       ; адрес начала текстовой видеопамяти
        mov       es,ax           ; в ES
        mov       ax,0003h
        int       10h             ; текстовый режим 03 (80x25)
        mov       ah,02h          ; установить курсор
        mov       bh,0
        mov       dh,26           ; на строку 26, то есть за пределы экрана
        mov       dl,1
        int       10h             ; теперь курсора на экране нет
        call      update_screen   ; вывести изображение

; основной цикл опроса клавиатуры
main_loop:
        mov       ah,08h          ; считать символ с клавиатуры
        int       21h             ; без эха, с ожиданием, с проверкой на Ctrl-Break
        test      al,al           ; если AL = 0
        jz        eASCII_entered  ; введен символ расширенного ASCII
        cmp       al,1Bh          ; иначе: если введен символ 1Bh (Esc),
        je        key_ESC         ; выйти из программы,
        cmp       al,'Z'          ; если введен символ Z,
        je        key_Z           ; перейти на его обработчик
        cmp       al,'z'          ; то же для z
        je        key_Z
        cmp       al,'X'          ; если введен символ X,
        je        key_X           ; перейти на его обработчик
        cmp       al,'х'          ; то же для х
        je        key_X
        jmp short main_loop       ; считать следующую клавишу

eASCII_entered:                   ; был введен расширенный ASCII-символ
        int       21h             ; получить его код (повторный вызов функции)
        cmp       al,48h          ; стрелка вверх
        je        key_UP
        cmp       al,50h          ; стрелка вниз
        je        key_DOWN
        cmp       al,4Bh          ; стрелка влево
        je        key_LEFT
        cmp       al,4Dh          ; стрелка вправо
        je        key_RIGHT
        jmp short main_loop       ; считать следующую клавишу
;
; обработчики нажатий клавиш
;
key_ESC:                               ; Esc
        ret                            ; завершить СОМ-программу

key_UP:                                ; стрелка вверх
        cmp       byte ptr start_row,0 ; если изображение на верхнем
                                       ; краю экрана,
        jna       main_loop            ; считать следующую клавишу,
        dec       byte ptr start_row   ; иначе - уменьшить номер строки,
        call      update_screen        ; вывести новое изображение
        jmp       short main_loop      ; и считать следующую клавишу

key_DOWN:                              ; стрелка вниз
        cmp       byte ptr start_row,25-number_of_lines ; если
                                       ; изображение на нижнем краю экрана,
        jnb       main_loop            ; считать следующую клавишу,
        inc       byte ptr start_row   ; иначе - увеличить номер строки,
        call      update_screen        ; вывести новое изображение
        jmp       short main_loop      ; и считать следующую клавишу

key_LEFT:                              ; стрелка влево
        cmp       byte ptr start_col,0 ; если изображение на левом краю
                                       ; экрана,
        jna       main_loop            ; считать следующую клавишу,
        dec       byte ptr start_col   ; иначе - уменьшить номер столбца,
        call      update_screen        ; вывести новое изображение
        jmp       short main_loop      ; и считать следующую клавишу

key_RIGHT:                             ; стрелка вправо
        cmp       byte ptr start_col,80-line_length ; если
                                       ; изображение на правом краю экрана,
        jnb       main_loop            ; считать следующую клавишу,
        inc       byte ptr start_col   ; иначе - увеличить номер столбца,
        call      update_screen        ; вывести новое изображение
        jmp       short main_loop      ; и считать следующую клавишу

key_Z:                                 ; клавиша Z (вращение влево)
        mov       ax,current_screen    ; считать номер текущего изображения
                                       ; (значения 0, 1, 2, 3),
        dec       ax                   ; уменьшить его на 1,
        jns       key_Z_ok             ; если получился -1 (поменялся знак),
        mov       ах,3                 ; АХ = 3
key_Z_ok:
        mov       current_screen,ax    ; записать номер обратно,
        call      update_screen        ; вывести новое изображение
        jmp       main_loop            ; и считать следующую клавишу

key_X:                                 ; клавиша X (вращение вправо)
        mov       ax,current_screen    ; считать номер текущего изображения
                                       ; (значения 0, 1, 2, 3),
        inc       ax                   ; увеличить его на 1,
        cmp       ax,4                 ; если номер стал равен 4,
        jne       key_X_ok
        xor       ах,ах                ; АХ = 0
key_X_ok:
        mov       current_screen,ax    ; записать номер обратно,
        call      update_screen        ; вывести новое изображение
        jmp       main_loop            ; и считать следующую клавишу

; процедура update_screen
; очищает экран и выводит текущее изображение
; модифицирует значения регистров АХ, ВХ, СХ, DX, SI, DI
update_screen:
        mov       cx,25*80           ; число символов на экране
        mov       ax,0F20h;          ; символ 20h (пробел) с атрибутом 0Fh
                                     ; (белый на черном)
        xor       di,di              ; ES:DI = начало видеопамяти
        rep       stosw              ; очистить экран
        mov       bx,current_screen  ; номер текущего изображения в ВХ
        shl       bx,1               ; умножить на 2, так как screens - массив слов
        mov       si,screens[bx]     ; поместить в ВХ смещение начала
                                     ; текущего изображения из массива screens,
        mov       ax,start_row       ; вычислить адрес начала
        mul       row_length         ; изображения в видеопамяти
        add       ax,start_col       ; (строка * 80 + столбец) * 2
        shl       ax,1
        mov       di,ax              ; ES:DI - начало изображения в видеопамяти
        mov       ah,0Fh             ; используемый атрибут - белый на черном
        mov       dx,number_of_lines ; число строк в изображении
сору_lines:
        mov       cx,line_length     ; число символов в строке
copy_1: lodsb                        ; считать ASCII-код в AL,
        stosw                        ; записать его в видеопамять
                                     ; (AL - ASCII, АН - атрибут),
        loop       copy_1            ; вывести так все символы в строке,
        add        di,(80-line_length)*2 ; перевести DI на начало
                                     ; следующей строки экрана,
        dec        dx                ; если строки не закончились -
        jnz        copy_lines        ; вывести следующую
        ret                          ; конец процедуры update_screen

; изображение пентамино F
screen1 db         " XX"             ; выводимое изображение
        db         "XX "
        db         " X "

screen2 db         " X "             ; поворот на 90 градусов вправо
        db         "XXX"
        db         "  X"

screen3 db         " X "             ; поворот на 180 градусов
        db         " XX"
        db         "XX "

screen4 db         "X  "             ; поворот на 90 градусов влево
        db         "XXX"
        db         " X "
; массив, содержащий адреса всех вариантов изображения
screens         dw     screen1,screen2,screen3,screen4
current_screen  dw     0             ; текущий вариант изображения
start_row       dw     10            ; текущая верхняя строка изображения
start_col       dw     37            ; текущий левый столбец
row_length      db     80            ; длина строки экрана для команды MUL

       end         start

В этом примере для вывода на экран используется прямое копирование в видеопамять, так как вызов функции BIOS вывода строки (INT 10h, АН = 13h) прокручивает экран вверх на одну строку при выводе символа в нижнем правом углу экрана.


п»ї
"target=_blank><\/a>") //-->