оконные функции из предыдущих глав, удовлетворяющие этим
соглашениям. Второе - TSR-программа должна быть скомпилирована в
крохотной (tiny) или малой (small) модели памяти.
В главе 10 демонстрируется интеграция всех предыдуших
примеров в одну программу, выполняемую под управлением оконных
меню. В этой главе та же программа превращается в TSR.
Интегрированный пример использует файловые функции ДОС и
выполняет в соответствии с этим все правила, описанные в главе
11. По причине сложности этой задачи и для того, чтобы облегчить
ваше знакомство с программированием TSR, первый пример не требует
соблюдения этих правил.
Пример TSR-программы - "часы".
-----------------------------------------------------------------
На листинге 12.1 приведена программа clock.c, простая
TSR-утилита, обеспечивающая постоянное отображение даты и времени
в верхнем левом углу экрана. В программе после активизации не
делаются вызовы ДОС, поэтому нет нужды в защите от
нереентерабельности ДОС.
Превращение программы в резидентную.
-----------------------------------------------------------------
Функция main производит все подготовительные действия и
объявляет себя резидентной. Сначала она сохраняет свой указатель
стека, что позволит затем восстановить свой собственный стек при
вызове. Затем в программе используется getvect для чтения
текущего вектора прерывания таймера, после чего с помощью setvect
в качестве обработчика таймерного прерывания устанавливается
функция newtimer. Указатель стека TSR-программы устанавливается
как функция от объявленного размера программы и адреса
видеопамяти, определяемого на основе значения, возвращаемого
функцией vmode. Для получение даты и времени используются функции
ДОС.
Прерывание по делению на ноль.
-----------------------------------------------------------------
При старте программ, написанных на Турбо Си, выполнение
начинается со стартового кода. При этом устанавливается начальные
величины стека и "кучи" и вызывается функция main. Стартовый код
находится в файлах c0t.obj (для крохотной модели) и c0s.obj (для
малой модели). Эти файлы поставляются вместе с Турбо Си. В
стартовом коде находится обработчик прерывания по делению на
ноль, который присоединяется к соответствующему вектору перед
вызовом функции main. При выполнении return из функции main этот
вектор устанавливается в предыдущее свое значение. Возврат из
функции main нормальной нерезидентной программы означает, что
программа закончила свои действия и готова к завершению. Но
TSR-программы не завершаются выдачей return из функции main. Они
используют одну из TSR-функций ДОС, и таким образом должны сами
восстанавливать предыдущее значение вектора прерывания по делению
на ноль до завершения и превращения в резидентную. Если этого не
будет сделано, то ошибка деления на ноль в другой программе будет
обрабатываться стартовым кодом вашей TSR-программы.
Фирма Borland поставляет исходные тексты стартового кода
Турбо Си. Они находятся в файлах c0.asm и rules.asi. Вам
понадобится модифицировать c0.asm и ассемблировать его дважды -
для крохотной и малой моделей памяти. Адрес, по которому в c0.asm
сохраняется вектор прерывания по делению на ноль, назван
ZeroDivVector. Эта переменная локальна в c0.asm. Чтобы ваша
программа могла восстановить значение этого вектора, к нему надо
получить доступ путем преобразования ZeroDivVector в
public-переменную. Турбо Си добавляет символ _ в начале имен
внешних переменных, и каждое вхождение переменной ZeroDivVector в
c0.asm вы должны заменить на _ZeroDivVector. Затем надо заменить
оператор, объявляющий ZeroDivVector в программе, на следующий:
PubSym@ ZeroDivVector
,_CDECL_
Ассемблируйте файл дважды с помощью следующих команд:
C>masm c0,c0t /ML /D_TINY_,
C>masm c0,c0s /ML /D_SMALL_,
после чего будут созданы файлы c0t.obj и c0s.obj. Замените
исходные файлы Турбо Си на эти.
Обратите внимание на объявление в clock.c указателя функции
прерывания ZeroDivVector. Внешний указатель - это как раз то, что
вы только что объявили public в стартовом коде. В функции main,
до объявления себя резидентной, clock.c использует функцию
setvect для восстановления вектора прерывания по делению на ноль.
Затем clock.c завершается с объявлением себя резидентной.
Если у вас нет исходных текстов стартового кода, можно найти
следующий выход: позволить TSR-программе указывать на ошибку
деления на ноль, как только она случилась. При возникновении
такой ошибки в другой программе стартовый код вашей программы
будет выдавать сообщение об ошибке и завершать программу, в
точности как и соответствующий обработчик ДОС. При завершении
программы ДОС устанавливает вектор прерывания на свой обработчик
деления на ноль. Ваша программа больше не будет работать.
Конечно, вы должны удалить ссылки на ZeroDivVector в clock.c и
resident.c.
Выполнение обработчика прерываний от таймера.
-----------------------------------------------------------------
С каждым "тиканьем часов", происходящим 18.2 раза в секунду,
вызывается функция newtimer, объявленная как interrupt в Турбо
Си. Это объявление означает, что при вызове функции регистры
сохраняются в стеке и регистр сегмента данных указывает на
сегмент данных программы, с которой функция связана с помощью
link. Такое объявление также гарантирует восстановление регистров
из стека и выполнение машинной команды IRET при завершении работы
функции. Команда IRET используется обычно для выхода из
обработчика прерывания. Она восстанавливает регистры программного
счетчика, флагов и сегмента кодов, сохраненные при возникновении
прерывания.
Связывание старого вектора прерывания по таймеру.
-----------------------------------------------------------------
При выполнении newtimer прежде всего вызывается обработчик
прерывания, на который указывает oldtimer. Это действие позволяет
другим программам, уже присоединенным к вектору, произвести
необходимые действия. Функция newtimer проверяет флаг, который
устанавливает сама же эта функция, и означающий, что она еще
работает.
Сохранение и переключение контекста стека.
-----------------------------------------------------------------
Функция newtimer сохраняет сегмент стека и регистры
указателей - эти величины принадлежат прерванному процессу.
Значения стековых регистров, сохраненные при выполнении clock.c,
восстанавливаются в регистрах процессора, поэтому clock. c может
использовать свой собственный стек и не портить стек прерванной
программы.
Вычисление времени.
-----------------------------------------------------------------
Функция newtimer подсчитывает сигналы таймера. При
прохождении 18 сигналов (19 каждый пятый раз, так как сигналы
приходят 18.2 раза в секунду), новая величина времени вычисляется
для отображения на экран.
Затем дата и время отображаются в верхнем левом углу экрана,
восстанавливаются значения регистров стека прерванной программы,
и newtimer возвращает ей управление.
Заметим, что newtimer не переводит дату в полночь и не
изменяет значения на экране после ввода новых даты и времени
командой ДОС. Эта программа лишь иллюстрирует работу простейшей
TSR-программы. Если вы не работаете после полуночи, вы можете
использовать ее для отображения даты и времени на экране. Она
обновляет значения каждую секунду, поэтому вывод на экран другими
программами ничего не испортит. В качестве эксперимента вы можете
добавить будильник в clock.c. Включите время, когда надо
"звонить", как параметр, передаваемый в командной строке при
первом запуске clock.exe. Затем, при каждом изменении значения
часов сравнивайте его с этим временем. При равенстве времен,
выдайте звуковой сигнал, избегая, естественно, использования
функций ДОС. Позднее, когда вы узнаете, как использовать
коммуникационный вектор прерывания, вы сможете модифицировать
clock.c для установки и изменения времени звонка путем запуска
clock.exe из командной строки с параметром в то время, как TSR
уже резидентна. Вы можете добавить комментарии к звонку путем
использования оконных функций и оконного редактора. Путем именно
таких последовательных улучшений были созданы программы мировой
известности.
Программа clock.c использует прерывание от таймера. Если вы
загрузите ее после Sidekick, часы перестанут идти при вызове
Sidekick. Так как newtimer просто считает секунды, а не читает
время ДОС, такая смесь программы с Sidekick'ом сделает ее
результаты неверными. Sidekick отбирает вектор прерывания от
таймера у любой TSR-программы, загружаемой после него, чем и
вызыает такой результат. Остерегайтесь Sidekick'а при загрузке
ваших резидентных программ.
Чтобы запустить "часы", введите следующую команду:
C>clock
( Листинг 12.1 ).
/*--------- clock.c -----------*/
#include
void interrupt (*oldtimer)();
void interrupt newtimer();
extern void interrupt (*ZeroDivVector)();
#define sizeprogram 375
unsigned intsp,intss;
unsigned myss,stack;
static union REGS rg;
struct date dat;
struct time tim;
unsigned vseg;
int running = 0;
char bf[20];
unsigned v;
char tmsk []= " %2d-%02d-%02d %02d:%02d:%02d ";
int ticker = 0;
static struct SREGS seg;
main()
{
segread(&seg);
myss = _SS;
oldtimer = getvect(0x1c);
setvect(0x1c,newtimer);
stack = (sizeprogram - (seg.ds - seg.cs))*16-300;
vseg = vmode() == 7 ? 0xb000 : 0xb800;
gettime(&tim);
getdate(&dat);
setvect(0,ZeroDivVector);
rg.x.ax = 0x3100;
rg.x.dx = sizeprogram;
intdos(&rg,&rg);
}
void interrupt newtimer()
{
(*oldtimer)();
if (running ==0)
{
running = 1;
disable();
intsp = _SP;
intss = _SS;
_SP = stack;
_SS = myss;
enable();
if (ticker ==0)
{
ticker = (((tim.ti_sec % 5) ==0)? 19 :18 );
tim.ti_sec++;
if (tim.ti_sec == 60)
{
tim.ti_sec =0;
tim.ti_min++;
if (tim.ti_min == 60)
{
tim.ti_min=0;
tim.ti_hour++;
if (tim.ti_hour == 24)
tim.ti_hour = 0;
}
}
sprintf(bf,tmsk,dat.da_day,dat.da_man,dat.da_year % 100,
tim.ti_hour,tim.ti_min,tim.ti_sec);
}
for (v=0;v<19;v++)
vpoke (vseg,(60+v)*2,0x7000+bf[]);
disable();
_SS = intsp;
_SS = intss;
enable();
running = 0;
}
}
Файл проекта для построения clock.exe с именем clock.prj
имеет следующий вид:
Листинг 12.2: clock.prj
clock
ibmpc.obj
ПРОГРАММЫ TSR-ДРАЙВЕРА.
-----------------------------------------------------------------
Чтобы расширить возможности TSR-программ по использованию
функций ДОС при ее вызове, в этой главе приводится два исходных
текста на Си. После их адаптации и связи с любой стандартной
программой на Си, та становится резидентной программой. Первый
текст содержит функцию main, и туда помещаются параметры,
зависящие от вашей программы. Второй файл - это основной
TSR-драйвер, управляющий присоединением векторов прерываний,
самих прерываний, арбитраж столкновений ДОС и BIOS, определение,
резидентна ли уже программа, приостановка и возобновление работы
TSR-программы, и удаление TSR-программы из памяти.
Третий модуль в этом наборе - ваша прогррамма на Си, которая