CodeNet / Языки программирования / C / C++ / Руководства и справочные материалы по C/C++ / C для профессиональных программистов
C для профессиональных программистов
ГЛАВА 3 -- 1 -- ------- Программы, остающиеся резидентными после завершения и формирующие при их вызове всплывающие изображения на экране дисплея. ----------------------------------------------------------------- Простая на первый взгляд идея создания программ, которые оставались бы резидентными в памяти после их завершения и реагировали на вызов формированием всплывающих изображений на экране дисплея, на самом деле является одной из наиболее трудных задач программирования для ПЭВМ. Такие программы называются ТSR-программами. При чтении данного раздела вы должны получше пристегнуться ремнями безопасности и одеть защитный шлем, т.к. создание TSR-программ связано с риском. Но этот риск оправдан возможным вознаграждением - поистине профессиональными результатами, которыми гордился бы любой программист мирового класса. Поскольку ТSR-программы, естественно, должны на низком уровне взаимодействовать с аппаратурой и операционной системой, то излагаемые в данном разделе сведения будут применимы только к ПЭВМ линии IBM PC, работающими под операционной системой DOS. По причинам, которые будут указаны ниже, приводимые в разделе программы рассчитаны на компилятор Turbo C, но могут быть модифицированы и для других компиляторов. Предупреждение. Для разработки и использования TSR-программ характерна модификация таблицы векторов прерываний. Приведенные в данном разделе программы транслируются с помощью Турбо Си версии 1.0 и в этом случае работают корректно и без посторонних эффектов. При использовании другого компилятора корректность работы не гарантируется. Кроме того, если вы будете набирать эти программы вручную, то можете внести свои ошибки. И в том, и в другом случае это может привести к фатальному сбою системы, в результате чего могут быть уничтожены данные на вашем винчестерском диске. Поэтому целесообразно делать резервные копии файлов. Я уверен, что приводил к краху мою модель 60 не менее 100 раз за те два дня, пока отлаживал основную логику своей программы. (К счастью, я не затирал при этом винчестерского диска). Глава III -- 2 -- Что такое TSR-программа? ----------------------------------------------------------------- ТSR-программы создаются путем вызова функции 49 DOS, по которой производится возврат из программы в DOS. При этом программа остается в области памяти, которую DOS в дальнейшем не использует. Таким образом, программа может быть мгновенно вызвана без повторной загрузки. Одним из многих широко известных примеров TSR-программ является программа Sidekick фирмы Вorland. Большинство TSR-программ вызываются с помощью прерывания, которое может быть сформировано несколькими способами. Наиболее распространенными являются прерывания по таймеру, прерывания клавиатуры и печати экрана. Для TSR-программ, формирующих изображение на экране, обычно используются прерывания от клавиатуры или печати экрана, поскольку позволяют пользователю вызывать TSR-программу путем одиночного нажатия клавиши. Глава III -- 3 -- Прерывания в семействе процессоров 8086. ----------------------------------------------------------------- Процессоры семейства 8086 поддерживают до 256 различных прерываний по вектору. Прерывание по вектору вызывает выполнение программы обработки прерываний (ISR), адрес которой содержится в таблице векторов прерываний. Хотя некоторые старшие процессоры семейства требуют, чтобы программы обработки прерывания располагались в определенных адресах памяти, механизм прерываний по вектору позволяет определять адреса программ обработки прерываний. Таблица векторов начинается с адреса 0000:0000 и ее размер составляет 1024 байта. Поскольку адрес программы обработки прерывания может быть любым, то для его определения требуется 32 разряда (4 байта). Следовательно, размер каждой записи в таблице векторов составляет 4 байта. Адреса ISR-программ в таблице записываются таким образом, что адрес программы обработки прерывания 0 находится по адресу 0000:0000, программы обработки прерывания прерывания 1 - по адресу 0000:0004, прерывания 2 - по адресу 0000:0008 и т.д. Когда происходит прерывание, то любые другие прерывания запрещаются. Ваша программа обработки прерывания сразу после того, как она начнет выполняться, должна разрешить прерывания, чтобы избежать краха системы. Программа обработки прерывания должна завершаться командой IRET. Глава III -- 4 -- Прерывания против DOS и BIOS: Tревога в стране DOS. ----------------------------------------------------------------- Программисты часто выражают недовольство тем, что DOS не является повторно входимой программой. Это означает, что когда одна программа обращается к DOS, то другая программа этого делать не может. (Этим объясняется, в частности, почему DOS не является мультизадачной операционной системой). Таким образом, программа обработки прерывания не может вызывать никакой функции DOS, такая попытка приводит к краху системы. Поэтому программа обработки прерывания должна сама выполнять те действия, которые производятся при обращении к функциям DOS. К счастью, для формирования видеоизображения мы можем использовать программы непосредственного обращения к видеопамяти из разделов 1 и 2. BIOS допускает некоторую повторную входимость. Например, прерывание 16, соответствующее вводу с клавиатуры, может быть использовано в этом режиме без каких-либо побочных эффектов. Некоторые другие подпрограммы использовать таким образом не столь безопасно. Обнаружить это можно только экспериментальным путем. О том, что функцию нельзя использовать в таком режиме, вы узнаете по фатальному сбою системы. Для приведенных в данном разделе примеров и для многих распространенных в мире программ прерывания 16 вполне достаточно. Поскольку многие из функций стандартной библиотеки языка Си обращаются к DOS или к BIOS, то они не должны использовать тех функций DOS и BIOS, которые не обеспечивают повторной входимости. Следует помнить, что не только функции ввода-вывода обращаются к DOS и BIOS. Например, функция распределения памяти malloc() обращается к DOS для определения размера свободной памяти в системе. К сожалению, программы, которые рассчитаны на один компилятор, могут не работать с другим компилятором. Этим и объясняется, почему TSR-программы так трудно создавать и переносить в другую среду и почему TSR-программ создано столь немного при их очень большой популярности. По существу, вы должны воспринимать TSR-программы как "заблудшие" программы, о существовании которых DOS не подозревает. И в дальнейшем, чтобы сохранить тайну о своем существовании эти программы должны избегать любого взаимодействия с DOS. Всего пары обращений к DOS достаточно, и вашей программе будет устроена кровавая резня. Чтобы этого избежать, вы должны ощущать себя шпионом и иметь нервы автогонщика. Глава III -- 5 -- Модификатор функций прерывания Турбо Си. ----------------------------------------------------------------- Хотя стандарт ANSI этого и не требует, Турбо Си включает специальный модификатор типа функции, который называется interrupt и позволяет использовать функции Си в качестве TSR-программ. (Большинство основных разработчиков компиляторов Си по всей вероятности включат это средство в свои будущие разработки, поскольку это очень важное расширение). Например, предположим, что функция test() используется для обработки прерываний. В этом случае вы должны определить ее так, как показано ниже. Параметры, описывающие значения соответствующих регистров во время прерывания, не нужно определять, если они не будут использоваться. void interrupt test(bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags) unsigned bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags; { . . . } Функция interrupt автоматически сохраняет значения всех регистров и восстанавливает их перед возвратом управления вызывающей программе. Эта функция использует для возврата управления команду IRET вместо обычной в таком случае команды RET. В представленных в данной книге примерах модификатор interrupt применяется только для тех функций, которые используются в качестве точек входа в программы обработки прерываний TSR-программ. Если ваш компилятор не поддерживает модификатор interrupt, то вам необходимо написать на ассемблере небольшой интерфейсный модуль, который будет сохранять значения регистров, переустанавливать разрешение прерываний, а затем вызывать соответствующую функцию Си. Для выхода из модуля необходимо использовать команду IRET. Средства создания функций на языке ассемблера различны для разных компиляторов, так что читайте имеющееся у вас руководство пользователя. Глава III -- 6 -- Общий план TSR-программы ----------------------------------------------------------------- Все TSR-программы обычно состоят из двух разделов. Первая часть используется для инициализации TSR-программы и возврата управления DOS путем использования реентерабельного системного вызова. Эта часть не выполняется до тех пор, пока не возникает необходимость в перезагрузке программы. При этом производится запись адреса точки входа TSR-программы в соответствующее место таблицы векторов. Вторая, прикладная часть, занимается формированием изображений. При этом почти всегда используются окна, а следовательно,и программы управления окнами. При этом изображение на экране восстанавливается после завершения работы прикладной части программы. Следует помнить, что у большинства TSR-программ прикладные части представляют собой утилиты формиривания изображения, как у программы типа "записной книжки" или "калькулятора". После своего завершения они восстанавливают изображение на экране в том же виде, каким оно было перед запуском этих программ. Глава III -- 7 -- Использование прерывания печати экрана. ----------------------------------------------------------------- Без сомнений, прерыванием, которое наиболее просто "украсть" у DOS, является прерывание номер 5. Это прерывание вызывается при нажатии клавиши PT SCR. Если вы готовы пожертвовать функцией печати экрана, то можете заменить адрес этой программы в таблице векторов адресом вашей TSR-программы. Таким образом, при каждом нажатии клавиши PT SCR будет вызываться ваша TSR-программа. Примером такой программы является резидентный калькулятор. Программы для работы с окнами и программа калькулятора из раздела 2 приводятся здесь с некоторыми небольшими изменениями. Глава III -- 8 -- Раздел инициализации ----------------------------------------------------------------- Раздел инициализации программы резидентного калькулятора очень небольшой и целиком помещается в нижеследующей функции main(). void interrupt tsr_ap(); /* вход в прикладную программу */ main() { struct address { char far *p; } ; /* адрес прерывания печати экрана */ struct address far *addr = (struct address far *) 20; addr->p = (char far *) tsr_ap; set_vid_mem(); tsr(2000); } TSR-программа первым делом должна заменить адрес программы обработки прерывания 5 указателем функции, определенной в самой TSR-программе. Есть несколько способов изменения адреса в таблице векторных прерываний. Один из способов состоит в использовании системного вызова DOS. Однако неудобство использования функции DOS заключается в том, что она требует задания значения адресного сегмента в регистре ЕS, который недоступен при использовании функции int86(). Некоторые компиляторы, как например Турбо Си, включают специальные функции, предназначенные для установки адреса в таблице прерываний. Однако способ, предлагаемый здесь, будет работать при использовании практически любого компилятора. Функция tsr_ap() является точкой входа в прикладную часть TSR-программы. Она использует указатель на содержимое таблицы векторов, соответствующее прерыванию 5. (Напоминаем, что вектор 5 расположен по адресу 20(4х5) в таблице, поскольку каждый вектор имеет размер 4 байта. Некоторые TSR-программы восстанавливают исходное значение адреса. Но при использовании приводимых здесь программ вы должны будете перезагружать систему, чтобы восстановить исходные значения векторов прерываний. В предыдущих разделах, проверка режима работы видеосистемы производилась динамически теми программами, которые с ней работали. Однако в данном случае это неприменимо, поскольку требует использования системных вызовов DOS. Вместо этого значение глобального указателя vid_mem устанавливается с помощью функции set_vid_mem, приводимой ниже. set_vid_mem() { int vmode; vmode = video_mode(); if((vmode!=2) && (vmode!=3) && (vmode!=7)) { printf("video must be in 80 column text mode"); exit (1); Глава III -- 9 -- } /* установить соответствующий адрес видеопамяти */ if(vmode==7) vid_mem = (char far *) 0xB0000000; else vid_mem = (char far *) 0xB8000000; } Наконец, выход из функции main() ocyществляется путем обращения к функции tsr(), приведенной ниже. /* завершить выполнение, но оставить резидентной */ tsr(size) unsigned size; { union REGS r; r.h.ah = 49; /* завершить и оставить резидентной */ r.h.al = 0; /* код возврата */ r.x.dx = size; int86(0x21, &r, &r); } Параметр size, определяемый в регистре DX, используется для того, чтобы сообщить DOS, сколько памяти требуется для размещения ТSR-программы. Размер памяти определяется в 16-байтных параграфах. Иногда бывает трудно определить, сколько памяти необходимо для размещения программы. И если в этом случае вы разделите размер загрузочного модуля вашей программы (файла с расширением .EXE) на 16, а полученную величину умножите на 2, то будете застрахованы от ошибки. Точно определить размер необходимой памяти трудно, поскольку загрузочные модули частично накладываются друг на друга при загрузке и необязательно размещаются в непрерывной области. (Если вы намереватесь продавать свои программы, то наверняка хотели бы знать точно, сколько потребуется памяти, чтобы не оказаться слишком расточительным. Наиболее просто это можно определить экспериментальным путем). Код возврата, устанавливаемый в регистре AL, передается системе. После завершения выполнения функции маin() программа остается в памяти, и никакая другая программа не может быть загружена на ее место. Это значит, что прикладная часть программы в любой момент времени готова быть запущенной нажатием клавиши PT SCR. Глава III -- 10 -- Прикладная часть TSR-программы ----------------------------------------------------------------- Точкой входа в прикладную часть TSR-программы должна быть функция типа interrupt. В представленном ниже примере запуск прикладной части выполняется путем вызова функции window_main(). /* Точка входа в прикладную часть TSR-программы */ void interrupt tsr_ap() { if(!busy) { busy = !busy; window_main(); busy = !busy; } } Глобальная переменная busy первоначально устанавливается в 0. Прикладная часть TSR-программы не является повторно входимой, следовательно, она не должна запускаться дважды за время одного использования. Переменная busy используется как раз для того, чтобы предотвратить это. (Некоторые компиляторы Си могут создавать реентерабельные программы, но безопаснее для вас не обсуждать здесь этого вопроса). В программы управления окнами необходимо внести некоторые изменения для того, чтобы их можно было использовать в TSR-программах. Во-первых, необходимо статически распределять память, необходимую для хранения текущего содержимого экрана, путем использования глобального массива. Вы могли привыкнуть к тому, что эта память распределялась динамически, но данный способ здесь непригоден, вследствие того, что функции динамического распределения используют системный вызов, который недопустим в TSR-программах. По этой же причине функция go_to_xy() не может быть использована для позиционирования курсора. Наконец, стандартные Си-функции sscanf() и sprintf() также не могут быть использованы (по крайней мере, в Турбо Си), потому что также осуществляют обращения к DOS. Вместо них используются функции атоi() и itoa(). Полный текст программы резидентного калькулятора представлен ниже. /* TSR-программа, использующая прерывание печати экрана */ #include "dos.h" #include "stdlib.h" #define BORDER 1 #define ESC 27 #define MAX_FRAME 1 #define REV_VID 0x70 #define NORM_VID 7 #define BKSP 8 void interrupt tsr_ap(); void save_video(), restore_video(); void write_string(), write_char(); Глава III -- 11 -- void display_header(), draw_border(); void window_gets(); void window_cleol(), window(); void calc(); char far *vid_mem; struct window_frame { int startx, endx, starty, endy; int curx, cury; /* текущее положение курсора в окне */ unsigned char *p; /* указатель буфера */ char *header; /* сообщение в верхней части окна */ int border; /* включение/отключение бордюра */ int active; /* активация/деактивация окна */ } frame[MAX_FRAME]; char wp[4000]; /* буфер для хранения текущего содержимого экрана /* busy установлена в 1, если программа активна, иначе - в 0 */ char busy = 0; main() { struct address { char far *p; } ; /* адрес прерывания печати экрана */ struct address far *addr = (struct address far *) 20; addr->p = (char far *) tsr_ap; set_vid_mem(); tsr(2000); } set_vid_mem() { int vmode; vmode = video_mode(); if((vmode!=2) && (vmode!=3) && (vmode!=7)) { printf("video must be in &0 column text mode"); exit(1); } /* установить соответсвующий адрес видеопамяти */ if(vmode==7) vid_mem = (char far *) 0xB0000000; else vid_mem = (char far *) 0xB8000000; } /* точка входа в прикладную часть TSR-программы */ void interrupt tsr_ap() { if(!busy) { busy = !busy; window_main(); busy = !busy; } } Глава III -- 12 -- /* завершить, но оставить резидентной */ tsr(size) unsigned size; { union REGS r; r.h.ah = 49; /* завершить, но оставить резидентной */ r.h.al = 0; /* код возврата */ r.x.ax = size; int86(0x21, &r, &r); } window_main() { /* первым делом, создать структуру окна */ make_window(0, " Calculator ", 8, 20, 12, 60, BORDER); /* для активации описанного окна используйте window() */ calc(); } /*************************************************************/ /* Функции управления окнами */ /*************************************************************/ /* Вывести на экран спускающееся меню */ void window(num) int num; /* номер окна */ { int vmode, choice; int x, y; /* сделать окно активным */ if(!frame[num].active) { /* используется не постоянно */ save_video(num); /* сохранить текущий экран */ frame[num].active = 1; /* установить флаг активности */ } if(frame[num].border) draw_border(num); display_header(num); /* вывести окно */ } /* Создать спускающееся окно если окно может быть создано, возвращается 1; иначе возвращается 0. */ make_window(num, header, startx, starty, endx, endy, border) int num; /* номер окна */ char *header; /* текст заголовка */ int startx, starty; /* координаты X,Y левого верхнего угла */ int endx, endy; /* координаты X,Y правого нижнего угла */ int border; /* без бордюра если 0 */ { Глава III -- 13 -- register int i; int choice, vmode; unsigned char *p; if(num>MAX_FRAME) { window_puts(0, "Too many windows\n"); return 0; } if((startx>24) || (starty>78) || (starty<0)) { window_puts(0, "range error"); return 0; } if((endx>24) || (endy>79)) { window_puts(0, "window won't fit"); return 0; } /* создать структуру окна */ frame[num].startx = startx; frame[num].endx = endx; frame[num].starty = starty; frame[num].endy = endy; frame[num].p = wp; frame[num].header = header; frame[num].border = border; frame[num].active = 0; frame[num].curx = 0; frame[num].cury = 0; return 1; } /* Деактивировать окно и удалить его с экрана */ deactivate(num) int num; { /* установить курсор в левый верхний угол */ frame[num].curx = 0; frame[num].cury = 0; restore_video(num); } /* Вывести заголовок окна в соответсвующее поле */ void display_header(num) int num; { register int i, y, len; y = frame[num].starty; /* Вычислить точное значение центральной позиции заголовка если отрицательное - заголовок не может быть выведен */ len = strlen(frame[num].header); len = (frame[num].endy - y - len) / 2; Глава III -- 14 -- if(len<0) return; /* don't display it */ y = y +len; write_string(frame[num].startx, y, frame[num].header, NORM_VID); } void draw_border(num) int num; { register int i; char far *v, far *t; v = vid_mem; t = v; for(i=frame[num].startx+1; i<frame[num].endx; i++) { v += (i*160) + frame[num].starty*2; *v++ = 179; *v = NORM_VID; v = t; v += (i*160) + frame[num].endy*2; *v++ = 179; *v = NORM_VID; v = t; } for(i=frame[num].starty+1; i<frame[num].endy; i++) { v += (frame[num].startx*160) + i*2; *v++ = 196; *v = NORM_VID; v = t; v += (frame[num].endx*160) + i*2; *v++ = 190; *v = NORM_VID; v = t; } write_char(frame[num].startx, frame[num].starty, 218, NORM_VID); write_char(frame[num].startx, frame[num].endy, 191, NORM_VID); write_char(frame[num].endx, frame[num].starty, 192, NORM_VID); write_char(frame[num].endx, frame[num].endy, 217, NORM_VID); } /**************************************************************/ /* Оконные функции ввода/вывода */ /**************************************************************/ /* Вывести строку начиная с текущей позиции курсора описанного окна. Возвратить 0 если окно не активное; и 1 в противном случае. */ window_puts(num, str) int num; char *str; { Глава III -- 15 -- /* убедиться, что окно активное */ if(!frame[num].active) return 0; for( ; *str; str++) window_putchar(num, *str); return 1; } /* Вывести символ в текущую позицию курсора описанного окна. Возвратить 0 если окно не активное, и 1 в противном случае. */ window_putchar(num, ch) int num; char ch; { register int x, y; char far *v; /* убедиться, что окно активное */ if(!frame[num].active) return 0; x = frame[num].curx + frame[num].startx + 1; y = frame[num].cury + frame[num].starty + 1; v = vid_mem; v += (x*160) + y*2; /* вычислить адрес */ if(y>=frame[num].endy) { return 1; } if(x>=frame[num].endx) { return 1; } if(ch=='\n') { /* символ перехода на новую строку */ x++; y = frame[num].startx+1; v = vid_mem; v += (x+160) + y*2; /* вычислить адрес */ frame[num].curx++; /* инкрементировать X */ frame[num].cury = 0; /* сбросить Y */ } else { frame[num].cury++; *v++ = ch; /* вывести символ */ *v++ = NORM_VID; /* нормальные атрибуты символа */ } window_xy(num, frame[num].curx, frame[num].cury); return 1; } /* Установка курсора в заданную позицию окна. Возвращает 0 при выходе за границу; не ноль в противном случае. Глава III -- 16 -- */ window_xy(num, x, y) int num, x, y; { if(x<0 || x+frame[num].startx>=frame[num].endx-1) return 0; if(y<0 || y+frame[num].starty>=frame[num].endy-1) return 0; frame[num].curx = x; frame[num].cury = y; return 1; } /* Считать строку из окна. */ void window_gets(num, s) int num; char *s; { char ch, *temp; temp = s; for(;;) { ch = window_getche(num); switch(ch) { case '\r': /* нажата клавиша ENTER */ *s='\0'; return; case BKSP: /* возврат */ if(s>temp) { s--; frame[num].cury--; if(frame[num].cury<0) frame[num].cury = 0; window_xy(num, frame[num].curx, frame[num].cury); write_char(frame[num].startx+ frame[num].curx+1, frame[num].starty+frame[num].cury+1, ' ', NORM_VID); } break; default: *s = ch; s++; } } } /* Ввод символа с клавиатуры в окно. Возвращает полный 16-разрядный скан-код. /* window_getche(num) int num; { union inkey { char ch[2]; int i; } c; Глава III -- 17 -- if(!frame[num].active) return 0; /* window not active */ window_xy(num, frame[num].curx, frame[num].cury); c.i = bioskey(0); /* обработать нажатие клавиши */ if(c.ch[0]) { switch(c.ch[0]) { case '\r': /* нажата клавиша ENTER */ break; case BKSP: /* возврат */ break; default: if(frame[num].cury+frame[num].starty < frame[num].endy-1) { write char(frame[num].startx+ frame[num].curx+1, frame[num].curx--; window_xy(num, frame[num].curx, frame[num].cury); } return c.i; } /* Очистить до конца строки */ void window_cleol(num) int num; { register int i, x, y; x = frame[num].curx; y = frame[num].cury; window_xy(num, frame[num].curx, frame[num].cury); for(i=frame[num].cury; i<frame[num].endy-1; i++) window_putchar(num,' '); window_xy(num, x, y); } /* Переместить курсор на одну строку вверх. При успешном завершении вернуть ненулевое значение; в противном случае - 0. */ window_upline(num) int num; { if(frame[num].curx>0) { frame[num].curx--; window_xy(num, frame[num].curx, frame[num].cury); return 1; } return 0; } /* Переместить курсор на одну строку вниз. При успешном завершении вернуть ненулевое значение; в противном случае - 0. */ window_downline(num) Глава III -- 18 -- int num; { if(frame[num].curx<frame[num].endx-frame[num].startx-1} { frame[num].curx++; window_xy(num, frame[num].curx, frame[num].cury); return 1; } return 1; } /* вернуться на одну позицию назад */ window_bksp(num) int num; { if(frame[num].cury>0) { frame[num].cury--; window_xy(num, frame[num].curx, frame[num].cury); window_putchar(num, ' '); frame[num].cury--; window_xy(num, frame{num}.curx, frame[num].cury); } } /********************************************************/ /* Дополнительные функции */ /********************************************************/ /* Вывести строку с установленными атрибутами */ void write_string(x, y, p, attrib) int x, y; char *p; int attrib; { register int i; char far *v; v = vid_mem; v += (x*160) + y*2; /* вычислить адрес */ for(i+y; *p; i++) { *v++ = *p++; /* вывести символ */ *v++ = attrib; /* вывести атрибуты */ } } /* Вывести символ с утановленными атрибутами */ void write_char(x, y, ch, attrib) int x, y; char ch; int attrib; { register int i; char far *v; v = vid_mem; Глава III -- 19 -- v += (x*160) +y*2; *v++ = ch; /* вывести символ */ *v = attrib; /* вывести атрибуты */ } /* Сохранить содержимое области экрана */ void save_video(num) int num; { register int i,j; char far *v, far *t; char *but_ptr; but_ptr = frame[num].p; v = vid_mem; t=v; for(i=frame[num].starty; i<frame[num].endy+1; i++) for(j=frame[num].startx; j<frame[num].endx+1; j++) { t = (v + (j*160) + i*2); *buf_ptr++ = *t++; *buf_ptr++ = *t; *(t-1) = ' '; /* очистить окно */ } } /* Восстановить содержимое области экрана */ void save_video(num) int num; { register int i,j; char far *v, far *t; char *but_ptr; but_ptr = frame[num].p; v = vid_mem; t=v; for(i=frame[num].starty; i<frame[num].endy+1; i++) for(j=frame[num].startx; j<frame[num].endx+1; j++) { v = t; v += (j*160) + i*2; *v++ = *but_ptr++; /* вывести символ */ *v = *but_ptr++; /* вывести атрибуты */ } frame[num].active = 0; /* восстановить изображение */ } /* Возвращает код текущего видеорежима */ video_mode() { union REGS r; r.h.ah =15; /* получить код видеорежима */ return int86(0x10, &r, &r) & 255; } Глава III -- 20 -- /********************************************************** калькулятор **********************************************************/ #define MAX 100 int *p; /* указатель стека */ int *tos; /* указатель вершины стека */ int *bos; /* указатель дна стека */ char in[80], out[80]; int stack[MAX]; /* Стековый, с постфиксной записью калькулятор на четыре функции */ void calc() { int answer; int a, b; p = stack; tos = p; bos = p + MAX - 1; window(0); do { window_xy(0, 0, 0); window_cleol(0); window_puts(0, " : "); /* промптер калькулятора */ window_gets(0, in); window_puts(0, " \n "); window_cleol(0); switch(*in) { case '+ ': a = pop(); b = pop(); answer = a + b; push(a+b); break; case '-': a = pop(); b = pop(); answer = b-a; push(b-a); break; case '- ': a = pop(); b = pop(); answer = b*a; push(b*a); break; case '/ ': a = pop(); b = pop(); Глава III -- 21 -- if(a==0) { window_puts(0, "divide by 0\n"); break; } answer = b/a; push(b/a); break; default: push(atoi(in)); continue; } itoa(answer, out, 10); window_puts(0, out); } while(*in); deactivate(0); } /* Поместить число в стек. Возвратить 1 при успешном завершении; и 0, если стек переполнен */ push(i) int i; { if(p>bos) return 0; *p = i; p++; return 1; } /* Извлечь верхний элемент стека Возвратить 0, если стек пуст. */ pop() { p--; if(p<tos) { p++; return 0; } return *p; } Вы можете сразу вводить эту программу в ЭВМ. Для того, чтобы установить прикладную часть, запустите ее на выполнение. Для вызова калькулятора нажмите клавишу PT SCR. Глава III -- 22 -- Использование прерывания по нажатию клавиши. ----------------------------------------------------------------- Прерывание печати экрана очень просто использовать, но у него есть три крупных недостатка. Во-первых, оно позволяет быть резидентным в системе только прикладной части TSR-программы. Во-вторых, вы не можете при этом пользоваться печатью экрана. В-третьих, это решение проблемы "в лоб", и потому оно не очень хорошее. Лучшим способом запуска TSR-программы является использование прерывания 9 по нажатию клавиши. Прерывание 9 выполняется при каждом нажатии клавиши на клавиатуре. При использовании прерывания 9 для запуска TSR-программ должны соблюдаться следующие основные положения. Во-первых, Вы должны переписать адрес из таблицы векторов, соответствующий прерыванию 9, в такое место таблицы, которое соответствует неиспользуемому DOS прерыванию. Мы будем использовать прерывание 60. Затем, занесите адрес точки входа в вашу TSR-программу по адресу прерывания 9 в таблице векторов. После запуска ваша TSR-программа первым делом вызовет через прерывание драйвер ввода с клавиатуры. Затем проверяется, не соответствует ли введенный символ "горячей клавише", которая используется для запуска прикладной части TSR-программы. Если соответствует, то прикладная часть начинает выполняться, в противном случае никакого действия не производится и TSR-программа деактивируется. Таким образом, при каждом нажатии происходит обращение к функции, реагирующей на нажатие клавиш, но прикладная часть TSR-программы запускается только при нажатии определенной клавиши. Использование прерывания по нажатию клавиши имеет два преимущества. Во-первых, при этом нет никакой потери функциональных возможностей. Во-вторых, появляется возможность использовать одновременно несколько различных прикладных частей TSR-программы, вызов которых осуществляется нажатием соответствующих им различных "горячих клавиш". Представленная в данном разделе TSR-программа использует эту возможность и включает в свой состав и "калькулятор", и "записную книжку" (из раздела 2), которые вызываются отдельно друг от друга. Прежде, чем использовать эту возможность, вы должны узнать кое-что об обработке BIOS нажатий клавиш. Глава III -- 23 -- Буфер символов, введенных с клавиатуры. ----------------------------------------------------------------- Как вы знаете, стандартные версии DOS буферизуют до 15 символов, введенных с клавиатуры, что позволяет выполнить ввод с опережением. При каждом нажатии клавиши наступает прерывание 9. Программа ISR реакции на нажатие клавиши принимает код символа из порта и помещает его в буфер. Когда вы обращаетесь к функциям DOS или BIOS ввода с клавиатуры, обрабатывается только содержимое буфера, а не текущее содержимое порта. Это позволяет вашим программам непосредственно обрабатывать содержимое буфера символов, так же, как это делают программы BIOS и DOS. Таким образом, это позволяет функции реагирования на нажатие клавиш вашей TSR-программы определять, была ли нажата "горячая клавиша", не уничтожая при этом содержимого буфера символов. Буфер ввода с клавиатуры расположен по адресу 0000:041 (1054 в десятичной системе счисления ). Поскольку при каждом нажатии клавиши формируется 16-битный скан-код, то для ввода 15 символов требуется 30 байт. Однако обычно используются 32 байта, т.к. скан -код клавиши RETURN автоматически добавляется к концу буфера. Буфер организован в виде циклической очереди, доступ к которой осуществляется через указатели начала и конца очереди. Указатель начала указывает на символ, который был введен последним. Указатель конца указывает на следующий символ, который будет передан по запросу на ввод символа от DOS или BIOS. Указатель начала хранится по адресу 0000:041C (1052 в десятичной с.с.). Значения указателей начала и конца фактически используются для индексной адресации очереди, и соответствует индексу текущей позиции +30. (Это связано с особенностями выполнения косвенной адресации процессором 8086). Значения указателей начала и конца очереди совпадают в том случае, если очередь пуста. Глава III -- 24 -- Функция инициализации. ----------------------------------------------------------------- Для прикладной TSR-программы, представленной в данном разделе, требуется небольшая по объему программа инициализации. Она оформлена в виде функции main(), которая приводится ниже. main() { struct address { char far *p; } temp; /* указатель вектора прерывания 9 */ struct address far *addr = (struct address far *) 36; /* указатель вектора прерывания 60 */ struct address far *int9 = (struct address far *) 240; /* Поместить адрес обработки прерывания от клавиатуры в вектор прерывания 60. Если вектора прерываний 60 и 61 содержат одинаковые адреса, то TSR-программа не была запущена. */ if(int9->p == (int9+1)->p) { int9->p = addr->p; addr->p = (char far *) tsr_ap; printf("tsr installed - F2 for note pad, F3 for calculator "); } else { printf ("tsr application already initialized\n "); exit(1); } } set_vid_mem(); tsr(2000); } Следует отметить, что данная версия программы не допускает, чтобы ее запускали более одного раза в течение одного сеанса работы. Это связано с тем, что повторный запуск программы приведет к записи адреса точки входа в TSR-программу в таблицу векторов по адресу 60-го прерывания, а содержавшийся там адрес программы реакции на нажатие клавиши будет запорчен. Во время работы функции проверяется, совпадает ли содержимое таблицы векторов, соответствующее прерываниям 60 и 61. (Прерывание 61 также не используется DOS). DOS обрабатывает все неиспользуемые ею прерывания одной и той же программой обработки недопустимого прерывания. Следовательно, перед запуском TSR-программы эти адреса будут совпадать, а после запуска они будут различны. Прикладная часть TSR-программы. ----------------------------------------------------------------- Применяемая здесь функция входа в TSR-программу является более сложной, чем при использовании прерывания по печати экрана. Первым делом она должна сформировать прерывание 60, для того чтобы ввод с клавиатуры осуществлялся стандартной программой ввода. Большинство компиляторов Си имеют функцию генерации прерывания. В Турбо Си это функция geninterrupt(), параметром которой является номер того прерывания, которое вы хотите вызвать. После возврата из прерывания 60 ваша функция должна проверить содержимое очереди, адресуемое с помощью указателя начала, на предмет того, не была ли нажата "горячая клавиша". Для представленной здесь программы "горячими" являются клавиши F2 и F3 с позиционными кодами 60 и 61 соответственно. Нажатие "горячей клавиши" должно быть обнаружено прежде, чем управление будет передано прикладной части программы. Глобальная переменная busy используется для того, чтобы предотвратить одновременное использование обеих прикладных частей программы, поскольку большинство компиляторов Си не позволяют создавать повторно-входимые программы. Если одна из прикладных частей активна, то другой части активация в этот момент запрещена. Функция tsr_ap() приводится ниже. /* Точка входа в прикладную часть TSR-программы */ void interrupt tsr_ap() { char far *t = (char far *) 1050; /* адрес указателя заголовка * geninterrupt(60); if(*t != *(t+2)) { /* если не пустой */ t += *t - 30 + 5; /* перейти к позиции символа */ if(*t == 60 || *t == 61) { bioskey(0); /* сбросить клавиши F2/F5 */ if(!busy) { busy = !busy; } } } } Следует отметить, что параметром функции window_main() является позиционный код "горячей клавиши", для того, чтобы она могла осуществить выбор соответствующей прикладной части. /* создать окно */ window_main(which) int which; { union inkey { char ch[2]; int i; } c; int i; char ch; Глава III -- 26 -- /* во-первых, создать рамку окна */ make_window(0," Notepad [F1 to exit] ", 5, 20, 17, 60, BORDER); make_window(1, " Colculator ", 8, 20, 12, 60, BORDER); /* использовать window() для активации созданного окна */ switch(which) { case 60: notepad(); break; case 61: calc(); break; } } Вы можете сразу вводить в ЭВМ представленную здесь программу. После того, как вы ее запустите, клавишей F2 будет выбираться программа "записная книжка", а клавишей F3 - "калькулятор". /* Программа, остающаяся резидентной после завершения и использующая прерывание 9 от клавиатуры. */ #include "dos.h " #include "stdlib.h " #include "ctype.h " #define BORDER 1 #define ESC 27 #define MAX_FRAME 2 #define REV_VID 0x70 #define NORM_VID 7 #define BKSP 8 void interrupt tsr_ap(); void save_video(), restore_video(); void write_string(), write_char(); void display_header(), draw_border(); void window_gets(); void window_cleol(), window(); void notepad(), calc(); char far *vid_mem; char wp[4000]; /* буфер для хранения текущего содержимого экрана */ struct window_trame { int startx, endx, starty, endy; int curx, cury; /* текущее положение курсора в окне */ unsigned char *p; /* указатель в буфере */ char *header; /* сообщение заголовка */ int border; /* включение/отключение бордюра */ int active; /* выводить/не выводить на экран */ } frame [MAX_FRAME]; Глава III -- 27 -- char in[80], out[80]; /* busy установлена в 1,когда программа активна, иначе - в 0 */ char busy = 0; main() { struct adaress { char far *p; }temp; /* указатель на вектор прерывания 9 */ struct address far *addr = (struct address far *) 36; /* указатель на вектор прерывания 60 */ struct address far *int9 = (struct address far *) 240; /* Поместить адрес программы обработки прерывания от клавиатуры по адресу прерывания 60. Если вектора прерываний 60 и 61 содержат одинаковые адреса, то TSR-программа не была запущена. */ if (int9->p == (int9+1)->p) { int9->p = addr->p; addr->p = (char far *) tsr_ap; printf ("tsr installed - F2 for note pad, F3 for calculator"); } else { printf ("tsr application already initialized\n"); exit (1); } set_vid_mem(); tsr (800); } set_vid_mem() { int vmode; vmode = video_mode(); if(( vmode != 2) && ( vmode != 3) && ( vmode != 7)) { printf("video must be in 80 column text mode"); exit(1); } /* установить соответствующий адрес видеопамяти */ if(vmode==7) vid_mem = (char far *) 0xB0000000; else vid_mem = (char far *) 0xB8000000; } /* Точка входа в прикладную часть TSR-программы */ void interrupt tsr_ap() { char far *t = (char far *) 1050; /* адрес указателя заголовка * geninterrupt(60);/* читать символ */ if(*t != *(t+2)) { /* если не пусто */ t += *t-30+5; /* перейти к позиции символа */ if(*t == 60 || *t == 61) { bioskey(0); /* сбросить клавиши F2/F3 */ if(!busy) { busy = !busy; Глава III -- 28 -- window_main(*t); busy = !busy; } } } } /* завершить но оставить резидентной */ tsr(size) unsigned size; { union REGS r; r.h.ah = 49; /* завершить и оставить резидентной */ r.h.al = 0; /* код возврата */ r.x.dx = size; /* размер программы/16 */ int86(0x21, &r, &r); } /* создать окно */ window_main(which) int which; { union inkey { char ch[2]; int i; } c; int i; char ch; /* во-первых, создать рамку окна */ make_window(0, " Notepad [F1 to exit] ", 5, 20, 17, 60, BORDER); make_window(1, " Calculator ", 8, 20, 12, 60, BORDER); /* использовать window() для активации созданного окна */ switch(which) { case 60: notepad(); break; case 61: calc(); break; } } /***************************************************************/ /* Функции управления окнами */ /***************************************************************/ /* Вывести спускающееся окно */ void window(num) int num; /* номер окна */ { /* сделать окно активным */ Глава III -- 29 -- if(!frame[num].active) { /* используется не постоянно */ save_video(num); /* сохранить текущий экран */ frame[num].active = 1; /* установить флаг активности */ } if(frame[num].border) draw_border(num); display_header(num); /* вывести окно */ } /* Создать рамку спускающегося окна. Если рамка может быть создана, возвращается 1, в противном случае возвращается 0. */ make_window(num, header, startx, starty, endx, endy, border) int num; /* номер окна */ char *header; /* текст заголовка */ int startx, starty; /* координаты X,Y верхнего левого угла */ int endx, endy; /* координаты X,Y нижнего правого угла */ int border; /* без бордюра если 0 */ { register int i; int choice, vmode; unsigned char *p; if(num>MAX_FRAME) { window_puts(0, "Too many windows\n"); return 0; } if((startx>24) || (startx<0) || (starty>78) || (starty<0)) { window_puts(0, "range error"); return 0; } if((endx>24) || (endy>79)) { window_puts(0, "window won't fit"); return 0; } /* Отвести достаточное количество памяти */ p= (unsigned char *) malloc(2*(endx-startx+1)*(endy-starty+1)); if(!p) exit(1); /* поместите здесь ваш собственный обработчик ошибок */ /* создать рамку */ frame[num].startx = startx; frame[num].endx = endx; frame[num].starty = starty; frame[num].endy = endy; frame[num].p = wp; frame[num].header = header; frame[num].border = border; frame[num].active = 0; frame[num].curx = 0; frame[num].cury = 0; return 1; } Глава III -- 30 -- /* Деактивировать окно и удалить его с экрана */ deactivate(num) int num; { /* установить курсор в левый верхний угол */ frame[num].curx = 0; frame[num].cury = 0; restore_video(num); } /* Вывести текст заголовка в соответствующее поле */ void display_header(num) int num; { register int i, y, len; y = frame[num].starty; /* Вычислить начальную позицию относительно центра заголовка, если отрицательная, то сообщение не подходит. */ len = strlen(frame[num].header); len = (frame[num].endy - y - len) / 2; if(len<0) return; /* не выводить его */ y = y +len; write_string(frame[num].startx, y, frame[num].header, NORM_VID); { void draw_border(num) int num; { register int i; char far *v, far *t; v = vid_mem; t = v; for(i=frame[num].startx+1; i<frame[num].endx; i++) { v += (i*160) + frame[num].starty*2; *v++ = 179; *v = NORM_VID; v = t; v += (i*160) + frame[num].endy*2; *v++ = 179; *v = NORM_VID; v = t; } for(i=frame[num].starty+1; i<frame[num].endy; i++) { v += (frame[num].startx*160) + i*2; *v++ = 196; *v = NORM_VID; v = t; v += (frame[num].endx*160) + i*2; *v++ = 196; *v = NORM_VID; Глава III -- 31 -- v = t; } write_char(frame[num].startx, frame[num].starty, 218, NORM_VID); write_char(frame[num].startx, frame[num].endy, 191, NORM_VID); write_char(frame[num].endx, frame[num].starty, 192, NORM_VID); write_char(frame[num].endx, frame[num].endy, 217, NORM_VID); } /*************************************************************/ /* Оконные функции ввода/вывода */ /*************************************************************/ /* Вывести строку начиная с текущей позиции в созданном окне. Возвратить 0, если окно не активное, и 1 - в противном случае. */ window_puts(num, str) int num; char *str; { /* убедитесь, что окно активное */ if(!frame[num].active) return 0; for( ; *str; str++) window_putchar(num, *str); return 1; } /* Вывести символ в текущую позицию курсора в созданном окне Возвратить 0, если окно не активное, и 1 - в противном случае. */ window_putchar(num, ch) int num; char ch; { register int x, y; char far *v; /* убедитесь, что окно активное */ if(!frame[num].active) return 0; x = frame[num].curx + frame[num].startx + 1; y = frame[num].cury + frame[num].starty + 1; v = vid_mem; v += (x*160) + y*2; /* вычислить адрес */ if(y>=frame[num].endy) { return 1; } if(x>=frame[num].endx) { return 1; } Глава III -- 32 -- if(ch=='\n') { /* символ перехода к новой строке */ x++; y = frame[num].startx+1; v = vid_mem; v += (x*160) + y*2; /* вычислить адрес */ frame[num].curx++; /* нарастить X */ frame[num].cury = 0; /* сбросить Y */ } else { frame[num].cury++; *v++ = ch; /* вывести символ */ *v++ = NORM_VID; /* нормальные атрибуты символа */ } window_xy(num, frame[num].curx, frame[num].cury); return 1; } /* Установить курсор в определенной позиции окна. Возвратить 0 при выходе за границу; и не ноль в противном случае. */ window_xy(num, x, y) int num, x, y; { if(x<0 || x+frame[num].startx>=frame[num].endx-1) return 0; if(y<0 || y+frame[num].starty>=frame[num].endy-1) return 0; frame[num].curx = x; frame[num].cury = y; return 1; } /* Считать строку из окна */ void window_gets(num, s) int num; char *s; { char ch, *temp; char out[10]; temp = s; for(;;) { ch = window_getche(num); switch(ch) { case '\r': /* нажата клавиша ENTER */ *s='\0'; return; case BKSP: /* возврат на шаг */ if(s>temp) { s--; frame[num].cury--; if(frame[num].cury<0) frame[num].cury = 0; Глава III -- 33 -- window_xy(num, frame[num].curx, frame[num].cury); write_char(frame[num].startx+ frame[num].curx+1, frame[num].starty+frame[num].cury+1,' ',NORM_VID); } break; default: *s = ch; s++; } } } /* Ввести символ в окно. Возвратить полный 16-разрядный скан-код */ window_getche(num) int num; { union inkey { char ch[2]; int i; } c; if(!frame[num].active) return 0; /* окно не активное */ window_xy(num, frame[num].curx, frame[num].cury); c.i = bioskey(0); /* обработать нажатие клавиши */ if(c.ch[0]) { switch(c.ch[0]) { case'\r': /* нажата клавиша ENTER */ break; case BKSP: /* возврат на шаг */ break; default: if(frame[num].cury+frame[num].starty < frame[num].endy-1) { write_char(frame[num].startx+ frame[num].curx+1, frame[num].starty+frame[num].cury+1, c.ch[0], NORM_VID); frame[num].cury++; } } if(frame[num].curx < 0) frame[num].curx = 0; if(frame[num].curx+frame[num].startx > frame[num].endx-2) frame[num].curx--; window_xy(num, frame[num].curx, frame[num].cury); } return c.i; } /* Очистить до конца строки */ void window_cleol(num) int num; { register int i, x, y; x = frame[num].curx; y = frame[num].cury; window_xy(num, frame[num].curx, frame[num].cury); Глава III -- 34 -- for(i=frame[num].cury; i<frame[num].endy-1; i++) window_putchar(num,' '); window_xy(num, x, y); } /* Переместить курсор вверх на одну строку. Возвратить не ноль в случае успеха и 0 в противном случае. */ window_upline(num) int num; { if(frame[num].curx>0) { frame[num].curx--; window_xy(num, frame[num].curx, frame[num].cury); return 1; } return 0; } /* Переместить курсор вниз на одну строку. Возвратить не ноль в случае успеха и 0 в противном случае. */ window_downline(num) int num; { if(frame[num].curx<frame[num].endx-frame[num].startx-1) } frame[num].curx++; window_xy(num, frame[num].curx, frame[num].cury); return 1; } return 1; } /* назад на один символ */ window_bksp(num) int num; { if(frame[num].cury>0) { frame[num].cury--; window_xy(num, frame[num].curx, frame[num].cury); window_putchar(num, ' '); frame[num].cury--; window_xy(num, frame[num].curx, frame[num].cury); } } /***************************************************************/ /* Дополнительные функции */ /***************************************************************/ /* Вывести строку с указанными атрибутами */ Глава III -- 35 -- void write_string(x, y, p, attrib) int x, y; char *p; int attrib; { register int i; char far *v; v=vid_mem; v += (x*160) + y*2; /* вычислить адрес */ for(i=y; *p; i++) { *v++ = *p++; /* записать символ */ *v++ = attrib; /* записать атрибуты */ } } /* Вывести символ с указанными атрибутами */ void write_char(x, y, ch, attrib) int x, y; char ch; int attrib; { register int i; char far *v; v = vid_mem; v += (x*160) + y*2; *v++ = ch; /* записать символ */ *v = attrib; /* записать атрибуты */ } /* Сохранить область экрана */ void save_video(num) int num; { register int i, j; char *buf_ptr; char far *v, far *t; buf_ptr = frame[num].p; v = vid_mem; for(i=frame[num].starty; i<frame[num].endy+1; i++) for(j=frame[num].startx; j<frame[num].endx+1; j++) { t = (v + (j*160) + i*2); *buf_ptr++ = *t++; *buf_ptr++ = *t; *(t-1) = ' '; /* очистить окно */ } } /* Восстановить область экрана */ void restore_video(num) int num; { register int i,j; Глава III -- 36 -- char far *v, far *t; char *buf_ptr; buf_ptr = frame[num].p; v = vid_mem; t = v; for(i=frame[num].starty; i<frame[num].endy+1; i++) for(j=frame[num].startx; j<frame[num].endx+1; j++) { v = t; v += (j*160) + i*2; *v++ = *buf_ptr++; /* записать симпол */ *v = *buf_ptr++; /* записать атрибуты */ } frame[num].active = 0;/* восстановить изображение */ } /* Возвратить код текущего видеорежима */ video_mode() { union REGS r; r.h.ah = 15; /* получить код видеорежима */ return int86(0x10, &r, &r) & 255; } /****************************************************/ /* Функции всплывающих окон */ /****************************************************/ #define MAX 100 int *p; /* указатель стека */ int *tos; /* указатель вершины стека */ int *bos; /* указатель дна стека */ int stack[MAX]; /* Стековый, с постфиксной записью калькулятор с четырьмя функциями */ void calc() { int answer; int a,b; p = stack; tos = p; bos = p+MAX-1; window(1); do { window_xy(1, 0, 0); window_cleol(1); window_puts(1, ": "); /* промптер калькулятора */ window_gets(1, in); window_puts(1, "\n "); window_cleol(1); switch(*in) { Глава III -- 37 -- case '+': a = pop(); b = pop(); answer = a+b; push(a+b); break; case '-': a = pop(); b = pop(); answer = b-a; push(b-a); break; case '* ': a = pop(); b = pop(); answer = b*a; push(b*a); break; case '/ ': a = pop(); b = pop(); if(a==0) { window_puts(0, "divide by 0\n"); break; } answer = b/a; push(b/a); break; default: push(atoi(in)); continue; } itoa(answer, out, 10); window_puts(1, out); } while(*in); deactivate(1); } /* Поместить число в стек. Возвратить 1 в случае успеха и 0 если стек переполнен */ push(i) int i; { if(p>bos) return 0; *p = i; p++; return 1; } /* Извлечь верхний элемент из стека. Возвратить 0 если стек пуст. */ pop() Глава III -- 38 -- { p--; if(p<tos) { p++; return 0; } return *p; } /**********************************************************/ /* Всплывающая записная книжка */ #define MAX_NOTE 10 #define BKSP 8 char notes[MAX_NOTE] [80]; void notepad() { static first = 1; register int i, j; union inkey { char ch[2]; int i; } c; char ch; /* инициализировать массив записей если это необходимо */ if(first) { for(i=0; i<MAX_NOTE; i++) *notes[i] = '\0 '; first = !first; } window(0); /* вывести существующие записи */ for(i=0; i<MAX_NOTE; i++) { if(*notes[i]) window_puts(0, notes[i]); window_putchar(0, '\n '); } i = 0; window_xy(0, 0, 0); for(;;) { c.i = bioskey(0); /* обработать нажатие клавиши */ if(tolower(c.ch[1])==59) { /* F1 для выхода */ deactivate(0); break; } /* если обычная клавиша */ if(isprint(c.ch[0]) || c.ch[0]==BKSP) { window_cleol(0); notes[i][0] = c.ch[0]; j = 1; window_putchar(0, notes[i][0]); do { Глава III -- 39 -- ch = window_getche(0); if(ch == BKSP) { if( j>0 ) { j--; window_bksp(0); } } else { notes[i][j] = ch; j++; } } while(notes[i][j-1]! = '\r '); notes[i][j-1] = '\0 '; i++; window_putchar(0, '\n '); } else { /* если специальная клавиша */ switch(c.ch[1]) { case 72: /* стрелка вверх */ if(i>0) { i--; window_upline(0); } break; case 80: /* стрелка вниз */ if(i<MAX_NOTE-1) { i++; window_downline(0); } break; } } } } Глава III -- 40 -- Тайна 28-го прерывания ----------------------------------------------------------------- Есть одно средство DOS, которое не описывается в документации, и которое может сделать TSR-программы более надежными в тот период времени, когда они используют много системных ресурсов. Вообще говоря, если прикладная часть вашей TSR-программы занимается обменом только с консолью, то вы застрахованы от неприятностей. Неприятности могут возникнуть при использовании таких объектов, как дисковые файлы или порты ввода- вывода. Хотя это и не описано в технических руководствах по операционной системе, но DOS вызывает прерывание 28Н, когда она находится в "безопасном", т.е. холостом состоянии. Как вам наверное известно, при выполнении определенных функций DOS, которые относятся к критическим участкам, после начала их выполнения прерывания должны быть запрещены. Прерывание 28Н никогда не вызывается DOS во время выполнения критического участка. Вы можете использовать это средство для защиты от сбоев вашей TSR-программы. Хотя здесь не представлено никаких примеров программ, но предлагаются следующие общие соображения по этому вопросу. Главное отличие, которое вы должны иметь ввиду при использовании прерывания 28Н, заключается в способе активации прикладной части вашей TSR-программы. Когда вызывается прерывание 28Н, прикладная часть TSR-программы не может больше активироваться через программу обработки нажатий клавиш. Вместо этого программа обработки нажатий клавиш при нажатии "горячей клавиши" просто устанавливает флаг (в дальнейшем именуемый is-hotkey). Перед тем, как прикладная часть вашей TSR-программы может быть вызвана, вы должны создать новый обработчик прерывания 28Н, который будет проверять, установлен флаг is-hotkey или нет. Если установлен, то прикладная часть активируется, сбрасывая при этом флаг is-hotkey. При этом вы обязательно должны не просто изменить первоначальное содержимое вектора прерывания 28Н, а напротив, сохранить его и вызывать исходное прерывание 28Н из вашего обработчика 28-го прерывания. Если вы собираетесь продавать ваши TSR-программы, то должны обязательно использовать прерывание 28Н (хотя по нему и нет документации), поскольку оно позволяет избежать случайных прерываний DOS во время выполнения критических участков. Глава III -- 41 -- Проблемы при создании TSR-программ ----------------------------------------------------------------- TSR-программы по своей природе очень склонны к сбоям. Например, использование TSR-программы, разработанной одним программистом, часто делает невозможным одновременное использование другой TSR-программы, разработанной другим программистом, поскольку обе программы будут пытаться переназначить адрес программы обработки прерывания 9 в таблице векторов на себя. Несомненно, при использовании своих TSR-программ вы можете избежать такого рода проблем, но будьте внимательны, если в вашей системе присутствует чужая TSR-программа.