Работа с консолью в программах на ассемблере


Бросая в воду камешки, смотри на круги, ими образуемые;
иначе бросание будет пустой забавой.
Козьма Прутков

На практике редко возникает необходимость разработки программы как «вещи в себе». В подавляющем большинстве случаев программа должна взаимодействовать с пользователем, получая от него данные посредством клавиатуры и выдавая результаты своей работы на экран. При знакомстве с новым языком программирования одним из первых вопросов, на которые ищет ответа программист, является выяснение средств этого языка для выполнения операций обмена с консолью (консоль — клавиатура и монитор). Что касается языка ассемблера, то собственных средств обмена с консолью у него нет. Чтобы выполнить подобную операцию, программа использует возможности самого компьютера (прерывания BIOS) и операционной системы, в среде которой эта программа работает. Каждый программист самостоятельно ищет решение проблемы обмена с консолью. Так как эта задача актуальна всегда, то есть необходимость на конкретных примерах показать порядок использования средств BIOS и ОС для обмена с консолью в программах на ассемблере. Примеры не очень сложны, и читатель легко сможет быстро встроить их в свои программы.

Функции BIOS для работы с консолью

В контексте нашего изложения ROM BIOS (Read Only Memory Basic Input Output System) представляет собой совокупность программ в энергонезависимой памяти компьютера, одной из задач которых является устранение специфики аппаратных компонент компьютера для функционирующего на нем программного обеспечения, включая операционную систему. Обслуживание клавиатуры и монитора выполняют программы BIOS, называемые драйверами. Структурно драйверы состоят из ряда подпрограмм, называемых функциями, каждая из которых выполняет определенные действия. Обращение к функциям BIOS производится аналогично обращению к функциям MS DOS. Для работы с клавиатурой и экраном BIOS содержит два программных прерывания — 16h и 10h, обращение к которым, исходя из вышесказанного, является обращением к драйверам этих устройств. Для вызова этих прерываний, как обычно, используется команда INT — int 16h или int 10h. Для выполнения определенной операции в регистре АН указывается номер функции. При необходимости в других регистрах может указываться дополнительная (параметрическая) информация. Ниже рассмотрим подробнее возможности BIOS для работы с консолью.

Функции BIOS для работы с клавиатурой

Прерывание 16 BIOS имеет функции для различных типов клавиатур: обычной — 84 клавиши и двух типов расширенной клавиатуры — 101\102 и 122-клавишной. Выяснить функциональные возможности клавиатуры позволяет функция 09п:

Вход: АН - 09h.
Выход: AL = битовое поле, установленные биты которого обозначают поддерживаемые функции: 7 - резерв; 6 — поддержка клавиатуры со 122 клавишами (и функций 20h-22h (int 16h)); 5 — поддержка расширенной клавиатуры со 101-102 клавишами (и функций 10h-12h (int 16h)); 4 — поддержка функции 0Ah (int 16h); 3 — поддержка функции 0З0бп (int 16h); 2 — поддержка функции 0305h (int 16h); 1 — поддержка функции 0304h (int 16h); 0 — поддержка функции 0З00п (int 16h).

Прежде чем вызывать эту функцию, необходимо удостовериться в том, что
она поддерживается данной версией BIOS. Сделать это можно, вызвав функцию
OcOh прерывания int 15h.

Вход: АН = COh получить конфигурацию.
Выход: CF = 1 — BIOS не поддерживает эту функцию; CF - 0 — в случае успеха: ES:BX — адрес конфигурационной таблицы в ROM-памяти; АН = состояние (ООп — успех; 8бп — функция не поддерживается).

Формат конфигурационной ROM-таблицы:

Смещение Размер Описание
00h 2 байта Число байтов в этой таблице
02h 1 байт Модель BIOS
03h 1 байт Подмодель BIOS
04h 1 байт Издание BIOS:
0 — 1-я редакция,
1 — 2-я редакция и т. д.
05h 1 байт 1-й байт свойств
06h 1 байт 2-й байт свойств
07h 1 байт 3-й байт свойств
08h 1 байт 4-й байт свойств
09h 1 байт 5-й байт свойств

Если в результате этого вызова бит б второго байта свойств установлен, то BIOS поддерживает функцию 09п прерывания int 16h, с помощью которой определяются функциональные возможности клавиатуры.

Вход: АН = 10h, 20h чтение символа с ожиданием (для 101-102- и 122-клавиш-ных клавиатур соответственно).
Выход: для обычных клавиш (АН = скан-код BIOS; AL = символ ASCII); для клавиш и комбинаций с расширенным кодом (АН = расширенный ASCII-код; AL = 0); для дополнительных клавиш (АН - расширенный ASCII-код; AL = 0Eh).
Для ввода строки символов данные функции необходимо использовать в цикле. На примере показанной ниже программы, используя отладчик, можно исследовать содержимое АХ при нажатии различных клавиш и их комбинаций.

;prg05_02.asm - программа на ассемблере для ввода строки ;с использований функции ввода символа 10h
.data
string db 5 dup (0) len_string =$-string adr_stringdd string .code
mov cx,len_string
les di.adr_string ml: mov ah.O10h
int 16h
stosb
loop ml

Программа вводит 5 символов и сохраняет их в строке str.

Проверка наличия символа (01h, 11h, 21h int 16h)

Вход: АН = Olh проверка наличия символа (для 84-клавишной клавиатуры).
Выход: если ZF=O, то регистры АН и AL содержат: для обычных клавиш (АН = скан-код BIOS; AL = символ ASCII); для клавиш и комбинаций с расширенным ASCII-кодом (АН = расширенный ASCII-код; AL = 0); если ZF=1, то буфер пуст.

Функция 01h получает информацию о символе, не считывая его из буфера клавиатуры. Исключение составляют нажатия дополнительных клавиш на расширенных клавиатурах, не совместимых с 83\84-клавишными клавиатурами. В процессе проверки функцией Olh они удаляются из буфера. Поэтому при работе с расширенными клавиатурами необходимо использовать функции 11h и 21h.

Вход: АН = llh, 21h проверка наличия символа (для 101-102- и 122-клавишных клавиатур соответственно).
Выход: если ZF=O, то регистры АН и AL содержат: для обычных клавиш (АН = BIOS скан-код; AL - символ ASCII); для клавиш и комбинаций с расширенным кодом (АН = расширенный ASCII-код; AL = 0); для дополнительных клавиш (АН = расширенный ASCII-код; AL = 0eh); если ZF=0, то буфер пуст. В большинстве случаев работу с результатами выполнения данной функции логично начинать с анализа флага ZF (командами JZ или JNZ). Что же касается содержимого регистра АХ, то оно аналогично содержим00h int 16h :пересылаем его:
stosb
jmpml

Получение состояния флагов клавиатуры (02h, 12h, 22h int 16h)

BIOS предоставляет функцию 02h для получения состояния световых индикаторов клавиатуры и некоторых управляющих клавиш.
Вход: АН = 02h получить состояние флагов клавиатуры (для 84-клавишной клавиатуры).
Выход: AL = битовое поле, установленные биты которого соответствуют состоянию следующих флагов: 7 — режим вставки активен; 6 — индикатор CapsLock включен; 5 — индикатор NumLock включен; 4 — индикатор ScrollLock включен; 3 — нажата клавиша Alt (любая клавиша Alt на 101-102-клавишной клавиатуре); 2 — нажата клавиша Ctrl (любая клавиша Ctrl на 101-102-клавишной клавиатуре); 1 — нажата левая клавиша Shift; 0 — нажата правая клавиша Shift.
Поддержка расширенных клавиатур осуществляется функциями 12h и 22h BIOS.
Вход: АН = 12h, 22h получить состояние флагов клавиатуры (для 101-102- и 122-клавишных клавиатур).
Выход: AL = первое битовое поле, установленные биты которого соответствуют состоянию флагов, возвращаемых в регистре AL функцией 02п; АН = второе битовое поле, установленные биты которого соответствуют следующему состоянию флагов: 7 — нажата клавиша SysReq (SysRq); 6 - нажата клавиша CapsLock; 5 — нажата клавиша NumLock; 4 — нажата клавиша Scrolllock; 3 — нажата правая клавиша Alt; 2 — нажата правая клавиша Ctrl; 1 — нажата левая клавиша Alt; 0 — нажата левая клавиша Ctrl.
Кроме этого, состояние данных флагов можно прочитать из оперативной памяти по адресам: 0040h:0017h (AL) и O040h:0010h (АН).

Запись символа в буфер клавиатуры (05h int 16h)

Вход: АН = 05h запись символа в буфер клавиатуры: СН = скан-код; CL = символ ASCII.
Выход: AL = состояние: 00h — успешная запись; Olh — ошибка (буфер клавиатуры заполнен).
С помощью этой функции можно осуществлять подыгрыш для программ, которые ожидают ввода с клавиатуры. Сам буфер клавиатуры организован по принципу кольца, имеет размер 16 байт и занимает в памяти диапазон адресов 0040h:001Eh...0040h:003Dh. В ячейке 0040h:001Ah хранится адрес начала буфера, а в ячейке 0040h: OOlCh — адрес конца. Если содержимое этих ячеек равно, то буфер пуст. Одному символу в буфере соответствует слово, в котором первый байт — скан-код клавиши, а второй — символ ASCII. Исследовать данную функцию можно с использованием операции сцепления (|) MS DOS. Для этого оформим фрагмент кода для определения наличия символа в буфере и его ввода в виде отдельной программы.

В командную строку MS DOS необходимо ввести строку: prog_1.exe | prog_2.exe >p.txt
В результате всех этих действий будет создан файл p.txt, который и будет содержать строку str из файла prog_1.asm.

Функции BIOS для работы с экраном

Работа с экраном средствами BIOS производится с помощью набора функций прерывания 10h. С помощью этих функций поддерживаются текстовый и графический режимы работы монитора. В данном разделе будут рассмотрены некоторые функции вывода текста в текстовом режиме.

Установка видеорежима (00h int 10h)

Любой дисплейный адаптер поддерживает несколько текстовых и графических режимов. Переключение между эт000h режимами производится с помощью функции 00h int 10h.
Вход: АН = 00h установить видеорежим: AL - номер видеорежима (если бит 7 регистра AL = 0, то экран очищается, в обратном случае (AL. 7=1) содержимое экрана не изменяется).
Номеров видеорежимов много, нумерация режимов с высоким разрешением (SVGA) зависит от производителя видеоадаптера. Мы не будет приводить никаких сведений по этому поводу, при необходимости информацию о нумерации видеорежимов можно получить из соответствующих источников.

Установка позиции курсора (02h int 10h)

Функция 02h позволяет изменить позицию курсора и сделать ее начальной для последующего вывода. Заметим, что среди функций MS DOS нет подобной функции и функцию 02h int 10h BIOS можно использовать в комбинации с функциями MS DOS для организации форматированного вывода на экран. Вход: АН = 02h — установить позицию курсора: ВН = номер видеостраницы (зависит от используемого видеорежима); DH = строка (00h — верх); DL = ко-" лонка (00h — левая).

Получение позиции курсора (03h int 10h)

Функция 03h позволяет получить текущую позицию курсора. Аналогично сказанному выше, среди функций MS-DOS нет подобной функции и функцию 03h > int 10h BIOS также можно использовать в комбинации с функциями MS-DOS. Вход: АН = 03h — получить позицию курсора; ВН - номер видеостраницы (зависит от используемого видеорежима).
Выход: DH = строка текущей позиции курсора (00h — верх); DL - колонка текущей позиции (00h — левая); СН = номер начальной строки курсора; CL = номер последней строки курсора.

Запись символа и его атрибута в видеопамять (09h int 10h)

Функция 09h предназначена для записи ASCII-кода символа и его атрибута непосредственно в видеопамять, причем сделать это можно с количеством повторений, заданных в регистре СХ.
Вход: АН = 09h — запись символа и его атрибута в текущую позицию курсора: ВН = номер видеостраницы; AL = ASCII-код символа; BL = байт-атрибут; СХ = число повторений.
Для вывода одного символа содержимое регистра СХ должно быть равно 1. В текстовом режиме для СХ>1 вывод осуществляется до конца текущей строки, после чего переходит на другую строку.
Кодировка байта-атрибута в этой и других функциях производится в соответствии со следующими таблицами.

Номера битов
Значение
7 Мигающий символ
6..4 Цвет фона
3 Символ яркого цвета
2..0 Символ цвета

 

Биты Цвет Яркий цвет
000b
Черный
Темно-серый
001b
Синий
Светло-синий
010b
Зеленый
Светло-зеленый
011b
Голубой
Светло-голубой
100b
Красный
Светло-красный
101b
Пурпурный
Светло-пурпурный
110b
Коричневый
Желтый
111b
Светло-серый
Белый

Чтение символа и его атрибута из видеопамяти (08h int 10h)

В памяти видеоадаптера каждый символ представлен двумя байтами, содержащими ASCII-код символа и его байт-атрибут. Функция 08h BIOS позволяет прочитать код символа и его атрибут непосредственно из видеопамяти.
Вход: АН = 08h — чтение символа и его атрибута в текущей позиции курсора;
ВН = номер видеостраницы. Выход: AL = ASCII-код символа; АН = байт-атрибут.
Ниже приведена программа, которая устанавливает курсор в заданную позицию.

:prg05_04.asm. устанавливающая курсор в заданную позицию.
.code main:
xorbh.bh
mov dh.10
movdl.10
movah.02h
int 10h установили позицию курсора (10.10) записываем символ и атрибут в видеопамять
moval. "a"
mov bl,10001100b :атрибут - ярко-красный мигающий
movcx.5 ;повторить 5 раз
movah.09h
int 10h :прочитаем символ из текущей позиции видеопамяти:
mov ah,08h
int 10h : выясним текущую позицию курсора
хог bh.bh
mov ап.ОЗn
kint 10h установили позицию курсора (10.10)
:все результаты смотрим в отладчике

Важно отметить, что текущая позиция курсора после выполнения функций 08п и 09п осталась неизменной. Отсюда следует важный вывод о том, что при использовании этих функций необходимо также заботиться и о движении курсора функцией 02h. BIOS предоставляет функцию 0Eh, которая выводит символ в режиме телетайпа, предполагающем автоматическую корректировку текущей позиции курсора после вывода символа.

Запись символа в видеопамять (0Ah int 10h)

Функция 0Ah предназначена для записи ASCII-кода символа с текущим значением атрибута в данной позиции непосредственно в видеопамять, причем сделать это можно с количеством повторений, заданных в регистре СХ.
Вход: АН = 0Ah — запись символа в текущую позицию курсора; ВН = номер видеостраницы; AL = ASCII-код символа; СХ = число повторений.
Аналогично функции 09h текущая позиция курсора не изменяется.

Запись символа в режиме телетайпа (0Eh int 10h)

Функция 0Eh выводит символ в текущую позицию курсора с автоматическим ее смещением (в отличие от функций 09h и 0Ah).
Вход: АН = 0Eh — запись символа в текущую позицию курсора; ВН = номер видеостраницы; AL ¦¦ ASCII-код символа; СХ = число повторений.
Запись символа в последнюю позицию строки автоматически переводит кур-Ь cop в первую позицию следующей строки.

Вывод строки (13h int 10h)

Эта функция появилась в BIOS компьютеров архитектуры AT.
Вход: АН = 13h вывод строки (AT); AL = режим записи: бит 0 — после вывода курсор в конец строки; бит 1 — каждый символ в строке представлен двумя байтами: байтом с ASCII-кодом и байтом-атрибутом; бит 2..7 — резерв; ВН = номер видеостраницы; BL = байт атрибут, если строка содержит только символы (AL. 1=0); СХ = число символов в строке; DH, DL = строка и столбец начала вывода строки; ES: ВР — адрес в памяти начала строки.

Обратите внимание, что содержимое строки для вывода может быть двух типов: с байтом-атрибутом, сопровождающим каждый символ строки, и без байта-атрибута. В последнем случае строка состоит из одних кодов символов с единым значением байта-атрибута, указываемым в регистре BL.
Как видно из рассуждения выше, многие функции BIOS работают непосредственно с видеопамятью. Из-за того что для видеопамяти отводится определенный диапазон адресов (для текстового режима — это 0b800h:0000h), доступ к ней можно производить обычными командами работы с памятью микропроцессора, в том числе и цепочечными.

Перемещение в окне вверх (06h int 10h)

Функция 06h позволяет определить на экране окно, в котрром возможно прокрутить определенное количество строк вверх. При такой прокрутке верхние строки исчезают и снизу добавляются пустые строки.
Вход: АН = 06h — перемещение строк в окне вверх; AL = число строк для заполнения снизу; ВН = атрибут символов (цвет) в строке для заполнения; СН и CL = строка и столбец верхнего левого угла окна; DH и DL = строка и столбец нижнего правого угла окна.
Строки для заполнения снизу имеют цвет, определенный в ВН. Если указать AL=0, то окно очистится и заполнится строками с цветом, заданным байтом-атрибутом в ВН.
Ниже приведена программа вывода нескольких строк на экран, после чего она определяет окно на экране и прокручивает его на несколько строк вверх.

:prg05_05.asm - программа для работы с окном на экране.
.data
String db "dfsh3453637869uioraepBBanB"
Ien_str1ng »$-string
adr_stringdd string
. code
..........
movcx,25 ml: mov al ,1 :после вывода - курсор в конец строки
xorbh.bh :номер видеостраницы
movbl.7 : атрибут push ex
mov cx,len_string :длина выводимой строки
les bp.adr_string :адрес строки в пару ES:BP
mov ah,l3h
int l0h
incdh ;строка начала вывода
incdl : столбец начала вывода pop ex
loop ml ¦.определяем и прокручиваем окно вверх
mov al.4 :4 строки
mov bh. 0
mov ch, 5
mov cl .5 . mov dh. 10
mov dl.30
mov ah.06h
int 10h

Заметьте, что функция 06h достаточно гибко работает с курсором.
Перемещение в окне вниз (07h int 10h)
Функция 07h позволяет определить на экране окно, в котором возможно прокрутить определенное количество строк вниз. При такой прокрутке нижние строки исчезают и сверху добавляются пустые строки.
Вход: АН = 07h — перемещение строк в окне вниз; AL = число строк для заполнения сверху; ВН = атрибут символов (цвет) в строке для заполнения; СН и CL - строка и столбец верхнего левого угла окна; DH и DL = строка и столбец нижнего правого угла окна.
Строки для заполнения сверху имеют цвет, определенный в ВН. Если указать А1_=0, то окно очистится и заполнится строками с цветом, заданным в ВН. Структура байта атрибута аналогична описанной выше.

Функции MS DOS для работы с консолью

Ценность программы прямо пропорциональна
весу ее «выдачи».
Прикладная Мерфология

Функции MS DOS для работы с консолью сосредоточены в обработчике прерывания int 21h. Они представляют собой набор средств работы с консолью, занимающий промежуточное положение между программами пользователя и средствами BIOS. Для достижения большей эффективности некоторые из функций BIOS можно комбинировать с функциями MS DOS. Как пример такого полезного взаимодействия можно привести использование возможностей BIOS по работе с курсором. Как будет видно из приведенного ниже материала, среди функций MS DOS подобные средства отсутствуют. При выполнении конкретных практических заданий можно найти и другие полезные примеры взаимодействия.

Функции MS DOS для ввода данных с клавиатуры

Для ввода данных с клавиатуры можно использовать два вида функций: универсальную функцию 3fh (ввод из файла) и группу специализированных функций MS DOS ввода с клавиатуры.
Подробно использование функции 3fh для ввода данных рассматривается в главе 7, а здесь сосредоточимся на второй группе функций, в которую входит семь функций, отличающихся друг от друга следующими характеристиками:

  • ожиданием ввода при отсутствии символа в буфере клавиатуры или только проверкой буфера на наличие символов для ввода;
  • количеством вводимых символов;
    наличием эха при вводе, то есть дублированием вводимого с клавиатуры символа на экране;
  • восприимчивостью к сочетанию клавиш Ctrl+C (код 03h).

Чтение с эхом символа с клавиатуры (10h int 21h)

Функция 01h позволяет ввести один символ с клавиатуры. Если символа нет, то функция ожидает его ввода. Вводимый символ отображается на экране (эхо).
Вход: АН = 01h — чтение символа с эхом. "
Выход: AL = ASCII-код символа или 0.
На выходе функция помещает в регистр AL ASCII-код символа или 0. Наличие нуля в регистре AL говорит о том, что в буфере клавиатуры находится расширенный ASCII-код и необходимо повторить вызов функции с тем, чтобы прочитать его второй байт. Также функция 01h проверяет наличие в буфере символов нажатия комбинации Ctrl+C (Ctrl+Break), при обнаружении которых производится вызов прерывания int 23h.
Для ввода нескольких символов данную функцию необходимо использовать в цикле.

:prg05_06.asm - программа ввода нескольких символов функцией 01h 21h
;.........
.data
string db 5 dup (0)
len_string =$-string
adr_string dd string
.code
......movcx.len_sthng
lesdi.adr_string ml: mov ah.01h
int 21h
cmpal.0 расширенный код???
jnem2
обрабатываем расширенный код
jmp m3
ni2: stosb .формируем строку символов
mЗ: loop ml

Проверяя работу программы, вместо ввода очередного символа введите комбинацию Ctrl+C и посмотрите реакцию программы.

Прямой ввод с эхом символа с клавиатуры (06h int 21h)

Функция 06h также позволяет ввести один символ с клавиатуры. Но в отличие от функции 01h она не ожидает ввода при отсутствии символа в буфере. Вводимый символ отображается на экране (эхо).
Вход: АН = 06h — чтение символа с эхом без ожидания; DL = 0ffn — признак того, что функция 06h используется для ввода; если DL <> 0ffn, то функция используется для вывода символа (см. ниже).
Выход: если ZF=O, то AL = ASCII-код символа; если ZF-1, то символа в буфере нет. Результаты работы этой функции необходимо оценивать прежде всего по значению флага ZF. Если ZF=O, то функция поместила в регистр AL ASCII-код символа или 0. Наличие нуля в регистре AL говорит о том, что в буфере клавиатуры находится расширенный ASCII-код и необходимо повторить вызов функции с тем, чтобы прочитать его второй байт. Функция 06h не проверяет наличие в буфере символов нажатия комбинации Ctrl+C (Ctrl+Break).

Чтение без эха символа с клавиатуры (07h int 21h)

Функция 07h аналогична функции 01h, за исключением того, что вводит символ с клавиатуры без ожидания его ввода, без эха и без проверки нажатия комбинации Ctrl+C (Ctrl+Break). Вход: АН = 07h — чтение символа без эха. Выход: AL = ASCII-код символа или 0 (см. описание функции 01h int 21h).
Наличие нуля в регистре AL говорит о том, что в буфере клавиатуры находится расширенный ASCII-код и необходимо повторить вызов функции с тем, чтобы прочитать его второй байт.

Чтение без эха символа с клавиатуры (08h int 21h)

Функция 08h аналогична функции 01h, за исключением того, что вводит символ
с клавиатуры без отображения его на экране (без эха).
Вход: АН = 08h — чтение символа без эха.
Выход: AL = ASCII-код символа или 0 (см. описание функции 01h int 21h).
Наличие нуля в регистре AL говорит о том, что в буфере клавиатуры находится расширенный ASCII-код и необходимо повторить вызов функции с тем, чтобы прочитать его второй байт. Функция производит проверку нажатия комбинации Ctrl+C (Ctrl+Break), при наличии которого вызывается прерывание int 23h.

Ввод строки символов с клавиатуры (0ah int 21h)

Функция 0ah вводит строку символов в буфер памяти специального формата. Если символов в буфере клавиатуры нет, то функция ожидает их ввода. Конец ввода — нажатие клавиши Enter (0dh). Формат буфера:

  • первый байт буфера содержит количество символов для ввода с учетом символа 0dh, завершающего процесс ввода;
  • второй байт содержит реальное число введенных символов, но уже без учета завершающего символа 0dh;
  • начиная с третьего байта содержится строка введенных символов с завершающим символом 0dh, максимальная длина строки — 254 символа.

Вход: АН = 0ah — ввод строки в буфер (до 254 символов); DS:DX — адрес буфера, первый байт которого должен содержать количество символов для ввода.
Выход: введенная строка начиная с третьего байта буфера по адресу в DS:DX, длина строки во втором байте буфера.
Перед вызовом функции 0ah в первый байт буфера необходимо поместить значение максимальной длины строки. Если первый байт равен нулю, то вызов функции игнорируется и программа продолжает выполнение без ожидания ввода строки. Функция производит проверку нажатия комбинации Ctrl+C (Ctrl+Break), при наличии которого вызывается прерывание int 23h. Вводимая строка отображается на экране. Буфер ввода для данной функции лучше оформлять в виде структуры.

;prg05_07.asm - программа ввода строки функцией OAh int 21h
buf_Oahstruc
len_bufdb 11 :длина buf_0ah
len_in db 0 действительная длина введенного слова (без учета Odh)
buf_in db 11 dup (20h) ;буфер для ввода (с учетом Odh)
ends
.data
buf buf_0ah<>
adr_bufdd buf
.code
..........
:вводим 10 символов с клавиатуры
Ids dx,adr_buf
movah.Oah
int 21h обработка введенной строки

Получить состояние клавиатуры (0Bh int 21h)

Функция 0Bh проверяет наличие в буфере символа для ввода.
Вход: АН = 0Bh — проверка состояния клавиатуры.
Выход: AL = 0ffh — буфер клавиатуры содержит символ для ввода; AL = 0 — буфер клавиатуры пуст.
Данная функция формирует только логический результат — присутствует символ в буфере или буфер пуст, поэтому вызов функции 0Bh необходимо комбинировать с одной из функций извлечения символа из буфера ввода. Использование этой функции удобно для программ, управление которыми производится с клавиатуры, — типа командного процессора. В процессе своей работы они постоянно ожидают ввода пользователем управляющих команд, в связи с чем периодически проверяют входной буфер.
Функция производит проверку нажатия комбинации Ctrl+C (Ctrl+Break), при наличии которого вызывается прерывание int 23h.

Ввод с клавиатуры с предварительной очисткой буфера (ОСh int 21h)

функция 0Ch выполняет ввод, предварительно очищая буфер клавиатуры. Это удобно для предотвращения чтения из буфера оставшихся там символов, возможно, введенных ошибочно или случайно. Функция гарантирует, что программа получит именно те данные, которые ввел оператор. Важно отметить, что функция 0Ch выполняет только очистку буфера, ввод символа осуществляет одна из функций, номер которой указывается в регистре AL при вызове этой функции/ Вход: АН = 0Ch — ввод с клавиатуры с предварительной очисткой; AL = номер
функции (01h, 06h, 07h, 08h, 0ah). Выход: определяется функцией, указанной в AL при вызове функции.
Функция производит проверку нажатия комбинации Ctrl+C (Ctrl+Break), при наличии которого вызывается прерывание int 23h.

Функции MS DOS для вывода данных на экран

Для вывода данных на экран можно использовать два вида функций: универсальную функцию 40h (вывод в файл) и группу специализированных функций MS DOS вывода на экран.
Использование функции 40h уже рассматривалось в разделе, посвященном работе с файлами. Материал, представленный ниже, посвящен второй группе функций — функциям MS DOS для вывода символов на экран. В группу входят три функции. Рассмотрим их.

Вывод символа на экран (02h int 21h)

Функция 02h позволяет вывести один символ на экран. Вход: АН » 02h — вывод символа; DL = символ для вывода.
Функция 02h проверяет наличие в клавиатурном буфере символов нажатия комбинации Ctrl+C (Ctrl+Break), при обнаружении которых производится вызов прерывания int 23h. В процессе вывода функция реагирует на управляющие символы, такие как 0dh (возврат каретки), 0ah (перевод строки), 08h (курсор назад на один символ), 07h (звуковой сигнал) и т. д.
Для того чтобы вывести строку, необходимо использовать цикл.

;выводим строку string на экран
mov ex.len_string :длину строки
lea si.string ;адрес строки
mov ah. 02h ml: mov dl.byte ptr [si]
int 21h
inc si
loop ml

Прямой вывод символа на экран (06h int 21h)

Функция 06h выводит один символ на экран. Эта функция универсальна, так как "используется и для ввода (см. выше), и для вывода символа.
Вход: АН = 06h — вывод символа на экран; DL = символ для вывода (за исключением 0ffn).
Функция 06h не проверяет наличие в буфере символов нажатия комбинации Ctrl+C (Ctrl+Break). Порядок использования данной функции аналогичен порядку использования функции 02h.

Вывод строки на экран (09h int 21h)

Функция 09h выводит строку символов на экран. Строка должна обязательно заканчиваться символом $. Данную функцию удобно использовать для вывода на экран различных диагностических сообщений. Если требуется организовать вывод строк, длина которых формируется динамически, то лучше либо использовать упомянутую выше функцию 40h, либо выводить их в цикле, тело которого содержит одну из функций 02h или 06h.
Вход: АН = 09h — вывод строки на экран; DS: DX — адрес строки для вывода с завершающим символом $.
Функция 09h проверяет наличие в клавиатурном буфере символов нажатия комбинации Ctrl+C (Ctrl+Break), при обнаружении которых производится вызов прерывания int 23h. В процессе вывода функция реагирует на управляющие символы, такие как 0dh (возврат каретки), 0ah (перевод строки), 08h (курсор назад на один символ), 07h (звуковой сигнал) и т. д.
Приведенный ниже фрагмент показывает порядок применения функции 09h.

:prg05_09.asm - программа вывода строки на экран функцией 09h int 21h
.data
string db "Строка для вывода функцией 09h $"
adr_string dd string
.code
:выводим строку string на экран
Ids dx.adr_string ;адрес строки в DS:DX
mov ah.09h

Работа с консолью в среде Windows

Если ничто другое не помогает,
прочтите, наконец, инструкцию.
Прикладная Мерфология

В этом разделе будут рассмотрены средства для работы с консолью в среде операционной системы Windows. Как известно, Windows поддерживает работу двух типов приложений — оконных, в полной мере использующих все достоинства графического интерфейса, и консольных, работающих исключительно в текстовом режиме. Поэтому не следует путать понятие «консоли», используемое выше, с понятием «консольного приложения» Windows. В предшествующем материале под «консолью» подразумевались средства для ввода информации с клавиатуры и вывода ее на экран. Для однозначности изложения далее под термином «консоль» мы будем иметь в виду либо само консольное приложение, либо его видимую часть — окно консольного приложения.
Далее мы рассмотрим порядок ввода-вывода данных в консольное приложение для Windows, написанное на ассемблере. Организация ввода-вывода в оконном приложении Windows здесь рассматриваться не будет, так как в уроках 18 «Создание Windows-приложений на ассемблере» и 19 «Архитектура и программирование сопроцессора» учебника этот вопрос был рассмотрен очень подробно и полно. Что-либо существенное добавить к уже сказанному трудно. Данная книга рассматривается как практическое продолжение учебника, поэтому повторяться просто не имеет смысла. Что же касается организации работы с консольным приложением, то этот вопрос в учебнике был рассмотрен слабо — в контексте одной из задач урока 20 «ММХ-технология микропроцессоров Intel». Поэтому есть смысл рассмотреть его более систематично, попутно осветив проблемы ввода-вывода в консольном приложении Windows. Это тем более актуально, что при программировании на ассемблере необходимость написания консольного приложения возникает более часто, чем оконного. Причина проста — малыми затратами нам становятся доступны многие возможности API Win32.

Организация ввода-вывода в консольном приложении Windows

Язык ассемблера — язык системных программистов, исследователей принципов работы операционных систем, программ и аппаратных средств. Здесь не нужны красивые графические оболочки, а, наоборот, велика потребность в удобных средствах для работы с текстовой информацией. Операционная система Windows обеспечивает встроенную поддержку консолей, которые, по определению, являются интерфейсами ввода-вывода для приложений, работающих в текстовом режиме.
Консоль состоит из одного входного и нескольких экранных буферов. Входной буфер представляет собой очередь, каждая запись которой содержит информацию относительно отдельного входного события консоли. Экранный буфер — h двумерный массив, содержащий символы, выводимые в окно консоли, и данные [ об их цвете.
Очередь входного буфера содержит информацию о следующих событиях:

  • нажатии и отпускании клавиш;
  • манипуляциях мышью — движение, нажатие-отпускание кнопок;
  • изменение размера активного экранного буфера, состояние прокрутки.

Для поддержки работы консольных приложений API Win32 содержит более сорока функций, предназначенных для интеграции в среду Windows программ, работающих в текстовом режиме. Данные функции предоставляют два уровня доступа к консоли — высокий и низкий. Консольные функции ввода высокого уровня позволяют приложению извлечь данные, полученные при вводе с клавиатуры и сохраненные во входном буфере консоли. Консольные функции вывода высокого уровня позволяют приложению записать данные в устройства стандартного вывода или ошибки с тем, чтобы отобразить этот текст в экранном буфере консоли. Функции высокого уровня также поддерживают переназначение стандартных дескрипторов ввода-вывода и управление режимами работы консоли. Консольные функции низкого уровня позволяют приложениям получить детальную информацию о вводе с клавиатуры, событиях нажатия-отпускания кнопок мыши и о манипуляциях пользователя с окном консоли, а также осуществить больший контроль над выводом данных на экран.
Таким образом, API Win32 предоставляет два разных подхода для обеспечения ввода-вывода с консолью, выбор нужного зависит от гибкости и полноты контроля, которыми хочет обладать приложение для обеспечения своей работы с консолью. Функции высокого уровня обеспечивают простоту процесса ввода-вывода путем использования стандартных дескрипторов ввода-вывода, но при этом невозможен доступ к входному и экранным буферам консоли. Функции низкого уровня требуют учета большего количества деталей и объема кода, но это компенсируется большей гибкостью.
Высокоуровневый и низкоуровневый консольный ввод-вывод не являются взаимоисключающими, и приложение может использовать любую комбинацию этих функций.
С каждой консолью связаны две кодовые таблицы — по одной для ввода и вывода. Консоль использует входную кодовую таблицу для трансляции ввода с клавиатуры в соответствующее символьное значение. Аналогичным образом используется кодовая таблица вывода — для трансляции символьных значений, формируемых различными функциями вывода, в символ, отображаемый в окне консоли. Для работы с кодовыми таблицами приложение может использовать пары — функции SetConsoleCP и GetConsoleCP для входных кодовых таблиц и функции SetConsoleOutputCP и GetConsoieOutputCP для выходных кодовых таблиц. Идентификаторы кодовых таблиц, доступные на данном компьютере, сохраняются в системном реестре следующим ключом: <
HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage

Минимальная программа консольного приложения

Минимальная программа консольного приложения на ассемблере выглядит так:

.model flat.STDCALL ;модель памяти flat.
i ncludeWindowConA.i nc
:Обьявление внешними используемых в данной программе функций Win32 (ASCII):
extrn AllocConsole:PROC
extrn SetConsoleTitleA:PROC
extrn ExitProcess:PROC
.data
TitleText db 'Минимальное консольное приложение Win32'.0
.code
start proc near :точка входа в программу:
:запрос консоли
call AllocConsole проверить успех запроса консоли
test eax.eax
jz exit :неудача :выведем заголовок окна консоли SetConsoleTitle:
push offset TitleText
call SetConsoleTitleA проверить успех вывода заголовка
test eax.eax
jz exit ;неудача
exit: :выход из приложения
:готовим вызов VOID ExitProcess(UINT uExitCode)
push 0
call ExitProcess start endp end start

Если убрать комментарии, то кода будет совсем немного. В нем представлены вызовы трех функций: AllocConsole, SetConsoleTitle, ExitProcess.
Первой функцией консольного приложения должна быть функция запроса консоли AllocConsole.

B00L AllocConsole(VOID);

Для вызова функции Al I ocConsol e не требуется никаких параметров. В случае успеха функция Al I ocConsol e возвращает ненулевое значение, при неудаче — нуль. Выделенная консоль представляет собой типичное для Windows окно. Процесс в конкретный момент времени может использовать одну консоль. Если ему нужно запустить еще одну консоль, то прежняя должна быть закрыта или освобождена с помощью функции FreeConsole.

B00L FreeConsole(VOID);

В случае успеха функция FreeConsol e возвращает ненулевое значение, при неудаче — нуль.
При завершении процесса выделенная процессу консоль освобождается автоматически. В нашем случае использован именно этот вариант закрытия консоли — функцией ExitProcess.

VOID ExitProcesstUINT uExitCode):

Функции ExitProcess передается код завершения процесса и всех завершаемых цепочек в этом процессе. Проанализировать этот код можно с помощью функ-
ций GetExitCodeProcess и GetExitCodeThread. В общем случае в различных ветвях кода может быть несколько точек выхода с вызовом ExitProcess. Задавая различные значения кода завершения, можно таким образом идентифицировать причину завершения процесса.
Окно консоли может иметь заголовок, для отображения которого предназначена функция SetConsoleTitle.

B00L SetConsolеTitle(LPCTSTR lpConsoleTitle) ;

Функция SetConsoleTitle имеет один параметр — указатель на строку с заголовком консоли, заканчивающуюся нулем.
Организация высокоуровневого консольного ввода-вывода
Для высокоуровневого ввода-вывода приложение может использовать файловые функции ReadFile и WriteFile, а также функции консольного ввода-вывода Read-Console и WriteConsole. Эти функции обеспечивают косвенный доступ к входному и экранным буферам пульта. Физически эти функции фильтруют записи входного буфера консоли так, чтобы возвратить ввод как поток символов, игнорируя все другие записи с расширенной информацией о мыши, клавиатуре и изменении размеров окна консоли. Отфильтрованный поток символов отображается в окне консоли начиная с текущей позиции курсора. Существуют два важных отличия в использовании пар функций ReadFile\WriteFile и ReadConsoleNWrite-Console.

  • Поддержка символов Unicode и ANSI. Консольные функции (ReadConso-le\WriteConsole) поддерживают эти наборы, а файловые (ReadFile\Write-File) — нет.
  • Функции файлового ввода-вывода могут использоваться как для обращения к файлам, так и к именованным каналам, устройствам, присоединенным к последовательному интерфейсу. Консольные функции ввода-вывода можно использовать только с тремя дескрипторами стандартного ввода-вывода (см. ниже описание функций ReadConsole\WriteConsole).

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

Операция чтения высокого уровня реализуется функцией ReadConsole, которая получает входные символы из буфера ввода консоли и сохраняет их в указанном буфере.

B00L ReadConsoleCHANDLE hConsolelnput. LPVOID ipBuffer. DWORD nNumberOfCharsToRead. LPDWORD lpNumberOfCharsRead. LPVOID lpReserved);

Параметры этой функции означают следующее:

  • hConsolelnput — дескриптор входного потока консоли;
  • IpBuffer — указатель на строку, в которую будет записана вводимая строка символов;
  • nNumberOfCharsToRead — размер буфера, указанного lpBuffer;
  • ipNumberOfCharsRead — количество действительно введенных символов;
  • lpReserved — этот параметр не используется, поэтому должен задаваться
    как NULL.

Операция записи высокого уровня реализуется функцией WriteConsole, которая извлекает символы из указанного буфера и записывает их в экранный буфер, начиная с текущей позиции курсора и продвигая ее по мере записи символов. B00L WriteConsoleCHANDLE hConsoleOutput. CONST VOID *lpBuffer.
DWORD nNumberOfCharsToWrite. LPDWORD ipNumberOfCharsWritten. LPVOID lpReserved);
Параметры этой функции означают следующее:

  • hConsoleOutput — дескриптор выходного потока консоли;
  • lpBuffer — указатель на выводимую строку;
  • nNumberOfCharsToWrite — размер буфера, указанного IpBuffer;
  • IpNumberOfCharsWritten — количество действительно выведенных символов;
  • lpReserved — этот параметр не используется, поэтому должен задаваться
    как NULL.

Для своей работы эти и некоторые другие консольные функции требуют получения стандартных дескрипторов ввода-вывода. Значения этих дескрипторов присваиваются параметрам hConsolelnput и hConsoleOutput. По умолчанию стандартный дескриптор ввода связан с клавиатурой, стандартный дескриптор вывода—с экраном. Получить стандартный дескриптор ввода-вывода можно с помощью функции GetStdHandle.

HANDLE GetStdHand 1 e(DWORD nStdHandle):

На вход функции GetStdHandle должно быть подано одно из следующих значений:

  • STD_INPUT_HANDLE = -10 — дескриптор стандартного входного потока;
  • STD_OUTPUT_HANDLE = -11 — дескриптор стандартного выходного потока;
  • STD_ERROR_HANDLE - -12 — дескриптор стандартного потока ошибок.

Используя функции высокоуровневого ввода-вывода, приложение может управлять цветом текста и фона, с которыми должны отображаться символы, записываемые в экранный буфер. Приложение может изменять следующие свойства высокоуровневого консольного ввода-вывода:

  • эхо-контроль вводимых символов на экране из активного экранного буфера;
  • ввод строки, окончание операции чтения которой происходит при нажатии клавиши Enter;
  • автоматическая обработка некоторых символов, вводимых с клавиатуры:
    перевода каретки, нажатия клавиш Ctrl+C и т. д.;
  • автоматическая обработка некоторых символов, выводимых на экран: перевода строки и каретки, возврата на один символ и т. д.

Функция SetConsol eCursorPosition предназначена для указания позиции, с которой начинается выполнение операций чтения-записи в окно консоли. B00L SetConsoleCursorPosition(HANDLE hConsoleOutput. COORD dwCursorPosition); Параметрами этой функции являются стандартный дескриптор вывода hCon-[' soleOutput, полученный функцией GetStdHandle, и указатель на структуру COORD с координатами новой позиции курсора:

COORD struc x dw 0 у dw 0 ends

По умолчанию цветовое оформление окна консоли достаточно унылое — черный фон, белый текст. Внести разнообразие во внешний вид окна консоли поможет функция SetConsoleTextAttribute, с помощью которой можно изменить установки цвета по умолчанию для текста и фона.
B00L SetConsoleTextAttributetHANDLE hConsoleOutput. WORD wAttributes):
Первый параметр — без комментариев, второй определяет цвет текста и фона. Второй параметр формируется как логическое ИЛИ следующих значений:

  • FOREGROUND_BLUE=0001h - синий текст;
  • FOREGROUND_GREEN=0002h - зеленый текст;
  • FOREGROUND_RED=0004h — красный текст;
  • FOREGROUND_INTENSITY=0008h — текст повышенной яркости;
  • BACKGROUND_BLUE=0010h - голубой фон;
  • BACKGROUND_GREEN=0020h - зеленый фон;
  • BACKGROUND_RED=0040h - красный фон;
  • BACKGROUND_INTENSITY=0080h — фон повышенной яркости.

Для задания белого цвета складываются три компоненты, для задания черного — компоненты не задаются вовсе.

Пример программы ввода-вывода в консоль

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

:prg05_11.asm - программа ввода-вывода в консоль с изменением атрибутов выводимого текста
!
.data
.code
start proc near -.точка входа в программу:
..........
:получим стандартные дескрипторы ввода-вывода
push STD_OUTPUT_HANDLE
call GetStdHandle
movdOut.eax :dOut-fleCKpnnTop вывода консоли
push STD_INPUT_HANDLE
call GetStdHandle
mov din.eax idln-дескриптор ввода консоли :введем строку .¦установим курсор в позицию (2,6)
mov con.хх.2
mov con.yy,6
push con
push dOut
call SetConsoleCursorPosition cmp eax. 0
jz exit ;если неуспех
push 0
push offset NumWri количество действительно введенных символов
push 80 :размер буфера TitleText для ввода
push offset TitleText
push din
call ReadConsoleA
cmp eax, 0
jz exit :если неуспех
:выведем введенную строку в заголовок окна консоли:
push offset TitleText
call SetConsoleTitleA проверить успех вывода заголовка
test eax.eax
jz exit ;неудача
:выведем строку в окно консоли с различных позиций и с разными цветами установим курсор в позицию (2.5)
mov ecx.10 ;строку выведем 10 раз
mov bl.10000001b начальные атрибуты
ml: push ecx
inc con.xx
inc con.yy
push con
push dOut
call SetConsoleCursorPosition
cmp eax.O
jz exit :если неуспех ;определим атрибуты выводимых символов - будем получать их циклически сдвигом - регистр
BL
хог еах.еах
rol Ы.1
mov al.bl
push eax
push dOut
call SetConsoleTextAttribute
cmp eax.O
jz exit ;если неуспех :вывести строку
push 0
push offset NumWri действительное количество выведенных на экран
push NumWri ;длина строки для вывода на экран
push offset TitleText :адрес строки для вывода на экран
push dOut
call WriteConsoleA
cmp eax.O
jz exit :если неуспех pop ecx
loop ml
exit: :выход из приложения

Каждый консольный процесс имеет свой собственный список функций-обработчиков, которые вызываются системой, когда происходят определенные собы тия, например при активном окне консоли пользователь нажимает комбинации клавиш Ctrl+C, Ctrl+Break или Ctrl+Close. При запуске консольного приложения список функций-обработчиков содержит только заданную по умолчанию функцию-обработчик, которая вызывает функцию ExitProcess. Консольный процесс может добавлять или удалять дополнительные функции-обработчики, вызывая функцию SetConsoleCtrlHandler.
B00L SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine. B00L Add): Данная функция имеет два параметра:

  • HandlerRoutine — указатель на определенную приложением функцию HandlerRoutine, которая должна быть добавлена или удалена;
  • Add — логическое значение, которое означает: 1 — функция должна быть
    добавлена, 0 — функцию необходимо удалить.

Функция HandlerRoutine — это определенная приложением функция обратного вызова. Консольный процесс использует эту функцию, чтобы обработать нажатия клавиш управления. На самом деле HandlerRoutine — идентификатор-заполнитель для определенного приложением имени функции. B00L WINAPI HandIerRoutine(DWORD dwCtrlType):
Параметр DwCtrlType определяет тип сигнала управления, получаемого обработчиком. Этот параметр может принимать одно из следующих значений:

  • CTRL_C_EVENT=O — сигнал, имитирующий нажатие клавиш Ctrl+C, может быть получен из двух источников: с клавиатуры или как сигнал, сгенерированный функцией GenerateConsoleCtrl Event;
  • CTRL_BREAK_EVENT=1 — сигнал имитирующий нажатие клавиш Ctrl+Break, может быть получен из двух источников: с клавиатуры или как сигнал, сгенерированный функцией GenerateConsoleCtrl Event;
  • CTRL_CL0SE_EVENT=2 — сигнал, который система посылает всем процессам, подключенным к данному консольному приложению, когда пользователь его закрывает (либо выбирая пункт Close в системном меню окна консоли, либо щелкая на кнопке завершения задачи в диалоговом окне Менеджера задач);
  • CTRL_LOGOFF_EVENT=5 — сигнал, который посылается всем консольным процессам, когда пользователь завершает работу в системе (этот сигнал не указывает, какой именно пользователь завершает работу);
  • CTRL_SHUTD0WN_EVENT=6 — сигнал, который система посылает всем консольным процессам при подготовке к выключению машины. Функция HandlerRoutine должна возвратить логическое значение: 1 — если она обрабатывает конкретный сигнал управления; 0 — для обработки полученного события будет использоваться другая функция-обработчик HandlerRoutine из списка функций-обработчиков для этого процесса (то есть включенная в этот список раньше данной функции).

Как уже было упомянуто, каждый консольный процесс может определить несколько функций HandlerRoutine, которые связываются в цепочку. Первоначально этот список содержит только заданную по умолчанию функцию обработчика, которая вызывает функцию ExitProcess и, как результат, приводит к завершению текущего консольного приложения. Консольный процесс добавляет или удаляет
Работа с консолью в среде Windows 233
дополнительные функции обработчика, вызывая функцию SetConsoleCtrl Handler, которая не затрагивает список функций-обработчиков для других процессов. Когда консольный процесс принимает любой из сигналов управления (см. выше), то вызывается последняя зарегистрированная функция-обработчик, если она не возвращает 1, то управление передается следующему (предыдущему) зарегистрированному обработчику и т. д., до тех пор пока один из обработчиков не возвратит 1. Если ни один из обработчиков этого не сделал, то вызывается обработчик, заданный по умолчанию.
Установка обработчиков для сигналов CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT и
CTRL_SHUTDOWN_EVENT дает процессу возможность выполнить специфичные для него
действия по корректному завершению приложения. Пользовательская функция
HandlerRoutine может быть вызвана для того, чтобы выполнить следующие действия:

  • вызвать функцию ExitProcess для завершения процесса;
  • возвратить 0 (ложь) — это означает, что завершение приложения должен
    выполнить обработчик, заданный по умолчанию;

В возвратить 1 — в этом случае никакие другие функции-обработчики не вызываются, а система отображает всплывающее диалоговое окно с запросом о необходимости завершения процесса; система также отображает диалоговое окно, если процесс не отвечает определенное время (5 секунд для CTRLCLOSEEVENT и 20 секунд для CTRLLOGOFFEVENT и CTRLSHUTDOWNEVENT); процесс может использовать функцию SetProcessShutdownParameters, чтобы запретить системе отображать последнее диалоговое окно, в этом случае система просто заканчивает процесс, когда HandlerRoutine возвращает истину или когда истекает определенный период времени. Ниже приведен пример пользовательского обработчика события — ввода комбинации Ctrl+C или Ctrl+Break. За основу взята предыдущая программа.

:prg05_12.asm - программа, демонстрирующая использование пользовательского обработчика события.
.data
Text_CTRL_C db "Нажаты CTRL+C"
Len_Text_CTRL=$-Text_CTRL_C
TextJREAK db "Нажаты CTRL+BREAK"
Len_BREAK=$-Text_BREAK
.code
CtrlHandler proc
arg @@dwCtrlType:DWORD
uses ebx.edi. esi ;эти регистры обязательно должны сохраняться
:анализируем тип сигнала управления
cmp @@dwCtrlType.CTRL_C_EVENT
je h_CTRL_C_EVENT
cmp (a@dwCtrlType.CTRL_BREAK_EVENT
je h_CTRL_BREAK_EVENT
jmp h_default
h_CTRL_C_EVENT: :при нажатии CTRL+C выводим сообщение: установим курсор

call SetConsoleCursorPosition :вывести строку Text_CTRL_C call WriteConsoleA
; возвращаем признак обработки
mov eax.l
jmp exit_CtrlHandler h_CTRL_BREAK_EVENT:
;при нажатии CTRL+BREAK выводим сообщение:
установим курсор
call SetConsoleCursorPosition : вывести строку
call WriteConsoleA
;возвращаем признак обработки
mov eax.l
jmp exit_CtrlHandler
h_default: mov eax.Offffffffh;возвращаем остальное не обрабатываем exit_CtrlHandler: ret CtrlHandler endp start proc near ;точка входа в программу:
:работаем .........
:получим стандартные дескрипторы ввода-вывода
установим функцию-обработчик сигналов управления
push TRUE
push offset cs: CtrlHandler
call SetConsoleCtrlHandler
onp eax. 0
jz exit :если неуспех ;введем строку в буфер TitleText установим курсор в позицию (2.6)
call SetConsoleCursorPosition call ReadConsoleA
:выведем введенную строку в заголовок окна консоли: push offset TitleText call SetConsoleTitleA
:выведем строку в окно консоли с различных позиций и с разными цветами
mov ecx.10 :строку выведем 10 раз
mov bl.10000001b начальные атрибуты ml: push ecx установим курсор в позицию
call SetConsoleCursorPosition
определим атрибуты выводимых символов - будем получать их циклически сдвигом регистра BL хог еах.еах
rol Ы .1
mov al ,Ы
push eax
push d0ut
call SetConsoleTextAttribute . :вывести строку TitleText
call WriteConsoleA cmp eax.0
jz exit ;если неуспех pop ecx
loop ml

Относительно этой программы можно сделать два замечания. Первое касается функции Handl erRoutine, которая в нашей программе называется Ctrl Handler. Как упоминалось, эта функция является функцией обратного вызова. Ее вызов производится при возникновении определенных событий неявно — из системы Windows. По структуре и алгоритму работы она аналогична оконной функции, которую мы рассматривали в уроке 18 «Создание Windows-приложений на ассемблере» учебника. Поэтому за всеми подробностями отсылаем читателя к этому материалу. Второе замечание касается порядка отладки приложений, содержащих определяемые пользователем функции (процедуры) обратного вызова. Первое, что нужно сделать в процессе пошагового выполнения программы в отладчике, — выяснить адрес процедуры обратного вызова. В программе выше это можно сделать, выяснив, какое значение будет помещено в стек при выполнении команд:

..........
[установим функцию-обработчик сигналов управления
push TRUE
push offset cs: Ctrl Handler
call SetConsoleCtrlHandler
cmp eax. 0
jz exit [если неуспех
.........

После этого, сделав активным окно отладчика CPU (выбрав в меню команду
View CPU), необходимо установить указатель мыши в окно с командами процес-; сора и щелкнуть правой кнопкой мыши. В появившемся контекстном меню вы-
бер*етс пункт Goto... В результате этих действий отладчик отобразит диалоговое ¦ окно, в которое необходимо внести адрес программы-обработчика Ctrl Handler. ; В результате этого в верхней части окна команд отобразится первая команда [' процедуры Ctrl Handler. Установите на нее курсор и нажмите клавишу F4. Все, S программа начнет выполняться по своему алгоритму. При нажатии пользователем
управляющих комбинаций клавиш, допустимых функцией Handl erRoutine, управ-I ление будет передано этой функции, и вы сможете произвести ее отладку.
Организация низкоуровнего консольного ввода-вывода
I Низкий уровень консольного ввода-вывода по сравнению с высоким уровнем И бладает более широкими и гибкими возможностями. Низкоуровневые функции консольного ввода-вывода обеспечивают прямой доступ к входному и экранным буферам консоли, предоставляя приложению доступ к событиям мыши и клавиатуры, а также к информации об изменении размеров окна консоли. Функции низкоуровневого ввода-вывода позволяют приложению иметь доступ по чтению-записи к указанному числу последовательных символьных ячеек в экранном буфере или к прямоугольному блоку символьных ячеек в указанной позиции экранного буфера.
Обсудим возможности низкоуровневого ввода-вывода на примере работы с входным буфером (входной очередью) и буферами экрана. Отметим, для работы с ними существуют разные группы команд. Так, для работы с входным буфером используются функции низкоуровневого ввода-вывода — Wn'teConsolelnputXRead-Consolelnput. Группа функций для работы с буферами экрана будет конспективно рассмотрена в конце этого раздела.

Поддержка работы с мышью в консоли

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

B00L ReadConsoleInput(HANDLE hConsolelnput. PINPUT_RECORD lpBuffer, DWORD nLength. LPDWORD lpNumberOfEventsRead);

Параметры этой функции:

  • Consolelnput — стандартный дескриптор ввода, полученный функцией GetStdHandle;
  • lpBuffer — указатель на буфер, в который записывается информация о событии мыши, — эта область памяти имеет структуру, называемую INPUT_ RECORD, ее формат рассмотрен чуть ниже (необходимо заметить, что возможно групповое чтение информации из входного буфера, поэтому указатель ipBuffer может указывать на массив структур; информация о том, сколько событий будет читаться в этот массив структур, определяется параметром nLength);
  • nLength — размер во входных записях буфера, на который указывает указатель lpBuffer;
  • lpNumberOfEventsRead — определяет переменную, в которую записывается
    действительное число прочитанных записей входного буфера.

Запись входного буфера консоли имеет структуру, называемую INPUTRECORD. Ее описание на языке C++ выглядит так:

typedef struct _INPUT_RECORD { WORD EventType; union {
KEYJVENT_RECORD KeyEvent;
MOUSE_EVENT_RECORD MouseEvent;
WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
MENU_EVENT_RECORD MenuEvent:
FOCUSJVENT_RECORD FocusEvent;
} Event: } INPUT_RECORD;

В этой структуре первое поле EventType размером в слово содержит тип события, а второе поле Event является объединением различных структур. Поля какой из структур будут заполнены, определяется типом события, то есть первым полем, которое может принимать значения:

  • KEY_EVENT=0001h - поле Event содержит структуру KEYEVENTRECORD с информацией относительно события клавиатуры;
  • MOUSE_EVENT=0002h — ноле Event содержит структуру MOUSEEVENTRECORD с информацией относительно движения мыши или нажатия кнопки;
  • WINDOW_BUFFER_SIZE_EVENT-O004h - поле Event содержит структуру WINDOW_ BUFFER_SIZE_RECORD с информацией относительно нового размера экранного буфера;
  • MENU_EVENT=OOO8h — поле Event содержит структуру MENUEVENTRECORD (это событие используется внутри Windows и должно игнорироваться);
  • FOCUS_EVENT=0010h - поле Event содержит структуру FOCUSEVENTRECORD (это
    событие используется внутри Windows и должно игнорироваться).

Для обработки события мыши структура MOUSEEVENTRECORD выглядит так:

typedef struct _MOUSE_EVENT_RECORD {
COORD dwMousePosition;
DWORD dwButtonState;
DWORD dwControlKeyState:
DWORD dwEventFlags;
} MOUSE_EVENT_RECORD;

Исходя из вышесказанного структура INPUTRECORD для обработки событий мыши в программе на ассемблере должна выглядеть так:
INPUT_RECORD struc EventType dw 0 dwMousePosition struc x dw 0 у dw 0 ends
dwButtonState dw 0 dwControlKeyState dw 0 DwEventFlags dw 0 ends
Поле EventType для события мыши содержит значение MOUSE_EVENT=0002h, а поля структуры MOUSEEVENTRECORD соответственно означают следующее:
ш dwMousePosition — координаты мыши в окне консоли (в символьных координатах);
м dwButtonState — состояние кнопок мыши в момент возникновения события, при нажатии кнопок устанавливаются следующие биты (при одновременном нажатии устанавливается несколько соответствующих битов):

  • если установлен бит 0 ноля dwButtonState, то в момент наступления события была нажата левая кнопка мыши;
  • если установлен бит 1 поля dwButtonState, то в момент наступления события была нажата правая кнопка мыши;
  • если установлен бит 2 поля dwButtonState, то в момент наступления события была нажата средняя кнопка мыши, если она есть;
  • dwControlKeyState — поле описывает состояние управляющих клавиш клавиатуры в момент наступления события мыши (если одновременно нажато несколько клавиш, то значение в этом поле является результатом операции логического сложения ИЛИ перечисленных ниже значений):
  • • RIGHT_ALT_PRESSED=0001h - нажата правая клавиша Alt;
    • LEFT_ALT_PRESSED=0002h - нажата левая клавиша Alt;
    • RIGHT_CTRL_PRESSED=0004h — нажата правая клавиша Ctrl; LEFT_CTRL_PRESSED=OOO8h — нажата левая клавиша Ctrl;
    • SHIFT_PRESSED=OOlOh - нажата любая клавиша SHIFT;
    • NUMLOCK_ON=0020h - индикатор NumLock включен;
    • SCROLLLOCK_ON=0040h — индикатор ScrollLock включен;
    • CAPSLOCK_ON=0080h — индикатор CapsLock включен;
    ENHANCED_KEY=0100h — нажата клавиша расширенной клавиатуры (101 и 102 клавиши): Ins, Del, Home, End, Page Up, Page Down, «-, t, -», I, / или Enter;
    Ш dwEventFl ags — поле содержит одно из двух значений: » MOUSE_MOVED=0001h — перемещение мыши;
    • DOUBLE_CLICK=0002h — выполнен двойной щелчок мыши.

В ПРИМЕРе приведена демонстрационная программа обработки событий мыши (prg05_13. asm), которые отслеживаются следующим образом: нажатие левой кнопки приводит к выводу сообщения в позиции нажатия, нажатие правой кнопки приводит к завершению работы программы.
В заключение обращу внимание читателя на то, что API Win32 имеет функцию Mouse_Event, которая позволяет генерировать события, соответствующие реальным движениям мыши и щелчкам ее кнопок. Тем самым API Win32 предоставляет механизм для создания обучающих и демо-версий программ. Формат этой функции:
VOID mouse_event(DWORD dwFlags. DWORD dx. DWORD dy, DWORD dwData. DWORD dwExtralnfo)

Расширенная поддержка клавиатуры в консоли

Функции работы с текстом высокого уровня не дают других возможностей работы с клавиатурой, кроме как примитивного ввода текста. При разработке программ текстового режима часто требуется информация о состоянии управляющих клавиш, о факте удержания клавиши, что может свидетельствовать о желании пользователя повторить ввод некоторого символа или просто о желании получить тривиальный скан-код клавиши. Эти и другие события клавиатуры доступны программе посредством описанной выше функции ReadConsolelnput.
События клавиатуры генерируются при нажатии любой клавиши. Процесс их обработки аналогичен обработке событий мыши. В первую очередь заполняется
о нажатии некоторых управляющих клавиш. Для всех остальных клавиш просто фиксируется факт нажатия. При этом необходимо помнить, что однократному нажатию клавиши реально соответствуют два события — нажатие и отпускание клавиши. В связи с этим программа выводит два сообщения. На практике этого можно избежать, анализируя поле bKeyDown: bKeyDown=l, когда клавиша нажата; bKeyDown=0, когда клавиша отпущена. Выход из программы — при выполнении любых действий с мышью.

Окно консоли и экранный буфер

И в заключение обсуждения особенностей работы с консольными приложениями поясним, что представляет собой экранный буфер консоли и какие средства представляет API Win32 для работы с ним.
Для того чтобы читатель мог легко понять соотношение понятий «окно консоли» и «экранный буфер консоли», представьте себе офисный календарь, на котором текущее число отмечается квадратной рамкой, закрепленной на прозрачной целлофановой ленте и перемещающейся вдоль нее. Теперь представим, что содержимое листа календаря вне этой рамки невидимо, то есть доступно только через окошко, которое образует рамка. Для того чтобы увидеть содержимое всего листа календаря, необходимо двигать рамку. В контексте этой ассоциации — лист календаря — это экранный буфер, а площадь внутри рамки — окно консоли, то есть видимая часть экранного буфера.
Возможна поддержка нескольких экранных буферов, связанных с данной консолью, но только один из них может подвергаться отображению в окне консоли — его называют активным экранным буфером. Другие экранные буферы, если они были созданы, являются неактивными. Для создания экранного буфера используется функция CreateConsoleScreenBuffer. К неактивным экранным буферам можно обращаться для чтения и записи, но отображаться в окне консоли будет только активный экранный буфер (или его часть). Для того чтобы сделать экранный буфер активным, используется функция SetConsoleActiveScreenBuffer. Функция CreateConsoleScreenBuffer имеет показанный ниже формат.
HANDLE CreateConsoleScreenBuffer(DWORD dwDesiredAccess,
DWORD dwShareMode, CONST LPSECURITY_ATTRIBUTES ipSecurityAttributes.
DWORD dwFlags. LPVOID lpScreenBufferData):
Параметры функции:

  • dwDesiredAccess — определяет желаемый тип доступа к экранному буферу консоли, этот параметр может быть либо одним из следующих значений либо их комбинацией:
  • GENERIC_READ=80000000h — запрашивается доступ по чтению к экранному буферу консоли для того, чтобы разрешить процессу прочитать данные из буфера;
  • GENERIC_WRITE=40000000h — запрашивается доступ для записи к экранному буферу консоли для того, чтобы разрешить процессу записать данные в буфер;
  • dwShareMode — определяет возможность разделения этого экранного буфера консоли; нулевое значение этого параметра указывает, что буфер не может
    быть разделен, ненулевое состояние этого буфера может быть одним из следующих значений или их комбинацией:
  • FILESHAREREAD — другие операции открытия могут быть выполнены для экранного буфера консоли с доступом для чтения;
    FILESHAREWRITE — другие операции открытия могут быть выполнены для экранного буфера консоли с доступом для записи;
  • IpSecurityAttributes — указатель на структуру SECURITY_ATTRIBUTES, которая определяет, может ли возвращаемый функцией CreateConsoleScreenBuffer дескриптор наследоваться дочерними процессами — если lpSecuri-tyAttributes=NULL, то дескриптор не может быть унаследован;
  • dwFlags — определяет тип создаваемого экранного буфера консоли, в настоящее время поддерживается только один такой тип — CONSOLE_TEXTMODE_ BUFFER=1;
    lpScreenBufferData — зарезервирован и должен быть равен NULL.

Функция CreateConsoleScreenBuffer формирует дескриптор созданного экранного буфера, который затем используется функциями для доступа к этому буферу.
Для того чтобы сделать буфер активным, используют функцию SetConsole-Acti veScreenBuf f er.
B00L SetConsoleActiveScreenBuffertHANDLE hConsoleOutput):
Функция имеет единственный параметр — hConsoleOutput — дескриптор экранного буфера, созданного функцией CreateConsoleScreenBuffer. Как уже было отмечено, консоль может иметь много экранных буферов. Функция SetConsoleActiveScreenBuffer определяет, какой из них будет отображен. Приложение может производить запись в неактивный экранный буфер и затем использовать функцию SetConsoleActiveScreenBuffer для отображения содержимого буфера. Чтение и запись в неактивный (и активный тоже) экранный буфер производится функциями низкоуровневого ввода-вывода — WriteConsoleOutputNWriteConsoleOutput-Character и ReadConsoleOutput\ReadConsoleOutputCharacter, которым при вызове передается дескриптор нужного экранного буфера, полученного предварительно функцией CreateConsol eScreenBuf fer.
Каждый из созданных экранных буферов поддерживает собственный текущий прямоугольник окна, определяемый координатами верхней левой и нижней правой символьных ячеек, которые будут отображены в окне консоли. Для определения видимого в окне консоли прямоугольника экранного буфера используется функция GetConsol eScreenBufferlnfo.
B00L GetConsoleScreenBufferInfo(HANDLE hConsoleOutput.
PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferlnfo);
Параметрами этой функции являются:

  • hConsoleOutput — дескриптор экранного буфера, созданного функцией CreateConsoleScreenBuffer; дескриптор должен иметь тип доступа GENERICREAD;
  • lpConsoleScreenBufferlnfo — указатель на структуру CONSOLE_SCREEN_BUFFER_ INFO, в которую помещается информация об экранном буфере.

Структура CONSOLESCREENBUFFERINFO имеет следующий вид:

typedef struct _CONSOLE_SCREEN_BUFFER_INFO
COORD dwSize: :размер экранного буфера в колонках и строках COORD dwCursorPosition: //координаты столбца и строки курсора в экранном буфере
WORD wAttributes: //цвет фона и текста, с которыми записываются
//и отображаются символы в экранном буфере функциями //WriteFile\WriteConsole и ReadFile\ReadConsole
SMALL_RECT srWindow; // определяет структуру
SMALL_RECT. которая содержит координаты // левого верхнего и нижнего правого углов экранного буфера, //видимого в окне консоли на экране дисплея COORD dwMaximumWindowSize; //определяет максимальный размер окна консоли
//с учетом текущего размера экранного буфера и шрифт } CONSOLE_SCREEN_BUFFER_INFO :

Для приложения интерес, в частности, может представлять параметр srWindow с координатами видимой части экранного буфера. Далее, руководствуясь действиями пользователя (выполняющего прокрутку окна или изменение его размера) по отношению к окну консоли, приложение может изменять значения в структуре SMALLRECT и передавать ее на вход функции SetConsoleWindowInfo, которая устанавливает текущий размер и позицию окна консоли относительно экранного буфера.
B00L SetConsoleWindowInf0ChANDLE hConsoleOutput. B00L bAbsolute.
CONST SMALL RECT *lpConsoleWindow);
Параметрами этой функции являются: .

  • hConsoleOutput — дескриптор экранного буфера, созданного функцией Create-Consol eScreenBuf fer; дескриптор должен иметь тип доступа GENERIC_WRITE;
  • bAbsol ute — определяет порядок использования координат в структуре, указанной параметром lpConsoleWindow; если bAbsol ute=l (истина), то координаты определяют новые левую верхнюю и нижнюю правую углы окна; если bAbsol ute=0 (ложь), то координаты — смещения относительно текущих координат углов окна;
  • lpConsoleWindow — указатель на структуру SMALL_RECT, в которую записывается информация о новых координатах экранного буфера.

Структура SMALL_RECT имеет следующий вид:

typedef struct _SMALL_RECT {
SHORT Left; //х-координата верхнего левого угла
SHORT Top: //у-координата верхнего левого угла
SHORT Right: //х-координата нижнего правого угла
SHORT Bottom: //у-координата нижнего правого угла } SMALL_RECT:

При работе с функцией SetConsoleWindowInfo следует иметь в виду, что она возвращает ошибку (нулевое значение), если координаты видимой части экранного буфера указывают за его действительные границы. Максимально допустимый раз мер окна для данной консоли можно получить с помощью функции GetConsole-ScreenBufferlnfo. Таким образом, обе эти функции можно использовать для листания экранного буфера.
Для закрытия экранного буфера используется функция CloseHandle, которой передается дескриптор закрываемого экранного буфера.
B00L CloseHandle(HANDLE hObject);
Работа с консолью в среде Windows 243
Для того чтобы завершить рассмотрение функций, предназначенных для поддержки консольного приложения, перечислим те из них, что остались «за кадром».

функция
Назначение
FiConsoleOutputAttribute
Устанавливает цвет текста и фона для указанного числа символьных ячеек, начинающихся по указанным координатам в экранном буфере
Fi П ConsoleOutputCharacter
Запись символа в экранный буфер указанное число раз по указанным координатам
Fl ushConsolelnputBuffer
Запись на диск входного буфера консоли. Все входные записи во входном буфере консоли до настоящего момента времени удаляются
GenerateConsoleCtrl Event
Посылка сигнала, определенного этой функцией, совместно использующим консоль процессам
GetConsoleCursorlnfo
Предоставление информации о размере и видимости курсора для указанного экранного буфера
GetConsoleMode
Предоставление информации о текущем входном режиме входного буфера консоли или текущем режиме вывода экранного буфера консоли
GetConsoleTitie
Извлечение строки из области заголовка для текущего окна консоли
GetLargestConsoleWi ndowSIze
Возвращает размер самого большого возможного окна консоли, основанного на текущем шрифте и размере изображения
GetNumberOfConsolelnputEvents
Возвращает число непрочитанных записей ввода во входном буфере пульта
GetNumberOfConsoleMouseButtons
Возвращает число кнопок на мыши, используемых текущей консолью
PeekConsolelnput
Чтение данных из входного буфера консоли без их удаления
Scrol1ConsoleScreenBuffer
Перемещение блока данных в экранном буфере. Действие перемещения может быть ограничено путем определения отсекающего прямоугольника. Содержание экранного буфера вне отсекающего прямоугольника будет неизменным
SetConsoleCursorlnfo
Установка размера и видимости курсора для указанного экранного буфера консоли
SetConsoleMode
Установка режима входного буфера консоли или режима вывода экранного буфера консоли
SetConsoleScreenBufferS1ze
Изменение размера указанного экранного буфера консоли
SetStdHandle
Установка некоторого дескриптора как дескриптора стандартного ввода, стандартного вывода или устройства ошибки. Может использоваться при перенаправлении ввода-вывода