При нажатии и отпускании любой клавиши контроллер клавиатуры генерирует аппаратное прерывание, при этом прекращается выполнение текущего вычислительного процесса и вызывается специальный драйвер, расположенный в 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), это зависит от установленного русификатора.
Доступ к таблице символов. Для доступа к таблице символов надо знать, где она расположена. В текстовых режимах таблицы располагались в видеопамяти. В графических режимах они находятся в оперативной памяти. Адрес текущей таблицы хранится в векторе прерывания 43h, состоящем из двух слов с адресами 0000:010С и 0000:010Е. В первом слове находится смещение начала таблицы в сегменте, а во втором — сам сегмент. При установке режимов VESA в вектор 43h записывается адрес англоязычной таблицы, находящейся в ROM BIOS. Нам, обычно, нужны таблицы с русскими символами, поэтому исходное содержимое вектора 43h не представляет интереса.
Как и при работе в текстовых режимах, задача может использовать собственную таблицу символов или одну из таблиц русификатора.
В первом случае место расположения таблицы в оперативной памяти выбирает программист по своему усмотрению. При работе с собственной таблицей выполнение задачи не зависит от наличия русификатора на конкретном компьютере и возможно использование символов произвольного размера и начертания. Графические видеорежимы не накладывают никаких ограничений на размеры и начертание символов — их выбирает программист, разрабатывающий конкретную прикладную задачу.
Если на компьютере установлен русификатор, например Keyrus, то можно использовать его таблицы со стандартными шрифтами трех размеров: 8x8, 8x14 и 8x16 точек. Их адреса определяются при выполнении задачи.
При общей характеристике BIOS в разделе говорилось, что ее процедуры используют пространство оперативной памяти, называемое областью данных BIOS. Оно расположено в нулевом сегменте оперативной памяти ПК, начиная с адреса 400h. В частности, при выводе символов на экран функции прерывания int 10h используют следующие слова и байты, расположенные в области данных.
Основные особенности графических режимов, имеющие непосредственное отношение к работе с текстом, заключаются в следующем:
видеобуфер располагается в сегменте доооь (а не B800h); в видеопамяти находятся коды цветов точек, а не символов; выключен знакогенератор, преобразующий коды символов в рисунки; выключена аппаратная поддержка работы с текстовым курсором.Из этого перечня следует, что после установки графических режимов (как VESA, так и IBM) изображения символов и курсора на экране должны рисовать специальные подпрограммы. При работе с текстом в графических режимах IBM можно использовать поддержку BIOS, но в режимах VESA задача должна самостоятельно выполнять все необходимые действия.
Эта функция предназначена для вывода строки текста с явным указанием координат ее начала на экране и возможностью раскрашивания текста. В процессе вывода в видеопамять записываются коды символов и атрибутов. Атрибут может быть общим для всех символов строки или индивидуальным для каждого символа. Во втором случае выводимая строка должна содержать не только коды символов, но и их атрибуты. После вывода текста функция может переместить курсор в текущую позицию или не изменять его исходную позицию.
Для реализации перечисленных возможностей перед вызовом функции I3h в регистре ai указывается код режима вывода, который изменяется от 0 до 3:
в режимах 0 и 1 код атрибута выбирается из регистра ы; в режимах 2 и 3 коды атрибутов выбираются из выводимой строки; в режимах 1 и 3 курсор перемещается после вывода текста; в режимах 0 и 2 курсор остается на исходной позиции.Кроме указания режима, перед обращением к BIOS должны быть заполнены следующие регистры: es:bp — адрес начала строки в оперативной памяти; сх — количество символов в строке; bh — номер страницы, который используется при выводе; dh, dl — номера строки и столбца.
При выполнении примера 5.10 строка commun, описанная в начале раздела, выводится в центр экрана. Символы строки будет расположены на синем фоне и окрашены в белый цвет. Курсор будет перемещен в позицию, расположенную после.выведенного текста.
Собственная таблица делает выполнение задачи независимым от наличия русификатора на компьютере. Отсутствие русификатора не такая уж редкость, если пользователи не работают в среде 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.
В режимах Hi-color код точки занимает слово (два байта), а код цвета содержит 15 или 16 разрядов этого слова.
В пример 5.18 вносятся следующие изменения. Значение переменной augment надо вычислять по формуле (horsize - 8)*2. Переменные grndcol и symbcol описываются директивой dw как слова (а не как байты), а их содержимое (цвет) кодируется так, как описано в главе 7.
В тексте примера 5.19 изменяемые команды будут выглядеть так:
out_int:mov ax, grndcol; !! ах = цвет точки окружающего фона
mov ax, symbcoi ; !! ах = цвет точки контура символа
stosw ; !! запись кода в видеопамять
add di, 16 ; !! адрес для следующего символа
В этих режимах код точки занимает двойное слово (четыре байта), а код цвета — 24 разряда этого слова.
В примере 5.18 значение переменной augment вычисляется по формуле (horsize - 8)*4. Переменные grndcol и symbcol описываются директивой dd как двойные слова, а их содержимое (цвет) кодируется так, как описано в главе 7.
В тексте примера 5.19 изменяемые команды будут выглядеть так:
out_l: mov eax, grndcol ; ! ! еах = цвет точки окружающего фона
mov eax, symbcoi ; ! ! еах = цвет точки контура символа
out_2: stosd ; ! ! запись кода в видеопамять
add di, 32 ; !! позиция для следующего символа
Подчеркнем, что в примере 5.19 заменять надо только те команды, комментарий для которых начинается с двух восклицательных знаков, не нарушая общей последовательности команд.
Рассмотренный вариант знакогенератора работает с символами, ширина которых составляет 8 точек. Для того чтобы подпрограмма примера 5.19 выводила символы шириной в 16 точек, код строки можно считывать в регистр bx, а константу выделения хранить в dx, ее исходное значение зоооь. Наконец, можно усложнить знакогенератор так, чтобы он выводил символы переменной ширины. Об этом следует поговорить особо.
Информационная строка, как правило, располагается не на пустом экране. Поэтому надо сохранять изображение в той части рабочей области экрана, которую займет текст, и восстанавливать его при удалении текста с экрана. Сохраняемые коды точек изображения копируются из видеопамяти в оперативную память, так чтобы потом их можно было вернуть на прежнее место.
Подпрограммы копирования строки видеопамяти в оперативную память приведены в примерах 3.19 и 3.20, нам остается применить их для пересылки нескольких строк. Восстановление сохраненного фона ничем не отличается от построения рисунка, полностью помещающегося в оперативной памяти. Соответствующая подпрограмма описана в примере 3.21.
Для использования указанных или аналогичных подпрограмм пересылки надо знать исходные адреса видео и оперативной памяти и количество пересылаемых байтов. Начнем с последнего.
Область применения таблиц ограничена тем, что в них хранятся готовые точечные (растровые) рисунки символов. Изменить размер такого рисунка, а тем более повернуть его без ущерба для качества изображения, достаточно сложно. Кроме того, рисунки рассчитаны на определенное разрешение устройства вывода: чем оно выше, тем больше точек должен содержать рисунок при том же размере символа. Разрешение современных принтеров превышает 1000 точек на дюйм, поэтому размеры таблиц, используемых при печати текста, будут очень большими. Пожалуй, именно стремление получать высококачественную печать текста стимулировало разработку масштабируемых шрифтов.
Идея заключается в том, чтобы использовать безразмерную заготовку, которую при выводе можно преобразовать в точечный рисунок конкретного размера, расположенный под заданным углом. Масштабируемые шрифты различаются по способу описания заготовки символа. В настоящее время наибольшее распространение получили шрифты форматов PostScript и True Type. Postscript — это язык программирования печатающих устройств. Первый интерпретатор этого языка для лазерных принтеров был разработан Adobe Systems inc. Позже появилась возможность вывода символов шрифтов PostScript на экран. True Type — это масштабируемые шрифты, стандарт на которые был разработан Microsoft для Windows и ее приложений. В настоящее время существует множество шрифтов, подготовленных в формате True туре и содержащих символы различного начертания (Typeface) и/или специальные значки. Однако при компьютерной верстке предпочтение отдается языку Postscript. В этом случае можно создать файл (а не распечатку) готового документа, структура которого не зависит от разрешающей способности принтера. Его можно преобразовать в нужную для размножения документа форму на специализированном типографском оборудовании.
Рассмотрение программирования вывода масштабируемых шрифтов выходит за рамки данной книги, поэтому мы заканчиваем их краткую характеристику и переходим к следующему разделу.
Текстовый курсор обычно мигает, т. е. его изображение периодически появляется и исчезает. Для получения эффекта мигания надо вызывать подпрограмму TglCrsr через равные промежутки времени, например через 0,5 сек, как это делают Windows и ее приложения.
При управлении курсором высокая точность измерения времени не требуется, поэтому можно использовать таймер, который "тикает" через каждые 55 миллисекунд, или 18,2 раза в секунду. Для выдержки паузы надо дождаться пока от таймера поступит нужное количество .тиков с момента начала паузы. Вопрос лишь в том, как узнать, что таймер "тикнул".
В области данных BIOS, начиная с адреса 0000:046с, зарезервировано 4 байта, содержащих 32-разрядный счетчик количества тиков. BIOS очищает счетчик при первоначальной загрузке, после чего его значение увеличивается на 1 с каждым тиком таймера. Для выдержки паузы надо запомнить исходное значение счетчика, в начале паузы, а затем время от времени сравнивать текущее значение с исходным. Пауза закончится, когда их разность достигнет нужного значения.
Необходимость периодически опрашивать состояние счетчика тиков является недостатком такого способа, поэтому он применяется только в тех случаях, когда задача ничего не делает во время паузы. В общем случае работа с таймером происходит в режиме прерываний. Для этого вам надо составить подпрограмму, которая будет выполняться при каждом тике таймера. О том, как ее составить, мы поговорим особо, а сначала рассмотрим, как сделать, чтобы она выполнялась при каждом тике таймера.
Если отвлечься от вспомогательных действий, то функции 09 и OAh вычисляют адрес видеобуфера, используя номера страницы, строки и столбца, и записывают по этому адресу либо код символа (0Ah), либо код символа и атрибут (09). Эти действия достаточно просты и могут выполняться задачей без обращения к функциям BIOS. В таком случае существенно сокращается время, затрачиваемое на обмен с буфером, и появляется возможность более гибкого управления процессом вывода текста на экран. По этой причине в большинстве руководств по программированию на языке ассемблера подробно рассматриваются способы прямой работы с видеобуфером и курсором без обращения к BIOS.
Следует также подчеркнуть, что существует определенная категория задач, которые по тем или иным причинам не должны использовать поддержку DOS или BIOS. В частности, если задача работает со страницами видеопамяти, то для вывода символов нельзя использовать функции 09, 0Ah и 0Еh прерывания int 10h.
Преимущества непосредственной работы с видеопамятью по сравнению с использованием функций BIOS заключаются в следующем:
1. При выводе текста вычисляется адрес только первого символа. Все последующие адреса на 2 больше предыдущих. 2. Существует возможность раскрашивания уже находящегося на экране текста. Для этого надо просто записать новые значения атрибутов в нечетные байты видеопамяти, не изменяя коды символов текста. 3. Возможны ввод, вывод и редактирование текста, находящегося на любой странице видеопамяти.Вычисление адреса по координатам. Расположение текста на экране удобно задавать в виде номеров строк и столбцов. Хранить значения строки и столбца можно в словах и байтах области данных BIOS (см. пример 5.3) или в области данных задачи. Мы выберем первый вариант, поскольку в таком случае приведенные ниже примеры применимы при работе в текстовых режимах как VESA, так и IBM.
Кроме того, мы будем считать, что задача поддерживает работу со страницами видеопамяти и при преобразовании координат в адрес надо учитывать смещение страницы от начала сегмента видеопамяти.
В примере 5.11 приведена подпрограмма, вычисляющая адрес видеопамяти по текущему значению координат. Перед обращением в регистре bx указывается номер страницы, вычисленный адрес помещается в регистр di. Все нужные величины выбираются из области данных BIOS.
Для того чтобы знакогенератор мог выбрать заготовку рисунка символа из таблицы и раскрасить ее в нужные цвета, в разделе данных задачи должны располагаться переменные, приведенные в примере 5.18.
При вводе с клавиатуры 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) и другие коды.
При выводе текста, для записи кодов символов в видеопамять, задача может использовать поддержку 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.
Структура заготовки символа (см. Рисунок 5.1) ничем не отличается от структуры упакованного двухцветного рисунка. Способ построения строки такого рисунка был показан в примере 3.18 раздела. Особенности построения небольших рисунков обсуждались в разделе, а соответствующая подпрограмма приведена в примере 3.21. Нам остается объединить эти примеры и учесть следующие обстоятельства.
В отличие от рисунка заготовка символа хранится не в файле, а в таблице символов и знакогенератор должен самостоятельно вычислять адрес ее начала в оперативной памяти. Для этого ему нужны следующие величины: адрес начала таблицы, ширина и высота символа (размер знакоместа для размещения символа) и код ASCII. Мы ограничимся случаями, когда ширина символов составляет 8 точек, т. е. подпрограмма рассчитана на стандартные таблицы символов. Адрес таблицы и высота символов будут находиться в специальных переменных, расположенных в разделе данных задачи.
К рисунку прилагается палитра, содержащая описание использованных в нем цветов.
Каждый тик таймера вызывает так называемое аппаратное прерывание. Текущий процесс вычислений приостанавливается и выполняется специальная процедура 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.
Для более гибкого управления процессом вывода текста на экран предназначены функции BIOS, входящие в группу "video Services" (int 10h) и выполняющие следующие действия:
09h — вывод символа и атрибута без перемещения курсора, страница 0; 0Аh — вывод символа без атрибута без перемещения курсора, страница 0; 0Еh — вывод символа без атрибута с перемещением курсора, страница 0; 13h — вывод строки символов с атрибутами на указанную страницу.Перед вызовом функции 09 и ОАh в регистры записываются следующие величины: в ah — код функции (09 или OAh); в ai — код выводимого символа (ASCII); bb — не используется; в сх — количество повторов символа; bl — код атрибута, который нужен только для функции 09.
Текст подпрограммы, восстанавливающей исходный фон из оперативной памяти, приведен в примере 5.22. Перед ее вызовом надо сохранить содержимое переменной Cur_win и поместить в нее номер окна из переменной infiinw, предварительная установка этого окна не требуется.
Текст подпрограммы, выполняющей ввод символов с клавиатуры и простые функции редактирования, приведен в примере 5.28. В разделе данных задачи надо выделить буфер, имеющий метку Linbuf, его размер должен быть достаточен для размещения вводимого текста (не более 80 байтов). В конце текста подпрограмма записывает пустой байт.
Текст подпрограммы, выполняющей сохранение исходного фона, приведен в примере 5.21. Перед ее вызовом надо сохранить содержимое переменной cur_win и поместить в нее номер окна из переменной infiinw, предварительная установка этого окна не требуется.
Для расположения текста в нужном месте экрана можно использовать описанную в разделе функцию прерывания int lOh, имеющую код 02. В примере 5.9 приведен фрагмент программы, при выполнении которого курсор будет перемещен в 42-й столбец 12-й строки.
При работе в графических режимах на экране югут находиться рисунки двух курсоров, один из которых указывает теку-цее положение манипулятора "мышь", а второй — место вводимого или изменяемого символа. Главную роль играет "указатель мыши", он нужен для 'правления процессом выполнения задачи и, в частности, для изменения юзиции текстового курсора. Указатель мыши может перемещаться по всей шбочей области экрана. В отличие от него текстовый курсор появляется только в определенных местах, например в диалоговых окнах.
Для работы с текстом в графических режимах на экране выделяются специ-льные строки или окна, размеры которых зависят от их назначения. Как правило, они не велики и предназначены для ввода различных установочных данных — числовых величин, зарезервированных (ключевых) слов, спецификаций файлов и т. п. Только у специализированных редакторов текстовые окна занимают большую часть экрана или весь экран.
Windows и ее приложения работают с текстом в черно-белом режиме — на елом фоне изображаются черные буквы. Текстовый курсор обычно имеет форму мигающей вертикальной черты, цвет которой совпадает с цветом укв. Высота черты зависит от высоты шрифта, а ширина составляет одну ли две точки. Рассмотрим, как программируется подобный рисунок тек-тового курсора (далее по тексту просто "курсора").
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.
хог bl, bl ; номер таблицы в знакогенераторе
mov ax, 1104h ; запрос "загрузка таблицы ROM 8x16"
int 10h ; выполнение запроса
Данный пример проще примера 5.1 и исключает необходимость хранения таблицы в теле задачи, но при отсутствии установленного русификатора в знакогенераторе окажется англоязычная таблица из ROM 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.
lea dx, commun ; помещаем в dx адрес начала строки
mov ah, 09 ; указываем код функции DOS
int 21h ; обращаемся к DOS
Функция DOS записывает только коды символов в четные байты видеопамяти, поэтому цвет символов и фона зависит от значений атрибутов, уже находящихся в нечетных байтах видеобуфера.
Текст будет обязательно виден на экране, поскольку он помещается на активную страницу, начиная с позиции, в которой находится курсор. После вывода курсор перемещается в конец текста.
Замечание 1
Замечание 1
Текст направляется на стандартное устройство вывода, которым по умолчанию является дисплей. Задача может изменить установленное по умолчанию устройство, выбрав, например, принтер или дисковод. В таком случае текст будет напечатан на принтере или записан в файл.
Описанная функция лучше всего подходит для вывода на экран заранее заготовленного текста. Заготовки располагают в разделе данных программы и оформляют с помощью директивы db, перед которой указывается метка, подобно тому, как оформлена приведенная выше строка commun. Текст заключается в одинарные или двойные кавычки, коды управляющих символов указываются явно и отделяются друг от друга и от заключенного в кавычки текста запятыми. В качестве управляющих символов могут использоваться "возврат каретки" (0dh), "перевод строки" (0Ah), "табуляция" (09) и др. Например, для привлечения внимания оператора в выводимую строку можно включить код звукового сигнала (07), при его исполнении встроенный динамик ПК издаст гудок. Ограничения на размер выводимого текста нет, если для его размещения на экране не хватит одной строки, то продолжение будет перенесено на следующую.
mov ah, OAh ; код запрашиваемой функции BIOS
mov al, OC4h ; код ASCII символа "-"
mov ex, 132 ; число повторений символа
int lOh ; обращение к группе "Video Services"
При выполнении функции OAh записываются только коды символов в четные байты видеопамяти, поэтому цвет линии и фона, на котором она нарисована, зависит от значений ранее записанных атрибутов.
Если вам надо вывести символы вместе с атрибутами, то измените в примере 5.5 код функции на 09 и добавьте команду, записывающую в регистр bl нужный код атрибута. Например, для того чтобы на голубом фоне нарисовать белую линию, атрибут должен иметь код iFh.
Функции 09 и OAh не изменяют позицию курсора, поэтому их неудобно использовать при выводе строки текста. В этом случае вам придется хранить в области данных номера строки и столбца, соответствующие текущим координатам курсора, корректировать их после вывода каждого символа, а перед выводом символа обращаться к BIOS для перемещения курсора в позицию, соответствующую этим координатам.
lea si, commun ; указываем адрес начала строки
mov ex, 48 ; задаем количество символов в строке
lp: lodsb ; читаем в al очередной символ строки
mov ah, OEh ; код запрашиваемой функции
int 10h ; вывод очередного символа
loop lp ; управление циклом
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 указывается адрес начала строки в оперативной памяти, а в сх — количество символов в строке, В регистре ы помещается код атрибута, ьь не используется.
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, на то же место выводится текст, который будет окрашен в соответствии с атрибутом, уже записанным в видеопамять.
хог bh, bh ; номер страницы О
BJOV dh, 12 ; номер строки 12
mov dl, 42 ; номер столбца 42
mov ah, 2 ; код функции BIOS
int lOh ; позиционирование курсора
Если после выполнения команд примера 5.9 в текущую позицию вывести строку commun, описанную в начале раздела, то при работе в режиме VESA I09h она окажется расположенной в центре экрана.
Напомним, что функции, выполняющие вывод текста на экран, вычисляют адрес видеопамяти по тем значениям координат, которые хранятся в одном из слов области данных BIOS (см. пример 5.3). Фактическое расположение курсора на экране будет соответствовать этим координатам, только если его перемещают функции BIOS.
push es сохранение содержимого es
push ds помещаем содержимое ris в стек
pop es и выталкиваем его в регистр es
lea bp, commun bp = адрес строки в сегменте ds
mov ex, 48 указываем в сх размер строки
mov bx, IFh bh = 0, Ы = IFh
mov dh, 12 dh = номер исходной строки
mov dl, 42 dl = номер исходного столбца
mov ax, 1301h ah = код функции, al = режим вывода
int iOh BIOS выводит строку
pop es восстанавливаем содержимое es
Из текста примера 5.10 видно, что перед обращением к функции 13h приходится выполнять достаточно много вспомогательных действий. Поэтому при программировании конкретной задачи вам придется выбирать, что лучше -составить собственную подпрограмму или использовать описанную функцию. Автор предпочитает работать с собственными подпрограммами.
Замечание 2
Замечание 2
Функции ОЕЬ и I3h анализируют установленный видеорежим, поэтому их можно использовать для вывода текста при работе во всех графических видеорежимах, соответствующих стандартам IBM (но не стандартам VESA).
GetAdr: Push =!.ед <ds, bx> сохранение регистров
mov ds , NulSeg очистка регистра ds
shl bx , 01 удвоение номера страницы
mov bx , [bx + 450h] Ы = столбец, bh = строка
mov ax , [44Ah] количество символов в строке
mul bh ах = размер строки номер строки
xor bh , bh очистка байта bh
add ax , bx прибавляем к ах номер столбца
shl ax , 01 удваиваем полученный результат
mov di , ax и сохраняем его в dx
mov ax , [44Ch] ах = размер страницы
pop bx восстанавливаем номер страницы
mul bl ах = смещение страницы в буфере
add di , ax вычисляем полный адрес
pop ds восстановление ds
ret возврат из подпрограммы
Напоминаем, что команды lodsb и stosw корректируют содержимое индексного регистра.
Поясним способ доступа к области данных BIOS. Она расположена в нулевом сегменте оперативной памяти. Для доступа к нулевому сегменту надо очистить один из сегментных регистров, лучше, если это регистр ds. В разделе данных задачи надо зарезервировать пустое слово с именем Nuiseg и при выполнении подпрограммы копировать его в ds.
Перед вызовом подпрограммы GetAdr значения координат должны быть указаны в слове BIOS, соответствующем нужной странице. Если задача не работает со страницами, точнее работает только с нулевой страницей, то координаты курсора хранятся в слове 450h. При этом из текста примера 5.11 надо исключить вычисление адреса слова и смещения страницы от начала сегмента видеопамяти.
Запись текста в видеопамять. Мы приведем пример подпрограммы, которая записывает-в видеопамять коды символов строки вместе с атрибутом, общим для всех символов, а затем покажем, как ее надо изменить для записи только кодов символов или только кодов атрибутов.
Замечание 1
Замечание 1
Напомним, что регистр es должен содержать код видеосегмента, который в текстовых режимах равен B800h.
Текст подпрограммы показан в примере 5.12. Перед обращением к ней надо вычислить адрес видеопамяти и поместить его в регистр di, например, с помощью подпрограммы примера 5.11. Адрес начала выводимого текста указывается в регистрах ds:si, количество выводимых символов помещается в регистр сх, а код общего для всех символов атрибута — в регистр bl.
OutLine: push ax сохраняем содержимое ах mov ah, Ы помещаем атрибут в ah
wrt: lodsb читаем в al очередной символ
stosw пишем ах в видеобуфер
loop wrt управление повторами цикла
pop ax восстанавливаем содержимое ах
ret возврат из подпрограммы
При выполнении примера 5.12 указанный в ы атрибут копируется в регистр ah. Далее в цикле wrt каждый символ строки копируется в регистр ai и содержимое регистра ах записывается в видеопамять.
В примерах 5.13 и 5.14 показано, как изменится подпрограмма outLine, если в видеопамять записываются только коды символов или атрибуты.
OutSym: movsb ; копирование символа в четный байт
inc di ; пропуск нечетного байта
loop outsym ; управление повторами цикла
ret ; возврат из подпрограммы
OutAtr: push ax сохраняем содержимое ах
mov ai, Ы помещаем атрибут в al
wrtatr: inc di пропускаем четный байт
stosb записываем код атрибута
loop wrtatr управление повторами цикла
pop ax восстанавливаем содержимое ах
ret возврат из подпрограммы
При обращении к подпрограмме OutAtr в регистре di указывается адрес видеопамяти, в регистре bl— атрибут, а в сх — сколько раз его надо записать в видеопамять (количество раскрашиваемых символов).
Перемещение курсора
Если задача использует текстовый курсор, то для его перемещения можно использовать функцию 02 прерывания int ion, или составить свою подпрограмму. Такая подпрограмма полезна, например, в тех случаях, когда недопустимо использование поддержки BIOS.
В примере 5.15 приведена подпрограмма перемещения курсора, к которой можно обратиться по двум именам. При обращении по имени Poseur происходит обращение к описанной в примере 5.11 подпрограмме GetAdr, которая пересчитывает координаты в адрес и помещает его в регистр di. В этом случае номер страницы указывается в регистре bх, а значения координат выбираются из области данных BIOS. При обращении по имени MovCur адрес байта должен находиться в регистре di.
Замечание
Не забывайте, что курсор будет виден только в том случае, когда его рисунок расположен на активной странице.
PosCur: call GetAdr ; пересчет координат в адрес
MovCur: PushReg <ds,ax,bx,dx> ; сохранение используемых регистров mov ds, NulSeg ; очистка сегментного регистра
mov bx, di bx = адрес позиции в байтах
shr bx, 01 преобразование байтов в слова
mov ah, bh ah = старший байт адреса
mov al, OEh al = ног^ер регистра видеоконтроллера
mov dx, [0463h] dx = базовый адрес видеоконтроллера
out dx, ax запись старшего байта в регистр OEh
mov ah, bl ah = младший байт адреса
inc al al = номер следующего регистра
out dx, ax запись младшего байта в регистр OFh
PopReg <dx,bx,ax,ds> восстановление регистров
ret возврат из подпрограммы
В примере 5.15 основные действия выполняют команды, первая из которых имеет метку Movcur. Для записи данных в регистры нужен базовый адрес (порт) видеоконтроллера, который хранится в слове 463h области данных BIOS. Для чтения содержимого этого слова в сегментный регистр ds копируется пустое слово Nuiseg, хранящееся в разделе данных задачи.
В регистре di должен находиться адрес байта, он копируется в регистр bx и уменьшается в два раза, в результате получается адрес слова, в котором должен быть расположен рисунок курсора. Этот адрес надо записать в регистры видеоконтроллера, имеющие коды ОЕЬ и OFh (14 и 15).
Для записи адреса в регистры видеокарты выполняются следующие действия. В регистр dx записывается адрес порта видеоконтроллера из слова 463h области данных BIOS. В al помещается код регистра (OEh или OFh), в который надо записать один из байтов адреса, а сам байт помещается в регистр ah. После этого команда out записывает байт в регистр видеокарты. Сначала записывается старший байт адреса в регистр OEh, а затем младший в регистр OFh.
После выполнения описанных действий восстанавливается сохраненное в стеке содержимое регистров и происходит возврат на вызывающий модуль.
Для перемещения курсора в конец выведенного текста после выполнения подпрограмм примеров 5.12 и 5.13 вызывается подпрограмма Movcur. После выполнения указанных подпрограмм регистр di содержит нужный адрес, и дополнительное обращение к подпрограмме GetAdr не требуется.
SelPag: PushReg <ds, ax, bx, dx> сохранение используемых регистров
mov ds , NulSeg очистка регистра ds
mov ax , [44Ch] ах = размер страницы в байтах
mul Ы умножаем его на номер страницы
mov bx , ax сохраняем результат в bx
mov al , OCh al = номер регистра видеоконтроллера
mov dx , [463h] dx = базовый адрес видеоконтроллера
out dx , ax запись старшего байта адреса
mov ah , bl ah = младший байт адреса
inc al номер второго регистра видеокарты
out dx , ax запись младшего байта адреса
PopReg <dx,bx, ax,ds> восстановление регистров из стека
ret возврат из подпрограммы
Для установки активной страницы надо старший и младший байты адреса ее начала записать в регистры ось и ooh (12 и 13). В примере 5.16 адрес вычисляется так же, как и в примере 5.11. Размер страницы выбирается из слова 44ch области данных BIOS, помещается в регистр ах и умножается на номер страницы, указанный в регистре bx. Полученный результат сохраняется в регистре bх, а его старший и младший байты записываются в регистры 0Ch и 0Dh. Способ записи такой же, как в примере 5.15, поэтому мы не будем повторяться.
mov bh, 06 ; код таблицы символов
mov ax, 1130h ; код запроса на получение информации
int 10h ; выполнение запроса
mov word ptr ftaddr, bp ; сохранение смещения в сегменте
mov ftaddr+2, es ; сохранение сегмента адреса таблицы
При пересылке смещения указатель типа word ptr нужен потому, что поле ftaddr является двойным словом, а регистр Ьр имеет размер слова. Благодаря явному указанию типа смещение будет записано в первое из двух слов ftaddr. Ftaddr+2 является словом и при пересылке сегмента явное указание типа не требуется.
При выполнении запроса в регистр сх помещается высота символа (размер заготовки в байтах). Эта величина нужна для дальнейшей работы, но она известна заранее и равна 16 байтам. Напомним, что записанный в ьь код Об означает, что мы запрашиваем адрес таблицы, содержащей символы размером 8x16 точек.
Кроме того, запрос возвращает в регистр di количество строк на экране, но при работе в графических режимах VESA эта величина нас не интересует.
ftaddr dd 00 ; полный адрес таблицы символов
hsymb dw 16 ; высота символа (размер заготовки в байтах)
augment dw 00 ; ! ! константа переадресации строк рисунка символа
grndcol db OFFh ; !! код цвета точек фона, окружающего символ
symbcol db 00 ; !! код цвета точек контура символа
Если адрес таблицы известен при составлении программы, то его значение указывается в исходном тексте вместо нулей. Если используется одна из таблиц русификатора, то ее адрес можно определить так, как это показано в примере 5.17.
Высота символов обычно известна заранее и указывается в исходном тексте программы. Напомним, что стандартные таблицы содержат символы высотой 8, 14 или 16 строк. В тех случаях, когда планируется работа с символами разной высоты, вам придется предусмотреть запись ее значения в переменную hsymb.
Переменная augment содержит величину, которая добавляется к текущему адресу видеопамяти для перехода в начало следующей строки рисунка символа. Напомним, что в режимах PPG она выражается в точках и вычисляется как разность между шириной рабочего поля экрана и шириной рисунка. Если установлен режим с разрешением 640x480 точек, то при ширине символа В 8 точек значение augment = horsize - 8 = 640 - 8 = 632.
В примере 5.18 значения переменных grndcol и symbcol выбраны исходя из предположения, что символы изображаются черным цветом на белом фоне и что коды черного и белого цветов находятся в DAC-регистрах видеокарты с номерами 00 и 0FFh. Например, именно их используют Windows и ее приложения при работе с текстом. В общем же случае значения указанных переменных зависят от того, в каких регистрах видеокарты расположены коды нужных вам цветов.
Подпрограмма знакогенератора. Текст подпрограммы знакогенератора приведен в примере 5.19. Перед обращением к ней код ASCII выводимого на экран символа помещается в регистр ai. В di записывается адрес видеопамяти для размещения кода точки левого верхнего угла изображения символа. Переменная cur_win содержит окно видеопамяти, которому принадлежит адрес, указанный в di. Предварительная установка окна не требуется. Как обычно при работе с графикой, в регистре ез должен находиться сегмент видеобуфера (значение переменной vbuff). Остальные параметры задаются неявно, это переменные примера 5.18. После выполнения подпрограммы регистр di содержит адрес начала следующего символа, а переменная cur_win — окно, к которому относится этот адрес.
outsgn: PushReg <ax,bx, ex, fs, si,di> ; сохранение используемых регистров
call Setwin установка исходного окна
push Cur win сохранение номера исходного окна
Ifs si, ftaddr; fs si = адрес таблицы символов
mov ex, hsymb; ex = количество строк рисунка
xor ah, ah очистка байта ah
mul cl смещение рисунка в таблице (ax*cl)
add si, ax полный адрес рисунка символа
; Построение изображения символа (внешний цикл)
out ext mov bh, f s : [si]; bh = код текущей строки таблицы
inc si адрес следующей строки таблицы
mov Ы, 8 Oh константа выделения (и счетчик)
; Построение текущей строки рисунка (внутренний цикл)
out int mov al, grndcol; ! al = цвет точки окружающего фона
test bh, Ы текущий бит установлен ?
jz @F => нет, на запись кода точки
mov al, symbcol; ! al = цвет точки контура символа
@@: stosb ! ! запись кода в видеопамять
or di, di достигнута граница сегмента ?
jne @F => нет
call Nxtwin установка следующего окна
@@: shr bl, 01 сдвиг константы выделения
jne out int управление внутренним циклом
add di , augment адрес следующей строки рисунка
jne @F => адрес в пределах окна
call Nxtwin установка следующего окна
@@: loop out ext управление внешним циклом
pop Cur win исходный номер окна
pop di восстановление исходного адреса
add di, 08 ! ! адрес для следующего символа
jne @F => адрес в пределах окна
mov ax, GrUnit константа для коррекции окна
add Cur win, ax вычисляем значение нового окна
@@: PopReg <si, fs, cx,bx, ах> ; восстанавливаем регистры
ret конец подпрограммы
Выполнение примера 5.19 начинается с сохранения в стеке содержимого используемых регистров. После этого устанавливается окно видеопамяти, в которое будет выводиться символ, и значение cur_win сохраняется в стеке, т. к. оно может измениться в процессе рисования символа.
Команда ifs загружает младшее слово ftaddr в регистр si, а старшее — в сегментный регистр fs, в результате пара регистров fs:si содержит адрес таблицы символов в оперативной памяти. После этого в сх записывается количество строк в рисунке символа. Эта величина определяет количество повторов внешнего цикла, она же используется при вычислении адреса начала заготовки символа в таблице. При умножении кода символа ASCII на высоту рисунка (ci) в регистре ах получается смещение заготовки символа, оно прибавляется к адресу начала таблицы, в результате чего в регистре si получается адрес первого байта заготовки рисунка символа.
Основные действия выполняют два вложенных цикла. Внешний имеет имя out_ext и начинается с чтения в регистры кода очередного байта заготовки изображения символа. После чтения адрес, находящийся в регистре si, увеличивается на 1. В ьь записывается константа выделения разрядов кода (80h) и начинается выполнение внутреннего цикла.
Внутренний цикл имеет имя out_int. В нем, начиная со старшего, последовательно выделяются биты строки, хранящейся в регистре ы. В зависимости от состояния текущего бита в ai записывается значение переменной grndcol или symbcol, затем оно копируется в видеопамять командой stosb. Константа выделения смещается на разряд вправо и если она не равна нулю, то внутренний цикл повторяется.
После построения текущей строки продолжается выполнение внешнего цикла. При этом формируется адрес байта видеопамяти, соответствующий началу следующей строки, и команда loop повторяет выполнение внешнего цикла, пока не будут нарисованы все строки.
Как обычно, при любых изменениях адреса видеопамяти проверяется принадлежность нового значения текущему сегменту. Если оно выходит за пределы сегмента, то устанавливается следующее окно видеопамяти.
После выхода из внешнего цикла из стека восстанавливаются значение исходного окна и адрес видеопамяти, который увеличивается на ширину символа. Если при этом произойдет выход за границу сегмента, то увеличивается номер окна. Для этого к нему прибавляется значение переменной Grunit (см. раздел). Перед возвратом из подпрограммы восстанавливается сохраненное в стеке исходное содержимое регистров.
Отметим, что при однократном обращении к знакогенератору не обязательно вычислять позицию следующего символа. Однако при выводе связного текста такие вычисления необходимы, и удобнее их делать именно в знакогенераторе.
Описанная подпрограмма рассчитана на выполнение в режимах PPG. Для того чтобы при описании режимов direct color не возвращаться к программированию знакогенератора, покажем, что надо изменить для его использования в этих видеорежимах. В примерах 5.18 и 5.19 комментарий к изменяемым командам отмечен двумя восклицательными знаками.
mov ax, versize ; ax = количество строк на экране
sub ax, hsymb ; уменьшаем его на высоту символа
mul horsize ; разность умножаем на размер строки
mov Inflino, ax ; сохраняем адрес в Inflino
mov ax, dx ; копируем содержимое dx в ах
mul GrUnit ; вьиисляем номер окна
mov Inf linw ; и сохраняем его в Inflinw
Если выводимый текст смещен относительно левого края информационной строки, то вычисленное в примере 5.20 значение переменной Inflino надо увеличить на соответствующее число столбцов.
Savinfo: PushReg <Cur_win,ax,ex,si,di,fs,es>; сохранение в стеке
call setwin установка исходного окна
mov fs, Vbuff fs = сегмент видеобуфера
mov si, Inflino si = адрес начала информ. строки
mov es, GenSeg es = сегмент общего назначения
xor di, di di = 0 — смещение в GenSeg
mov ax, horsize ax = ширина экрана
mul byte ptr hsymb умножаем ее на высоту символа
mov ex, ax копируем результат в сх
shr сх, 02 уменьшаем его в 4 раза
Savlp: movs dword ptr [di] fs:[si]; копирование двойного слова
or si, si адрес в пределах видеосегмента ?
jnc @F -> да, переход на команду loop
call nxtwin установка следующего окна
@@: loop savlp » управление циклом копирования
PopReg <es,fs,di,si,cx,ax,Cur_win>; восстановление из стека
call setwin ; восстановление исходного окна
ret ; возврат из подпрограммы
В примере 5.21 копирование содержимого видеопамяти в оперативную выполняет строковая операция movs, у которой приемник находится в регистрах es:di, а источник в fs:si. Содержимое этих регистров формируется перед циклом пересылки. Затем вычисляется размер информационной области в байтах, и результат преобразуется в количество двойных слов. Строки рабочей области экрана копируются полностью, поэтому нужен только один цикл, в котором пересылается сразу по 4 байта. Цикл пересылки повторяет аналогичный цикл из примера 3.20 с той разницей, что копируются не байты, а двойные слова.
После пересылки восстанавливается содержимое сохраненных в стеке регистров и значение переменной cur_win, восстанавливается исходное окно видеопамяти и происходит возврат на вызывающий модуль.
Delinfo : PushReg <Cur win, ax , ex, si, di, fs>; сохранение в стеке
call setwin установка исходного окна
mov di, Inflino di = адрес начала информ. строки
mov fs, GenSeg fs = сегмент общего назначения
xor si, si si = смещение в GenSeg (0)
mov ax, horsize ах = ширина экрана
mul byte ptr hsymh ) умножаем ее на высоту символа
mov ex , ax копируем результат в сх
shr ex, 02 и уменьшаем его в 4 раза
Dellp: movs dword prr [di fs:[si]; копирование двойного слова
or di, di адрес в пределах видеосегмента ?
jne @F -> да, переход на команду loop
call nxtwin установка следующего окна
@@: loop dellp управление циклом копирования
PopReg <fs,di,si,cx , Cur win>; восстановление из стека
call setwin восстановление исходного окна
ret возврат из подпрограммы
В примере 5.22 данные пересылаются из оперативной в видеопамять, что и объясняет все различия текстов примеров 5.21 и 5.22. Цикл пересылки, практически, повторяет анапогичный цикл из примера 3.15 с той разницей, что копируются не байты, а двойные слова. Подразумевается, что регистр es содержит код видеосегмента, указанный в переменной vbuff.
Замечание 1
Замечание 1
При компиляции инструкции movs в том виде, как она записана в примерах 5.21 и 5.22, Макроассемблер MASM 5.1 выдает предупреждающее сообщение, но генерирует правильный код. На это сообщение можно не обращать внимание.
Подпрограммы примеров 5.21 и 5.22 рассчитаны на общий случай, когда информационная строка занимает всю ширину рабочей области экрана, но может начинаться с любой строки и располагаться в двух смежных окнах видеопамяти. В частных случаях их можно упростить и ускорить пересылку.
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 ;
Здесь могут выполняться сопутствующие действия pop Cur_win восстановление исходного значения Cur win
call setwin восстановление исходного окна
ret возврат из подпрограммы
Собственно вывод текста в примере 5.23 выполняется в цикле, состоящем из четырех команд. Первая из них имеет имя outl, но точкой входа является следующая команда, имеющая метку outstr. Код очередного символа строки считывается в регистр al, и если он не равен нулю, то происходит возврат на метку outl для обращения к подпрограмме outsgn (см. пример 5.19). Цикл повторяется, пока в строке не будет обнаружен пустой байт.
При желании вы можете изменить цикл так, чтобы использовался другой признак конца строки или задавалось количество символов в строке. Однако формат ASCIIZ является наиболее удобным.
После цикла вывода текста в примере вставлен комментарий, указывающий на возможность выполнения других действий, например выдержки паузы или ввода ответа оператора с клавиатуры. В разделе мы опишем вставку в это место примера.
Если другие действия не нужны, то восстанавливается исходное значение переменной Cur_win, устанавливается исходное окно и происходит возврат на вызывающую программу. Выведенный текст остается на экране, а сохраненный фон — В буфере GenSeg.
Замечание 2
Замечание 2
При планировании текстового оформления задачи имеет смысл выделить неизменяемые фрагменты текста, расположенные в различных окнах и заставках. Их можно заранее включить в рисунки окон или заставок с помощью графического редактора. При этом текст становится частью соответствующего рисунка, выводится, перемещается или удаляется вместе с ним. В некоторых случаях это удобно, а современные графические редакторы позволяют включать в рисунки текст, состоящий из символов различных размеров, начертаний и цветов.
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.
хо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 разрешает прерывания.
хо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, и разрешаются прерывания. Вектор восстановлен, и можно завершать выполнение задачи.
Замечание 1
Замечание 1
Для работы с векторами прерываний предназначены две специальные функции 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.
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, указан код операции (ОЕАЬ). При ее выполнении микропроцессор интерпретирует содержимое следующих двух слов как адрес, на который производится переход. Это наиболее простой, но не единственно возможный способ вернуться на сохраненное значение вектора прерывания.
Замечание 2
Замечание 2
Если при выполнении вашей задачи содержимое сегментных регистров ds и ез не изменяется после их первоначальной установки, то из текста примера 5.27 можно исключить 5 команд, комментарий к которым начинается с двух восклицательных знаков.
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, которая понадобится, если вы добавите обработку клавиши <левая стрелка> для перемещения влево по строке.
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. Насколько ему это удалось — судить читателю, а мы переходим к рассмотрению следующей, не менее важной темы, связанной с управлением процессом вычислений, выполняемых в задачах.
В данном разделе будет описана подпрограмма, которая по коду ASCII выбирает из таблицы соответствующую заготовку и рисует изображение символа на экране. В технической литературе для обозначения подобных подпрограмм используется термин "программный знакогенератор". Подпрограмма будет предназначена для выполнения в видеорежимах PPG. Однако, учитывая ее значимость, мы специально обсудим те изменения, которые позволят выводить текст при работе в видеорежимах direct color.
В рукописном тексте символы имеют разную ширину. Такая форма записи текста привычна для человеческого глаза, поэтому она применяется при оформлении печатной продукции различного назначения. Все текстовые редакторы позволяют использовать при подготовке документов пропорциональные шрифты. Свое название они получили потому, что при размещении на экране (или на бумаге) выделяется место, пропорциональное ширине символа. Например, для размещения символа достаточно четырех столбцов, а для буквы щ их надо намного больше.
К таблице пропорциональных шрифтов обязательно прилагается массив, содержащий ширину каждого символа в точках. При подготовке таблицы рисунки символов располагают в левой части знакоместа. Широкие символы заполняют все или почти все знакоместо, а узкие только его левую часть. Соответственно в строках таблицы заполняются старшие биты, а младшие могут не использоваться. Размер строки должен быть удобным для вычисления адреса начала заготовки символа. У большинства экранных шрифтов строка занимает одно слово.
Для вывода пропорциональных символов вам придется составить специальный знакогенератор. Его основные отличия от описанного в примере 5.19 заключаются в следующем:
во внешнем цикле, кроме адреса начала заготовки символа, надо определять его ширину, которая хранится в отдельном массиве, прилагаемом к таблице; количество повторов внутреннего цикла равно ширине символа, поэтому для управления его повторами придется использовать счетчик; исходный код константы выделения зависит от размера строки, например если она занимает 1 слово, то код константы равен 8000h; после построения рисунка при вычислении адреса позиции следующего символа, содержимое регистра di надо увеличивать не на восемь, а на ширину нарисованного символа.Таким образом, при работе с текстом в графических режимах появляется возможность выводить на экран символы разной высоты и постоянной или переменной ширины, что было невозможно в текстовых режимах. В заключение несколько слов о другой, весьма распространенной категории шрифтов, используемых Windows и ее приложениями.
При выполнении графических задач на экран выводятся различные текстовые сообщения. Это могут быть названия окон, пояснения к выбранным значкам, информационные строки различного назначения, подсказки оператору и т. п. Программирование вывода текста при работе в графических режимах имеет свои специфические особенности, которые описаны в данной главе.
Все видеорежимы делятся на текстовые и графические. Первые предельно упрощают работу с текстом, но исключают возможность работы с рисунками. Вторые позволяют работать только с отдельными точками, из которых, как известно, складываются любые рисунки, в том числе и изображения символов текста. В соответствии с этим данная глава делится на две основные части, в первой описана работа в текстовых режимах, а во второй — в графических.
В связи с непрерывным совершенствованием технических характеристик мониторов и видеокарт и широким распространением Windows и ее приложений текстовые режимы отошли на второй план и потеряли свою былую значимость. Однако они входят в число стандартных режимов VESA, поэтому автор счел целесообразным описать их в первом разделе главы.
BIOS не содержит специальных функций, изменяющих только атрибуты символов, но сочетание функций 09 и ОЕЬ иногда позволяет раскрашивать символы и фон в нужные цвета. Рассмотрим два примера, иллюстрирующих сказанное.
При однократном обращении к функции 09 можно очистить и окрасить выбранным вами цветом все рабочее пространство экрана монитора. Для этого надо переместить курсор в начало активной страницы и заполнить ее отображаемую часть кодами символа "пробел" и атрибута, соответствующего цвету символов и фона. Как это можно сделать, показано в примере 5.7, рассчитанном на выполнение при установленном режиме VESA I09h.
Прежде чем выводить символ, надо решить, в каком месте экрана он будет располагаться, и определить, какому участку видеопамяти соответствует это место. Способы указания координат точек и вычисления их адресов при работе в графических видеорежимах описаны в разделе. Здесь мы рассмотрим конкретные примеры.
Где бы ни располагалась информационная строка для работы, надо знать адрес ее начала в видеопамяти и соответствующее ему окно. При построении изображения символа знакогенератор выводит точки на экран в том порядке, в котором их коды хранятся в таблице, а именно слева направо и сверху вниз. Поэтому для вывода текста надо знать адрес верхнего левого угла первого символа информационной строки. Для его хранения в разделе данных программы надо описать две следующие переменные:
Inflino dw 0; для хранения адреса начала информационной строки
Inflinw dw 0; для хранения окна, к которому относится этот адрес
Если информационная строка расположена в начале рабочей области экрана, то надо просто очистить указанные переменные при их описании.
Если расположение строки связано с расположением курсора на экране, например, курсор указывает на значок, назначение которого надо пояснить, то в описанные переменные просто копируется текущий, или несколько измененный, адрес изображения курсора. Текущие координаты курсора и адрес его изображения на экране нужны во многих случаях, поэтому обычно они хранятся в переменных, зарезервированных в разделе данных задачи.
Подсказки о необходимости ввода данных удобно располагать в последних строках рабочей области экрана. Количество и размер строк на экране зависят от установленного видеорежима, поэтому номера и адреса последних строк рабочей области экрана вычисляются при выполнении задачи.
Если верхнюю линию изображения текста поместить в строку с номером (versize - hsymb), то его нижняя линия совпадет с нижней границей рабочей области экрана.
Замечание
Напомним, что переменные Horsize и versize содержат соответственно размер строк и их количество на экране, а переменная hsymb— высоту символа (см. пример 5.18), ее значение зависит от используемой таблицы.
Предположим, что левый край информационной строки расположен в нулевом столбце рабочей области экрана. В таком случае нам нужен адрес видеопамяти, соответствующий точке, расположенной .в рабочей области экрана на пересечении нулевого столбца и строки с номером (versize - hsymb). Его можно вычислить, например, как приведено в примере 5.20.
Высота информационной строки нам известна, точнее мы знаем, что ее значение хранится в переменной hsymb. Ширина строки равна произведению ширины символов на их количество, но последнее является переменной величиной. Ее значение зависит как от размера текста сообщения, так и от ответа оператора, если таковой подразумевается. Поэтому лучше выбрать ширину информационной строки равную ширине рабочей области экрана (значению переменной Horsize). При использовании стандартных таблиц в такой строке можно разместить 80, 100, 128 или 160 символов, в зависимости от установленного видеорежима. Чем выше разрешение, тем мельче изображение символов на экране и тем труднее читать текст, поэтому при работе с высоким разрешением вам могут понадобиться таблицы с более крупными символами.
Если информационная строка занимает всю ширину экрана, то количество сохраняемых байтов и размер буфера для их размещения вычисляются как произведение значений переменных Horsize и hsymb. Если hsymb = 16, то, в зависимости от видеорежима PPG, информационная строка содержит следующее количество байтов: 101h - 7680, 103h - 9600, 105h - 12288, 107h - 16384. При работе в режимах Hi-Color указанные числа увеличатся в два раза, а в режимах True Color — в четыре раза. Очевидно, что буфер таких размеров нецелесообразно располагать в разделе данных задачи, для него надо выделить отдельное место в оперативной памяти ПК.
Способы резервирования пространства оперативной памяти описаны в приложении Б данной книги. При программировании задачи надо следить за тем, чтобы это пространство не использовалось по другому назначению. Для этого надо имя переменной, содержащей адрес выделенного сегмента, использовать только в подпрограммах сохранения и восстановления исходного фона информационной строки, но есть и другой способ защиты.
Нужный нам буфер можно расположить в начале сегмента общего назначения, выделенного в разделе для временного хранения распакованной строки рисунка. Код сегмента хранится в переменной GenSeg, а начало свободного в нем пространства в переменной GenOffs. Если Genoffs присвоить значение horsize и hsymb, то соответствующая часть сегмента будет недоступна другим подпрограммам, при условии, что они используют адрес, хранящийся в Genoffs, и не уменьшают его.
Таким образом, адрес начала информационной строки в видеопамяти хранится в переменных inf lino и inf linw, а буфер для ее размещения расположен в начале сегмента, указанного в переменных Genoffs и GenSeg.
Прежде всего, разберемся с тем, как появляются символы на экране. Задача самостоятельно или с помощью функций BIOS помещает выводимый текст в видеопамять. Коды символов должны соответствовать стандарту ASCII (American Standard Code for Information Interchange — американский стандартный код для обмена информацией). После кода каждого символа в видеопамять записывается атрибут, назначение которого будет описано ниже.
В процессе редактирования текста изображение урсора может перемещаться по строкам и располагаться на месте уже выеденных символов. Поэтому надо принять специальные меры, для того тобы при перемещении курсор не искажал изображение символов текста, апример сохранять рисунок расположенного под курсором символа и вос-танавливать его после перемещения курсора. При работе с текстом обычно спользуют более простой способ, при котором для записи кодов точек ри-унка курсора в видеопамять используется команда хоr.
Двухадресная команда хоr вычисляет логическую функцию exclusive OR исключающее ИЛИ), ее операндами являются источник и приемник, результат помещается в приемник. При выполнении инструкции хоr микропроцессор запрещает перенос единиц переполнения из младших разрядов старшие и производит поразрядное сложение операндов. Результат выполнения операции для одного бита показан в табл. 5.1.
Если не принять специальные меры, то в текстовых режимах задача работает только с младшей частью сегмента видеопамяти, размер которой зависит от установленного видеорежнма. Для доступа ко всему пространству видеобуфера оно делится на страницы, которые могут использоваться независимо друг от друга.
Создание страниц и их отображение на экране при работе в графических режимах 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) смещение активной страницы от начала сегмента видеобуфера.
Состояние бита источника | 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).
Режимы 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 |
В любом случае при загрузке надо знать полный адрес таблицы (сегмент и смещение в нем). Для хранения этих величин в разделе данных программы выделяются два слова, например:
RsFntl6: dw 0 ; Смещение начала таблицы в указанном ниже сегменте
dw 0 ; Значение сегмента, в котором расположена таблица
Если расположение таблицы известно при составлении программы, то вместо нулей в директивах указываются конкретные значения сегмента и смешения. В противном случае содержимое обоих слов формируется при выполнении задачи.
При работе в текстовых режимах аппаратный знакогенератор выбирает коды цветов точек изображения символа (foreground) и фона (background) из байта атрибута, находящегося в видеопамяти (см. раздел). В нем можно закодировать 16 разных цветов foreground и 8 цветов background, коды которых соответствуют стандартной палитре CGA (см. табл. 4.2).
При работе в графических режимах для каждого выводимого символа указываются коды цветов точек его изображения и фона. В режимах PPG кодами являются номера регистров цвета видеокарты, содержащих нужные цвета. В режимах direct color это коды самих цветов, имеющие размер слова (Hi-Color) или двойного слова (True Color). В зависимости от используемого видеорежима в разделе данных задачи надо зарезервировать два байта, два слова или два двойных слова, содержащие цвета для раскрашивания изображения символов и окружающего их фона.
Для вывода текста на экран нужен набор заготовок рисунков всех используемых символов. Обычно эти заготовки хранятся в специальных таблицах символов. Структура стандартных таблиц не зависит от режима, в котором они будут использоваться. При описании текстовых видеорежимов (см. раздел ) нас не интересовала структура таблиц, поскольку изображения символов рисовал видеоконтроллер. В данном случае нам необходимо знать способ хранения данных в таблицах символов, для того чтобы составить подпрограмму, выполняющую функции знакогенератора.
Структура стандартных таблиц. В стандартных таблицах ширина символа составляет восемь точек, т. е. код одной строки (линии) рисунка символа занимает один байт. В зависимости от высоты символа (количества строк в знакоместе) заготовка полного рисунка занимает 8, 14 или 16 байтов, расположенных последовательно друг за другом. Полная таблица содержит 256 заготовок символов и занимает в оперативной памяти пространство 256x8=2048, 256x14=3584 или 256x16=4096 байтов.
Заготовка строки кодируется следующим образом. Старший бит (разряд) байта соответствует крайней левой позиции в строке, а младший бит -крайней правой позиции. Если текущая позиция содержит одну из точек рисунка символа, то соответствующий ей бит установлен (содержит 1), в противном случае он очищен (содержит 0).
На Рисунок 5.1 схематически изображено расположение русской буквы Б в стандартном прямоугольнике (знакоместе) размером 8x16 точек. Первый столбец рисунка содержит коды байтов каждой строки. В остальных восьми столбцах расположен рисунок буквы Б.
Если клетка содержит точку изображения символа, то в ней записана буква Х, пустые клетки соответствуют фону, окружающему изображение символа.
00 | ||||||||
00 | ||||||||
FE | X | X | X | X | X | X | X | |
62 | X | X | X | |||||
62 | X | X | X | |||||
60 | X | X | ||||||
7С | X | X | X | X | X | |||
66 | X | X | X | X | ||||
66 | X | X | X | X | ||||
66 | X | X | X | X | ||||
66 | X | X | X | X | ||||
FC | X | X | X | X | X | X | ||
00 | ||||||||
00 | ||||||||
00 | ||||||||
00 |
Рисунок 5.1. Буква Б из таблицы символов размером 8x16
При отображении содержимого видеопамяти в текстовых режимах видеоконтроллер последовательно выбирает коды символов из видеобуфера. По коду символа вычисляется адрес начала его рисунка, который и выводится на экран. Знакогенератором называется область видеопамяти, в которой размещаются таблицы символов.
Таким образом, изображение символов, которое мы видим на экране при работе с текстом, зависит от используемой таблицы знакогенератора. Его размеры позволяют загрузить 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 и в других руководствах. Ниже мы рассмотрим только те функции, которые нужны для установки русифицированных таблиц.
Текстовые режимы отличаются от графических следующими особенностями:
видеобуфер расположен в сегменте ввооь (а не A000h); в видеобуфере хранятся коды символов в стандарте ASCII и их атрибуты; преобразование кодов символов в рисунки выполняет видеоконтроллер;Стандартом VESA предусмотрено пять текстовых режимов высокого разрешения, имеющих коды от 108h до 10Ch (см. табл. 1.1). Большинство видеокарт поддерживает только два из них — юэь и ЮАЬ. Поэтому, так же как при работе с графикой, до или после установки режима надо проверить, поддерживает его видеокарта или нет.
Если в соответствии с рекомендациями, приведенными в главе 2, ваша задача выбрала из массива info значения переменных, перечисленные в примере 2.11, то переменная vbuff будет содержать код видеосегмента ввооь, переменная Horsize — количество символов в строке, a versize — количество строк на экране. При выполнении подготовительных действий необходимо проверить состояние второго разряда нулевого байта массива info (разряды пронумерованы начиная с нуля). Если он содержит 1, то BIOS и DOS поддерживают работу в выбранном режиме. Поэтому все приемы программирования остаются в силе. В текстовых режимах VESA изменяются только размер и количество строк на экране. В данном разделе описано программирование вывода текста на экран монитора в режимах VESA.
При вводе и редактировании текста, для указания позиции вводимого или изменяемого символа, традиционно используется курсор. В графических режимах аппаратная поддержка текстового курсора выключена, поэтому применяется "программный" курсор. В данном разделе мы рассмотрим способ построения мигающего текстового курсора, а его использование при вводе символов с клавиатуры будет описано в следующем разделе.
Изменение состояния курсора можно разрешить перед началом ввода строки и запретить в конце ввода. Однако в таком случае при редактировании вводимого текста придется следить за текущим состоянием курсора, неоднократно удалять его с одного места и выводить в другом, т. е. выполнять много вспомогательных действий.
Мы выберем другой способ, при котором курсор виден (и мигает) только во время ожидания ввода символа с клавиатуры.
Для разрешения работы с курсором в байт Ntick записывается число 9, что соответствует паузе примерно в 0,5 сек, а в байт curstat — число 3. Напомним, что 1 в байте Curstat разрешает прерывающей подпрограмме выполнять отсчет времени и изменять состояние курсора, а 2 указывает на то, что курсор нарисован. После этого надо вызвать подпрограмму Tgicrsr, которая нарисует курсор. Теперь при каждом тике таймера состояние курсора будет изменяться на противоположное.
После ввода символа очищается байт curstat, а рисунок курсора удаляется с экрана, если он там находился.
Таким образом, мы "привязали" рисунок курсора к тому знакоместу, в которое помещается вводимый с клавиатуры символ и никаких других действий для управления курсором не требуется.
Во всех режимах PPG при выбранном нами размере и расположении информационная строка полностью помещается в последнем окне видеопамяти. Поэтому в цикле пересылки проверять значение текущего адреса видеопамяти не имеет смысла. В примерах 5.21 и 5.22 циклы пересылки состоят из пяти команд и имеют метки savip и oeiip. Все пять команд надо исключить из текста примеров, а вместо них записать одну команду, одинаковую в обоих случаях:
rep movs dword ptr [di],fs: [si]/цикл пересылки для примеров 5.21, 5.22.
Кроме этого, из списка параметров макровызовов PushReg и PopReg надо исключить имя переменной cur_win и удалить команду-call setwin перед ret.
Упрощенный вариант подпрограмм можно использовать, только если информационная строка полностью расположена в одном окне. Кроме рассмотренного нами случая, это условие выполняется, если строка расположена вверху рабочей области экрана (коды ее точек расположены в нулевом окне видеопамяти).
Вывод информационной строки. Мы описали сопутствующие действия и можем, наконец, рассмотреть конкретную подпрограмму для вывода строки на экран. Ее текст приведен в примере 5.23. Перед обращением адрес начала строки помещается в регистр si, предполагается, что строка расположена в разделе данных задачи. Признаком конца строки является пустой байт, т. е. строка подготавливается в формате ASCIIZ.
Если по каким-то причинам вы не можете использовать функцию 05 прерывания int lOh, то в текст программы надо включить собственную подпрограмму аналогичного назначения.
Текст подпрограммы для смены активной страницы приведен в примере 5.16. Перед ее вызовом номер активной страницы указывается в регистре эх. Содержимое этой страницы появится на экране/ Для вывода рисунка курсора на новую страницу используйте подпрограмму Poseur.
Как уже говорилось в разделе, в состав прерывания int 10h входит функция иь, обрабатывающая запросы, относящиеся к знакогенератору. Одним из них является следующий.
Запрос 1130h "Get video Font information" возвращает сведения о таблицах шрифтов. Перед его изданием в регистре ьь указывается код таблицы, сведения о которой надо получить. BIOS возвращает в регистрах следующие величины: es:bp— полный адрес таблицы, сх — размер заготовки символа в байтах, dl — количество строк на экране.
Код таблицы указывается в регистре ы-i, он может иметь значения от 0 до 7. Нас будут интересовать значения 2, 3 и 6, соответствующие таблицам 8x14, 8x8 и 8x16 точек. Запрос возвращает адреса англоязычных таблиц, расположенных в ROM BIOS. Русификатор перехватывает запрос и при обнаружении указанных кодов возвращает адрес одной из своих таблиц, находящихся в оперативной памяти.
Если используется только одна таблица, то ее адрес определяется в начале выполнения задачи, сохраняется в специально выделенном двойном слове и используется по мере необходимости. Адрес таблицы желательно хранить в разделе данных программы. Полный адрес состоит из сегмента и смещения, для их размещения нужно двойное слово. Оно описывается с помощью директивы ассемблера dd, перед которой располагается метка, например:
ftaddr dd 00; поле для размещения адреса таблицы символов
Фрагмент программы, определяющий адрес таблицы символов размером 8x16 точек и сохраняющий его в ftaddr, приведен в примере 5.17.
Перед завершением задачи восстанавливается исходное значение вектора ich, т. е. подпрограмма Timeint исключается из списка заданий таймеру. Если это не сделать, то при первом же тике таймера произойдет обращение к области памяти, в которой уже нет прерывающей подпрограммы, что приведет к аварийной ситуации.
В примере 5.26 приведена группа команд, выполняющих восстановление исходного значения вектора ich. Эти команды могут быть выполнены непосредственно перед завершением задачи, т. е. перед возвратом в DOS.
Для иллюстрации способов работы с описанными подпрограммами мы рассмотрим ввод текста в специально выделенный буфер строки. Такой буфер нужен для того, чтобы оператор мог исправить допущенные им ошибки до того, как задача начнет обрабатывать введенную строку. Обычно содержимое буфера становится доступным для дальнейшей обработки после того, как оператор нажмет клавишу <Enter> ("возврат каретки").
Работа клавиатуры никак не связана с текущим видеорежимом, но от него зависит способ отображения вводимых символов на экране (эхо-печать).
В процессе выполнения графических программ на экран могут выводиться информационные строки, которые делятся на две основные категории.
К первой категории относятся напоминания о назначении различных значков, находящихся на экране, не требующие конкретной реакции оператора. Например, одно из подобных сообщений, выдаваемых Windows 9X, выглядит так:
"Начните работу с нажатия этой кнопки".
Обычно такие строки через некоторое время удаляются с экрана.
К другой категории относятся подсказки, требующие от оператора выполнения конкретных действий, например ввода числовых величин, спецификаций файлов и т. п. Такие строки остаются на экране до тех пор, пока оператор не выполнит требуемое действие.
В данном разделе мы рассмотрим общую схему вывода информационных строк, а в следующих — программирование ввода текста в ответ на подсказку оператору. При изложении материала нас будет интересовать вывод отдельных строк, а не больших объемов текста, именно поэтому в заголовке раздела использовано выражение "информационная строка". При работе с текстами большего объема применяются совершенно другие приемы.
Функции 09 и 0Ah хорошо подходят для вывода повторяющихся символов. В примере 5.5 приведен фрагмент программы, рисующий горизонтальную линию, в которой 132 раза повторяется один из символов псевдографики, имеющий код oc4h (или 196).
Проще всего вывести строку текста на экран с помощью специальной функции DOS, имеющей код 09, поэтому этот способ часто встречается на практике. Перед обращением к DOS адрес начала строки помещается в регистры ds:dx, а код запрашиваемой функции (оэ) — в регистр ah, после чего вызывается программное прерывание int 2in, которое выполняет обращение к DOS.
Предположим, для определенности, что выводимый текст хранится в разделе данных и оформлен одним из следующих способов:
coiranun db 'Проверка возможности вывода текста в режиме SVGA$'
commun db 'Проверка возможности вывода текста в режиме SVGA', 24h
Для функции оэ признаком конца строки является код 24п, которому обычно соответствует изображение знака доллара $. В первой из двух приведенных строк он записан в виде символа, а во второй — в виде кода. Если вы забудете указать признак конца строки, то поведение компьютера при выводе текста будет непредсказуемо.
Для вывода строки commun в нужное место текста задачи включаются три команды, приведенные в примере 5.4.
Для вывода последовательности символов лучше использовать функцию ОЕЬ, которая после записи кода символа в четный байт видеопамяти перемещает курсор вперед на следующую позицию на экране и корректирует сохраняемые в области данных BIOS текущие координаты курсора на используемой странице (см. пример 5.3).
Перед вызовом функции OEh в регистры записываются следующие величины:
в ah — код функции (ОЕЬ); в ai — код выводимого символа (ASCII); bh — не используется, bl— атрибут (только для графических режимов).
Для вывода строки организуется цикл обращений к функции OEh. Управлять его повторами можно с помощью счетчика или повторять процесс вывода до обнаружения в строке специального признака, например символа $, или пустого байта (строка формата ASCIIZ).
В примере 5.6 цикл организован с использованием счетчика, в который перед входом в цикл помещается размер строки commun.
Функции BIOS позволяют загрузить в знакогенератор собственные таблицы выполняемой задачи или таблицы, принадлежащие русификатору. В первом случае используется запрос nooh.
Запрос 11ooh "Load User-defined Font" предназначен для загрузки в знако-генератор таблицы, указанной при обращении к BIOS. Перед изданием запроса в регистры помещаются следующие данные: es:bp— адрес начала загружаемой таблицы в оперативной памяти, сх — количество загружаемых символов, dx — порядковый номер первого символа (начиная с 0), bh — размер рисунка символа в байтах, bl— порядковый номер таблицы в знакогенераторе, который может изменяться от 0 до 7. По умолчанию доступна нулевая таблица, поэтому если вы выберете значение ы отличное от нуля, то для работы с таблицей придется принимать специальные меры.
Размеры символов таблицы должны соответствовать характеристикам установленного видеорежима, например, для режима VESA I09h они составляют 8x16 точек, а для режима ЮАЬ — 8x8 точек. Здесь первая цифра указывает ширину, а вторая высоту символа. Для большинства текстовых режимов ширина символов составляет 8 точек.
При разработке видеоадаптера VGA IBM стандартизировала как состав регистров видеоконтроллера, так и способы их программирования. Стандарт распространяется на все режимы работы контроллера. Со временем более совершенные графические режимы SVGA потеснили режимы VGA, но текстовые режимы остались без изменений. Все современные видеокарты соответствуют стандартам как VESA, так и VGA IBM (одно не противоречит другому). Поэтому при работе в текстовых режимах задача может самостоятельно манипулировать с регистрами видеокарты без использования функций BIOS и оставаться при этом переносимой с одного компьютера на другой.
В случае частичного или полного отказа от использования функций BIOS вам придется самостоятельно разработать подпрограммы аналогичные тем, которые были описаны в данном разделе. При этом вы получите возможность более гибкого управления процессами ввода и вывода текста. Именно по этой причине текстовые редакторы, предназначенные для выполнения в среде DOS, используют собственные процедуры для работы с текстом.