При выполнении графических задач на экран выводятся различные текстовые
сообщения. Это могут быть названия окон, пояснения к выбранным значкам,
информационные строки различного назначения, подсказки оператору и т.
п. Программирование вывода текста при работе в графических режимах имеет
свои специфические особенности, которые описаны в данной главе.
Все видеорежимы делятся на текстовые и графические. Первые предельно упрощают
работу с текстом, но исключают возможность работы с рисунками. Вторые
позволяют работать только с отдельными точками, из которых, как известно,
складываются любые рисунки, в том числе и изображения символов текста.
В соответствии с этим данная глава делится на две основные части, в первой
описана работа в текстовых режимах, а во второй — в графических.
В связи с непрерывным совершенствованием технических характеристик мониторов
и видеокарт и широким распространением Windows и ее приложений текстовые
режимы отошли на второй план и потеряли свою былую значимость. Однако
они входят в число стандартных режимов VESA, поэтому автор счел целесообразным
описать их в первом разделе главы.
Текстовые режимы отличаются от графических следующими особенностями:
Стандартом VESA предусмотрено пять текстовых режимов высокого разрешения,
имеющих коды от 108h до 10Ch
(см. табл. 1.1). Большинство
видеокарт поддерживает только два из них — юэь и ЮАЬ. Поэтому, так же
как при работе с графикой, до или после установки режима надо проверить,
поддерживает его видеокарта или нет.
Если в соответствии с рекомендациями, приведенными в главе
2, ваша задача выбрала из массива info значения переменных, перечисленные
в примере 2.11, то переменная
vbuff будет содержать код видеосегмента ввооь, переменная Horsize — количество
символов в строке, a versize — количество строк на экране. При выполнении
подготовительных действий необходимо проверить состояние второго разряда
нулевого байта массива info (разряды пронумерованы начиная с нуля). Если
он содержит 1, то BIOS и DOS поддерживают работу в выбранном режиме. Поэтому
все приемы программирования остаются в силе. В текстовых режимах VESA
изменяются только размер и количество строк на экране. В данном разделе
описано программирование вывода текста на экран монитора в режимах VESA.
Прежде всего, разберемся с тем, как появляются символы на экране. Задача самостоятельно или с помощью функций BIOS помещает выводимый текст в видеопамять. Коды символов должны соответствовать стандарту ASCII (American Standard Code for Information Interchange — американский стандартный код для обмена информацией). После кода каждого символа в видеопамять записывается атрибут, назначение которого будет описано ниже.
При отображении содержимого видеопамяти в текстовых режимах видеоконтроллер последовательно выбирает коды символов из видеобуфера. По коду символа вычисляется адрес начала его рисунка, который и выводится на экран. Знакогенератором называется область видеопамяти, в которой размещаются таблицы символов.
Таким образом, изображение символов, которое мы видим на экране при работе с текстом, зависит от используемой таблицы знакогенератора. Его размеры позволяют загрузить 8 разных таблиц, содержащих по 256 рисунков символов (в режиме EGA только 4 таблицы).
При установке видеорежима после включения компьютера или при переходе от одного видеорежима к другому BIOS загружает в знакогенератор свои таблицы символов из ROM BIOS. По понятным причинам в них отсутствуют рисунки русских букв. Для поддержки работы с русским текстом в процессе загрузки DOS вызывается программа-русификатор, которая остается резидентной в памяти компьютера. Она выполняет множество функций, в том числе отслеживает все прерывания int 10h, и при смене текущего видеорежима загружает в знакогенератор таблицы с рисунками русских букв. Пока русификатор находится в оперативной памяти, русский текст будет выводиться при установке любого стандартного видеорежима. Одним из распространенных русификаторов является программа Keyrus Д. Гуртяка.
Установка режимов VESA происходит без использования стандартной команды "set video Mode" прерывания int 10h, поэтому русификатор не "замечает" смены видеорежима и не заменяет русскоязычными базовые шрифты, загруженные из ROM BIOS. Такую замену должна произвести задача, работающая с русским текстом в режимах VESA. При этом она может загрузить собственную таблицу или "заставить" русификатор загрузить одну из его таблиц. В любом случае задача обращается к одной из функций BIOS.
Группа функций 11h. В состав прерывания int 10h входит группа, состоящая из 15-ти функций. Они выполняют загрузку знакогенератора, изменение количества строк и столбцов на экране, установку таблиц символов для текстового и графического режимов и получение информации об используемых таблицах символов. Группа создавалась для обслуживания мониторов EGA и была несколько расширена для мониторов VGA. Расширение заключалось в том, что добавились шрифты высотой в 16 строк и возможность загружать в знакогенератор до 8 (вместо 4) разных таблиц. При запросах функций код группы (llh) указывается в регистре ah, а код нужной функции — в регистре ai, его значение может изменяться от 0 до 30h. Реально используются не все значения из этого интервала. Описание всей группы можно найти в книгах, в TECH HELP и в других руководствах. Ниже мы рассмотрим только те функции, которые нужны для установки русифицированных таблиц.
Загрузка собственной таблицы
Функции BIOS позволяют загрузить в знакогенератор собственные таблицы выполняемой задачи или таблицы, принадлежащие русификатору. В первом случае используется запрос nooh.
Запрос 11ooh "Load User-defined Font" предназначен для загрузки в знако-генератор таблицы, указанной при обращении к BIOS. Перед изданием запроса в регистры помещаются следующие данные: es:bp— адрес начала загружаемой таблицы в оперативной памяти, сх — количество загружаемых символов, dx — порядковый номер первого символа (начиная с 0), bh — размер рисунка символа в байтах, bl— порядковый номер таблицы в знакогенераторе, который может изменяться от 0 до 7. По умолчанию доступна нулевая таблица, поэтому если вы выберете значение ы отличное от нуля, то для работы с таблицей придется принимать специальные меры.
Размеры символов таблицы должны соответствовать характеристикам установленного
видеорежима, например, для режима VESA I09h они составляют 8x16 точек,
а для режима ЮАЬ — 8x8 точек. Здесь первая цифра указывает ширину, а вторая
высоту символа. Для большинства текстовых режимов ширина символов составляет
8 точек.
Таблица должна располагаться в выделенном для задачи пространстве оперативной
памяти. Если при выполнении задачи таблица загружается в знакогенератор
один раз, то постоянно держать ее в оперативной памяти не целесообразно.
Ее можно хранить в отдельном файле и в нужный момент прочитать, например,
в буфер обмена.
В любом случае при загрузке надо знать полный адрес таблицы (сегмент и смещение в нем). Для хранения этих величин в разделе данных программы выделяются два слова, например:
RsFntl6: dw 0 ; Смещение начала таблицы в указанном ниже
сегменте
dw 0 ; Значение сегмента, в котором расположена таблица
Если расположение таблицы известно при составлении программы, то вместо нулей в директивах указываются конкретные значения сегмента и смешения. В противном случае содержимое обоих слов формируется при выполнении задачи.
Пример загрузки таблицы.
Фрагмент программы, приведенный в примере 5.1, составлен с учетом того, что адрес таблицы указан в переменной RsFntie, а размер символов таблицы составляет 8x16 точек.
Пример 5.1. Загрузка таблицы символов 8x16 в знакогенератор
push es сохранение содержимого es
les bp, dword ptr RsFntl6; es:bp = адрес начала таблицы
mov ex, 256 количество символов в таблице
xor dx, dx смещение первого символа в таблице
mov bh, 16 количество байтов на символ
хог Ы, Ы номер таблицы в знакогенераторе
mov ax, llOOh код запроса "загрузить таблицу"
int 10h выполнение запроса
pop es восстановление содержимого es
В приведенном примере таблица загружается полностью, что, вообще говоря, делать не обязательно. Первая половина любой таблицы содержит латинский алфавит, цифры, знаки препинания и пр., она является стандартной и уже находится в знакогенераторе. Поэтому можно хранить (в файле или в памяти) и загружать в знакогенератор только вторую половину таблицы, содержащую русские буквы, символы псевдографики и несколько специальных символов. Для загрузки второй половины таблицы в примере 5.1 в регистры сх и dx надо помещать значение 128.
Использование таблиц русификатора
Собственная таблица делает выполнение задачи независимым от наличия русификатора на компьютере. Отсутствие русификатора не такая уж редкость, если пользователи не работают в среде DOS, то он просто не нужен.
Если же русификатор установлен, то можно "заставить" его загружать свои таблицы. Русификатор контролирует все запросы, имеющие отношение к таблицам символов. В частности, при попытке загрузить одну из таблиц ROM Bios, он загружает свою (хранящуюся в ОЗУ) таблицу с русскими шрифтами. BIOS позволяет загрузить из ROM три таблицы со шрифтами следующих размеров:
запрос HOlh "Load ROM 8x14 Character Font"; запрос 1102h "Load ROM 8x8 Character Font"; запрос 1104h "Load ROM 8x16 Character Font".Перед изданием этих запросов заполняется только регистр ы, содержащий номер таблицы в знакогенераторе (обычно нуль), все остальные величины функция BIOS формирует самостоятельно. В примере 5.2 приведен фрагмент программы, загружающий таблицу шрифтов 8x16 из ROM.
Пример 5.2. Загрузка таблицы символов 8x16 точек из ROM BIOS
хог bl, bl ; номер таблицы в знакогенераторе
mov ax, 1104h ; запрос "загрузка таблицы ROM 8x16"
int 10h ; выполнение запроса
Данный пример проще примера 5.1 и исключает необходимость хранения таблицы в теле задачи, но при отсутствии установленного русификатора в знакогенераторе окажется англоязычная таблица из ROM BIOS.
При выводе текста, для записи кодов символов в видеопамять, задача может использовать поддержку BIOS и DOS или делать это самостоятельно. Мы опишем оба способа вывода текста на экран, но предварительно обсудим общие особенности программирования работы с текстом.
Из личного опыта работы с компьютером вы знаете, что символы и окружающий их фон могут иметь разные цвета. Например, после загрузки DOS текстовые сообщения выводятся белыми символами на черном фоне, а на обеих панелях оболочки Norton commander фон имеет синий цвет (если разрешена работа с цветом). Таблицы шрифтов не содержат никакой информации о цветах символов и фона, их формирует задача в процессе вывода текста. Покажем, как это делается.
Атрибуты символов предназначены для раскрашивания выводимого на экран текста. В видеопамяти код атрибута располагается после кода символа, т. е. четные байты видеопамяти содержат коды символов, а нечетные — коды их атрибутов. Если слово видеопамяти прочитать в один из регистрон общего назначения, например в ах, то код символа окажется в младшем байте регистра (аx), а код атрибута — в старшем байте (ah).
Для размещения символа на экране всегда выделяется прямоугольная область, которую в литературе принято называть "знакоместо". Размер знакоместа (количество точек по горизонтали и вертикали) зависит от видеорежима. Для стандартных текстовых режимов его размеры составляют 8x8, 8x14 или 8x16 точек. Собственно изображение символа занимает часть знакоместа, в англоязычной документации ее принято называть передним планом (foreground). Свободная часть знакоместа, не занятая рисунком символа, называется задним планом (background), мы будем называть ее фоном. Например, изображение символа "пробел" состоит только из фона.
Байт атрибута рассматривается как три группы независимых разрядов. Младшая
тетрада (разряды 0—3) содержит код цвета точек изображения символа (foreground).
Следующие три разряда (4—6) содержат код цвета фона (background). Старший
(седьмой) разряд байта атрибута управляет миганием точек символа (foreground
flashes). Его установка разрешает, а очистка запрещает мигание
символа на экране.
Коды цветов точек изображения символа могут принимать значения от о до
F, а фона от о до 7. Если задача не изменяла установленную по умолчанию
палитру, то указанным кодам соответствуют цвета стандартной палитры CGA
(см. табл. 4.2). Следовательно,
точки фона могут иметь следующие цвета: черный, синий, зеленый, циан,
красный, фиолетовый, коричневый или белый. Для раскрашивания точек символа
можно дополнительно использовать цвета второй половины табл. 4.2.
Вы можете выбрать другие цвета для раскраски изображения символов и фона, для этого задача должна установить собственную палитру в 16-ти младших регистрах цвета видеокарты. Способы установки палитры не зависят от видеорежима. Используемые для этого функции BIOS описаны в предыдущей главе.
На практике наиболее часто используются атрибуты, имеющие следующие коды:
07 — белые символы на черном фоне; OF — яркие белые символы на черном фоне; IF — яркие белые символы на голубом фоне.В процессе загрузки ПК BIOS заполняет видеобуфер кодами символа "пробел" (20h) и кодами атрибутов 07, поэтому мы видим белые символы на черном фоне.
Таким образом, для раскрашивания выводимого текста вы должны выбрать подходящие коды атрибутов и предусмотреть их запись в видеопамять после кодов символов, к которым они относятся. Если задача записывает в видеопамять только коды символов, то сохраняются те значения атрибутов, которые уже находятся в нечетных байтах.
Управление текстовым курсором. Текстовый курсор — это мигающий символ прямоугольной формы, формируемый видеоконтроллером при работе в текстовых режимах. После загрузки DOS он имеет форму горизонтальной черты, расположенной в двух нижних строчках знакоместа, в которое будет помещен очередной введенный символ. Прикладные задачи могут изменять количество строк в изображении курсора и их расположение в прямоугольнике, но ширину курсора изменить невозможно, она зависит от установленного видеорежима. Кроме того, задачи могут гасить (выключать) курсор, но не могут изменить частоту его миганий.
В соответствии со стандартом VGA IBM в состав видеоконтроллера входят четыре однобайтовых регистра, содержащие следующие характеристики курсора:
регистр 10 (0АЬ) — первая строка рисунка курсора в прямоугольнике; регистр 11 (0Bh) — последняя строка рисунка курсора в прямоугольнике; регистр 14 (0Еh) — старший байт адреса курсора в видеопамяти; регистр 15 (0Fh) — младший байт адреса курсора в видеопамяти.Доступ к этим регистрам производится через порт с адресом 3D4h, если монитор цветной, или с адресом 3B4h, если монитор черно-белый.
В регистрах 10 и И номер строки располагается в 5-ти младших разрядах
(0—4) и может изменяться от 0 до 31. Отсчет строк ведется сверху вниз.
Если, например, высота символов равна 16-ти точкам (стандартная для VGA)
и рисунок курсора занимает две последние строчки прямоугольника, то в
регистрах 10 и 11 находятся коды ОЕb и OFh.
Вам не обязательно программировать работу с портами, хотя в данном случае
это не сложно. Функция 01 прерывания int ioh
предназначена для записи данных в регистры 10 и 11. Перед ее использованием
в регистрах ch и d указываются номера первой и последней строк рисунка
курсора. В регистр ah помещается код запроса (oi)
и выполняется команда int ioh. При выполнении
этой функции новая форма курсора запоминается в слове 0460 области данных
BIOS (см. пример 5.3).
Расположение (позицию) курсора или символа на экране удобнее задавать не в виде адреса, а в виде номера строки и столбца, на пересечении которых он должен находиться. Если строки и столбцы пронумерованы начиная с нуля, то для вычисления адреса слова видеопамяти надо умножить номер строки на количество символов в строке и к произведению прибавить номер столбца. Если нужен номер байта (для записи кода символа), то полученный результат умножается на 2. Количество символов в строке является одной из характеристик видеорежима. BIOS сохраняет эту величину в своей области данных, и в нужных случаях ее используют функции прерывания int 10h. Эту величину можно прочитать из массива info, описанного в главе 2, или из слова 044Ah области данных BIOS (см. пример 5.3).
Задача может самостоятельно пересчитывать координаты в адрес и записывать его в регистры видеокарты (14 и 15). Однако если при выводе текста используется поддержка BIOS, то лучше обратиться к специальной функции 02 прерывания int lOh. Она не только выполняет указанные вычисления, но и сохраняет значения строки и столбца в одном из 8-ми слов, расположенных в области данных BIOS (см. пример 5.3). Сохраненные координаты курсора используются процедурами, выводящими текст на экран. Для вызова функции 02 код запроса (02) записывается в регистр ah, номера строки и столбца, на пересечении которых должен располагаться рисунок курсора указываются в регистрах dh и di, а в регистр bb помещается номер страницы видеопамяти, на которой располагается курсор (см. ниже). После этого выполняется команда int 10h.
Изображение курсора можно удалить с экрана тремя разными способами:
1. Запретить его вывод, указав в регистре 10 код 20h. 2. Записать в регистр 10 код равный высоте символов. 3. Переместить рисунок курсора за пределы рабочей области экрана.Для перемещения курсора за пределы рабочей области его помещают в строку, которая не выводится на экран, но этого лучше не делать по причинам, описанным ниже.
Текстовый курсор является самостоятельным рисунком, а его увязка с процессами
ввода и вывода символов производится программно. При вводе и редактировании
текста курсор нужен для привлечения внимания оператора. Он указывает позицию
на экране, в которую будет помещен очередной символ при вводе с клавиатуры.
Если процесс вывода текста не связан с процессом ввода, то изменение позиции
курсора на экране не несет никакой смысловой нагрузки. Он может указывать
конец выведенной строки, но едва ли такая информация пригодится тому,
кто читает выводимый текст.
Разработчики BIOS использовали курсор для указания позиции, в которую выводится каждый символ текста. Это значит, что при вызове процедур BIOS координаты выводимого символа не указываются явно. В качестве координат процедуры используют текущую позицию курсора, сохраняемую в области данных BIOS. Если же курсор был перемещен за пределы рабочей области, то и выводимый текст окажется там же и не будет виден.
Сейчас трудно судить, почему был выбран такой способ позиционирования при выводе текста. Возможно, в свое время, это решение было оправдано, но постепенно оно превратилось в серьезное препятствие, ограничивающее возможности применения поддержки BIOS.
Если не принять специальные меры, то в текстовых режимах задача работает только с младшей частью сегмента видеопамяти, размер которой зависит от установленного видеорежнма. Для доступа ко всему пространству видеобуфера оно делится на страницы, которые могут использоваться независимо друг от друга.
Создание страниц и их отображение на экране при работе в графических режимах VESA обсуждалось в разделе . В текстовых режимах (неважно IBM или VESA) страницы располагаются в одном сегменте видеопамяти, что существенно упрощает их создание и отображение на экране.
При делении видеопамяти на страницы полный адрес для записи символа состоит из двух частей — точки расположения символа на странице и смещения страницы от начала сегмента видеобуфера. Кроме того, записанный в видеопамять символ будет виден на экране, только в том случае, если он расположен на активной, т. е. отображаемой на экране в данный момент времени, странице.
При установке текстовых видеорежимов BIOS вычисляет необходимый для работы объем видеопамяти. Если режим позволяет выводить на экран м строк, содержащих по N символов каждая, то для хранения кодов символов вместе с атрибутами требуется M*N*2 байта видеопамяти. Для определения размера страницы это произведение увеличивается до ближайшего значения, кратного числу 4096, и сохраняется в слове 044Ch области данных BIOS. В зависимости от видеорежима страница может занимать 4096, 8192 или 16384 байта. Соответственно, в сегменте видеопамяти может располагаться 16, 8 или 4 страницы.
Для хранения позиции курсора на каждой странице в области данных BIOS (см. пример 5.3) выделяется слово, содержащее номер строки (старший байт) и столбца (младший байт), на пересечении которых он расположен. Всего в области данных зарезервировано восемь слов, в соответствии с количеством страниц, поддерживаемых BIOS. В исходном состоянии эти слова очищены, что соответствует расположению курсора в начале каждой страницы.
Номер страницы указывается при вызове всех функций прерывания int 10h, выполняющих вывод символов или строк, но фактически функции 09, ОАЬ и ОЕb прерывания int 10h размещают выводимые символы на нулевой странице. Реально номер страницы использует только функция I3h. По нему она определяет текущее положение курсора на странице и пересчитывает его в относительный адрес (адрес на странице). Затем номер страницы умножается на ее размер и получается смещение начала страницы в видеобуфере. Сумма этих двух величин и является адресом, начиная с которого записываются выводимые символы.
Страница является активной, если ее содержимое отображается на экране. В соответствии со стандартом VGA IBM в состав видеоконтроллера входят два однобайтовых регистра, содержащих адрес видеопамяти, начиная с которого ее содержимое выводится на экран. В регистре 12 (ось) хранится старший байт этого адреса, а в регистре 13 (ось) — младший. Доступ к этим регистрам осуществляется через порт 3D4h, если монитор цветной, или 3B4h, если он черно-белый.
После включения или перезагрузки компьютера BIOS очищает указанные регистры, поэтому активной является нулевая страница, расположенная в начале сегмента видеобуфера. Для изменения отображаемой области надо записать в регистры 12 и 13 новый адрес видеопамяти.
Задача может самостоятельно изменять адрес начала отображаемой области, но если при выводе текста используется поддержка BIOS, то для смены активной страницы лучше вызывать функцию 05 прерывания int 10h. Перед ее вызовом в регистре ai указывается номер новой активной страницы, а в регистре ah — код 05. Эта функция извлекает размер страницы из слова 044сь области данных BIOS, умножает его на указанный номер и результат помещает в регистры 12 и 13. На экране появляется содержимое новой страницы. Кроме того, функция записывает в слово 044Eh (пример 5.3) смещение активной страницы от начала сегмента видеобуфера.
Фрагмент области данных BIOS
При общей характеристике BIOS в разделе говорилось, что ее процедуры используют пространство оперативной памяти, называемое областью данных BIOS. Оно расположено в нулевом сегменте оперативной памяти ПК, начиная с адреса 400h. В частности, при выводе символов на экран функции прерывания int 10h используют следующие слова и байты, расположенные в области данных.
Пример 5.3. Фрагмент области данных BIOS
0000: 0449 - (байт) установленный видеорежим;
0000: 044А - (слово) количество столбцов (размер строки) ;
0000: 044С - (слово) размер страницы в байтах;
0000: 044Е - (слово) смещение активной страницы от начала видеобуфера;
0000: 0450 - (8 слов) позиции курсора на восьми страницах видеопамяти;
0000: 0460 - (слово) номера первой и последней строк рисунка курсора;
0000: 0462 - (байт) номер активной страницы;
0000: 0463 - (байт) базовый адрес видеоконтроллера (3D4h или 3B4h) ;
0000: 0484 - (байт) номер последней строки на экране;
0000: 0485 - (слово) размер рисунка символа в байтах.
Перечисленные в этом примере величины могут пригодиться в тех случаях, когда вы программируете вывод текста без использования функций BIOS.
Проще всего вывести строку текста на экран с помощью специальной функции DOS, имеющей код 09, поэтому этот способ часто встречается на практике. Перед обращением к DOS адрес начала строки помещается в регистры ds:dx, а код запрашиваемой функции (оэ) — в регистр ah, после чего вызывается программное прерывание int 2in, которое выполняет обращение к DOS.
Предположим, для определенности, что выводимый текст хранится в разделе данных и оформлен одним из следующих способов:
coiranun db 'Проверка возможности вывода текста в режиме
SVGA$'
commun db 'Проверка возможности вывода текста в режиме SVGA', 24h
Для функции оэ признаком конца строки является код 24п, которому обычно
соответствует изображение знака доллара $. В первой из двух приведенных
строк он записан в виде символа, а во второй — в виде кода. Если вы забудете
указать признак конца строки, то поведение компьютера при выводе текста
будет непредсказуемо.
Для вывода строки commun в нужное место текста задачи включаются три команды,
приведенные в примере 5.4.
Пример 5.4. Вывод строки на экран с помощью функции DOS
lea dx, commun ; помещаем в dx адрес начала строки
mov ah, 09 ; указываем код функции DOS
int 21h ; обращаемся к DOS
Функция DOS записывает только коды символов в четные байты видеопамяти, поэтому цвет символов и фона зависит от значений атрибутов, уже находящихся в нечетных байтах видеобуфера.
Текст будет обязательно виден на экране, поскольку он помещается на активную страницу, начиная с позиции, в которой находится курсор. После вывода курсор перемещается в конец текста.
Замечание
Текст направляется на стандартное устройство вывода, которым по умолчанию
является дисплей. Задача может изменить установленное по умолчанию устройство,
выбрав, например, принтер или дисковод. В таком случае текст будет напечатан
на принтере или записан в файл.
Описанная функция лучше всего подходит для вывода на экран заранее заготовленного текста. Заготовки располагают в разделе данных программы и оформляют с помощью директивы db, перед которой указывается метка, подобно тому, как оформлена приведенная выше строка commun. Текст заключается в одинарные или двойные кавычки, коды управляющих символов указываются явно и отделяются друг от друга и от заключенного в кавычки текста запятыми. В качестве управляющих символов могут использоваться "возврат каретки" (0dh), "перевод строки" (0Ah), "табуляция" (09) и др. Например, для привлечения внимания оператора в выводимую строку можно включить код звукового сигнала (07), при его исполнении встроенный динамик ПК издаст гудок. Ограничения на размер выводимого текста нет, если для его размещения на экране не хватит одной строки, то продолжение будет перенесено на следующую.
Поддержка вывода BIOS
Для более гибкого управления процессом вывода текста на экран предназначены функции BIOS, входящие в группу "video Services" (int 10h) и выполняющие следующие действия:
09h — вывод символа и атрибута без перемещения курсора, страница 0; 0Аh — вывод символа без атрибута без перемещения курсора, страница 0; 0Еh — вывод символа без атрибута с перемещением курсора, страница 0; 13h — вывод строки символов с атрибутами на указанную страницу.Перед вызовом функции 09 и ОАh в регистры записываются следующие величины: в ah — код функции (09 или OAh); в ai — код выводимого символа (ASCII); bb — не используется; в сх — количество повторов символа; bl — код атрибута, который нужен только для функции 09.
Вывод повторяющихся символов
Функции 09 и 0Ah хорошо подходят для вывода повторяющихся символов. В примере 5.5 приведен фрагмент программы, рисующий горизонтальную линию, в которой 132 раза повторяется один из символов псевдографики, имеющий код oc4h (или 196).
Пример 5.5. Построение горизонтальной линии с помощью функции 0Ah
mov ah, OAh ; код запрашиваемой функции BIOS
mov al, OC4h ; код ASCII символа "-"
mov ex, 132 ; число повторений символа
int lOh ; обращение к группе "Video Services"
При выполнении функции OAh записываются только коды символов в четные байты видеопамяти, поэтому цвет линии и фона, на котором она нарисована, зависит от значений ранее записанных атрибутов.
Если вам надо вывести символы вместе с атрибутами, то измените в примере 5.5 код функции на 09 и добавьте команду, записывающую в регистр bl нужный код атрибута. Например, для того чтобы на голубом фоне нарисовать белую линию, атрибут должен иметь код iFh.
Функции 09 и OAh не изменяют позицию курсора, поэтому их неудобно использовать при выводе строки текста. В этом случае вам придется хранить в области данных номера строки и столбца, соответствующие текущим координатам курсора, корректировать их после вывода каждого символа, а перед выводом символа обращаться к BIOS для перемещения курсора в позицию, соответствующую этим координатам.
Вывод строки текста
Для вывода последовательности символов лучше использовать функцию ОЕЬ, которая после записи кода символа в четный байт видеопамяти перемещает курсор вперед на следующую позицию на экране и корректирует сохраняемые в области данных BIOS текущие координаты курсора на используемой странице (см. пример 5.3).
Перед вызовом функции OEh в регистры записываются
следующие величины:
в ah — код функции (ОЕЬ); в ai — код выводимого
символа (ASCII); bh — не используется, bl—
атрибут (только для графических режимов).
Для вывода строки организуется цикл обращений к функции OEh. Управлять его повторами можно с помощью счетчика или повторять процесс вывода до обнаружения в строке специального признака, например символа $, или пустого байта (строка формата ASCIIZ).
В примере 5.6 цикл организован с использованием счетчика, в который перед входом в цикл помещается размер строки commun.
Пример 5.6. Вывод строки с использованием функции BIOS OEh
lea si, commun ; указываем адрес начала строки
mov ex, 48 ; задаем количество символов в строке
lp: lodsb ; читаем в al очередной символ строки
mov ah, OEh ; код запрашиваемой функции
int 10h ; вывод очередного символа
loop lp ; управление циклом
Раскрашивание текста.
BIOS не содержит специальных функций, изменяющих только атрибуты символов, но сочетание функций 09 и ОЕЬ иногда позволяет раскрашивать символы и фон в нужные цвета. Рассмотрим два примера, иллюстрирующих сказанное.
При однократном обращении к функции 09 можно очистить и окрасить выбранным
вами цветом все рабочее пространство экрана монитора. Для этого надо переместить
курсор в начало активной страницы и заполнить ее отображаемую часть кодами
символа "пробел" и атрибута, соответствующего цвету символов
и фона. Как это можно сделать, показано в примере 5.7, рассчитанном на
выполнение при установленном режиме VESA I09h.
Пример 5.7. "Заливка" экрана синим цветом
mov ах, 920h ; ah = код функции, al = код символа "пробел"
mov bx, IFh ; bh = О, Ы = код атрибута
mov сх, 132*25 ; сх = количество символов 132*25 = 3300
int 10h ; обращение к BIOS
При выполнении примера 5.7 в нулевую страницу видеопамяти, начиная с позиции, соответствующей текущим координатам курсора, будет записано 3300 слов, каждое из которых содержит код iF20h. Если нулевая страница видеопамяти является активной, а курсор находится в ее левом верхнем углу, то все рабочее пространство экрана будет очищено от находившихся там символов и окрашено в синий цвет. После этого символы, выводимые на экран с помощью функции ОЕЬ, будут окрашены в белый цвет (напомним, что код атрибута iFh соответствует белым символам и синему фону). Исходные координаты курсора не изменяются.
В том случае, когда надо выделить заданным цветом одно слово или фразу, можно использовать подпрограмму, приведенную в примере 5.8. Перед обращением к ней в регистрах ds:si указывается адрес начала строки в оперативной памяти, а в сх — количество символов в строке, В регистре ы помещается код атрибута, ьь не используется.
Пример 5.8. Подпрограмма для вывода символов и атрибутов
Coiortxt: mov ax, 920h ah = код функции, al = код символа
"пробел"
int 10h закрашивание нужного пространства
Outsym: lodsb al = код очередного символа, si = si + 1
mov ah, OEh код функции BIOS
int lOh вывод очередного символа
loop Outsym управление циклом
ret возврат из подпрограммы
В примере 5.8 используется тот факт, что для функции 09 количество повторов указывается в регистре сх, а при ее выполнении позиция курсора не изменяется. При выполнении функции 09 в видеопамять записывается код символа "пробел" вместе с указанным в регистре ы атрибутом. Затем в цикле, имеющем метку Outsym, на то же место выводится текст, который будет окрашен в соответствии с атрибутом, уже записанным в видеопамять.
Позиционирование текста
Для расположения текста в нужном месте экрана можно использовать описанную в
Если отвлечься от вспомогательных действий, то функции 09 и OAh вычисляют адрес видеобуфера, используя номера страницы, строки и столбца, и записывают по этому адресу либо код символа (0Ah), либо код символа и атрибут (09). Эти действия достаточно просты и могут выполняться задачей без обращения к функциям BIOS. В таком случае существенно сокращается время, затрачиваемое на обмен с буфером, и появляется возможность более гибкого управления процессом вывода текста на экран. По этой причине в большинстве руководств по программированию на языке ассемблера подробно рассматриваются способы прямой работы с видеобуфером и курсором без обращения к BIOS.
Следует также подчеркнуть, что существует определенная категория задач, которые по тем или иным причинам не должны использовать поддержку DOS или BIOS. В частности, если задача работает со страницами видеопамяти, то для вывода символов нельзя использовать функции 09, 0Ah и 0Еh прерывания int 10h.
Преимущества непосредственной работы с видеопамятью по сравнению с использованием функций BIOS заключаются в следующем:
1. При выводе текста вычисляется адрес только первого символа. Все последующие адреса на 2 больше предыдущих. 2. Существует возможность раскрашивания уже находящегося на экране текста. Для этого надо просто записать новые значения атрибутов в нечетные байты видеопамяти, не изменяя коды символов текста. 3. Возможны ввод, вывод и редактирование текста, находящегося на любой странице видеопамяти.Вычисление адреса по координатам. Расположение текста на экране удобно задавать в виде номеров строк и столбцов. Хранить значения строки и столбца можно в словах и байтах области данных BIOS (см.
Основные особенности графических режимов, имеющие непосредственное отношение к работе с текстом, заключаются в следующем:
видеобуфер располагается в сегменте доооь (а не B800h); в видеопамяти находятся коды цветов точек, а не символов; выключен знакогенератор, преобразующий коды символов в рисунки; выключена аппаратная поддержка работы с текстовым курсором.Из этого перечня следует, что после установки графических режимов (как VESA, так и IBM) изображения символов и курсора на экране должны рисовать специальные подпрограммы. При работе с текстом в графических режимах IBM можно использовать поддержку BIOS, но в режимах VESA задача должна самостоятельно выполнять все необходимые действия.
Для вывода текста на экран нужен набор заготовок рисунков всех используемых символов. Обычно эти заготовки хранятся в специальных таблицах символов. Структура стандартных таблиц не зависит от режима, в котором они будут использоваться. При описании текстовых видеорежимов (см.
В данном разделе будет описана подпрограмма, которая по коду ASCII выбирает из таблицы соответствующую заготовку и рисует изображение символа на экране. В технической литературе для обозначения подобных подпрограмм используется термин "программный знакогенератор". Подпрограмма будет предназначена для выполнения в видеорежимах PPG. Однако, учитывая ее значимость, мы специально обсудим те изменения, которые позволят выводить текст при работе в видеорежимах direct color.
Структура заготовки символа (
В процессе выполнения графических программ на экран могут выводиться информационные строки, которые делятся на две основные категории.
К первой категории относятся напоминания о назначении различных значков, находящихся на экране, не требующие конкретной реакции оператора. Например, одно из подобных сообщений, выдаваемых Windows 9X, выглядит так:
"Начните работу с нажатия этой кнопки".
Обычно такие строки через некоторое время удаляются с экрана.
К другой категории относятся подсказки, требующие от оператора выполнения конкретных действий, например ввода числовых величин, спецификаций файлов и т. п. Такие строки остаются на экране до тех пор, пока оператор не выполнит требуемое действие.
В данном разделе мы рассмотрим общую схему вывода информационных строк, а в следующих — программирование ввода текста в ответ на подсказку оператору. При изложении материала нас будет интересовать вывод отдельных строк, а не больших объемов текста, именно поэтому в заголовке раздела использовано выражение "информационная строка". При работе с текстами большего объема применяются совершенно другие приемы.
Прежде чем выводить символ, надо решить, в каком месте экрана он будет располагаться, и определить, какому участку видеопамяти соответствует это место. Способы указания координат точек и вычисления их адресов при работе в графических видеорежимах описаны в разделе. Здесь мы рассмотрим конкретные примеры.
Где бы ни располагалась информационная строка для работы, надо знать адрес ее начала в видеопамяти и соответствующее ему окно. При построении изображения символа знакогенератор выводит точки на экран в том порядке, в котором их коды хранятся в таблице, а именно слева направо и сверху вниз. Поэтому для вывода текста надо знать адрес верхнего левого угла первого символа информационной строки. Для его хранения в разделе данных программы надо описать две следующие переменные:
Inflino dw 0; для хранения адреса начала информационной
строки
Inflinw dw 0; для хранения окна, к которому относится этот адрес
Если информационная строка расположена в начале рабочей области экрана, то надо просто очистить указанные переменные при их описании.
Если расположение строки связано с расположением курсора на экране, например, курсор указывает на значок, назначение которого надо пояснить, то в описанные переменные просто копируется текущий, или несколько измененный, адрес изображения курсора. Текущие координаты курсора и адрес его изображения на экране нужны во многих случаях, поэтому обычно они хранятся в переменных, зарезервированных в разделе данных задачи.
Подсказки о необходимости ввода данных удобно располагать в последних строках рабочей области экрана. Количество и размер строк на экране зависят от установленного видеорежима, поэтому номера и адреса последних строк рабочей области экрана вычисляются при выполнении задачи.
Если верхнюю линию изображения текста поместить в строку с номером (versize - hsymb), то его нижняя линия совпадет с нижней границей рабочей области экрана.
Замечание
Напомним, что переменные Horsize и versize содержат соответственно размер
строк и их количество на экране, а переменная hsymb— высоту символа (см.
При вводе и редактировании текста, для указания позиции вводимого или изменяемого символа, традиционно используется курсор. В графических режимах аппаратная поддержка текстового курсора выключена, поэтому применяется "программный" курсор. В данном разделе мы рассмотрим способ построения мигающего текстового курсора, а его использование при вводе символов с клавиатуры будет описано в следующем разделе.
При работе в графических режимах на экране югут находиться рисунки двух курсоров, один из которых указывает теку-цее положение манипулятора "мышь", а второй — место вводимого или изменяемого символа. Главную роль играет "указатель мыши", он нужен для 'правления процессом выполнения задачи и, в частности, для изменения юзиции текстового курсора. Указатель мыши может перемещаться по всей шбочей области экрана. В отличие от него текстовый курсор появляется только в определенных местах, например в диалоговых окнах.
Для работы с текстом в графических режимах на экране выделяются специ-льные строки или окна, размеры которых зависят от их назначения. Как правило, они не велики и предназначены для ввода различных установочных данных — числовых величин, зарезервированных (ключевых) слов, спецификаций файлов и т. п. Только у специализированных редакторов текстовые окна занимают большую часть экрана или весь экран.
Windows и ее приложения работают с текстом в черно-белом режиме — на елом фоне изображаются черные буквы. Текстовый курсор обычно имеет форму мигающей вертикальной черты, цвет которой совпадает с цветом укв. Высота черты зависит от высоты шрифта, а ширина составляет одну ли две точки. Рассмотрим, как программируется подобный рисунок тек-тового курсора (далее по тексту просто "курсора").
Способ построения курсора
В процессе редактирования текста изображение урсора может перемещаться по строкам и располагаться на месте уже выеденных символов. Поэтому надо принять специальные меры, для того тобы при перемещении курсор не искажал изображение символов текста, апример сохранять рисунок расположенного под курсором символа и вос-танавливать его после перемещения курсора. При работе с текстом обычно спользуют более простой способ, при котором для записи кодов точек ри-унка курсора в видеопамять используется команда хоr.
Двухадресная команда хоr вычисляет логическую функцию exclusive OR исключающее ИЛИ), ее операндами являются источник и приемник, результат помещается в приемник. При выполнении инструкции хоr микропроцессор запрещает перенос единиц переполнения из младших разрядов старшие и производит поразрядное сложение операндов. Результат выполнения операции для одного бита показан в табл. 5.1.
Таблица 5.1. Схема выполнения операции хоr
Состояние бита источника | 0 | 0 | 1 | 1 |
Состояние бита приемника | 0 | 1 | 0 | 1 |
Состояние бита результата | 0 | 1 | 1 | 0 |
Oбратите внимание на последний столбец таблицы. Если состояние всех разрядов у приемника и источника одинаково, то в результате получится нуль, т. е. приемник будет очищен. Это свойство команды хоr мы неоднократно использовали в примерах для очистки регистров. Здесь нас интересует ее другое свойство.
Если у источника установлены все разряды, то установленные разряды приемника будут очищены, а очищенные — установлены, т. е. код приемника будет инвертирован. При повторной инверсии произойдет восстановление исходного кода приемника. Таким образом, хоr позволяет инвертировать код приемника, а затем вернуть его первоначальное значение.
Использовать это свойство для построения текстового курсора можно только при определенных ограничениях. Напомним, что в режимах PPG код точки является номером регистра цвета видеокарты. Поэтому, инвертируя код точки, мы изменяем номер регистра видеокарты, а получаемый при этом цвет зависит от того, что записано в этом регистре, т. е. от установленной палитры цветов. При описании системной палитры в разделе мы рекомендовали размещать код черного цвета в регистре 0, а белого — в регистре OFFh. В таком случае при инверсии кода точки будет инвертирован и ее цвет.
Точки изображения символа имеют черный цвет, если рисунок курсора затрагивает эти точки, то они станут белыми. Для уменьшения наложения рисунку курсора придают форму узкой вертикальной черты, расположенной в начале или в конце знакоместа. Например, у редактора Microsoft Word ширина курсора составляет 2 точки. Первая из них расположена в конце одного знакоместа, а вторая — в начале следующего, в которое будет помещен введенный с клавиатуры символ. При таком расположении курсор не закрывает основной рисунок символа.
Подпрограмма TglCrsr. В примере 5.24 приведен текст подпрограммы, изменяющей состояние курсора на противоположное. При первом обращении изображение курсора появляется на экране, а при втором — удаляется с экрана. Оно имеет форму вертикальной линии, расположенной в двух левых столбцах знакоместа. Высота линии равна высоте символа, а ширина составляет две точки.
Подпрограмма рассчитана на то, что для вывода символов на экран используется знакогенератор из примера 5.19. Поэтому адрес видеопамяти, соответствующий верхнему левому углу рисунка курсора, выбирается из регистра di, а код цвета фона и высоту символов задают переменные grndcoi и hsymb (см. пример 5.18). При коррекции адреса видеопамяти используется переменная bperiine, описанная в примере 2.11, она равна значению Horsize, умноженному на размер кода точки в байтах (1—4).
Пример 5.24. Подпрограмма изменения состояния текстового курсора
TglCrsr: PushReg <ax,ex,di,Cur_win>; сохранение в
стеке
call Setwin ; установка исходного окна
mov al, grndcol ! i al = код цвета фона
mov ex, hsymb ex = высота символов
Tcrsr: xor es:[di], al !! изменяем код первой точки
xor es:[di+1], al !! изменяем код второй точки
add di, bperline коррекция видеоадреса
jnc @F => адрес в пределах сегмента
call Nxtwin установка следующего окна
@@: loop Tcrsr управление циклом
PopReg <Cur_win,di,ex,ax> ; восстановление из стека
ret завершение работы подпрограммы
Выполнение подпрограммы примера 5.24 начинается с сохранения в стеке тех величин, значения которых могут измениться, и установки исходного окна. После этого в регистр al копируется код цвета фона, а в сх — количество строк в символе.
Цикл изменения состояния курсора имеет метку Tcrsr. Его первые две команды изменяют состояние двух первых точек очередной строки рисунка. Затем вычисляется адрес следующей строки, если при этом вырабатывается признак переполнения, то производится смена окна видеопамяти. Команда loop повторяет выполнение цикла, пока не будут изменены все строки. После этого восстанавливаются сохраненные в стеке величины и происходит возврат на вызывающий модуль.
Исходный текст примера 5.24 рассчитан на выполнение в видеорежимах PPG. Комментарий к командам, зависящим от видеорежима, начинается с двух восклицательных знаков. При работе в режимах direct color используйте варианты переменных команд, приведенные в табл. 5.2.
Таблица 5.2. Варианты переменных команд для примера 5.24
Режимы PPG |
Режимы Hi-Color |
Режимы True Color |
mov al, grndcol |
mov ax, grndcol |
mov eax, grndcol |
xor es:[di], al |
xores:[di], ax |
xor es:[di], eax |
xor es:[di+1], al |
xor es:[di+2], ax |
xor es:[di+4], eax |
Мигающий курсор
Текстовый курсор обычно мигает, т. е. его изображение периодически появляется и исчезает. Для получения эффекта мигания надо вызывать подпрограмму TglCrsr через равные промежутки времени, например через 0,5 сек, как это делают Windows и ее приложения.
При управлении курсором высокая точность измерения времени не требуется, поэтому можно использовать таймер, который "тикает" через каждые 55 миллисекунд, или 18,2 раза в секунду. Для выдержки паузы надо дождаться пока от таймера поступит нужное количество .тиков с момента начала паузы. Вопрос лишь в том, как узнать, что таймер "тикнул".
В области данных BIOS, начиная с адреса 0000:046с, зарезервировано 4 байта, содержащих 32-разрядный счетчик количества тиков. BIOS очищает счетчик при первоначальной загрузке, после чего его значение увеличивается на 1 с каждым тиком таймера. Для выдержки паузы надо запомнить исходное значение счетчика, в начале паузы, а затем время от времени сравнивать текущее значение с исходным. Пауза закончится, когда их разность достигнет нужного значения.
Необходимость периодически опрашивать состояние счетчика тиков является недостатком такого способа, поэтому он применяется только в тех случаях, когда задача ничего не делает во время паузы. В общем случае работа с таймером происходит в режиме прерываний. Для этого вам надо составить подпрограмму, которая будет выполняться при каждом тике таймера. О том, как ее составить, мы поговорим особо, а сначала рассмотрим, как сделать, чтобы она выполнялась при каждом тике таймера.
Перехват прерываний от таймера
Каждый тик таймера вызывает так называемое аппаратное прерывание. Текущий процесс вычислений приостанавливается и выполняется специальная процедура BIOS. Она обслуживает процессы, синхронизированные с таймером, и вызывает подпрограмму, адрес начала которой указан в векторе ich. Этот вектор специально выделен для нужд прикладных задач. Сразу после загрузки ПК в нем находится адрес команды iret, расположенной в ROM BIOS. Если в прикладной задаче есть подпрограмма, выполнение которой должно быть синхронизировано с таймером, то ее адрес указывается в векторе ich.
Исходное значение вектора ich надо сохранить в теле задачи. Оно нужно для того, чтобы после вашей подпрограммы можно было начать выполнение той программы (внешней по отношению к задаче), адрес которой находился в векторе ich до изменения его содержимого. Такой трюк в литературе называется "перехват вектора прерывания", он широко используется при работе прикладных задач с внешними устройствами.
Для изменения содержимого вектора надо знать его адрес. Векторы пронумерованы начиная с нуля, каждый из них занимает 4 байта оперативной памяти, а нулевой вектор расположен по адресу 0000:0000. Следовательно, умножив номер вектора на 4, мы получим его адрес в оперативной памяти. В нашем случае 4*ich = 70h (4-28 = 7-16 = 112).
В примере 5.25 приведена группа команд, выполняющих перехват вектора ich, неизвестные вам имена переменных описаны ниже в примере 5.27.
Пример 5.25. Сохранение и изменение содержимого вектора 1Ch
хоr ах, ах ; очистка регистра ах
mov CurStat, al ; запрет построения рисунка курсора
mov fs, ax ; очистка сегментного регистра fs
lea ax, cs:Timeint ах=адрес прерывающей подпрограммы
mov bx, cs bx=сегмент прерывающей подпрограммы
cli запрещаем прерывания
xchg fs:[7Oh], ax перестановка содержимого ах и 7Oh
xchg fs:[72h], bx перестановка содержимого bx и 72h
mov cs:VeclC, ax \/ес1С=исходное значение слова 70h
mov cs:VeclC+2, bx Уес1С+2=исходное значение слова 72h
sti разрешаем прерывания
Перехват вектора ich производится в начале выполнения задачи, но после того, как подготовлено все необходимое для корректной работы прерывающей подпрограммы. В нашем случае имя прерывающей подпрограммы Timeint, а для ее корректной работы надо запретить построение рисунка текстового курсора. Для этого вторая команда примера очищает переменную CurStat.
Для доступа к словам вектора прерывания очищается один из сегментных регистров, в примере 5.25 это регистр fs, его очищает третья команда. Затем в регистры ах и bx записываются адрес прерывающей подпрограммы и сегмент, в котором она находится. Прежде чем изменять содержимое вектора, надо запретить прерывания. Это делается потому, что выполнение задачи никак не синхронизировано с таймером и прерывание от последнего может произойти в тот момент, когда задача начала, но еще не завершила изменение и запоминание содержимого вектора ich.
Прерывание запрещает команда cli, после нее производится обмен содержимого (xchg) слов вектора и регистров ах и bx. В результате в словах вектора ich окажется новый, а в регистрах ах и bx старый адрес, который надо запомнить. Следующие две команды пересылают старый адрес в слова vecic и vecic+2, после чего команда sti разрешает прерывания.
Восстановление вектора прерывания
Перед завершением задачи восстанавливается исходное значение вектора ich, т. е. подпрограмма Timeint исключается из списка заданий таймеру. Если это не сделать, то при первом же тике таймера произойдет обращение к области памяти, в которой уже нет прерывающей подпрограммы, что приведет к аварийной ситуации.
В примере 5.26 приведена группа команд, выполняющих восстановление исходного значения вектора ich. Эти команды могут быть выполнены непосредственно перед завершением задачи, т. е. перед возвратом в DOS.
Пример 5.26. Восстановление исходного содержимого вектора 1Ch
хоr ах, ах ; очистка регистра- ах
mov fs, ax ; очистка сегментного регистра fs
mov ax, cs:VeclC ; ax = содержимое VeclC
mov bx, cs:VeclC+2 ; bx = содержимое VeclC+2
cli ; запрещаем прерывания
mov fs: [70h] , ax ; восстановление 1-го слова вектора
mov fs: [72h] ,bx ; восстановление 2-го слова вектора
sti ; разрешаем прерывания
В примере 5.26 для доступа к словам вектора используется регистр fs, поэтому его содержимое предварительно очищается. Затем в регистры ах и bx копируются первое и второе слово сохраненного ранее вектора ich. После запрещения прерываний содержимое регистров ах и bx копируется в слова 70h и 72h, и разрешаются прерывания. Вектор восстановлен, и можно завершать выполнение задачи.
Замечание
Для работы с векторами прерываний предназначены две специальные функции
DOS (прерывания int 2lh). Функция Get Vector (код 35h) читает содержимое
вектора, a Set vector (код 25h) записывает в вектор новое содержимое.
Однако их применение просто не оправдано, в чем вы можете убедиться самостоятельно.
Прерывающая подпрограмма подсчитывает
количество тиков таймера и, как только оно будет равно 9 (примерно через
каждые 0,5 сек), изменяет текущее состояние курсора. Рисунок курсора находится
на экране, только если задача работает с текстом. Поэтому необходим специальный
признак, разрешающий или запрещающий изменение состояния курсора. Кроме
того, нужен признак, позволяющий узнать, в каком состоянии находится курсор
— включен или погашен (виден или не виден на экране).
Для хранения счетчика тиков и признаков в разделе данных задачи надо зарезервировать
две однобайтовые переменные, имеющие следующие имена:
Ntick db 9 ; счетчик тиков таймера изменяется от 0 до 9 CurStat db 0 ;
текущее состояние курсора изменяется от 0 до 3.
В байте CurStat используются только два младших бита. Нулевой бит разрешает (1) или запрещает (0) изменение состояния (мигание) курсора, он устанавливается в основной программе. Первый бит отражает текущее состояние курсора на экране: 0 — выключен, 1 — включен. Им управляет прерывающая подпрограмма, текст которой приведен в примере 5.27.
Пример 5.27. Подпрограмма, создающая эффект мигающего курсора
Timeint: PushReg <ax,ds,es>; !! сохранение регистров
ах, ds и es
mov ax, data ; ! ! ах = значение сегмента данных
mov ds, ах ; ! ! ds = код сегмента данных
mov es, VBuff ; ! ! es = код сегмента видеобуфера
test CurStat, 01 работа с курсором разрешена ?
jz GoVIC -> нет, завершение подпрограммы
dec Ntick Ntick = Ntick — 1
jnz GoVIC -> пауза продолжается
mov Ntick, 09 Ntick = 9 (примерно 0,5 сек)
xor CurStat, 02 изменение признака состояния курсора
call TglCrsr изменение состояния рисунка курсора
GoVIC: PopReg <es,ds,ax> !! восстановление es, ds и ax
db OEAh код инструкции jmp, на удаленный адрес
VeclC dw 00, 00 старое содержимое вектора ICh
Прежде всего отметим, что подпрограмму примера 5.27 нельзя вызывать командой call Timeint, поскольку ее выполнение завершается не командой ret, а безусловным переходом на тот адрес, который раньше находился в векторе ich. Подпрограмму вызывает процедура BIOS, обрабатывающая задания, адресованные таймеру. Действия, которые надо выполнить для разрешения или запрещения вызовов данной подпрограммы при каждом тике таймера, показаны в примерах 5.25 и 5.26.
При вызове прерывающей подпрограммы в стеке сохраняется содержимое регистров ах, ds и es. В общем случае при входе содержимое регистра ds не определено, и в него надо записать код сегмента данных. Имя сегменту данных присваивается при его описании (см. пример 2.11), обычно это data. Если вы выбрали другое имя, то измените команду mov ax, data. Регистр es используется в подпрограмме TglCrsr, изменяющей текущее состояние рисунка курсора, поэтому он должен содержать код сегмента видеобуфера, который хранится в переменной vbuff.
Основные действия, выполняемые в примере 5.27, достаточно просты. Проверяется состояние младшего разряда байта CurStat, если он очищен, то команда jz GOVIC завершает выполнение подпрограммы, в противном случае работа с рисунком курсора разрешена. Содержимое счетчика тиков уменьшается на 1 и если разность больше нуля, то пауза не закончена и происходит выход из подпрограммы без изменения рисунка курсора. Наконец, если разность равна нулю, то в счетчик тиков записывается число 9, и инвертируются рисунок курсора и признак его состояния.
Перед выходом из подпрограммы восстанавливается сохраненное в стеке содержимое регистров es, ds и ах, а затем выполняется команда jmp, которая передает управление на адрес, сохраненный в двух словах переменной vecic (при выполнении примера 5.25).
В тексте примера 5.27 использован следующий трюк. Имя команды jmp не записано явно, вместо этого в байте, расположенном перед переменной vecic, указан код операции (ОЕАЬ). При ее выполнении микропроцессор интерпретирует содержимое следующих двух слов как адрес, на который производится переход. Это наиболее простой, но не единственно возможный способ вернуться на сохраненное значение вектора прерывания.
Замечание
Если при выполнении вашей задачи содержимое сегментных регистров ds и
ез не изменяется после их первоначальной установки, то из текста примера
5.27 можно исключить 5 команд, комментарий к которым начинается с двух
восклицательных знаков.
Для иллюстрации способов работы с описанными подпрограммами мы рассмотрим ввод текста в специально выделенный буфер строки. Такой буфер нужен для того, чтобы оператор мог исправить допущенные им ошибки до того, как задача начнет обрабатывать введенную строку. Обычно содержимое буфера становится доступным для дальнейшей обработки после того, как оператор нажмет клавишу <Enter> ("возврат каретки").
Работа клавиатуры никак не связана с текущим видеорежимом, но от него зависит способ отображения вводимых символов на экране (эхо-печать).
При нажатии и отпускании любой клавиши контроллер клавиатуры генерирует аппаратное прерывание, при этом прекращается выполнение текущего вычислительного процесса и вызывается специальный драйвер, расположенный в ROM BIOS. Он считывает код введенного символа (scan code), преобразует его в код ASCII и помещает оба кода в специальный буфер, расположенный в области данных BIOS, начиная с адреса оооо:041Е. Последующая обработка введенного символа, включая вывод его изображения на экран, находится вне компетенции драйвера.
scan code — это просто порядковый номер нажатой или отпущенной клавиши, для его преобразования в код ASCII надо учитывать состояние одной или нескольких функциональных клавишей. Например, scan code lEh соответствует сразу четырем буквам — латинским "А", "а" и русским "Ф", "ф", в то время как ASCII коды этих букв равны, соответственно, 4ih, 6ih, 94h и OE4h.
BIOS работает только с латинским алфавитом, для ввода с русских букв нужны специальные программы русификаторы, например Keyrus, или драйверы, входящие в состав русифицированных версий Windows. Они перехватывают аппаратное прерывание от клавиатуры и формируют коды русских букв. Причем коды русских букв зависят от используемой кодовой таблицы, а их не так уж мало.
Задача может считывать введенные с клавиатуры символы как в режиме прерываний, так и путем опроса состояния буфера, расположенного в области данных BIOS. В первом случае задача должна перехватывать вектор прерывания 09, подобно тому, как это делалось для вектора ich. Нас интересует режим опроса. В этом режиме задача может самостоятельно работать с буфером клавиатуры, или вызывать специальную функцию BIOS.
Функции, обслуживающие клавиатуру, сгруппированы в прерывание int I6h (Keyboard Services). Нам нужна функция с кодом 0 (ah=o), которая опрашивает буфер клавиатуры до тех пор, пока в него не будет записан код введенного символа. После этого происходит возврат в задачу, scan code находится в регистре ah, a ASCII код — в регистре ai. Заметим, что при вводе русских букв scan code может отсутствовать (ah=o), это зависит от установленного русификатора.
Управление курсором
Изменение состояния курсора можно разрешить перед началом ввода строки
и запретить в конце ввода. Однако в таком случае при редактировании вводимого
текста придется следить за текущим состоянием курсора, неоднократно удалять
его с одного места и выводить в другом, т. е. выполнять много вспомогательных
действий.
Мы выберем другой способ, при котором курсор виден (и мигает) только во
время ожидания ввода символа с клавиатуры.
Для разрешения работы с курсором в байт Ntick записывается число 9, что соответствует паузе примерно в 0,5 сек, а в байт curstat — число 3. Напомним, что 1 в байте Curstat разрешает прерывающей подпрограмме выполнять отсчет времени и изменять состояние курсора, а 2 указывает на то, что курсор нарисован. После этого надо вызвать подпрограмму Tgicrsr, которая нарисует курсор. Теперь при каждом тике таймера состояние курсора будет изменяться на противоположное.
После ввода символа очищается байт curstat, а рисунок курсора удаляется с экрана, если он там находился.
Таким образом, мы "привязали" рисунок курсора к тому знакоместу, в которое помещается вводимый с клавиатуры символ и никаких других действий для управления курсором не требуется.
Обработка служебных символов
При вводе с клавиатуры BIOS не отображает символы на экране, это должна делать подпрограмма, используемая для записи кодов символов в буфер строки. Перед выводом изображения символа на экран надо убедиться в том, введен ли код ASCII. Он генерируется только при нажатии на определенные клавиши (их примерно половина), а в остальных случаях если код и существует, то не имеет практического смысла.
Вопрос о существовании кода ASCII решается на основании анализа scan code, если его значение меньше чем Збь, то код ASCII существует, в противном случае нажата одна из служебных клавиш. Если значение кода ASCII больше или равно 20п (код символа "пробел"), то изображение символа можно выводить на экран, в противном случае прочитан один из управляющих символов. По традиции управляющими называют те символы, у которых код ASCII имеет значения от о до iFh.
В тех случаях, когда код ASCII меньше, чем 20h или scan code больше чем 35h, нужен дополнительный анализ введенного кода для выбора вспомогательных действий. При редактировании строки текста достаточно использовать следующие коды: <левая стрелка> (4вь), <правая стрелка> (4Dh), <Delete> (53h), <возврат на шаг> (ОБЬ), <Enter> (ich); в скобках указано значение scan code. Дополнительно могут использоваться <стрелка вверх> (48h), <стрелка вниз> (зоь), <табуляция> (OFh) и другие коды.
Подпрограмма Inline
Текст подпрограммы, выполняющей ввод символов с клавиатуры и простые функции редактирования, приведен в примере 5.28. В разделе данных задачи надо выделить буфер, имеющий метку Linbuf, его размер должен быть достаточен для размещения вводимого текста (не более 80 байтов). В конце текста подпрограмма записывает пустой байт.
Пример 5.28. Ввод символов текста в буфер строки (Linbuf)
Inline: lea si, Linbuf si = адрес буфера строки
; Ввод очередного символа и управление курсором
Gs: cli запрещаем прерывания
mov CurStat, 03 CurStat = 3
mov Ntick, 09 Ntick =9 (0,5 сек)
sti разрешаем прерывание
call TglCrsr рисуем изображение курсора
mov ah, 00 код запроса ввода символа
int 16h ожидание ввода символа
cli запрещаем прерывания
test CurStat, 02 курсор нарисован ?
mov CurStat, 00 CurStat = О
sti разрешаем прерывания
je Prevanl -> нет, курсор погашен
call TglCrsr гашение курсора ; Предварительный анализ введенного кода
Prevanl: cmp ah, 35h код ASCII существует ?
ja Detail -> нет, это служебный символ
cmp al, 2Oh это управляющий символ ?
jb Detail -> да
mov ds:[si], al запись ASCII кода в буфер строки
inc si коррекция адреса
call outsgn вывод символа на экран
jmp SHORT Gs переход на продолжение ввода ; Детальный анализ служебных
кодов
Detail: crap ah, ICh это символ "Enter" ?
jne Cont_l -> нет, продолжение анализа
mov byte ptr ds:[di], 00; запись признака конца текста
ret возврат из подпрограммы
Cont_l: cmp ah, OEh это символ "возврат на шаг" ?
jne Cont_2 -> нет, продолжение анализа
call prevpos адрес предыдущего знакоместа
mov al, ' ' al = код символа "пробел"
call outsgn стираем предыдущий символ
dec si и удаляем его из буфера строки
call prevpos адрес предыдущего знакоместа
jmp short Gs переход на продолжение ввода
Cont 2: jmp short Gs здесь можно продолжить анализ
; Вьиисление позиции предыдущего символа
Prevpos:sub di, 08 уменьшение адреса видеопамяти
jnc @F -> смена окна не нужна
mov ax, GrUnit ax = единица приращения памяти
sub Cur_win, ax Cur_win = Cur_win — GrUnit
@@: ret возврат из подпрограммы
В текст примера 5.28 вставлены строки комментария, поясняющего назначение основных групп команд. В каждой из этих групп, кроме подпрограммы Prevpos, выполняются действия, смысл которых описан выше, поэтому здесь мы уточним некоторые особенности реализации.
Прерывания от таймера надо запрещать на время работы с переменными Ntick
и CurStat. Напомним, что команда cli запрещает,
a sti разрешает маскируемые прерывания. В примере
5.28 они используются дважды при разрешении и запрещении изменения состояния
рисунка курсора. Во втором случае между test Curstat,
02 и je Prevani расположены две команды,
выполнение которых не изменяет состояние регистра флагов (признаков).
Это просто трюк, упрощающий проверку состояния и очистку байта
CurStat.
Коды введенного с клавиатуры символа находятся в регистрах ah
(scan) и al (ASCII). Если код ASCII существует
и его значение больше, чем iFh, то изображение
символа выводится на экран и продолжается ввод.
Если обнаружен код служебного символа, то производится его детальный анализ.
В примере 5.28 обрабатываются коды только двух символов — <Enter>
и <возврат на шаг>. В других случаях подпрограмма просто игнорирует
введенный код и ждет ввода с клавиатуры очередного символа. Если вы захотите
дополнить подпрограмму, то вместо команды jmp short GS, имеющей метку
Cont_2, вставьте команды, выполняющие анализ
и обработку нужных вам кодов.
При обнаружении кода символа <Enter> в буфер строки записывается пустой байт и происходит возврат из подпрограммы.
Символ <позврат на шаг> обрабатывается так. Устанавливается адрес предыдущего знакоместа, в него выводится символ <пробел>, последний введенный символ удаляется из буфера строки и повторно устанавливается адрес предыдущего знакоместа. Установку адреса предыдущего знакоместа выполняет подпрограмма Prevpos, которая понадобится, если вы добавите обработку клавиши <левая стрелка> для перемещения влево по строке.
Пример вызова Inline. Для иллюстрации использования описанной подпрограммы мы перепишем текст примера 5.23, заменив в нем строку комментария двумя командами. Результат показан в примере 5.29.
Пример 5.29. Вывод текста информационной строки
Outlnf: push Cur win сохранение исходного значения Cur
win
mov ax, Inflinw ax = номер окна информационной строки
mov Cur_win, ax Cur_win = ax
call Savinfo сохранение исходного фона
jmp short outstr переход на выборку первого символа
outl: call outsgn вывод на экран очередного символа
outstr: lodsb al = код очередного символа (al = ds:si)
or al, al конец выводимого текста ?
jne outl -> нет, переход на метку outl
call Inline ввод строки теста с клавиатуры
call Delinfo удаление информационной строки с экрана
pop Cur_win восстановление исходного значения Cur_win
call setwin восстановление исходного окна
ret возврат из подпрограммы
При выполнении примера 5.29 исходный фон будет сохранен на месте информационной строки, на экран будет выведен текст подсказки оператору, введен его ответ с эхо-печатью и записью введенных кодов в массив Linbuf и, после нажатия оператором на клавишу <Enter>, восстановлен исходный фон на месте информационной строки. Перечисленные действия выполняются с помощью подпрограмм, описанных в данном разделе. Использование данного примера для ввода спецификации файла описано в приложении А данной книги.
В этой главе автор стремился ответить на основные вопросы, которые приходится решать при программировании вывода текста на экран в графических режимах VESA. Насколько ему это удалось — судить читателю, а мы переходим к рассмотрению следующей, не менее важной темы, связанной с управлением процессом вычислений, выполняемых в задачах.