|
.: Учебник по практическому программированию ( Бейсик, Си, Паскаль ) :.
Глава
10.
Использование
системных функций
Системные
функции MS-DOS и BIOS (Basic Input/Output System — базовая система ввода/вывода)
предоставляют довольно широкие возможности по управлению внешними устройствами
— дисками, видеосистемой, таймерами, манипулятором мыши и т. п. Интерфейс их
вызова из программ, написанных на языке ассемблера, построен на использовании
программного прерывания — машинной команды INT — по следующей схеме:
В принятой
схеме аргумент п в команде INT задает номер группы системных функций, код регистра
АН — номер подфункции, а содержимое других регистров, если это необходимо, рассматривается
как исходные данные для работы выбранной подфункции. Результат работы системной
функции также заносится в машинные регистры, содержимое которых должно быть
сохранено для последующей обработки.
Перечень
машинных регистров, задействованных в обработке программных прерываний, приведен
в табл. 10.1. Вас не должно смущать, что некоторые из терминов в этой таблице
использованы без детального объяснения.
Таблица
10.1. Машинные регистры обработки программных прерываний
Содержимое
регистров АХ, вх, сх и DX довольно часто рассматривается как пара 8-разрядных
компонент, для обозначения которых используются сочетания АН (старшие 8 битов
регистра АХ), AL (младшие 8 битов регистра АХ), ВН, BL, CH, CL, DH, DL.
Если в программу
на Си или Паскале не включены непосредственные команды ассемблера, то прямого
доступа к машинным регистрам у пользователя нет. Для вызова системной функции
по описанной выше схеме приходится обращаться к специальным процедурам-посредникам,
которые пересылают значения из данных программы в машинные регистры, моделируют
соответствующее программное прерывание и возвращают на поля программы содержимое
машинных регистров.
Мы остановимся
более детально на двух таких посредниках, считая, что остальными вы научитесь
пользоваться самостоятельно.
ТС: int86(n,&in_regs,&out_regs)
;
intdos(&in_regs,&out_regs);
//частный случай при n=0х21
ТР: Intr(n,regs);
MSDOS(regs);
{частный случай при n=$21}
Часть названия
указанных процедур произошла от английского слова interrupt — прерывание.
В функциях Си прослеживаются фрагменты обозначений старых процессоров фирмы
Intel — 8086, 80186, 80286, 80386, 80486.
Основная
часть системных функций поддерживается встроенным программным обеспечением материнской
платы, которое раньше было жестко "зашито" в микросхемы BIOS, а теперь
находится в более современной перепрограммируемой флэш-памяти. Значительная
группа системных функций с общим номером 33 (Ox2i=$2i) составляет часть операционной
системы MS-DOS. В этой группе насчитывается 85 подфункций.
В заголовочном
файле dos. h описано следующее объединение двух структур:
Struct WORDREGS {unsigned int ax,t>x, cx,dx, si,di,cflags, flags;} ; struct BYTEREGS {unsigned char al,ah,bl,bh,cl,ch,dl,dh;};
union REGS {struct
WORDREGS x; struct BYTEREGS h;};
К типу REGS
относятся аргументы in_regs и out_regs, адреса которых задаются при вызове функций
intse и intdos. Если мы включаем в свою программу, например, объединение с именем
reg (union REGS reg;), то можем манипулировать c именами полей reg.x.
ax, reg.x.bx, reg.h.al, reg.h. ah.
При работе
с программными прерываниями значения этих полей можно считать идентичными с
содержимым соответствующих машинных регистров — АХ, вх, AL, АН. На практике
в программах на Си редко используют два разных объединения in_regs и out_regs,
первое из которых выполняет роль полей с входной информацией, а второе — роль
полей, на которые заносятся результаты работы системной функции. Обычно входные
и выходные данные располагают на одном и том же поле.
В этом смысле
авторы системы Turbo Pascal поступили более рационально, использовав в аналогичных
процедурах на один аргумент меньше. Данные процедур intr и MSDOS располагаются
на полях записи с вариантами типа Registers, описанного в модуле DOS следующим
образом:
type
Registers=record
case integer of
0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,flags:word);
1: (AL,AH,BL,BH,CL,CH,DL,DH:byte)
; end;
Если в программе
на Паскале объявлена запись reg (reg:Registers;), то мы можем использовать переменные
с именами reg.АХ, RеG.вх, reg.AL, reg.АН, имея в виду, что их значения идентичны
содержимому машинных регистров
АХ, ВХ, AL,
АН.
Приводимые
ниже фрагменты программ заменяют процедуру gotoxy, перемещающую курсор дисплея
в позицию с заданными координатами на текстовой странице с указанным номером.
Новая функция (процедура) movetoxy обходится только входными данными.
Программа
10_1.с
#include <dos.h>
void movetoxy(int
x, int y, int page)
{
union REGS r;
r.h.ah=2; //номер
подфункции позиционирования курсора
г.h.bh=page;
//номер текстовой страницы, от 0 до 7
r.h.dl=x; //номер
столбца, от 1 до 80
r.h.dh=y; //номер
строки, от 1 до 25 int86(0xl0,&r,&r); //обращение к группе функций BIOS с номером 16
}
Программа
10_1.pas
program cursor;
uses Dos; procedure movetoxy(x,y,page:byte);
var
Registers r;
begin
r.AH:=2;
r.BH:=page;
r.DL:=x;
r.DH:=y; Intr ($10, r) ;
end;
Примером
системной функции, возвращающей несколько результатов, является процедура вывода
текущей даты, использующая подфункцию с номером 0х2А ($2A) в прерывании 0x33
($33). После ее выполнения в участках памяти, соответствующих машинным регистрам,
находится следующая информация:
AL — день
недели (0 — воскресенье, 1 — понедельник, ..., 6 — суббота); DL — день месяца;
он — номер месяца; сх — год.
Программа
10_2.с /*0прос текущей даты через прерывание, функция 33:42*/ #include <dos.h> #include <stdio.h>
#include <conio.h>
void DatePrint(void);
void main()
(
DatePrint();
getch();
}
/ *----------------------------------*
/
void DatePrint(void)
{
char *WeekDays[7]={"воскресенье","понедельник", "вторник","среда","четверг","пятница","суббота"};
union REGS r; r.h.ah=0x2A; /*Номер подфункции опроса текущей даты*/ intdos(&r,&r); /*DL-день, DH-месяц, СХ-год, AL-день недели*/
printf("\n
Сегодня %d/%d/%d - %s",r.h.dl,r.h.dh,r.x.ex,
WeekDays[r.h.al]);
}
Программа
10_2.pas
program intl;
uses Dos;
procedure DatePrint;
const
WeekDays:array
[0..6] of string=('воскресенье','понедельник1,
'среда','четверг','пятница','суббота');
var r:Registers;
begin
with r do begin AH:=$2A; {Номер подфункции опроса текущей даты} MSDOS(r); {DL-день, DH-месяц, СХ-год, AL-день недели} write('Сегодня ',DL,':',DH:2,':',СХ,' - '); writeln(WeekDays[AL]); end; end;
begin DatePrint; readln;
end.
К сожалению,
система программирования QBasic не содержит в своем составе функции, аналогичной
вышеописанным. Однако реализовать нечто подобное можно с помощью подпрограммы
в машинных кодах, встраиваемой в текст на Бейсике операторами DATA, пересылаемой
в оперативную память на место с известным адресом и вызываемой с помощью оператора
CALL ABSOLUTE. Одна из таких подпрофамм описана в книге [7] и содержит всего
38 байт. Мы приводим полный текст аналогичного, но более экономного (32 байта),
варианта на языке ассемблера для того, чтобы желающие могли расширить область
сохраняемых данных и, при необходимости, подменить байт с номером прерывания.
Приведенный
ниже текст представляет собой так называемый билистинг, получающийся в результате
трансляции исходной программы в язык машинных команд. В левой колонке находятся
адреса команд, справа от которых размещаются числовые коды операций и адреса
операндов (некоторые машинные команды в явном виде не содержат адреса операндов).
В средней колонке команды записаны на языке ассемблера. Каждая строка соответствует
отдельной машинной команде или управляющему оператору языка ассемблера. Перед
мнемоническим кодом команды может находиться метка, на которую могут ссылаться
другие команды (в нашем примере такими метками являются символьные обозначения
двухбайтовых слов RegAX, RegBx, RegCx, RegDx, предназначенных для хранения,содержимого
машинных регистров). Команды пересылки (код операции mov) перемещают значение
второго операнда по первому адресу. Команды записи в стек (коды операции push,
pusha) производят пересылку содержимого одного или нескольких машинных регистров
в стек. Команды выборки из стека (коды операций pop, рора) осуществляют обратную
пересылку из стека значения одного или нескольких регистров. Команда ret возвращает
управление вызывающей программе.
Первая и
последняя строки билистинга окаймляют подпрограмму. В них содержится название
подпрограммы (intoos) и указание о том, что она может находиться в памяти достаточно
далеко от точки обращения (far -признак дальнего вызова).
В каждой
строке может находиться комментарий, размещаемый справа после точки с запятой.
В нашем примере прокомментированы все строки для тех, кто впервые сталкивается
с текстом программы на языке ассемблера.
Текст приведенной
выше подпрограммы в шестнадцатеричном формате запоминается в блоке данных: DATA &Н60,&Н1Е,&НОЕ,&H1F,&НА1,&Н18,SH00,&HCD,&НЗЗ,&Н89 DATA &Н1Е,&Н1А,&Н00,&Н89,&Н0Е,&Н1С,SH00,&H89,&H16,&H1E
DATA &H00,&H1F,&H61,&HCB
Для размещения
этой подпрограммы и расположенных в ее хвосте четырех двухбайтовых полей в памяти
резервируется массив длиной в 32 байта -intProgd то 16) AS" INTEGER. Переписи
в этот массив подлежат только первые 24 байта блока данных — собственно текст
программы без полей
RegAX, RegBX,
RegCX И RegDX:
DIM IntProgd
T0 16) AS INTEGER
' установка
сегмента для работы подпрограммы DEF SEG=VARSEG(IntProg(1))
' установка
смещения для работы подпрограммы Int33&=VARPTR(IntProgd) )
' Перепись подпрограммы
из блока данных в массив F0R J%=0 T0 23
READ K% : P0KE
Int33&+J%,K% NEXT J%
Обратите
внимание на команды, изменяющие базовый (начальный) адрес сегмента. В качестве
новой базы устанавливается адрес начала массива intProg, что позволяет не производить
перенастройку адресов машинной программы в зависимости от ее местоположения
в оперативной памяти.
В тексте
программы, как и в ее прототипе [7], имеется небольшой дефект -после обработки
прерывания не запоминается содержимое регистра АХ. А некоторые прерывания заносят
в этот регистр результаты своей работы. Например, при опросе текущей даты в
младшие разряды регистра АХ зано- сится день недели. Исправить эту оплошность
довольно просто -необходимо после команды int добавить команду mov RegAX,ax,
которая занимает три байта и в числовом представлении имеет вид АЗ ххуу (здесь
ххуу — относительный адрес поля RegDX). При этом адреса переменных RegAX, RegBX,
RegCX и RegDX увеличатся на три байта. Но их желательно сохранить на границе
полуслова, т. к. с ними оперируют как с элементами целочисленного массива. Поэтому
в текст приводимой ниже модификации вставлен еще один байт — пустая команда
NOP (числовой код — 90h):
Если вам
понадобится вставить в текст подобной программы другие машинные команды, то
для определения их цифровых кодов советуем зайти в программу td.exe (Turbo Debugger
— Турбо Отладчик). Эта программа входит в комплект поставки любой Borland-системы.
Набирая в окне ввода команды на языке ассемблера, вы сразу же увидите их аналог
в шестнадцатеричной кодировке.
Замену команды
INT 33h на команду INT 21h в новом варианте можно выполнить следующим образом:
IntProg(5)=SH21CD
Из-за того,
что код операции команды INT занимает младший байт полуслова, т. е. байт с меньшим
адресом, а номер прерывания — старший байт, в целочисленной константе &H21CD
соответствующие значения переставлены местами.
Наверное, вы уже обратили внимание на аналогичные перестановки байтов в двух
байтовых адресах команд mov.
Задание номера
подфункции, который должен попасть в регистр АХ, следует выполнить путем присвоения
нужного значения элементу массива intProg, соответствующего полю RegAX. Так
как нулевой адрес ссылается на начало этого массива, т. е. на элемент intProg(1),
то адрес &Н1С=28 определяет местоположение элемента intProg(15). Поэтому
адреса полей "регистров" АХ, ВХ, СХ и DX указывают на элементы
IntProg(15),
IntProg(16), IntProg(17) И
IntProg(18).
После выполнения
подпрограммы обработки прерывания необходимо извлечь полученные данные из элементов
массива intProg, в которых были сохранены значения соответствующих регистров.
Ниже приведен
текст программы опроса текущей даты на Бейсике, использующий в качестве подпрограммы
обработки прерывания расширенную версию машинных кодов.
Программа
10_2.bas DATA &Н60,&Н1Е,SH0E,&H1F,&НА1,&Н1С,&Н00, &Н90, &HCD, &НЗЗ DATA &HA3,&H1C,&H00,&H89,&H1E,&H1E,SH00,SH89,&H0E,&H20
DATA &H00,&H89,&Д16,&H22,&H00,&H1F,&H61,&HCB
DIM IntProg(1
T0 18) AS INTEGER
'установка сегмента
для работы подпрограммы
DEF SEG=VARSEG(IntProg(1))
'установка смещения
для работы подпрограммы
Int33& =
VARPTR{IntProg(1))
'Перепись подпрограммы
из блока данных в массив
F0R J%=0 T0
27
READ K%: P0KE
Int33&+J%,К% NEXT J%
IntProg(5)=&H21CD
IntProg(15)=&H2A00 CALL ABS0LUTE(Int33&)
F0R K=15 T0
18
PRINT IntProg(K)
NEXT К WeekDay=IntProg(15) M0D 256: ' День недели = AL
Day=IntProg(18)
MOD 256: ' День месяца = DL
Month=IntProg(18)
256: ' Номер месяца = DH
Year=IntProg(17):
' Год = CX
PRINT USING
"Сегодня - ##/##/#### - #";Day;Month;Year;WeekDay
DEF SEG
END
В составе
штатных поставок систем ТС, ТР и QBasic отсутствуют средства управления мышью,
а без этого манипулятора на порядок снижается ценность игровых и диалоговых
программ. Непосредственную работу с мышью осуществляет системная программа —
драйвер мыши, — загружаемая одновременно с загрузкой операционной системы MS-DOS
или входящая в состав Windows. Драйверы мыши, ориентированные на работу с манипуляторами
различных конструкций, могут отличаться друг от друга, например по количеству
обслуживаемых кнопок. Но в большинстве своем их главные функции одинаковы и
наша программа может ими воспользоваться через механизм прерываний.
В состав
прерывания 0x33 ($33) входит более 30 подфункций, обеспечивающих связь с драйвером
мыши. Наиболее полный их перечень приводится в [16]. Далеко не все эти функции
являются предметом первой необходимости. Поэтому познакомимся только с некоторыми
из них на примере программы, отслеживающей перемещения мыши по экрану в текстовом
режиме. Достаточно полные модули управления мышью вы можете найти в книге В.
В. Фаронова "Turbo Pascal 7.0. Практика программирования. Учебное пособие"
и в [13].
Подфункция
с номером 0 осуществляет "сброс" драйвера. В результате ее работы
в регистр АХ заносится состояние мыши и драйвера, а в вх — количество кнопок.
Следует отметить, что обращение к этой подфункции в программе, работающей строго
под управлением MS-DOS и запущенной из-под Windows 95/98, дает разные результаты.
Можно, например, узнать, что на вашей трехкнопочной мыши присутствуют только
две кнопки, что мышь или драйвер не установлены (АХ=0). Дело в том, что обращаемся
мы к разным драйверам и их ответы не всегда идентичны. Но основные действия
по инициализации мыши все драйверы выполняют одинаково. К их числу относятся
перевод курсора в центр экрана и его гашение, установка стандартной формы курсора,
разрешение перемещать курсор по всему рабочему полю.
Подфункция
с номером 1 включает режим отображения позиции курсора. Гашение образа курсора
осуществляет подфункция 2, но драйвер продолжает отслеживать перемещения и погашенного
курсора.
Наиболее
важной является подфункция 3, которая сообщает текущие координаты курсора в
пикселах (х=CX, у=DX) и состояние кнопок мыши в момент вызова подфункции (BX=1
— нажата левая кнопка, BX=2 — нажата правая кнопка, BX=4 — нажата средняя кнопка).
В принципе, значением вх может быть любая комбинация одновременно нажатых кнопок.
В текстовом режиме значения координат однозначно определяются номерами текущей
строки (row) и текущего столбца (col):
x=8*(col-l)
y=8*(row-l)
В этом вы
можете убедиться на примерах следующих программ, построенных по единой схеме.
После "сброса" драйвера дается 5-секундная задержка, чтобы рассмотреть
текст сообщения о состоянии драйвера и мыши (курсор в это время не виден). Затем
включается режим отображения курсора. Далее 60 раз с задержкой в 1 с выполняется
цикл, в котором Опрашивается и отображается на экране состояние мыши. Во время
этого цикла вы можете перемещать курсор мыши, устанавливая его на пронумерованные
позиции строк экрана и зажимая ту или иную кнопку. Координата х при этом меняется
от 0 до 632, а координата у — от 0 до 192.
Программа
10_3.bas
' Модификация
программы [7], использована первая версия подпрограммы в ' машинных кодах) DATA &Н60,SH1E,SH0E,SH1F,&НА1,&Н18,SH00,&HCD, &H33,&H89 DATA &H1E,&H1A,&H00,&H89,SH0E,&H1C,&H00,&H89,&H16,&H1E
DATA &H00,&H1F,&H61,&HCB
DIM IntProgd
T0 16) AS INTEGER ' установка сегмента для работы подпрограммы DEF
SEG=VARSEG(IntProg(1)} ' установка смещения для работы подпрограммы
Int33&=VARPTR(IntProg(1)) ' Перепись подпрограммы из блока данных в массив побайтно
F0R j%=0 T0 23
READ K%: P0KE
IntDos&+j%, К% NEXT j%
' Роспись экрана
линейками через строку
CLS
C0L0R 7,1
F0R I=1 ТО 11
F0R j%=0 T0
79
PRINT USING
"#";j% M0D 10;
NEXT j%
PRINT : PRINT
NEXT I COLOR 7,0 IntProg(13)=0: ' Подфункция сброса драйвера мыши
CALL ABSOLUTE(Int33&)
LOCATE 2,1 IF IntProg(13)=-1 THEN PRINT "Работает драйвер MS-DOS "; PRINT "Число кнопок = "; IntProg(14);
SLEEP 5 IntProg(13)=1: ' Подфункция визуализации курсора на экране
CALL ABSOLUTE(Int33&) IntProg(13)=3: ' Подфункция опроса состояния мыши 15:СХ=х 16:DX=y 14:ВХ=состояние кнопок: ' 1 - нажата левая,- 2 - правая, 4 - средняя
FOR j%=l TO 60
CALL ABSOLUTE(Int33&)
LOCATE 4,1
PRINT USING
"x=### y=### кнопка #";IntProg(15);IntProg(16);
IntProg(14)
SLEEP 1 NEXT
j% LOCATE 6,1: PRINT "Цикл окончен. Нажмите любую'клавишу"
SLEEP DEE SEG
END
Программа
10_3.с /* Работа с мышью в текстовом режиме */ #include <dos.h> #include <stdio.h>
#include <conio.h>
void main()
(
int i,j;
union REGS r;
clrscr () ; /*Роспись экрана линейками через строку*/ cextcolor (7); textbackground (1) ;
for(1=1; i<12;
i++) { for(j=0; j<80; j++) cprintf("%d", j%10) ;
printf{"\n");
}
r.x.ax=0; /* подфункция сброса драйвера */ int86(0x33,&r,&r); textbackground(0);
gotoxy(1,2); if(r.x.ax==0xFFFF) cprintf("Работает драйвер MS-DOS "); cprintf("Число кнопок = %d",r.x.bx);
r.x.ax=l; sleep(5); /* Подфункция визуализации курсора на экране */
int86(0x33,&r,&r);
r.x.ax=3; /* Подфункция опроса состояния мыши*/
for(j=0; j<60;
j++) { int86(0x33,&r,Sr); gotoxy(1,4);
clreol(); cprintf("x=%d y=%d нажата кнопка %d",r.x.ex,r.x.dx,r.x.bx);
sleep(1); }
gotoxy(1,6); cprintf("Цикл окончен. 'Нажмите любую клавишу");
getch (); }
Программа
10_3.pas
program intl;
uses Crt,Dos;
var
i,j:longint; r:Registers;
begin
clrscr; {Роспись
экрана линейками через строку}
textcolor(7);
textbackground(1);
s
for i:=0 to
10 do
begin for j:=0 to 79 do
write(j mod
10); writeln;
end; textbackground(0); {подфункция сброса драйвера} r.AX:=0; Intr($33,r);
gotoxy(l,2); if r.AX=$FFFF then write('Работает драйвер MS-DOS '); write('Число кнопок = ',r.ВХ);
r.AX:=l; { Подфункция визуализации курсора на экране }
Intr($33,r);
r.AX:=3; {Подфункция опроса состояния мыши}
for j:=1 to 60
do begin
Intr($33,r);
gotoxy(l,4);
clreol;
write('x=',r.CX,
' y=',r.DX, ' кнопка =',r.BX); for i:=0 to 10000000 do i:=i;
end;
gotoxy(1,6);
write('Цикл окончен. Нажмите Enter');
readln;
end. Красивые
окна в текстовом режиме
В ЭТОМ разделе
мы познакомим вас с небольшим пакетом программ на Си, разработанным одним из
авторов этой книги 1990 г., когда большинство программистов были вынуждены изобретать
разные средства для управления выводом данных из-за их отсутствия в среде MS-DOS.
Аналогичные пакеты с меньшими функциональными возможностями вы можете найти
в книгах Р. Уинера "Язык Турбо Си" и В. В. Фаронова "Программирование
на персональных ЭВМ в среде Турбо Паскаль".
Пакет с условным
названием ТЕХТ_ВОХ предназначен для оформления различных окон на экране дисплея
и управления выводом текстовых данных в таких окнах. В его состав входит 21
функция для манипуляции со строками и текстовыми окнами, которые реализованы
на базе подфункций прерывания BIOS с номером 0х10. Их список приведен в табл.
10.2. Прерывание Oxio обслуживает видеосистему не только в текстовых, но и в
графических режимах, и представленные здесь возможности раскрывают примерно
четверть этого арсенала.
Таблица
10.2. Функции манипуляции
Для удобства
общения между функциями пакета определены следующие глобальные переменные:
С целью сокращения
обозначений и приближения их к идентификаторам регистров на Ассемблере введены
следующие подстановки:
Программа
ask_attr — опрос цветовых атрибутов
Если вы забыли,
как выглядит байт цветовых атрибутов, то загляните в раздел 3.5.5.
int ask_attrtint
*cs,int *cf,int *in,int *bl)
// cs - цвет
символов, от 0 до 7
// cf - цвет
фона, от 0 до 7
//in - яркость,
0 или 1
// b1 - признак
мерцания, 0 или 1
{ АН=8;
//Подфункция
опроса цветовых атрибутов BH=_PAGE;
//Номер текущей
страницы INT10h ;
//Чтение текущего
символа и атрибутов _ATTR=AH;
//Байт цветовых
атрибутов __COLOR__S=_ATTR & 0x07;
//Цвет символа _COLOR_F=(_ATTR & 0x70) >> 4; //Цвет фона _INTENS=(_ATTR & 0x08) >> 3; //Бит интенсивности _BLINK=(_ATTR & 0x80) >> 7; //Бит мерцания *cs=_COLOR__S;
//возврат цвета
символов *cf=_COLOR_F;
//возврат цвета
фона *in= INTENS;
//возврат признака
яркости *b1=_BLINK;
//возврат признака
мерцания return ( ATTR); //возврат всех атрибутов цвета
}
Программа
set_attr — установка цветовых атрибутов
void set_attr(int
cs,int cf,int in,int b1) { //анализ атрибутов цвета на допустимость
if(cs>=0 &&
cs<8 && cf>=0 && cf<8 &&
in>=0 &&
in<2 && bl>=0 && bl<2) {
_COLOR__S=cs
;
_COLOR_F=cf;
_INTENS=in;
_BLINK=bl; //объединение
атрибутов цвета в одном байте
_ATTR=_BLINK
<< 7 | _COLOR_F << 4 | _INTENS << 3 | _COLOR__S;
}
else err_out("Ошибка
при вызове set_attr"); }
Программа
move_cur — перемещение курсора на п позиций вправо
Используя
текущие координаты курсора (_ROW_CUR, _COL_CUR), функция вычисляет строку и
столбец новой позиции и с помощью функции set_cur перемещает туда курсор. Если
курсор выходит за пределы экрана, то его принудительно устанавливают в верхний
левый угол. void move_cur(int n)
{
int pos; pos=_ROW_CUR* 80+_COL_CUR+n; _ROW_CUR=pos/80; _COL_CUR=pos-_ROW_CUR*8 0; if(_ROW_CUR > 24)
{ _ROW_CUR=0; _COL_CUR=0;
} set_cur(_ROW_CUR+1,_COL_CUR+1) ;
}
Программа
box_abs — построение прямоугольника с рамкой и тенью
Контуры рамки
образуются пробелами, одинарными и/или двойными "линиями" с помощью
символов псевдографики. Массивы lu, id, ru и rd заполнены, кодами символов,
используемыми для отображения левого верхнего (lu), левого нижнего (id), правого
верхнего (ru) и правого нижнего (rd) углов рамки. В массивах horiz и vert находятся
коды символов, формирующие горизонтальные и вертикальные линии рамки. По индексу
bord из них извлекаются знаки соответствующей окантовки и некоторые из них повторяются
rpth раз по горизонтали и rptv раз по вертикали.
Тень создается
с помощью строки и столбца пробелов, окрашенных в серый цвет и расположенных
со сдвигом на одну позицию относительно нижней и левой (shade=-1) или нижней
и правой (shade=1) границ рамки. Внутренность окна заполняется пробелами цветом
фона, ранее установленного с помощью функции set_attr.
void box_abs(int
row1,int coll,int row2,int col2, int bord,int shade)
// rowl,coll
- левый верхний угол,
// row2,co12
- правый нижний угол
// bord -
номер типа рамки, от 0 до 4
// shade = -1(тень
слева), 0(без), 1(тень справа)
{
char lu[5]={
0x20,0xDA,0хС9,0xD6,0xD5 };
char ld[5]={
0x20,0xC0,0xC8,0xD3,0xD4 };
char ru[5]={
0x20,0xBF,0xBB,0xB7,0xB8 };
char rd[5]={
0x20,0xD9,0xBC,0xBD,0xBE };
char horiz[5]=(
0x20,0xC4,0xCD,0xC4,0xCD };
char vert[5]
={ 0x20,0хВЗ,0хВА,0хВА,0хВЗ };
int rpth,rptv,attr; rptv=co12-coll-l;
//длина вертикали
if(rptv <=
0) rptv=l; rpth=row2-rowl;
//длина горизонтали
if{rpth <=
0) rpth=l;
//анализ на
допустимость параметров окна
if(shade ==
1 && coll+rptv >= 79) goto ml;
if(shade ==-1
&& coll==l) goto ml;
if(shade !=
0 && rowl+rpth >= 25) goto ml;
if(rowl+rpth
> 25 || coll+rptv+1 > 80) goto ml; xy_s_out(rowl,coll,lu[bord]);
//верхний левый
угол s_out_h(horiz[bord],rptv);
//верхняя горизонталь s_out_h(ru[bord],1);
//верхний правый
угол set_cur(rowl+1,coll);
//курсор в начало
левой вертикали s_out_v(vert[bord],rpth);
//левая вертикаль set_cur(rowl+1,co!2);
//курсор в начало
правой вертикали s_out_v(vert[bord],rpth);
//правая вертикаль
//роспись внутренности
пробелами
sbox_rel(rowl+1,coll+1,rpth,rptv,
32) ; xy__s_out(row2,coll,Idfbord]);
//левый нижний
угол s_out_h(horiz[bord],rptv);
//нижняя горизонталь s_out_h(rd[bord],1);
//правый нижний
угол if(shade == 0) goto m;
//обход, если
нет тени attr=_ATTR;
//запоминание
атрибутов цвета set_attr(7,0,0,0);
//серый цвет
для тени, if(shade == -1)
//если тень слева
{
set_cur(rowl+1,coll-1);
//установка курсора левее и ниже
s_out_v{219,rpth+1);
//вертикаль тени
s_out_h(219,rptv+1);
//горизонталь тени, }
else //если
тень справа {
set_cur(rowl+1,col2+l);
//курсор правее и ниже
s_out_v(219,rpth+1);
//вертикаль тени
set_cur(row2+l,coll+1);
//курсор в начало горизонтали
s_out_h(219,rptv+1);
//горизонталь тени }
_ATTR=attr;
//восстановление атрибутов цвета
m: set_cur (rowl+1,
coll+1) ; //курсор в начало окна
return;
ml:err_out("Ошибка
при вызове box... "); }
Программа
bох_rе1 — построение прямоугольника с рамкой и тенью
Эта программа
отличается от предыдущей только способом задания габаритов рамки — вместо второго
противоположного угла здесь задается количество строк (rows) и столбцов (cols).
Программа определяет координаты противоположного угла и обращается к предыдущей
функции.
void box__rel(int
rowl,int coll,int rows,int cols,int bord,int shade)
// rows,cols
- число строк и столбцов
{ box_abs(rowl,coll,rowl+rows-1,col1+cols-l,bord,shade);
}
Программа
cl_rect — очистка прямоугольной области экрана
void cl_rect(int
row,int col,int rows,int cols,int color)
// row, col
- левый верхний угол,
// rows, cols
- число строк и столбцов,
// color - цвет
заливки, от 0 до 7
//( RED=4 GREEN=2
BLUE=1 )
{
AL=rows+l; //количество
строк
АН=0х06; //номер
подфункции
CH=row-l; //номер
начальной строки
CL=col-l; //номер
начального столбца
DH=row+rows-2;
//номер конечной строки
DL=col+cols—2;
//номер конечного столбца
ВН=со1ог*1б;
//дает фона
INT10h;
set_cur(row,col);
//перевод курсора в начало окна }
Программа
s_out — вывод символа в текущую позицию
void s_out(char
s) {
АН=9; //подфункция
вывода символа
AL=s; //код
выводимого символа
ВН= PAGE; //номер
активной страницы
BL=_ATTR; //текущие
цветовые атрибуты
СХ=1; //количество
повторяемых символов
INT10h;
move_cur(1);
//сдвиг курсора на 1 позицию вправо }
Программа
s_out_h — размножение символа с текущей позиции по строке
Функция отличается
от предыдущей только установкой счетчика повторений символа. После размножения
символа курсор переводится в ближайшую свободную позицию справа. void s_out_h(char s,int rpt) // s - размножаемый символ
// rpt - количество
повторений {
АН=9; //номер
подфункции
AL=s ;
BH=_PAGE ;
BL=_ATTR;
CX=rpt;
INT10h;
move_cur(rpt);
}
Программа
s_out_v — размножение символа с текущей позиции по столбцу
В цикле организуется
перемещение курсора по вертикали вниз и вывод размножаемого символа. По окончании
цикла курсор переводится за последний символ. void s_out_v(char s,int rpt) //s- размножаемый символ раз // rpt - количество повторений
{ int row,col,i; row=_ROW_CUR; col=_COL_CUR; BH=_PAGE; DL=col; BL=_ATTR;
CX=1; for(i=row; i<row+rpt; i++)
{
AH=2; //номер
подфункции установки курсора
DH=i; //номер
строки
INT10h;
АН=9; //номер
подфункции вывода символа
AL=s; //код
выводимого символа
INT10h; if(i==25) break;
}
set_cur(i,col+1);
//перевод курсора правее колонки }
Программа
sbox_abs — заполнение прямоугольной области заданным символом
Организуется
цикл по количеству строк, в каждой из которых курсор устанавливается в начальную
колонку строки и с помощью функции s_out_h выводится нужное количество символов
по горизонтали. После заполнения области курсор переводится в левый верхний
угол прямоугольника.
void sbox_abs(int
rowl,int coll,int row2,int col2,char s)
// s - символ-заполнитель
// rowl,coll
- левый верхний угол
// row2,col2.
- правый нижний угол
Программа
s_out_c — вывод строки с центровкой в заданной полосе
Функция определяет
длину выводимой строки и сравнивает ее с длиной предоставляемой полосы. Если
полоса задана с запасом, то вывод текста производится с позиции, отстоящей от
начала полосы на половину разницы длин. В противном случае строка размещается
с начала полосы.
void st_out__c(int
row,int col,int nc,char *string)
// row,col -
начало полосы,
//nc - длина
полосы,
// string -
выводимая строка
(
int ls,i;
ls=strlen(string);
i=(nc-ls)/2;
if(Is <=
nc)
st_out_l(row,col+i,nc,string)
;
else
st_out_l(row,col,nc,&string[i]);
}
Программа
s_out_l — вывод строки в полосу с прижимом влево
Строка выводится
посимвольно с начала полосы до тех пор, пока либо не будут исчерпаны все символы
строки, либо не будет заполнена последняя позиция полосы. За пределами правой
границы полосы вывод не производится. void st_out_l{int row,int col,int nc,char *string) // row,col - начало полосы, // nc - длина полосы, // string - выводимая строка
{
char s; int
i ; for(i=0; i<nc && string[i] != 0x00; i++)
{ s=string[i]; xy_s_out(row,col+i, s); }
}
Программа
s_out_r — вывод строки в полосу с прижимом вправо
Если ширина
полосы превышает длину строки, то вычисляется начальная позиция строки в полосе,
при которой последний символ строки попадает в последнюю позицию полосы, и вывод
осуществляется по функции st_out_1. Если длина выводимой строки превышает ширину
отведенной полосы, то у строки отсекаются лишние символы с начала и остаток
строки заполняет полосу целиком.
void st_out_r(int
row,int col,int nc, char *string)
// row,col -
начало полосы,
//nc - длина
полосы,
// string -
выводимая строка
{
int ls,i;
ls=strlen(string);
i=nc-ls;
if(Is <=
nc)
st_out_l(row,col+i,Is,string);
else
st_out_l(row,col,nc,
&string[i] ) ; }
Программа
ask__page — опрос активной страницы
Вместо этой
функции вызывающая программа может проанализировать значение глобальной переменной
_PAGE.
int ask_page(void)
{
return (_PAGE);
}
Программа
set_page — установка текущей страницы
Помимо записи
номера активной страницы в глобальную переменную _PAGE, необходимо довести эту
информацию и до BIOS с помощью подфункции номер 5.
void set_page(int
p) //р - номер, от 0 до 7 {
if(p>=0 &&
р<8)
{
_PAGE=p;
AH=5;
AL=p;
INT10h; } else
err_out("Ошибка при вызове set_page");
}
Программа
err_out — вывод сообщения об ошибке
Сообщение
об ошибке выдается красными мигающими буквами в самой нижней строке экрана.
После вывода сообщения делается выдержка до нажатия любой клавиши.
void err_out
(char *string) {
set_attr(7,4,1,1);
st_out_l(25,1,80,string);
getch(); }
Программа
init_txt — инициализация пакета text_box
void init_txt
(void)
/*********************************************/ /* Инициализация экрана в текстовом режиме : */
/* размер экрана
-25x80 */
/* маска символов
- 8 х 14 */
/*********************************************/
{
extern union
REGS reg;
AH=0;
AL=2 ;
INT10h; /* установка
режима */
set_page(0);
/* 0-я страница */
set_cur(1,1);
/* курсор - в начало */
set_attr(7,0,0,0);/*
цветовые атрибуты */
АН=9;
AL=32; /* код
пробела */
BL=7; /* белым
по черному */
ВН=0; /* 0-я
страница */
СХ=2000;
INT10h ; /*
очистка экрана */
}
Программа
tst_text — проверки пакета text_box
Для проверки
работоспособности описанного выше пакета предлагается следующий тест, охватывающий
почти все функции пакета:
#include "text.h"
void main()
{
init_txt();
set_attr(4,2,1,0);
//красный цвет, зеленый фон
box_abs(2,2,10,30,4,1);
//прямоугольник с тенью справа
getch();
move_cur(2);
//сдвиг курсора вправо
getch();
s_out('А');
//вывод одной буквы
getch();
s_out_h('В',3);
//вывод трех букв в строке
getch();
s_out_v('С',4);
//вывод четырех букв по вертикали
getch();
cl_rect(3,3,7,27,4)
; //заливка внутренности окна красным
getch();
set_attr(7,1,0,1);
box_rel(13,
40,8,28,4,-1);//прямоугольник с тенью слева
getch();
set_attr(7,1,0,0);
sbox_rel(14,41,6,26,'
7 ') ;
getch();
st_out_l(13,5,16,"1234567890123456");
//линейка в полосе
st_out_l(14,5,16,"
"); //очистка полосы
getch();
st_out_l(14,5,16,"Привет");
//вывод в полосу с прижимом влево
getch();
st_out_l(15,5,16,"
"); //очистка полосы
getch();
st__out_c (15,
5,16, "Привет") ; //вывод в полосу по центру getch();
st_out 1(16,5,16,"
"}; //очистка полосы
getch (); st_out_r(16,5,16,"Привет"); //вывод в полосу с прижимом вправо
getch () ; }
В состав
файла с текстом головной программы можно включить все функции
пакета и набрать заголовочный файл text.h: #include <conio.h> #include <dos.h> #include <string.h>
union REGS reg; #define AH reg.h.ah #define AL reg.h.al #define BH reg.h.bh #define BL reg.h.bl #define CH reg.h.ch #define CL reg.h.cl #define CX reg.x.ex #define DL reg.h.dl #define DH reg.h.dh #define INTlOh
int86(0x10,®,Sreg) unsigned char _PAGE,_ATTR,_COLOR_S,_COLOR_F,_INTENS,_BLINK;
unsigned ctear
_ROW_CUR,_COL_CUR;
int ask__attr
(int *cs,int *cf, int *in,int *bl) ;
void ask_cur
(int *r, int *c) ;
int ask_page
(void);
void box_abs(int
rowl,int coll,int row2,int col2, int bord,int shade);
void box_rel(int
rowl,int coll,int rows,int cols, int bord,int shade);
void cl_rect(int
rowl,int coll, int rows, int cols, int color) ;
void err_out(char
*string);
void init txt
(void);
void move_cur
(int n) ;
void s_out (char
s);
void s_out_h
(char s,int rpt) ;
void s_out_v
(char s,int rpt);
void sbox_abs
(int row1,int coll,int row2,int col2,char s) ;
void sbox rel
(int rowl,int coll,int rows,int cols,char s) ;
void set_attr
(int cs,int cf,int in,int b1);
void set_cur
(int r,int c);
void set_page
(int p);
void st_out_c
(int row,int col,int nc, char *string);
void st_out_l
(int row,int col,int nc, char *string);
void st__out_r
(int row, int col, int nc, char *string);
void xy_s_out
(int row,int col,char s); Если этот пакет вам понадобится в будущем, то функции пакета следует протранслировать и с помощью сервисной программы tlib.exe поместить полученные объектные модули в библиотеку. Эта библиотека может быть подключена к проекту вашего приложения.
|
®Сайт разработал: Nek по вопросам пишите сюда NekSuper@yandex.ru |