ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 4

                             ГЛАВА 3                          -- 1 --
                             -------

              Программы,   остающиеся   резидентными  после
              завершения  и  формирующие   при   их  вызове
              всплывающие  изображения  на  экране дисплея.
     -----------------------------------------------------------------

          Простая  на  первый  взгляд  идея создания программ, которые
     оставались  бы  резидентными  в  памяти  после  их  завершения  и
     реагировали  на  вызов  формированием  всплывающих изображений на
     экране дисплея, на самом деле является одной из наиболее  трудных
     задач  программирования  для  ПЭВМ.  Такие  программы  называются
     ТSR-программами. При чтении данного  раздела  вы  должны  получше
     пристегнуться  ремнями  безопасности  и одеть защитный шлем, т.к.
     создание TSR-программ связано с риском.  Но  этот  риск  оправдан
     возможным    вознаграждением    -    поистине   профессиональными
     результатами, которыми гордился  бы  любой  программист  мирового
     класса.

          Поскольку   ТSR-программы,  естественно,  должны  на  низком
     уровне взаимодействовать с аппаратурой и  операционной  системой,
     то  излагаемые в данном разделе сведения будут применимы только к
     ПЭВМ линии IBM PC, работающими под операционной системой DOS.  По
     причинам,  которые  будут  указаны  ниже,  приводимые  в  разделе
     программы  рассчитаны  на  компилятор  Turbo  C,  но  могут  быть
     модифицированы и для других компиляторов.

          Предупреждение.  Для разработки и использования TSR-программ
     характерна модификация таблицы векторов прерываний. Приведенные в
     данном разделе программы транслируются с помощью Турбо  Си версии
     1.0  и  в  этом  случае  работают  корректно  и  без  посторонних
     эффектов.  При  использовании  другого  компилятора  корректность
     работы не гарантируется.  Кроме того, если вы будете набирать эти
     программы вручную,  то можете внести свои ошибки.  И в том,  и  в
     другом  случае  это  может привести к фатальному сбою системы,  в
     результате  чего  могут   быть   уничтожены   данные   на   вашем
     винчестерском диске. Поэтому целесообразно делать резервные копии
     файлов. Я уверен, что приводил к краху мою модель 60 не менее 100
     раз   за  те  два  дня,  пока  отлаживал  основную  логику  своей
     программы.  (К счастью,  я не  затирал  при  этом  винчестерского
     диска).

















                                 "C" для профессиональных программистов
Глава III                                                       -- 2 --


               Что такое TSR-программа?
     -----------------------------------------------------------------

          ТSR-программы  создаются  путем  вызова  функции  49 DOS, по
     которой  производится  возврат  из  программы  в  DOS.  При  этом
     программа  остается в области памяти, которую DOS в дальнейшем не
     использует. Таким образом, программа может быть мгновенно вызвана
     без повторной загрузки. Одним из многих широко известных примеров
     TSR-программ является программа Sidekick фирмы Вorland.

          Большинство TSR-программ вызываются  с  помощью  прерывания,
     которое  может быть сформировано несколькими способами.  Наиболее
     распространенными  являются  прерывания  по  таймеру,  прерывания
     клавиатуры   и   печати  экрана.  Для  TSR-программ,  формирующих
     изображение  на  экране,  обычно   используются   прерывания   от
     клавиатуры  или  печати экрана,  поскольку позволяют пользователю
     вызывать TSR-программу путем одиночного нажатия клавиши.







































                                 "C" для профессиональных программистов
Глава III                                                       -- 3 --


               Прерывания в семействе процессоров 8086.
     -----------------------------------------------------------------

          Процессоры семейства  8086  поддерживают  до  256  различных
     прерываний  по вектору. Прерывание по вектору вызывает выполнение
     программы обработки прерываний (ISR), адрес которой содержится  в
     таблице  векторов  прерываний.  Хотя некоторые старшие процессоры
     семейства   требуют,   чтобы   программы   обработки   прерывания
     располагались  в определенных адресах памяти, механизм прерываний
     по  вектору  позволяет  определять  адреса   программ   обработки
     прерываний.

          Таблица  векторов  начинается с адреса 0000:0000 и ее размер
     составляет  1024  байта.  Поскольку  адрес  программы   обработки
     прерывания  может быть любым, то для его определения требуется 32
     разряда (4 байта). Следовательно, размер каждой записи в  таблице
     векторов  составляет  4  байта.  Адреса  ISR-программ  в  таблице
     записываются  таким  образом,  что  адрес   программы   обработки
     прерывания  0  находится по адресу 0000:0000, программы обработки
     прерывания прерывания 1 - по адресу 0000:0004, прерывания 2 -  по
     адресу 0000:0008 и т.д.

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





























                                 "C" для профессиональных программистов
Глава 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 достаточно,  и вашей программе
     будет  устроена кровавая резня.  Чтобы этого избежать,  вы должны
     ощущать себя шпионом и иметь нервы автогонщика.











                                 "C" для профессиональных программистов
Глава 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.  Средства создания  функций  на  языке
     ассемблера  различны  для  разных  компиляторов,  так что читайте
     имеющееся у вас руководство пользователя.













                                 "C" для профессиональных программистов
Глава III                                                       -- 6 --


               Общий план TSR-программы
     -----------------------------------------------------------------

          Все TSR-программы обычно состоят из  двух  разделов.  Первая
     часть  используется  для  инициализации  TSR-программы и возврата
     управления DOS путем  использования  реентерабельного  системного
     вызова.  Эта  часть  не выполняется до тех пор, пока не возникает
     необходимость в перезагрузке  программы.  При  этом  производится
     запись  адреса  точки входа TSR-программы в соответствующее место
     таблицы векторов.

          Вторая,   прикладная    часть,   занимается    формированием
     изображений.   При   этом   почти  всегда  используются  окна,  а
     следовательно,и программы управления окнами. При этом изображение
     на  экране  восстанавливается  после завершения работы прикладной
     части программы. Следует помнить, что у большинства  TSR-программ
     прикладные   части   представляют   собой   утилиты  формиривания
     изображения,  как  у  программы  типа   "записной   книжки"   или
     "калькулятора".   После  своего  завершения  они  восстанавливают
     изображение на экране  в  том  же  виде,  каким  оно  было  перед
     запуском этих программ.



































                                 "C" для профессиональных программистов
Глава III                                                       -- 7 --


               Использование прерывания печати экрана.
     -----------------------------------------------------------------

          Без сомнений, прерыванием, которое наиболее просто "украсть"
     у DOS, является прерывание номер 5. Это прерывание вызывается при
     нажатии  клавиши  PT  SCR.  Если  вы готовы пожертвовать функцией
     печати экрана, то можете заменить адрес этой программы в  таблице
     векторов  адресом  вашей TSR-программы. Таким образом, при каждом
     нажатии клавиши PT SCR будет вызываться ваша TSR-программа.

          Примером  такой  программы является резидентный калькулятор.
     Программы для работы с окнами и программа калькулятора из раздела
     2 приводятся здесь с некоторыми небольшими изменениями.











































                                 "C" для профессиональных программистов
Глава 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);


                                 "C" для профессиональных программистов
Глава 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.














                                 "C" для профессиональных программистов
Глава 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();


                                 "C" для профессиональных программистов
Глава 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;
         }
      }


                                 "C" для профессиональных программистов
Глава 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 */
       {


                                 "C" для профессиональных программистов
Глава 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;


                                 "C" для профессиональных программистов
Глава 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].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 при выходе за границу;
          не ноль в противном случае.


                                 "C" для профессиональных программистов
Глава 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;



                                 "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; i0) {
           frame[num].curx--;
           window_xy(num, frame[num].curx, frame[num].cury);
           return 1;
        }
        return 0;
      }

      /* Переместить курсор на одну строку вниз.
         При успешном завершении вернуть ненулевое значение;
         в противном случае - 0.
      */
      window_downline(num)


                                 "C" для профессиональных программистов
Глава III                                                       -- 18 --



     int num;
     {
       if(frame[num].curx0) {
         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;


                                 "C" для профессиональных программистов
Глава 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; ibos) return 0;
       *p = i;
       p++;
       return 1;
     }

     /* Извлечь верхний элемент стека
        Возвратить 0, если стек пуст.
     */
     pop()
     {
       p--;
       if(pp == (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-программы  эти
     адреса будут совпадать, а после запуска они будут различны.












                                 "C" для профессиональных программистов
Глава III                                                       -- 25 --


                 Прикладная часть 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;


                                 "C" для профессиональных программистов
Глава 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];


                                 "C" для профессиональных программистов
Глава 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;


                                 "C" для профессиональных программистов
Глава 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; /* номер окна */
     {
       /* сделать окно активным */


                                 "C" для профессиональных программистов
Глава 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;
     }



                                 "C" для профессиональных программистов
Глава 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].endy) {
         return 1;
       }
       if(x>=frame[num].endx) {
         return 1;
       }


                                 "C" для профессиональных программистов
Глава 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;


                                 "C" для профессиональных программистов
Глава 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);


                                 "C" для профессиональных программистов
Глава III                                                       -- 34 --


       for(i=frame[num].cury; i0) {
         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].curx0) {
       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);
        }
     }

     /***************************************************************/
     /* Дополнительные функции                                      */
     /***************************************************************/

     /* Вывести строку с указанными атрибутами */


                                 "C" для профессиональных программистов
Глава 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; ibos) return 0;
       *p = i;
       p++;

        return 1;
     }
     /* Извлечь верхний элемент из стека.
        Возвратить 0 если стек пуст.
     */
     pop()


                                 "C" для профессиональных программистов
Глава III                                                       -- 38 --


     {
       p--;
       if(p0 ) {
                   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




?????? ???????????