Практика программирования (Бейсик, Си, Паскаль)

         

предоставляют довольно широкие возможности по


Использование системных функций

Системные функции MS-DOS и BIOS (Basic Input/Output System — базовая система ввода/вывода) предоставляют довольно широкие возможности по управлению внешними устройствами — дисками, видеосистемой, таймерами, манипулятором мыши и т. п. Интерфейс их вызова из программ, написанных на языке ассемблера, построен на использовании программного прерывания — машинной команды INT — по следующей схеме:

запоминание в стеке содержимого регистров центрального процессора;

формирование в регистрах исходной информации для работы системной функции INT n;

извлечение из регистров результатов работы системной функции;
восстановление регистров по информации из стека.

В принятой схеме аргумент п в команде INT задает номер группы системных функций, код регистра АН — номер подфункции, а содержимое других регистров, если это необходимо, рассматривается как исходные данные для работы выбранной подфункции. Результат работы системной функции также заносится в машинные регистры, содержимое которых должно быть сохранено для последующей обработки.

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

Таблица 10.1. Машинные регистры обработки программных прерываний


Регистр

Назначение



АХ

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

вх

Дополнительный сумматор. Чаще используется как начальный адрес (база) в командах с индексной адресацией

сх

Счетчик циклов. Может использоваться в качестве операнда в арифметических операциях

DX

Основной регистр в операциях ввода/вывода. В сочетании с АХ используется как часть 32-разрядного сумматора в "длинной арифметике"



Регистр

Назначение

ВР Указатель базы. Используется для организации локальных стеков в процедурах и функциях
SI В сочетании с базой сегмента данных (регистр DS) задает адрес источника информации (индекс источника)
DI В сочетании с базой сегмента данных (регистр DS) задает адрес приемника информации (индекс приемника)
DS Начальный адрес (база) основного сегмента данных
ES Регистр базы дополнительного сегмента данных
Flags

Регистр с битами признаков состояния центрального процессора

<


Содержимое регистров АХ, вх, сх и 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 -признак дальнего вызова).

В каждой строке может находиться комментарий, размещаемый справа после точки с запятой. В нашем примере прокомментированы все строки для тех, кто впервые сталкивается с текстом программы на языке ассемблера.



0000


IntDos PROC far


/начало подпрограммы


0000 60


pusha


; сохранение всех регистров


0001 1E


push ds


; сохранение ds


0002 ОЕ


push cs


; сохранение cs


0003 1F


pop ds


; пересылка cs n ds


0004 А1 0018


mov ax, RegAX


; пересылка поля RegAX в ах




0007


CD 33





int


33h


; прерывание с номером 33h


0009


89 IE


001A


mov


RcgBX,bx


; запоминание регистра bк


000D


89 0E


001C


mov


RegCX, cx


; запоминание регистра сх


00Н


89 16


001E


mov


RegDX, dx


;запоминание регистра dx


0015


IF





pop


ds


;восстановление ds


0016


61





popa


; восстановление регистров


0017


CB





ret


; возврат из подпрограммы


0018


0000


RegAX


DW


0


;поле для содержимого ах


001A


0000


RegBX


DW


0


;поле для содержимого Ьх


001C


0000


RegCX


DW


0


;поле для содержимого сх


001E


0000


RegDX


DW


0


;поле для содержимого dx








IntDos


ENDP





конец подпрограммы


Текст приведенной выше подпрограммы в шестнадцатеричном формате запоминается в блоке данных:

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):



0000


IntDos PROC far


начало подпрограммы


0000 60


pus ha


сохранение всех регистров


0001 IE


push ds


сохранение ds


0002 ОЕ


push cs


сохранение cs


0003 IF


pop ds


пересылка cs в ds


0004 Al 001C


mov ax, RegAX


пересылка поля RegAX в ах


0007 90


пор


для выравнивания границы


0008 CD 33


int 33h


прерывание с номером 33h


ОООА A3 001С


mov RegAX, ax


запоминание регистра ах


OOOD 89 IE 001E


mov RegBX,bx


запоминание регистра Ьх


ООН 89 ОЕ 0020


mov RegCX, ex


запоминание регистра сх


0015 89 16 0022


mov RegDX,dx


запоминание регистра dx


0019 IF


pop ds


восстановление ds


001А 61


popa


восстановление регистров


001В СВ


ret


возврат из подпрограммы


001С 0000


RegAX DW 0


поле для содержимого ах


001Е 0000


RegBX DW 0


поле для содержимого Ьх


0020 0000


RegCX DW 0


поле для содержимого сх


0022 0000


RegDX DW 0


поле для содержимого dx





IntDos ENDP


конец подпрограммы

<


Если вам понадобится вставить в текст подобной программы другие машинные команды, то для определения их цифровых кодов советуем зайти в программу 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



Красивые окна в текстовом режиме


В ЭТОМ разделе мы познакомим вас с небольшим пакетом программ на Си, разработанным одним из авторов этой книги 1990 г., когда большинство программистов были вынуждены изобретать разные средства для управления выводом данных из-за их отсутствия в среде MS-DOS. Аналогичные пакеты с меньшими функциональными возможностями вы можете найти в книгах Р. Уинера "Язык Турбо Си" и В. В. Фаронова "Программирование на персональных ЭВМ в среде Турбо Паскаль".

Пакет с условным названием ТЕХТ_ВОХ предназначен для оформления различных окон на экране дисплея и управления выводом текстовых данных в таких окнах. В его состав входит 21 функция для манипуляции со строками и текстовыми окнами, которые реализованы на базе подфункций прерывания BIOS с номером 0х10. Их список приведен в табл. 10.2. Прерывание Oxio обслуживает видеосистему не только в текстовых, но и в графических режимах, и представленные здесь возможности раскрывают примерно четверть этого арсенала.

Таблица 10.2. Функции манипуляции

Формат вызова функции

Назначение

ask attr (&cs, &cf, sin, sbl)

Опрос цветовых атрибутов

set attr (cs, cf , in, bl)

Установка цветовых атрибутов

ask_cur (&x, &y)

Опрос позиции курсора

set_ cur(x,y)

Установка курсора в заданную позицию

move_cur(n)

Перемещение курсора на n позиций

box_abs (rowl, coll, row2, col2, bord, shade)

Оформление окна

box rel (rowl, coll, rows, cols, bord, shade)

Оформление окна

cl_rect (row, col, rows, cols, color)

Очистка окна

s_out (ch)

Вывод символа в текущую позицию

s_out h (ch, n)

Вывод n символов по горизонтали

s_out v(ch,n)

Вывод n символов по вертикали

s_box abs (rowl, coll, row2, col2,ch)

Заполнение окна символом

s_box rel (rowl, coll, rows, cols, ch)

Заполнение окна символом

s_out_с (row, col, nc, str) Вывод строки по центру
s_ out_1 (row, col, nc, str) Вывод строки с прижимом влево
s_out_ r (row, col, nc, str ) Вывод строки с прижимом вправо
ask_page ( ) Опрос активной страницы
set_page (n) Установка активной страницы
out_err (str) init_txt ( ) Вывод сообщения об ошибке Инициализация текстового режима

xy_s_out (row, col, ch)

Вывод символа в заданную позицию

<


Для удобства общения между функциями пакета определены следующие глобальные переменные:

PAGE — байт с номером активной страницы (начальное значение — 0);

1 _ATTR — байт текущих цветовых атрибутов выводимого текста;

_COLR__S — байт с номером цвета выводимых символов;

_COLOR_F — байт с номером цвета фона;

__INTENS — байт с признаком обычной (0) или повышенной (1) яркости;

_BLINK — байт с признаком мерцания (0 — мерцания нет);

_ROW_CUR — байт с номером текущей строки;

_COL_CUR — байт с номером текущего столбца.

С целью сокращения обозначений и приближения их к идентификаторам регистров на Ассемблере введены следующие подстановки:

union REGS reg;

#define АН 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.dl

#define INT10h int86 ( 0x10 , &reg, &reg)

Программа 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,&reg,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 поместить полученные объектные модули в библиотеку. Эта библиотека может быть подключена к проекту вашего приложения.


Управление мышью


В составе штатных поставок систем ТС, ТР и 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.