 Герберт Шилдт С для профессионалов

Герберт Шилдт. С для профессионалов.

Содержание
ГЛАВА 1. Исчезающие и иерархические меню.
Глава 2. ВСПЛЫВАЮЩИЕ ОКНА
Глава 3. Программы резидентны
Глава 4. ГРАФИКА
Глава 5. ВИДЕОИГРЫ
Глава 6. ИСПОЛЬЗОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА ПРОСТЕЙШИЕ ЛВC
Глава 7. ИНТЕРПРЕТАТОРЫ ЯЗЫКА.
Глава 8. О МАНИПУЛИРОВАНИИ ЭКРАНОМ И ГЕНЕРАЦИИ ЗВУКА
Глава 9. ИНТЕРФЕЙС С "МЫШЬЮ"
Глава 10. СОЗДАНИЕ КОММЕРЧЕСКИХ ДИАГРАММ


              T---¬
              ¦   ¦ T---¬ T---¬ T---¬ T---¬ T---¬ --T-¬
              ¦     ¦     ¦   ¦ +---¬ ¦     ¦   ¦   ¦
              ¦     +--   +---- ¦   ¦ +--   +----   ¦
              ¦     +---- +     +---- +---- +       +

                 T     T
                 ¦     ¦  T   T   ---¬   T--T  --T-¬
                 ¦  T  ¦  ¦  /¦   ¦  ¦   ¦  ¦    ¦
                 ¦  ¦  ¦  ¦/  ¦   ¦  ¦   ¦  ¦    ¦
                 ¦--¦--¦  +   +  --  +  -+--+¬   +

                          -----------------
                          -               -
                          -    --------   -
                          -    -      -----
                          -    -
                          -    -
                          -    -
                          -    -
                          -    -      -----
                          -    --------   -
                          -               -
                          -----------------

                                д л я
                   п р о ф е с с и о н а л ь н ы х
                      п р о г р а м м и с т о в

                           Москва, 1989 г.

                         С О Д Е Р Ж А Н И Е

Предисловие ................................................... I- 1

   Глава I. ИСЧЕЗАЮЩИЕ И ИЕРАРХИЧЕСКИЕ МЕНЮ
Что такое исчезающие и иерархические меню? .................... I- 4
Работа видеоадаптеров ......................................... I- 5
Доступ к экрану через BIOS .................................... I- 7
Использование int86() ......................................... I- 8
Сохранение части экрана ....................................... I- 9
Восстановление экрана ......................................... I-11
Создание исчезающих меню ...................................... I-12
Высвечивание меню ............................................. I-13
Высвечивание рамки ............................................ I-15
Ввод выбора пользователя ...................................... I-16
Функция popup() ............................................... I-19
Общий обзор ................................................... I-21
Прямой доступ к видео памяти .................................. I-27
Определение расположения видео памяти ......................... I-28
Изменение save_video() и restore_video() ...................... I-29
Создание иерархических окон ................................... I-37
Фреймы меню ................................................... I-38
Создание фрейма меню .......................................... I-39
Функция pulldown() ............................................ I-41
Восстановление экрана ......................................... I-42
Простая программа, использующая процедуру pulldown ............ I-43
Добавочные функции ............................................ I-52

   Глава II. ВСПЛЫВАЮЩИЕ ОКНА
Теория всплывающих окон ...................................... II- 2
Оконные структуры ............................................ II- 3
Создание структуры окна ...................................... II- 4
Активирование и деактивирование окна ......................... II- 6
Оконные функции ввода/вывода ................................. II- 8
Функция позиционирования курсора в окне ...................... II- 9
Функция window_getche() ...................................... II-10
Функция window_gets() ........................................ II-12
Функция window_putchar() ..................................... II-13
Функция window_puts .......................................... II-15
Дополнительные функции управления экраном .................... II-16
Изменение размера и положения окна во время вып. программы ... II-18
Создание прикладных программ, использующих всплывающие окна .. II-22
Программа преобр. из десят. в шестнад. систему счисления ..... II-23
Калькулятор с четырьмя функциями ............................. II-24
Всплывающая записная книжка .................................. II-27
Совместное использование всех программ ....................... II-29
Модификации программ управления окнами ....................... II-48

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

   Глава III. ПРОГРАММЫ, ОСТАЮЩИЕСЯ РЕЗИДЕНТНЫМИ
Что такое TSR-программа? .................................... III- 2
Прерывания в семействе процессоров 8086 ..................... III- 3
Прерывания против DOS и BIOS: Tревога в стране DOS .......... III- 4
Модификатор функций прерывания Турбо Си ..................... III- 5
Общий план TSR-программы .................................... III- 6
Использование прерывания печати экрана ...................... III- 7
Раздел инициализации ........................................ III- 8
Прикладная часть TSR-программы .............................. III-10
Использование прерывания по нажатию клавиши ................. III-22
Буфер символов, введенных с клавиатуры ...................... III-23
Функция инициализации ....................................... III-24
Прикладная часть TSR-программы .............................. III-25
Тайна 28-го прерывания ...................................... III-40
Проблемы при создании TSR-программ .......................... III-41

   Глава IV. ГРАФИКА
Видеорежимы и цветовая палитра ............................... IV- 2
Запись точки растра .......................................... IV- 4
Работа адаптеров CGA/EGA в графическеом режиме ............... IV- 5
Вычерчивание линий ........................................... IV- 8
Изображение и закрашивание прямоугольников ................... IV-10
Вычерчивание окружностей ..................................... IV-11
Простейшая тестовая программы ................................ IV-13
Сохранение и загрузка графических изображений ................ IV-18
Дублирование части экрана .................................... IV-21
Вращение точки в плоскости экрана ............................ IV-23
Вращение обьекта ............................................. IV-25
Сборка подпрограмм ........................................... IV-33

   Глава V. ВИДЕОИГРЫ
Спрайты ....................................................... V- 2
Поле игры ..................................................... V- 3
Мультипликация на экране ...................................... V- 4
Мультипликация спрайта ........................................ V-11
Организация данных в видеоиграх ............................... V-13
Контроль границ ............................................... V-14
Изменение цвета ............................................... V-15
Табло счета активного противника .............................. V-16
Разработка видеоигры .......................................... V-17
Тело главной программы ........................................ V-20
Программа генерации движения спрайта компьютера ............... V-24
Программа контроля касания спрайтов ........................... V-28
Полный текст программы игры TAG ............................... V-29
Некоторые соображения по возможной модификации программы ...... V-41

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

   Глава VI. ИСПОЛЬЗОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА
Асинхронная последовательная передача данных ................. VI- 2
Стандарт RS-232 .............................................. VI- 4
Аппаратное подтверждение связи ............................... VI- 6
Проблемы передачи данных ..................................... VI- 7
Переполнение регистра-приемника .............................. VI- 8
Доступ к последовательному порту компьютера через BIOS ....... VI- 9
Инициализация порта .......................................... VI-10
Передача байтов .............................................. VI-13
Контроль состояния порта ..................................... VI-14
Прием байтов ................................................. VI-16
Передача файлов между компьютерами ........................... VI-18
Программное подтверждение связи .............................. VI-19
Семь и восемь бит данных ..................................... VI-20
Перекачка файла .............................................. VI-21
Прием файла .................................................. VI-24
Перекачка программы .......................................... VI-26
Использование средств перекачки программ ..................... VI-31
Дальнейшее совершенствование программы ....................... VI-32
Простейшая ЛВС ............................................... VI-33
Файловый сервер .............................................. VI-34
Загрузка удаленных файлов в узел сети ........................ VI-44
Хранение файлов .............................................. VI-48
Использование ЛВС ............................................ VI-52
Совершенствование ЛВС ........................................ VI-53

   Глава VII. ИНТЕРПРЕТАТОРЫ ЯЗЫКА
Синтаксический разбор выражений ............................. VII- 2
Выражения ................................................... VII- 3
Лексемы ..................................................... VII- 5
Порядок построения выражений ................................ VII-10
Синтаксический анализатор выражений ......................... VII-12
Как анализатор обрабатывает переменные ...................... VII-19
Интерпретатор языка SMALL BASIC ............................. VII-20
Основной цикл работы анализатора ............................ VII-23
Команда присваивания значений ............................... VII-25
Команда PRINT ............................................... VII-26
Команда INPUT ............................................... VII-28
Команда GOTO ................................................ VII-29
Оператор IF ................................................. VII-32
Цикл FOR .................................................... VII-34
Оператор GOSUB .............................................. VII-37
Полный файл интерпретатора .................................. VII-39
Пример использования интерпретатора SMALL BASIC ............. VII-49
Расширение возможностей интерпретатора ...................... VII-51

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

   Глава VIII. О МАНИПУЛИРОВАНИИ ЭКРАНОМ И ГЕНЕРАЦИИ ЗВУКА
Использование цвета в текстовом режиме ..................... VIII- 2
Атрибутный байт текстового режима .......................... VIII- 3
Отображение строки в определенном цвете .................... VIII- 5
Использование цвета ........................................ VIII- 8
Изменение размера курсора .................................. VIII- 9
Скроллинг части экрана ..................................... VIII-11
Простейшая демонстрационная программа ...................... VIII-13
Сохранение копии экрана в дисковом файле ................... VIII-18
А теперь добавим звук ...................................... VIII-20
Программируемый таймер 8253 ................................ VIII-21
Простейший способ проверки слуха ........................... VIII-23
Имитация звука сирены и взврывы ............................ VIII-25
Создание "космической музыки" .............................. VIII-27

   Глава IX. ИНТЕРФЕЙС С "МЫШЬЮ"
Некоторые начальные сведения о "мыши" ........................ IX- 3
Виртуализация и реальный экран ............................... IX- 4
библиотека поддержки "мыши" .................................. IX- 5
Функции поддержки "мыши" верхнего уровня ..................... IX- 8
Простейшая демонстрационная программа ........................ IX-12
Ввод информации с помощью "мыши" в программе рисования ....... IX-17
Основной цикл работы программы ............................... IX-20
Определение объектов с помощью "мыши" ........................ IX-26
Полный текст модифицированной программы рисования ............ IX-29
Некоторые возм. расширения выполняемых функций программы ..... IX-50

   Глава X. СОЗДАНИЕ КОММЕРЧЕСКИХ ДИАГРАММ
Нормализация данных ........................................... X- 2
Разработка функций построения диаграмм ........................ X- 3
Программа вычерчивания диаграмм ............................... X-13
Отображение диаграмм на экране дисплея ........................ X-26
Некоторые интересные идеи по модификации программ ............. X-28

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


                                                               -- 1 --

               Предисловие
     -----------------------------------------------------------------

          Если вы  хотите   создавать   программы   мирового   уровня,
     написанные на Си, то эта книга - для вас!

          Меня зовут Герб Шилдт.  Я ветеран программирования во многих
     компаниях и программирую на Си более десяти лет.  Перед тем,  как
     начать  писать  эту  книгу,  я изучил большое количество удачного
     программного обеспечения,  пытаясь определить,  какие  черты  они
     имеют  в отличие от менее удачных программ. Я  хотел понять,  что
     делает одни программы более удачными, чем другие, аналогичные им.
     После  этого  прояснились  общие  черты.  Удачные  программы были
     написаны  людьми,  которые  не  только  имели  крепкую  хватку  в
     специальной  области,  но  и  в совершенстве освоили оборудование
     компьютера,   включая   операционную   систему    и    аппаратное
     обеспечение.  Только программист,  осуществляющий полный контроль
     над  ними  может  писать  программы  с  дружелюбным   интерфейсом
     пользователя  которые эффективно выполняются и дают пользователям
     большую гибкость.

          Эта книга открывает многие секреты,  используемые  мастерами
     программирования  для достижения профессиональных результатов.  С
     ее  помощью  вы  расширите  подходы  и  методы,  которые   делают
     программы  интересными.  После прочтения книги вы будете способны
     писать  программы,    которые     заслужат     внимание.    Здесь
     рассматриваются следующие вопросы:

     # Прямой доступ к памяти экрана для быстрого отображения
     # Исчезающие и иерархические (popup и pulldown) меню
     # Процедуры работы с окнами
     # Завершение программ и оставление их в памяти
     # Интерфейс с мышью
     # Графические функции, включая вращение объектов
     # Языковые интерпретаторы
     # Передача файлов через последовательный порт

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

          Исходные тексты этой  книги  соответствуют  стандарту  ANSI,
     кроме  некоторых функций,  специфичных для ПК.  Таким образом все
     эти программы можно компилировать на любом  компиляторе,  который
     поддерживает стандарт. Я использовал для их разработки Турбо Си и
     Microsoft Си.

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

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

     на дискете.  Цена услуги - 24.95$.  Заполните приложенную форму и
     пошлите  ее  по  указанному  адресу  вместе  с   оплатой.   Можно
     использовать карточки MasterCard и Visa.

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

                          ГЛАВА 1.
                          --------
               Исчезающие и иерархические меню.
     -----------------------------------------------------------------

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

          Создание исчезающих  и  иерархических  меню  требует прямого
     управления  экраном.  Хотя  основные  программы  меню   полностью
     мобильны,  программы  доступа  к  экрану  зависят от операционной
     системы  и  оборудования  и  не  используют  обычные  функции  Си
     ввода/вывода на консоль.  Программы видео доступа разработаны для
     работы с любым  компьютером,  использующим  ДОС  и  имеющим  BIOS
     операционной  системы  совместимый с IBM.  BIOS-ДОС выбран потому
     что он широко используется,  но вы можете применить основные идеи
     и в других системах.

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

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

               Что такое исчезающие и иерархические меню?
     -----------------------------------------------------------------

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

          Когда используется исчезающее или иерархическое меню, то оно
     покрывает прямо  содержимое  экрана.  После выбора режима,  экран
     возвращается в предыдущее состояние. Вы выбираете нужный режим из
     меню  одним  из  двух  способов:  (1)  нажимая  активную клавишу,
     которая является буквой или номером, связанным с выбором, или (2)
     используя    клавиши   управления   курсором   для   передвижения
     подсвеченного поля  и   клавишу   Ввод.   Обычно   текущее   поле
     показывается   в   инверсном   виде.   Основная   разница   между
     стандартными меню и исчезающими и иерархическими меню в  том, что
     стандартное меню прерывает программу.  Исчезающие и иерархические
     меню только приостанавливают текущие действия программы.  С точки
     зрения  пользователя  стандартное меню - прерывание концентрации,
     тогда  как  исчезающее  меню  -   просто   легкая   приостановка,
     концентрация внимания пользователя не нарушена.

          Разница между  исчезающими  и  иерархическими  меню  проста.
     Только одно исчезающее меню может быть на экране в  данный момент
     времени.  Оно используется когда меню имеет только один уровень в
     глубину,  это бывает,  когда выбор из меню не имеет подвыборов. С
     другой  стороны  несколько  иерархических меню могут быть активны
     одновременно.  Они используются когда выбор из одного меню  может
     потребовать  использования другого меню для определения некоторых
     альтернатив. Например, вы можете использовать иерархическое меню,
     если   вы   пишете  программу,  которая  определяет  фрукт.  Если
     пользователь выбрал "яблоко",  следующее меню предлагает  выбрать
     цвет   яблока,   а   третье   меню  высвечивает  яблоки,  которые
     удоволетворяют предыдущим выборам.

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

          Хотя имеется много способов  расположения  меню  на  экране,
     функции,  разработанные  в  этой  главе имеют наиболее общий вид.
     Этот метод помещает очередное  поле  меню  на  новую  строку  под
     первым полем.

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

               Работа видеоадаптеров.
     -----------------------------------------------------------------

          Из-за того,  что создание исчезающих  и  иерархических  меню
     требует  прямого  управления  экраном,  важно понимание адаптеров
     дисплея. Три основных типа адаптеров - это  одноцветный  адаптер,
     цветной/графический    адаптер    (CGA)   и   усовершенствованный
     графический адаптер  (EGA).  CGA  и  EGA  могут  иметь  несколько
     режимов   работы,  включая  40-  или  80-  символьный  текст  или
     графические операции.  Эти  режимы  показаны   в   таблице   1-1.
     Программы  меню,  разработанные  в  этой  главе,  разработаны для
     использования  режима  80-символьного  текста,  который  является
     наиболее общим  режимом  для общецелевых применений.  Это значит,
     что видео режим системы должен быть 2,  3 или  7.  Независимо  от
     используемого режима - координаты левого верхнего угла - 0,0.

     Таблица 1-1.
     ------------

     Режим    Тип                    Размеры          Адаптеры
     ---------------------------------------------------------------
      0       текст,ч/б              40*25            CGA,EGA
      1       текст 16 цветов        40*25            CGA,EGA
      2       текст ч/б              80*25            CGA,EGA
      3       текст 16 цветов        80*25            CGA,EGA
      4       графика 4 цвета        320*200          CGA,EGA
      5       графика 4 серых тона   320*200          CGA,EGA
      6       графика ч/б            640*200          CGA,EGA
      7       текст ч/б              80*25            монохромный
      8       графика 16 цветов      160*200          PCjr
      9       графика 16 цветов      320*200          PCjr
     10       графика 4 или 16 цв.   640*200          PCjr,EGA
     13       графика 16 цветов      320*200          EGA
     14       графика 16 цветов      640*200          EGA
     15       графика 4 цвета        640*350          EGA
     --------------------------------------------------------------

          Символы, выводимые  на   экран,   содержатся   в   некоторой
     зарезервированной  области  памяти  на  адаптере  дисплея.  Адрес
     одноцветной информации В0000000H. И CGA, и EGA хранят информацию,
     начиная  с  B80000000H.  (Они различны для того,  чтобы позволить
     использовать раздельно текстовый и  графический  экран  -  но  на
     практике  это  делается редко.) Хотя функции CGA и EGA различны в
     разных режимах, они одинаковы в режимах 2 и 3.

          Каждый символ,  выводимый на экран,  требует два байта видео
     памяти.  Первый байт содержит собственно символ,  второй содержит
     аттрибуты   экрана.   Для   цветного   экрана   байт   аттрибутов
     интерпретируется так,  как показано в таблице 1-2. Если у вас EGA
     или CGA, то по умолчанию принимается режим 3, и символы выводятся
     с байтом аттрибутов 7.  Это значение включает три основных цвета,
     производя для символа белый цвет.  Для переключения  в  инверсный

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

     режим  небходимо  выключить  три  основных  бита  и  включить три
     фоновых бита, что дает значение 70H.

          Одноцветный адаптер распознает биты мигания и интенсивности.
     К счастью,  он разработан так, что интерпретирует аттрибут 7, как
     нормальный текст (белое на черном) и  70Н  как  инверсное  видео.
     Кстати, значение 1 дает подчеркнутые символы.

     Таблица 1-2
     -----------
     Байт видеоадаптера

          Бит      Двоичная величина     Значение при установке
     ----------------------------------------------------------------
           0             1               голубой основной
           1             2               зеленый основной
           2             4               красный основной
           3             8               малая интенсивность
           4            16               голубой фоновый
           5            32               зеленый фоновый
           6            64               красный фоновый
           7           128               мигающий символ
     ----------------------------------------------------------------

          Каждый адаптер имеет по крайней мере в 4 раза больше памяти,
     чем необходимо для вывода  текста  в  80-символьном  режиме.  Для
     этого есть  две  причины.  Во-первых,  лишняя  память  нужна  для
     графики, (конечно кроме  одноцветного  адаптера).  Во-вторых  это
     позволяет  держать  в  памяти  несколько  экранов  и потом просто
     переключаться между ними по мере  необходимости.  Каждая  область
     памяти  называется  видеостраницей и эффект от переключения между
     видеостраницами впечатляющ.  По умолчанию при  инициализации  ДОС
     используется  страница 0,  и виртуально все приложения используют
     страницу 0.  По этой причине она используется  и  в  этой  главе.
     Однако,  вы можете использовать и другие страницы, если захотите.

          Имеется три  способа  доступа  к  видеоадаптеру.  Первый это
     через прерывание ДОС, которое достаточно медленно для исчезающего
     меню.  Второй - это через процедуры BIOS,  которые быстрее,  и на
     быстродействующих машинах,  таких,  как AT  или  PS/2  достаточно
     быстры,  если меню невелики.  Третий способ - это чтение и запись
     прямо в видеопамять,  что происходит  очень  быстро,  но  требует
     большей работы от вас. Эта глава рассматривает два разных подхода
     в видео процедурах.  Один использует BIOS, а другой прямой доступ
     к видеопамяти.

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

               Доступ к экрану через BIOS
     -----------------------------------------------------------------

          Из-за того,  что  исчезающие  и  иерархические  меню  должны
     сохранять   информацию  с  того  места  экрана,  на  котором  они
     расположены, и восстанавливать его после выбора,  вы должны иметь
     процедуры,  которые  сохраняют  и  загружают часть экрана.  Метод
     сохранения и восстановления части экрана,  рассматриваемый в этом
     разделе связан с вызовами двух встроенных в BIOS функций, которые
     читают и записывают символы на экран.

          Как вы знаете,  вызовы BIOS  могут  быть  очень  медленными.
     Однако,  они  (более  или  менее)  гарантируют  работу  на  любом
     компьютере,  который имеет BIOS,  совместимый с  IBM,  даже  если
     аппаратура  экрана  другая.  Позже  в этой главе вы узнаете,  как
     выполнять прямой  доступ  к  видеопамяти  на  IBM   PC   и   100%
     совместимых   машинах   для   того,   чтобы   увеличить  скорость
     выполнения. Однако,  использование прямого доступа к  видеопамяти
     снижает в некоторой степени переносимость, так как требуется 100%
     совместимость компьютера с IBM PC.  Программы меню, основанные на
     BIOS следует использовать в применениях,  которые требуют большей
     мобильности.

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

               Использование int86()
     -----------------------------------------------------------------

          Вызовы BIOS  используют  программные прерывания.  BIOS имеет
     несколько различных прерываний для разных целей.  Одно из них  мы
     будем использовать для доступа к экрану. Это прерывание 16 (10Н),
     которое используется для доступа к дисплею. (Если вы не знакомы с
     доступом к BIOS, то вы найдете хорошее описание в моей книге "Си:
     Полный справочник",  Беркли, 1987). Как и многие прерывания BIOS,
     прерывание    16   имеет   несколько   режимов,   выбор   которых
     выполняется по значению  регистра  AH.  Если  функция  возвращает
     значение,  то  оно  заносится  в регистр AL.  Однако,  иногда для
     возвращения нескольких значений используются другие регистры. Для
     доступа  к  прерываниям BIOS вам придется использовать функцию Си
     int86(). (Некоторые компиляторы могут называть эту функцию другим
     именем,   но   MicroSoft  C  и  Турбо  Си  называют  ее  int86().
     Последующие рассуждения ориентированы на эти трансляторы,  но  вы
     можете их обобщить.

         Функция int86() имеет следующую форму:

      int int86(num,inregs,outregs)
      int num; /* номер прерывания */
      union REGS *inregs; /* входные значения регистров */
      union REGS *outregs; /* выходные значения регистров */

         Функция int86() возвращает значение  регистра  АХ.  Тип  REGS
     описывается в заголовке DOS.H. Этот тип показан здесь так, как он
     определен в Турбо Си, однако, он аналогично определен в MisroSoft
     C и в других компиляторах.

     struct WORDREGS {
      unsigned int ax, bx, cx, dx, si, di, cflag, flags;
     };
     struct BYTEREGS {
     unsigned char al, ah, bl, bh, cl, ch, dl, dh;
     };
     union REGS {
     struct WORDREGS x;
     struct BYTEREGS h;
     };

          Как вы можете видеть,  REGS - это объединение двух структур.
     Использование структуры WORDREGS позволяет рассматривать регистры
     ЦП как 16-битные числа.  BYTREGS дает вам доступ к  отдельным  8-
     битным регистрам.  Например, для доступа к прерыванию 16, функции
     5, вы должны использовать следующую последовательность.

     union REGS in,out;

     in.h.ah=5;
     int86(16,&in,&out);

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

               Сохранение части экрана.
     -----------------------------------------------------------------

          Для сохранения содержимого экрана,  должно быть прочитано  и
     запомнено текущее значение каждой позиции экрана.  Для считывания
     символа с определенной позиции  экрана,  используется  прерывание
     16,  функция  8,  которая  возвращает  символ  и  связанный с ним
     аттрибут текущей  позиции  курсора.  Для  считывания  символа   с
     определенного  места  экрана,  вы  должны  иметь способ установки
     курсора.  Хотя некоторые компиляторы Си поддерживают эту функцию,
     многие ее   не  имеют.  Тем  не  менее  показанная  ниже  функция
     goto_xy() может быть использована.  Она использует прерывание 16,
     функцию  2  с  координатой  столбца в DL и координатой ряда в DH.
     Видеостраница задается  в  ВН   (используется   страница   0   по
     умолчанию).

     /* установка курсора в x,y */
     void goto_xy(x,y)
     int x,y;
     {
      union REGS r;

      r.h.ah=2; /* функция установки курсора */
      r.h.dl=y; /* координата колонки       */
      r.h.dh=x; /* координата строки        */
      r.h.bh=0; /* видео страница           */
      int86(0x10,&r,&r);
     }

          Прерывание 16,  функция  8  возвращает  символ  из   текущей
     позиции курсора  в  AL и его атрибут в AH.  Функция save_video(),
     показанная здесь,  считывает часть экрана, сохраняет информацию в
     буфер, и очищает эту часть экрана.

     /* сохранение части экрана */
     void save_video(startx,endx,starty,endy,buf_ptr)
     int   startx,endx,starty,endy;
     unsigned int *buf_ptr;
     {
      union REGS r;
      register int i,j;

      for(i=starty;i [] = {
     "первая строка",
     "вторая строка",
      .
      .

      .
     "N-ая   строка" };

          Это описание   автоматически   заставляет   компилятор    Си
     поместить строки в таблицу строк.  Переменная указывает на первый
     символ первой строки в таблице.  Например,  это описание  создает
     переменную fruit (фрукт), которая указывает на "Я" в "Яблоко".

     char *fruit[] = {
      "Яблоко",
      "Апельсин",
      "Груша",
      "Грейпфрут",
      "Малина",
      "Клубника"
     };

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

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

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

     void draw_border(startx,starty,endx,endy)
     int   startx,starty,endx,endy;
     {
        register int i;
        for(i=startx+1;i24)||(x<0)||(y>79)||(y<0)) {
       printf(" выход за пределы экрана");
       return -2;
      }
     /* вычисление размеров */
     len=0;
     for(i=0;i len) len=strlen(menu[i]);
     endy=len+2+y;
     endx=count+1+x;
     if((endx+1>24) || (endy+1>79)) {
       printf(" выход за пределы экрана");
       return -2;
      }
     /* размещение памяти для видео буфера */
     p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));
      if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

     /* сохранение части экрана */
     void save_video(startx,endx,starty,endy,p);

     if(border) draw_border(x,y,endx,endy);

     /* высвечивание меню на своем месте */
     void display_menu(menu,x,y,count);

     /* ввести выбор пользователя */
     choice=get_resp(x,y,count,menu,keys)

     /* восстановление части экрана */
     void restore_video(startx,endx,starty,endy,p);
     free(p);

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

     return choice;
     }

          Как вы  можете  видеть,  popup()  проверяет выход за пределы
     экрана и слишком большой размер меню.  Она  возвращает  -2,  если
     возникла  одна  из  этих  ситуаций.  Из-за  того,  что get_resp()
     возвращает -1,  при  нажатии  клавиши  ESC,   возвращение   этого
     значения  функцией  popup следует рассматривать как "уничтожение"
     меню.  Если пользователь сделал выбор,  то возвращаемое  значение
     будет в пределах от 0 до count-1 с соответствием первому значению
     меню 0.  Как уже  указывалось,  popup()  использует  динамическое
     размещение памяти для обеспечения временной памяти для информации
     об экране.  Обычно это  лучший  подход,  но  вы  можете  свободно
     изменить его, если это важно для вашего приложения.

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

               Общий обзор
     -----------------------------------------------------------------

          Простая программа,   показанная   здесь,   использует    все
     программы, разработанные для использования исчезающих меню. Вы не
     видели только функции cls(),  которая  очищает  экран.  Некоторые
     трансляторы Си не имеют функции для этого,  и если это так, то вы
     не можете использовать следующую программу  (в чистом виде).

     /* процедура исчезающего меню для работы в текстовом режиме */
     #include "stdio.h"
     #include "dos.h"
     #include "stdlib.h"

     #define BORDER 1
     #define ESC 27
     #define REV_VID 0x70
     #define NORM_VID  7

     void save_video(),restore_video();
     void goto_xy(),cls(),write_video();
     void display_menu(),draw_border();

     char *fruit[] = {
      "Яблоко",
      "Апельсин",
      "Груша",
      "Грейпфрут",
      "Малина",
      "Клубника"
     };

     char *color[]={
      "Красный",
      "Желтый",
      "Оранжевый",
      "Зеленый"
     };

     char *apple_type[] = {
      "Красный деликатес",
      "Джонатан",
      "Белый налив",
      "Антоновка"
     };

     main()
     {
      int i;

     cls();
     goto_xy(0,0);
     for(i=0;i<25;i++)
      printf("Это тест исчезающего меню\n");

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

     popup(fruit,"яагрмк",6,1,3,BORDER);
     popup(color,"кжоз",4,5,10,BORDER);
     popup(apple_type,"кдба",4,10,18,BORDER);
     }

     /* вывести исчезающее меню и вернуть выбор
        возвращает -2, если меню не может быть создано
        возвращает -1, если пользователь нажал клавишу ESC
        в остальных случаях она возвращает номер выбранной
        альтернативы, начиная с 0 */
     int popup(menu,keys,count,x,y,border)
     char *menu[];  /* текст меню */
     char *keys;    /* горячие клавиши */
     int count;     /* число альтернатив */
     int x,y;       /* координаты левого верхнего угла */
     int border;    /* если 0 то без рамки */
     {
      register int i,len;
      int endx endy choice;
      unsigned char *p;

      if((x>24)||(x<0)||(y>79)||(y<0)) {
       printf(" выход за пределы экрана");
       return -2;
      }
     /* вычисление размеров */
     len=0;
     for(i=0;i len) len=strlen(menu[i]);
     endy=len+2+y;
     endx=count+1+x;
     if((endx+1>24) || (endy+1>79)) {
       printf(" выход за пределы экрана");
       return -2;
      }
     /* размещение памяти для видео буфера */
     p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));
      if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

     /* сохранение части экрана */
     void save_video(x,endx+1,y,endy+1,p);

     if(border) draw_border(x,y,endx,endy);

     /* высвечивание меню на своем месте */
     void display_menu(menu,x,y,count);

     /* ввести выбор пользователя */
     choice=get_resp(x,y,count,menu,keys)

     /* восстановление части экрана */
     void restore_video(x,endx+1,y,endy+1,p);
     free(p);
     return choice;

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

     }

     /* высвечивание меню на своем месте */
     void display_menu(menu,x,y,count)
     char *menu[];
     int x,y,count;
     {
      register int i;

     for(i=0;i24)||(x<0)||(y>79)||(y<0)) {
       printf(" выход за пределы экрана");
       return -2;
      }
     /* вычисление размеров */
     len=0;
     for(i=0;i len) len=strlen(menu[i]);
     endy=len+2+y;
     endx=count+1+x;

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

     if((endx+1>24) || (endy+1>79)) {
       printf(" выход за пределы экрана");
       return -2;
      }
     vmode = video_mode();
      if((vmode!=2) && (vmode!=3) && (vmode!=7)) {
        printf(" должен быть 80 символьный текстовый режим");
        exit(1);
     }

      /* присвоить соответствующий адрес видео памяти */
     if(vmode==7) vid_mem=(char far *)0xB0000000;
     else   vid_mem=(char far *)0xB8000000;
     /* размещение памяти для видео буфера */
     p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));
      if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

     /* сохранение части экрана */
     void save_video(x,endx+1,y,endy+1,p);

     if(border) draw_border(x,y,endx,endy);

     /* высвечивание меню на своем месте */
     void display_menu(menu,x,y,count);

     /* ввести выбор пользователя */
     choice=get_resp(x,y,count,menu,keys)

     /* восстановление части экрана */
     void restore_video(x,endx+1,y,endy+1,p);
     free(p);
     return choice;
     }

     /* высвечивание меню на своем месте */
     void display_menu(menu,x,y,count)
     char *menu[];
     int x,y,count;
     {
      register int i;

     for(i=0;iMAX_FRAME) {
       printf("Слишком много меню");
       return 0;
     }
      if((x>24)||(x<0)||(y>79)||(y<0)) {
       printf(" выход за пределы экрана");
       return 0;
      }

     /* вычисление размеров */
     len=0;
     for(i=0;i len) len=strlen(menu[i]);
     endy=len+2+y;
     endx=count+1+x;
     if((endx+1>24) || (endy+1>79)) {
       printf(" выход за пределы экрана");
       return 0;
      }

     /* размещение памяти для видео буфера */
     p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));
      if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

     /* создание фрейма */
     frame[num].startx=x;
     frame[num].endx=endx;
     frame[num].starty=y;
     frame[num].endy=endy;
     frame[num].p = p;
     frame[num].menu = (char **) menu;
     frame[num].border = border;
     frame[num].keys = keys;

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

     frame[num].count = count;
     frame[num].active =0;
     return 1;
     }

          Вы вызываете   make_menu   с   теми  же  аргументами,  какие
     используются в popup() кроме номера  меню,  который  должен  быть
     определен   в  первом  аргументе.  Этот  номер  используется  для
     идентификации меню.

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

               Функция pulldown()
     -----------------------------------------------------------------

          Функция pulldown() показана здесь:

     /* Высветить меню и получить выбор
        возвращает -1, если пользователь нажал клавишу ESC
        в остальных случаях номер альтернативы, начиная с 0 */

     int pulldown(num)
     int num; /* номер фрейма */
     {
      int vmode,choice;

      vmode=video_mode();
      if((vmode!=2) && (vmode!=3) && (vmode!=7)) {
        printf(" должен быть 80 символьный текстовый режим");
        exit(1);
     }

      /* присвоить соответствующий адрес видео памяти */
     if(vmode==7) vid_mem=(char far *)0xB0000000;
     else   vid_mem=(char far *)0xB8000000;

     /* узнать активность окна */
     if(!frame[num].active) { /* не активно */
         save_video(num);
         frame[num].active= 1; /* установить признак активности */
     }

     if( frame[num].border) draw_worder(num);

     display_menu(num);  /* высветить меню */
     return get_resp(num);  /* возвратить выбор */
     }

          Для использования pulldown()  просто  передайте  номер  того
     меню,  которое  вы  хотите высветить.  Однако вы должны помнить о
     восстановлении окна,  используя  restore_video()  везде  в  своей
     программе.  Запомните,  что основное отличие иерархических меню в
     том,  что они позволяют двум или более меню оставаться на  экране
     одновременно  и  быть  потенциально  активными,  по мере того как
     пользователь  выбирает  опции.  Тем  не  менее  вам  не   следует
     восстанавливать  экран  до  того,  как  процесс  выбора полностью
     завершен.

          Обратите внимание,  что часть экрана, используемая для меню,
     сохраняется  только  если  признак  активности  равен 0.  Так как
     иерархическое меню может быть вызвано повторно,  экран не  должен
     сохраняться  много раз.  (Другими словами при повторном вхождении
     будет сохранено  само  меню,  записанное  поверх  первоначального
     содержимого экрана, которое уже сохранено.)

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

               Восстановление экрана
     -----------------------------------------------------------------

          Как и   другие   функции    поддержки    меню,    измененная
     restore_video   показанная  здесь,  преобразована  для  работы  с
     фреймами.  Поэтому  функции  restore_video()  теперь   передается
     только номер меню, что делает интерфейс более очевидным.

     /* восстановление части экрана */
     void restore_video(num)
     int   num;
     {
      register int i,j;
      char far *v, far *t;
      char *buf_ptr;

      buf_ptr=frame[num].p;
      v=vid_mem;
      t=v;
      for(i=frame[num].starty;iMAX_FRAME) {
       printf("Слишком много меню");
       return 0;
     }
      if((x>24)||(x<0)||(y>79)||(y<0)) {
       printf(" выход за пределы экрана");

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

       return 0;
      }

     /* вычисление размеров */
     len=0;
     for(i=0;i len) len=strlen(menu[i]);
     endy=len+2+y;
     endx=count+1+x;
     if((endx+1>24) || (endy+1>79)) {
       printf(" выход за пределы экрана");
       return 0;
      }

     /* размещение памяти для видео буфера */
     p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));
      if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

     /* создание фрейма */
     frame[num].startx=x;
     frame[num].endx=endx;
     frame[num].starty=y;
     frame[num].endy=endy;
     frame[num].p = p;
     frame[num].menu = (char **) menu;
     frame[num].border = border;
     frame[num].keys = keys;
     frame[num].count = count;
     frame[num].active =0;
     return 1;
     }

     /* высвечивание меню на своем месте */
     void display_menu(num)
      int num;
     {
      char **m;
      register int i, x;

      x = frame[num].startx+1;
      m = frame[num].menu;

     for(i=0;i


                               ГЛАВА 2                         -- 1 --
                               -------

                           ВСПЛЫВАЮЩИЕ ОКНА
     -----------------------------------------------------------------

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

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

          Программы управления   окнами   используют  функции  прямого
     доступа к видеопамяти,  представленные в Главе 1. Из-за того, что
     окна в большинстве случаев, имеют значительно больший размер, чем
     меню,  то использование функций из ROM-BIOS просто невозможно,  -
     даже на самых быстрых компьютерах.

          Однако перед   рассмотрением  оконных  функций  очень  важно
     правильно понять,  что  же  такое  всплывающие  окна  и  как  они
     используются.

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

                          Теория всплывающих окон.
     -----------------------------------------------------------------

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

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

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

          Разработка функций,  управляющих  окнами,  является  сложной
     задачей  из-за  необходимости  обеспечения запрета для прикладной
     программы осуществлять вывод за границы окна.  Поскольку  размеры
     окна   могут   изменяться  без  "сообщения"  об  этом  прикладной
     программе,  то именно функции управления окнами,  а не прикладная
     программа,    должны   предохранить   от   выхода   за   границы.
     Следовательно,  все обычные функции Си, осуществляющие ввод/вывод
     на  консоль  (например,  printf()  и  lets()  )  ,  не могут быть
     использованы и должны быть заменены на  функции,  ориентированные
     на ввод/вывод с использованием окон.

          Теория использования  окон  крайне проста.  Каждая отдельная
     задача программы использует свое собственное  окно.  При  запуске
     задачи активируется и ее окно.  При завершении работы задачи - ее
     окно удаляется.  Если же задача прерывается,  то,  хотя ее работа
     приостанавливается,  но  ее  окно  не  удаляется,  а инициируемая
     прерыванием задача,  просто создает свое окно поверх предыдущего.
     (Обычно те задачи, которые не используют окон, очищают экран. Это
     приводит к рассеиванию внимания пользователя.  В то же время  при
     использовании  окон  подобные  прерывания  выглядят как временные
     паузы).

          Чтобы понять,  как  окна  могут  быть  наиболее   эффективно
     использованы, предположим, что вы разработали текстовый редактор,
     включающий  ряд  дополнительных  функций,  таких  как   "записная
     книжка",  калькулятор  с  четырьмя  математическими  операциями и
     конвертер чисел из десятичного в шестнадцатиричное представление.
     Так  как  все  эти  функции  в  действительности  не  относятся к
     операциям  редактирования  текста,   то   их   реализация   тесно
     переплетается   с  концепцией  всплывающих  окон.  Таким  образом
     получается,  что  использование  какой-либо  из   вспомогательных
     функций лишь приостанавливает основную задачу (редактирование), а
     не прерывает ее.

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

               Оконные структуры.
     -----------------------------------------------------------------

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

                  struct window_frame
                    int startx, endx, starty, endy; /*позиция окна*/
                    int curx, cury; /*текущая позиция курсора в окне*/
                    unsigned chsr *p; /*указатель буфера*/
                    char *header; /*имя окна*/
                    int border; /*включение/выключение границ*/
                    int active; /*на экране или невидимо*/
                  } frame [MAX_FRAME];

          Переменные startx,  starty,  endx и endy  хранят  координаты
     верхнего  левого  и  нижнего правого углов окна.  Текущая позиция
     курсора в окне содержится в переменных curx  и  cury.  Сохранение
     этих переменных осуществляется из-за того,  что положение курсора
     может изменяться и вручную и путем использования оконных функций.
     Указатель p указывает на область памяти,  хранящей первоначальное
     содержимое  части  экрана,  занятой  данным  окном.  Часто   окно
     снабжается  заголовком,  иденцифирующим содержимое окна.  На этот
     заголовок и указывает header.  Переменная border используется для
     определения   необходимости   вычерчивания  границ  вокруг  окна.
     Переменная active установлена в "1", если в данный момент окно на
     экране, и в "0" - в противном случае.

          С точки   зрения   программирования  использование  окон  не
     составляет труда.  Во-первых,  вы создаете структуру окна, затем,
     когда  появляется  необходимость в окне,  то при записи в него вы
     используете    специальные     оконно-ориентированные     функции
     ввода/вывода.  Когда  же  окно больше не нужно,  вы деактивируете
     его.

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

                         Создание структуры окна.
     -----------------------------------------------------------------

          Ниже показана    функция    под   названием   make_window(),
     используемая для создания структуры окна.

         /* Создать рамку спускающегося окна.
          Возвратить 1, если рамка окна  может быть создана
          и 0 в противном случае */
         маке_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 */
         {
           unsigned char *p;

           if(num>MAX_FRAME) {
             printf("Too many windows\n");
             return 0;
           }

          if((startx>24) || (startx<0) || (starty>78) || (starty<0)) {
             printf("range error");
             return 0;
           }

           if((endx>24) || (endy>79)) {
             printf("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 = p;
           frame[num].haeder = header;
           frame[num].border = border;
           frame[num].active = 0;
           frame[num].curx = 0; frame[num].cury = 0;
           return 1;
         }

          Как вы  можете  видеть  из  описания  функции,  она  требует
     передачи  номера  окна,  структуру которого вы хотите создать,  и
     всей остальной соответствующей информации.  Ниже  показан  пример
     вызова  make_window()  для  создания  окна  номер  0 с заголовком

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

     "Редактор [Esс для выхода]",  с верхним левым углом  в  0,0  и  с
     нижним правым углом в 24,78 и имеющем границы.

      make_window (0, "Редактор [Ese для вывода]", 0,0,24,78, BORDER);

          Отметим, что переменные, определяющие позицию курсора curx и
     cury устанавливаются 0.  Это означает,  что при первой  активации
     окна,  курсор будет установлен в его верхний левый угол.  Функция
     также управляет размещением окна на экране.

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

                      Активирование и деактивирование окна.
     -----------------------------------------------------------------

          Для активирования окна используется функция window (). Здесь
     num  будет  содержать  номер  структуры  окна,  которое вы хотите
     использовать.

         /* Вывести на экран спускающееся окно */
         void window(num)
         int num; /* номер окна */
         {
           int vmode, choice;
           int x, y;

           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 *) 0xB0000000;

           /* сделать окно активным */
           if(!frame[num].active) { /* используется не постоянно */
           save_video(num); /* сохранить текущее содержимое экрана */
           frame[num].active = 1; /* установить флаг активности */
           }

           if(!frame[num].border) draw border(num);
           display_header(num); /* вывести окно на экран */

           x = frame[num].startx + frame[num].curx + 1;
           y = frame[num].starty + frame[num].cury + 1;
           goto_xy(x,y);
         }

          Как вы можете видеть, данная функция очень похожа на функцию
     menu(),  показанную  в  предыдущей  главе.   Переменная   vid-mem
     является глобальным указателем типа char far.

          Функция display_header(),  показанная ниже, используется для
     отображения в центре верхней границы окна его заголовка.  Если же
     заголовок не помещается в эту строку, то он не выводится.

         /* вывести текст заголовка начиная с определенной
         позиции */
         void display_header(num)
         int num;
         {
           register int y,len;

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

           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);
         }

          Если вы  хотите,  чтобы  заголовок  выводился  в   инверсном
     изображении,  то  вместо NORМ_VID (имеющем значение 7) подставьте
     REV_VID (со значением 70Н).

          Для деактивации окна используется  показанная  ниже  функция
     deactivate(), которой передается номер удаляемого окна.

         /* Деактивировать окно и удалить его с экрана */
         deactivate(num)
         int num;
         {
           /* установить курсор в левый верхний угол */
           frame[num].curx = 0;
           frame[num].cury = 0;
           restore_video(num);
         }

          Как вы видите,  функция устанавливает позицию курсора в 0,0.
     Однако,  поскольку вам могут встретиться  некоторые  ситуации,  в
     которых  более  желательно  не  устанавливать  курсор  в  нулевую
     позицию,  то  вы  можете   изменить   поведение   этой   функции,
     сообразуясь со своими намерениями.

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

               Оконные функции ввода/вывода.
     -----------------------------------------------------------------

          Перед использованием     окна     необходимо     разработать
     значительное   число  консольных  оконно-ориентированных  функций
     ввода/вывода.  Чтобы понять,  почему требуется так много функций,
     вспомните  о  том,  сколько  консольных  функций  ввода/вывода  в
     стандартной  библиотеке  Си.  Функции,  представленные  в  данной
     главе,  в  действительности  составляют  лишь  минимальный набор,
     необходимый для использования окон.  Хотя они не включают в  себя
     ориентированные  на  использование  окон  версии  всех консольных
     Си-функций, но все же имеют большой объем. Как вы убедитесь, даже
     простейшие  операции,  такие  как чтение символа с клавиатуры или
     вывод его на экран,  реализуются  программами  большого  объеема,
     поскольку  необходимо  отслеживать  и  сохранять  текущую позицию
     курсора и не допускать выхода за границы окна.  Помните,  что для
     манипулирования  экраном  нельзя использовать никаких стандартных
     возможностей,  предоставляемых DOS.  Например,  при необходимости
     выполнить    "возврат    каретки"   это   должно   быть   сделано
     самостоятельно,  внутри функции;  нельзя просто вызвать  DOS  для
     вывода соответствующей последовательности.

          Для облегчения    идентификации    все    оконные    функции
     ввода/вывода начинаются со слова  window.  Кроме  того,  все  эти
     функции в качестве своего первого аргумента принимают номер окна,
     к которому осуществляется доступ.

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

                  Функция позиционирования курсора в окне.
     -----------------------------------------------------------------

          Возможно, кому-то это покажется странным, но первая функция,
     в которой возникает потребность,  это оконный эквивалент  функции
     goto_xy().  Объяснение  этому  очень  простое.  Поскольку оконные
     функции  ввода/вывода   должны   самостоятельно   позиционировать
     курсор, то должен существовать некоторый способ установки курсора
     в  нужную  позицию.  Функция  window_xy(),  представленная  ниже,
     делает  именно  это.  (Для представленных здесь программ работы с
     окнами позиция 0,0 соответствует левому верхнему углу окна).

         /* Установить курсор в определенную позицию окна.
            Возвратить 0 при выходе за границу и не ноль -
            в противном случае */

         window_xy(num, x, y,)
         int num, x, y;
         {
           if(x<0 || x+frame[num].startx>=frame[num].endx-1)
             return 0;
           if(x<0 || y+frame[num].starty>=frame[num].endy-1)
             return 0;
           frame[num].curx = x;
           frame[num].cury = y;
           goto_xy(frame[num].startx+x+1, frame[num].starty+y+1);
           return 1;
           }

          Ключом к  пониманию функции window_xy() является напоминание
     о  том,  что  значения  координат  X,Y   внутри   окна   остаются
     неизменными,  независимо от расположения окна на экране.  То есть
     координаты X,Y вычисляются относительно окна,  а не  относительно
     экрана.  Другими  словами,  если  вы  установите  курсор в окне в
     позицию с координатами 2,2,  то он  всегда  будет  находиться  во
     второй   строке  сверху  и  второй  позиции  относительно  левого
     верхнего угла окна, независимо от положения окна на экране.

          Фактически функция  window_xy()  производит   преобразование
     координат   курсора   относительно  окна  в  реальные  координаты
     относительно  экрана.  Кроме  этого,   функция   window_xy()   не
     позволяет курсору выйти за пределы окна.

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

                  Функция window_getche()
     -----------------------------------------------------------------

          При  работе  с  окнами  вы  не  можете  использовать функцию
     getche(), которая  считывает  введенный  с  клавиатуры  символ  и
     отображает его на экране,  поскольку это может привести к  выходу
     за пределы окна.  Поэтому должна быть использована альтернативная
     оконная функция window_getche().  Эта функция  считывает  символ,
     который вводится в текущую позицию окна.

        /*  Ввести  с  клавиатуры  символ  в   окно.   Возвратить
            полный 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;
         }

          В отличие   от  функции  getche(),  функция  window_getche()
     возвращает полный 16-разрядный скан-код.  Это  означает,  что  вы
     имеете  доступ  как к стандартным кодам символов в младших восьми
     разрядах,  так и к позиционным кодам символов  в  старших  восьми
     разрядах. Если вам не нужны позиционные коды, то вы можете просто
     назначить   возвращаемое   функцией   window_getche()    значение

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

     символьной переменной.

          Функция работает  следующим  образом.  Если окно не является
     активным  (т.е.  его  нет  на  экране),  функция  возвращает   0.
     Поскольку  код  0 не может соответствовать символу,  введенному с
     клавиатуры,  то ваша программа сможет  обнаружить  эту  ситуацию.
     Затем  курсор  устанавливается  в свою текущую позицию в окне,  и
     считывается код нажатой клавиши.  Если это обычная клавиша,  а не
     клавиша  типа  RETURN  или  BACKSPASE,  инкрементируется  текущее
     значение  переменной  Y,  соответствующее  положению  курсора,  и
     соответствующий символ выводится на экран.  Если курсор находится
     на границе окна, значение Y декрементируется. Последнее обращение
     к  функции  window_xy() используется для того,  чтобы переместить
     курсор в следующую позицию экрана.

          Функция window_getche не позволит  вам  вводить  символы  за
     границей  окна.  Следует  помнить,  что  все  окна имеют границы,
     которые могут и не отображаться явно в виде линий.  Если  граница
     не  отображается,  это  значит,  что  для  этой цели используется
     символ пробела.  Это имеет смысл,  если окно отображается на фоне
     изображения,  имеющего  другой  цвет.  Даже  если  граница  и  не
     обозначена явно,  то символы все равно не могут  отображаться  за
     пределами окна.

          Как утверждается в разделе 1,  функция bios_key() cпецифична
     для Турбо Си.  Если вы используете другой компилятор  Си,  то  вы
     можете  использовать версию функции bios_key(),  представленную в
     главе 1.

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

               Функция window_gets()
     -----------------------------------------------------------------

          Для чтения  строки,  введенной  в  окно,  можно использовать
     представленную  ниже  функцию   window_gets().   Она   не   такая
     изощренная, как большинство функций gets(),  но может служить для
     многих  целей.  Вы  всегда  можете  расширить  ее  функциональные
     возможности, если пожелаете.

         /* Считать строку из окна */
         void window_gets(nums, 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++;
             }
           }
         }

          При нажатии клавиши ВАСКSPASE,  необходимо вернуть курсор на
     одну позицию влево,  стереть записанный там символ и на его место
     записать пробел.

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

               Функция window_putchar()
     -----------------------------------------------------------------

          При выводе символа в окно необходимо проверять,  является ли
     окно  активным  и  не  выходит  ли символ за границу окна.  После
     вывода символа курсор продвигается на одну позицию. Выполняет эти
     действия представленная ниже функция window_putchar().

         /* Вывести символ в текущую позицию курсора в созданном окне.
         Возвратить  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;
         }

          Эта функция  не занимается обнаружением ошибочнных ситуаций,
     когда символ выходит за границу окна.  Смысл этого состоит в том,
     что  размер  окна  может  динамически  изменяться и то сообщение,

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

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

          Обратите внимание,  что  нажатие  клавиши  возврата  каретки
     требует  перевода  курсора  к левой границе окна и на одну строку
     вниз, если это возможно.

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

               Функция window_puts
     -----------------------------------------------------------------

          Функция window_puts выводит заданную строку в активное окно,
     используя при этом функцю window_putchar().

      /* Вывести строку, начиная с текущей позиции курсора в окне.
                                                         /* 60 */
         Возвратить 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;
         }

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

               Дополнительные функции управления экраном.
     -----------------------------------------------------------------

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

          Функция             Назначение
         ---------            ------------
        window cls()          очищает окно
        window cleol()        очищает часть окна от текущей позиции
                              до конца строки
        window_upline()       перемещает курсор на одну строку вверх
        window_downline()     перемещает курсор на одну строку вниз
        window_bksp()         перемещает курсор на одну позицию влево

          Эти функции  представлены  ниже.  Следуя  используемым в них
     общим  принципам,  вы  можете  создать  свои  функции  управления
     экраном.

         /* Очистить окно */
         void window_cls(num)
         int num;
         {

           register int i,j;
           char far *v, far *t;

           v = vid_mem;
           t = v;
           for(i=frame[num].starty+1; i=frame[num].endx) startx--;
             if(starty<0) starty++;
             if(starty>=frame[num].endy) starty--;
             deactivate(num);  /* стереть окно старого размера */
             frame[num].startx = startx;
             frame[num].starty = starty;
             window(num);      /* вывести окно с новым размером */
           } while(ch!=59);  /* F1 для подтверждения нового размера */
           deactivate(num);
         }

         /* Интерактивное перемещение окна */
         void move(num)
         int num;
         {
           char ch;
           int x, y, ex, ey, startx, starty, endx, endy;

           /* активировать, если необходимо */
           if(!frame[num].active) window(num);
           startx = x = frame[num].startx;
           starty = y = frame[num].starty;
           endx = ex = frame[num].endx;
           endy = ey = frame[num].endy;
           window_xy(num, 0, 0);

           do {
             ch = get_special();
             switch(ch) {
               case 75:     /* влево */
                 starty--;
                 endy--;
                 break;
               case 77:     /* вправо */
                 starty++;
                 endy++;
                 break;
               case 72:     /* вверх */
                 startx--;
                 endx--;
                 break;
               case 80:     /* вниз */
                 startx++;

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

                 endx++;
                 break;
               case 71:     /* влево вверх */
                 startx--;starty--;
                 endx--;endy--;
                 break;
               case 73:     /* вправо вверх */
                 startx--;starty++;
                 endx--;endy++;
                 break;
               case 79:     /* влево вниз */
                 startx++;starty--;
                 endx++;endy--;
                 break;
               case 81:     /* вправо вниз */
                 startx++;starty++;
                 endx++;endy++;
                 break;
       case 60:     /* F2: отменить и вернуться к исходному размеру */
                 startx = x;
                 starty = y;
                 endx = ex;
                 endy = ey;
                 ch = 59;
             }

             /* смотри при выходе за диапазоном */
             if(startx<0) {
               startx++;
               endx++;
             }
             if(endx>=25) {
               startx--;
               endx--;
             }
             if(starty<0) {
               starty++;
               endy++;
             }
             if(endy>=79) {
               starty--;
               endx--;
             }
             /* стереть окно в старой позиции */
             deactivate(num);
             frame[num].startx = startx;
             frame[num].starty = starty;
             frame[num].endx = endx;
             frame[num].endy = endy;
             /* вывести окно в новую позицию */
             window(num);
             } while(ch!=59);  /* F1 для подьверждения изменения */
           deactivate(num);
         }

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

          При использовании как функции size(),  так и функции  move()
     после  завершения  изменения  параметров окна нажмите клавишу F1.
     Окно будет иметь установленные размеры  и  положение  при  каждой
     последующей активации и до тех пор, пока вы не измените их снова.
     Для прерывания выполнения обеих функций используется  клавиша F2,
     при этом окно сохраняет старые значения размеров и положения. При
     использовании этих функций  допускается,  чтобы  окна,  в  момент
     изменения их размеров или положения необязательно были активными.

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

          Создание прикладных программ, использующих всплывающие окна
     -----------------------------------------------------------------

          Работая с окнами очень важно помнить,  что при  вводе-выводе
     должны  использоваться специальные оконные функции. Использование
     для этих целей стандартных  функций  Си  чревато  неприятностями,
     потому  что  создает возможность нарушения границы окна.  Оконной
     функции,  аналогичной по выполняемым действиям  функции  prinf(),
     разработано не было,  и вы,  возможно,  захотите создать для этих
     целей свою собственную функцию.  Но простейший  способ  вывода  в
     окно данных, тип которых отличен от символов и строк, заключается
     в   использовании   стандартной    Си-функции    sprintf()    для
     преобразования любых типов данных в строку определенного формата,
     а затем в выводе этой строки в окно с помощью функции window_puts
     ().  Аналогичный  способ  позволяет  вводить данные,  отличные от
     символов  и  строк.  При  этом  функция  window_gets()  считывает
     данные,   а  приводит  их  к  соответствующему  типу  стандартная
     Си-функция sscanf(), которая выполняет преобразование поступающих
     от клавиатуры данных.

          Изображение в   окне   обычно   формируется   другой  частью
     программы,  не той,  которая содержит функции управления  окнами.
     Обычно это делается в функции main() или в функции инициализации,
     которая вызывается в начале программы. Предлагаем вашему вниманию
     три простые программы, использующие окна.

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

                 Программа  преобразования  из  десятичной
                 в шестнадцатиричную систему счисления.
     -----------------------------------------------------------------

         /* Десятично-шестнадцатиричный преобразоваель */
         void dectohex()
         {
           char in[80], out[80]
           int n;

           window(1);
           do {
             window_xy(1, 0, 0)  /* перейти к первой строке */
             window_cleol(1);    /* очистить строку */

             window_puts(1, "dec: "); /* промптер */
             window_gets(1, in);      /* считать число */
            window_putchar(1,  '\n'); /* перейти к следующей строке */
             window_cleol(1);          /* очистить ее */
          sscanf(in,"%d", &n); /* преобразовать во внутрений формат */
             sprintf(out, "%s%X", "hex: ",n);  /* преобразовать в
                                  шестнадцатиричное представление */
            window_puts(1, out); /* вывести шестнадцатиричное число */
           } while(*in);
           deactivate(1);
         }

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

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

               Калькулятор с четырьмя функциями.
     -----------------------------------------------------------------
          Очень подходящей   и   популярной   областью   использования
     всплывающих   окон   являются   программы   калькуляторов.  Здесь
     представлена программа стекового калькулятора.  Это означает, что
     при работе с ним вы должны сначала вводить операнды, а затем знак
     операции (т.н. постфиксная запись). Операнды помещаются в стек. В
     каждый  момент времени выполняется операция над двумя операндами.
     При  этом  операнды  извлекаются  из  стека,  результат  операции
     отображается и помещается в стек.  Например,  для того, вычислить
     результат выражения (10+5)/5,  вы сначала должны ввести 10, затем
     5, затем знак +. Результат этой операции, число 15, будет выведен
     на дисплей и помещен в вершину стека.  Затем вы вводите 5 и  знак
     /.  Отображается  результат  3.  Стек рассчитан на 100 элементов.
     Можно вводить несколько операндов перед знаком  операции. Функция
     calc(),  а также подпрограммы push() и pop() для работы со стеком
     приводятся ниже.  Хотя эта версия  программы  работает  только  с
     целыми числами,  вы легко можете изменить ее таким образом, чтобы
     она работала с действительными числами.

         #define MAX 100
         int *p;  /* указатель стека */
         int *tos;  /* указатель вершины стека */
         int *bos;  /* указатель дна стека */

     /* стековый, с постфиксной записью калькулятор с четырьмя
        функциями */
         void calc()
         {
           chra in[80], out[80];
           int answer, stack[MAX];
           int a,b;

           p = stack;
           tos = p;
           bos = p+MAX-1;

           window(2);
           do {
              window_xy(2, 0,0);
              window_cleol(2);
              window_puts(2, ": "); /* промптер калькулятора */
              window_gets(2, in);
              window_puts(2, "\n ");
              window_cleol(2);
              switch(*in) {
                case '+':
                  a = pop();
                  b = pop();
                  answer = a+b;
                  push(a+b);
                  break;
                case '-':
                  a = pop();

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

                  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_putch("divide by 0\n");
                      break;
                    }
                    answer = b/a;
                    break;
                  default:
                    push(atoi(in));
                    continue;
                  }
                  sprintf(out, "%d", answer);
                  window_puts(2, out);
                } while(*in);
                deactivate(2);
              }

            /* Поместить число в стек.  Возвратить 1 в случае успеха и
               0, если стек переполнен */
              push(i)
              int i;
              {
                if(p>bos) return 0;

                *p=i;
                p++;
                return 1;
              }

              /* Извлечь верхний элемент из стека.  Возвратить 0, если
                 стек переполнен */

              pop()
              {
                p--;
                if(p0) {
                     j--;
                     window_bksp(3);
                   }
                 }
                 else {
                   notes[i][j] = ch;
                   j++;
                 }
                 } while(notes[i][j-1]!='\r');
                 notes[i][j-1] = '\0';
                 i++;
                 window_putchar(3, '\n');
               }
               else {             /* это специальная клавиша */
                 switch(c.ch[1]) {
                   case 72:       /* стрелка вверх */
                     if(i>0) {
                       i--;
                       window_upline(3);
                     }
                     break;
                   case 80:       /* стрелка вниз */
                     if(iMAX_FRAME) {
             printf("Too many windows\n");

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

             return 0;
           }

         if((startx>24) || (startx<0) || (starty>78) || (starty<0)) {
             printf("range error");
             return 0:
           }

           if((endx>24) || (endy>79)) {
             printf("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 = p;
           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 size(num)
         int num;
         {
           char ch;
           int x, y, startx, starty;

           /* активировать, если необходимо */
           if(!frame[num].active) window(num);

           startx = x = frame[num].startx;
           starty = y = frame[num].starty;
           window_xy(num, 0, 0);

           do {

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

             ch = get_special();
             switch(ch) {
               case 75:    /* влево */
                 starty--;
                 break;
               case 77:    /* вправо */
                 starty++;
                 break;
               case 72:    /* вверх */
                 startx--;
                 break;
               case 80:    /* вниз */
                 startx++;
                 break;
               case 71:    /* влево вверх */
                 startx--; starty--;
                 break;
               case 73:    /* вправо вверх */
                 startx--; starty++;
                 break;
               case 79:    /* влево вниз */
                 startx++; starty--;
                 break;
               case 81:    /* вправо вниз */
                 startx++; starty++;
                 break;
               case 60:    /* F2: отменить и вернуться к исходным
                                                         размерам */
                 startx = x;
                 starty = y;
                 ch = 59;
             }

             if(startx<0) startx++;
             if(startx>=frame[num].endx) startx--;
             if(starty<0) starty++;
             if(starty>=frame[num].endy) starty--;
             deactivate(num);
             frame[num].startx = startx;
             frame[num].starty = starty;
             window(num);
           } while(ch!=59);
           deactivate(num);
         }

         /* Интерактивное перемещение окна */
         void move(num)
         int num;
         {
           char ch;
           int x, y, ex, ey, startx, starty, endx, endy;

           /* активировать, при необходимости */

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

           if(!frame[num].active) window(num);

           startx = x = frame[num].startx;
           starty = y = frame[num].starty;
           endx = ex = frame[num].endx;
           endy = ey = frame[num].endy;
           window_xy(num, 0, 0);

           do {
             ch = get_special();
             switch(ch) {
               case 75:   /* влево */
               starty--;
               endy--;
               break;
               case 77:   /* вправо */
               starty++;
               endy++;
               break;
               case 72:   /* вверх */
               startx--;
               endx--;
               break;
               case 80:   /* вниз */
               startx++;
               endx++;
               break;
               case 71:   /* влево вверх */
               startx--; starty--;
               endx--; endy--;
               break;
               case 73:   /* вправо вверх */
               startx--; starty++;
               endx--; endy++;
               break;
               case 79:   /* влево вниз */
               startx++; starty--;
               endx++; endy--;
               break;
               case 81:   /* вправо вниз */
               startx++; starty++;
               endx++; endy++;
               break;
               case 60:    /* F2: отменить и вернуться к исходным
                                                         размерам */
                 startx = x;
                 starty = y;
                 endx = ex;
                 endy =ey;
                 ch = 59;
         }

         /* см. при выходе за диапазон */
         if(startx<0) {

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

           startx++;
           endx++;
         }
         if(endx>=25) {
           startx--;
           endx--;
           }
           if(starty<0) {
             starty++;
             endy++;
           }
           if(endy>=79) {
             starty--;
             endy--;
           }
           deactivate(num);
           frame[num].startx = startx;
           frame[num].starty = starty;
           frame[num].endx = endx;
           frame[num].endy = endy;
           window(num);
         } while(ch!=59);
         deactivate(num);
       }

         /* Вывести текст заголовка, начиная с определенной позиции */
         void display_header(num)
         int num;
         {
           register int 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;
           }
           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;
           goto_xy(frame[num].startx+x+1, frame[num].starty+y+1);
           return 1;
         }

         /* Считать строку из окна */
         void window_gets(num, s)
         int num;
         char *s;
         {

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

           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;

             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++;

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

                   }
               }
            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_cls(num)
           int num;
           {
             register int i,j;
             char far *v, far *t;

             v = vid_mem;
             t = v;
             for(i=frame[num].starty+1; ibos) return 0;

             *p=i;
             p++;
             return 1;
           }

           /* Извлечь верхний элемент из стека.
              Возвратить 0 если стек пуст */
           pop()
           {
             p--;
             if(p0) {
                     j--;
                     window_bksp(3);
                       }
                     }
                     else {
                       notes[1][j] = ch;
                       j++;
                     }
                   } while(notes[i][j-1]!='\r');
                   notes[i][j-1] = '\0';
                   i++;
                   window_putchar(3, '\n');
                 }
                 else {    /* если специальная клавиша */
                   switch(c.ch[1]) {
                     case 72:     /* стрелка вверх */
                       if(i>0) {
                         i--;
                         window_upline(3);
                       }break
                     case 80:     /* стрелка вниз */
                       if(i


                             ГЛАВА 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; i 0) {
           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].curx 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;

                                 "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; i 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 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);
        }
     }

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

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

                                 "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


                                  ГЛАВА 4                     -- 1 --
                                  -------

                                  ГРАФИКА
     -----------------------------------------------------------------

          В этой  главе  приводится  базовый  набор  функций  графики,
     которые позволяют рисовать (отображать на экране)  точки,  линии,
     прямоугольники,  окружности,  используя  возможности  графических
     адаптеров CGA или EGA.  Эти программы используются как основа, на
     которой строятся функции графики более высокого уровня. Для более
     детального изучения этих базовых графических функций предлагается
     книга   "С:   The  Complete  Reference"  Herb  Schild  (Osborn  /
     McGrow-Hall, 1987).

          Помимо краткого представления  базовых  функций  графики,  в
     этой главе приводятся следующие программы:

          - сохранение графических изображений в файле на диске;
          - загрузка графических изображений из файла;
          - вращение объектов в двумерном пространстве;
          - копирование или пересылка графических изображений.

          В   конце   главы   привевен   текст  "программы-художника",
     позволяющей рисовать на экране терминала, с использованием клавиш
     перемещения курсора.

          По мере возрастания вашего опыта  по  использованию  функций
     графики,   вы   сможете   самостоятельно   разрабатывать  хорошие
     программы.  Например,  используя функции  сохранения  и  загрузки
     графических   изображений,   вы  сможете  создавать  графики  или
     диаграммы и  успешно  использовать  их  в  случае  необходимости.
     Используя  функции  вращения  изображений  объектов,  вы  сможете
     разрабатывать  программы  "звездных  войн"  -   образец   "живой"
     графики,  которые будут представлять большой интерес для вас.  Вы
     сможете  также,  использовать  эти   функции   как   основу   для
     использования  систем автоматизированного проектирования (CAD/CAM
     system).

          Для  корректного  использования  программ,  описаных  в этой
     главе,  вам необходимы компьютеры типа IBM PC  XT,  IBM PC AT или
     другие    совместимые  с  ними  модели,  снабженные  графическими
     адаптерами CGA  или  EGA.  Все  программы,  приведенные  в данной
     главе,  кроме программ изображения точки, аппаратно-независимы, и
     вы можете с минимальными усилиями  сделать их  рабочими на других
     типах графических машин.

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

                      ВИДЕОРЕЖИМЫ  И  ЦВЕТОВАЯ ПАЛИТРА
     -----------------------------------------------------------------

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

          В  таблице 4-1  приведены различные  видеорежимы,  в которых
     могут работать  компьютеры  IBM PC.  Для  функций,  приведенных в
     этой главе,  требуется 4 видеорежим, предполагающий использование
     цветного графического дисплея  с размерностью экрана 320  на 200.
     Хотя адаптер EGA поддерживает и режимы  с расширенной разрешающей
     способностью дисплея, 4 видеорежим выбран в качестве базового для
     разработки и использования функций графики в связи с тем,  что он
     поддерживается как адаптером EGA, так и CGA. Использование особых
     режимов EGA требует только изменения функций записи точки (смотри
     книгу  по  использованию графики в  EGA  "Advance  Grafics  in C"
     Nelson   Jobson   Osborn/McGrow-Hall,   1987).   Вам   необходимо
     запомнить,  что во всех кодах верхний левый угол имеет координаты
     0,0.

          BIOS-прерывание 16,  функция  0,  устанавливает видеорежим и
     используется в функции mode(),  текст которой приведен  ниже.  Но
     прежде  чем  вы  ознакомитесь  с ней,  предлагаем вам внимательно
     изучить   все   видеорежимы,   поддерживаемые    соответствующими
     адаптерами. Для этого обратитесь к таблице 4-1.

     Таблица 4-1 Режимы терминала для машин IBM PC

     -----------------------------------------------------------------
      Режим             Тип            Размерность  Адаптер
                                         экрана
     -----------------------------------------------------------------

        0    алфавитно-цифровой, ч/б      40х25    CGA, EGA
        1    алфавитно-цифровой, 16 цв.   40х25    CGA, EGA
        2    алфавитно-цифровой, ч/б      80х25    CGA, EGA
        3    алфавитно-цифровой, 16 цв.   80х25    CGA, EGA
        4    графический, 4 цвета       320х200    CGA, EGA
        5    графический, 4 серых тона  320х200    CGA, EGA
        6    графический, ч/б           640х200    CGA, EGA
        7    алфавитно-цифровой, ч/б      80х25   монохромный
        8    графический, 16 цветов     160х200     PCjr
        9    графический, 16 цветов     320х200     PCjr
       10    графический, 4 цвета-PCjr  640х200    PCjr, EGA
                         16 цветов-EGA
       13    графический, 16 цветов     320х200      EGA
       14    графический, 16 цветов     640х200      EGA
       15    графический, 4 цвета       640х350      EGA
     -----------------------------------------------------------------

          А теперь приведем текст функции mode().

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

          /* Установка видеорежима */
          void mode(mode_code)
          int mode_code;
               {
               union REGS r;
               r.h.al = mode_code;
               r.h.ah = 0;
               int86(0x10,&r,&r);
               }

          В 4-ом режиме доступны  две палитры (набора  цветов). Каждая
     палитра   определяет  четыре   цвета,   отображаемые   на  экране
     терминала.  В  IBM PC  палитра  0  определяет  желтый,  зеленый и
     красный   цвета,   палитра  1   определяет   белый,  ярко-красный
     (пурпурный)  и голубой цвета. Для каждой палитры четвертым цветом
     является цвет  фона,  который обычно черный.  BIOS-прерывание 16,
     функция 11, устанавливает палитру.

          Функция  pallet(),  приведенная  ниже,  устанавливает  номер
     палитры, который задается в качестве значения ее аргумента:

          /* Установка палитры */
          void palette(pnum)
          int pnum;
               {
               union REGS r;

               r.h.bh=1;    /* код 4 графического режима */
               r.h.bl=pnum; /* номер палитры */
               r.h.ah=11;   /* устанавливается для вызова палитры */
               int86(0x10,&r,&r);
               }

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

                           ЗАПИСЬ  ТОЧКИ  РАСТРА
     -----------------------------------------------------------------

          Одной из основных программ графики является программа записи
     точки растра - наименьшей адресуемой точки на экране  дисплея.  В
     данном  случае  термин  "точка  растра"  будет использоваться для
     описания наименьшей  адресуемой  точки  в  отдельных  графических
     режимах.  Так  как  функция  записи  точки  растра используется в
     других программах более высокого уровня,  ее эффективность  очень
     важна   для  быстродействия  программ  верхних  уровней,  которые
     непосредственно  реализуют  функции  графики.   На   IBM   PC   и
     совместимых  с  ними  компьютерах  существуют  два способа вывода
     информации  с  использованием  точек   растра.   Первый   способ,
     использующий прерывания ROM-BIOS, является наиболее простым, но и
     наименее быстродействующим (слишком медленным для  наших  целей).
     Вторым  и  более  быстродействующими способом является размещение
     информации непосредственно в видеопамяти  дисплея  (ROM).  Именно
     этот метод и рассматривается ниже.

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

     Работа адаптеров CGA/EGA в графическеом режиме
     ----------------------------------------------

          Адаптер CGA  всегда  располагается  в  видеопамяти по адресу
     8000000h.  Адаптер EGA имеет  аналогичное  расположение  для  тех
     режимов,   которые   совместимы  с  режимами  CGA  (более  полную
     информацию о аппаратных средствах  поддержки  графики  вы  можете
     получить   в   руководстве   "IBM   Technical  Reference").  В  4
     графическом режиме каждый байт содержит информацию о цвете  для 4
     точек  растра (для каждой точки растра по 2 бита). Следовательно,
     для работы с  экраном  размерностью  320  на  200  требуется  16К
     памяти.  Так  как  два  бита  могут  содержать только 4 различных
     значения, в 4 видеорежиме поддерживаются только 4 цвета. Значение
     каждого  двухбитового  блока  определяет  цвет  в  соответствии с
     таблицей, приведенной ниже:

     ---------------------------------------------------
      Значение   Цвет в палитре 0     Цвет в палитре 1
     ---------------------------------------------------
                      фон                   фон
         1          желтый                голубой
         2          красный               пурпурный
         3          зеленый               булый
     ---------------------------------------------------

          Особенность адаптера CGA заключается в том, что четные точки
     растра будут располагаться по адресу B8000000h,  а нечетные -  на
     2000h  (8152  -  в десятичном виде) байтов выше,  т.е.  по адресу
     B8002000h.  Следовательно,  каждая строка точек растра требует 80
     байтов  (40 для четных точек растра,  40 - для нечетных).  Внутри
     каждого  байта  точки  располагаются  слева  направо,   как   они
     появляются на экране терминала.  Это означает, что точка растра с
     номером 0 занимает 6 и 7 биты,  в то время,  как точка  растра  с
     номером 3 - 0 и 1 биты.

          Так как  каждый байт кодирует значение четырех точек растра,
     вы должны сохранять значение трех точек при  изменении  одной  из
     них.  Лучший способ сделать это - создание битовой маски со всеми
     битами,  установленными в 1, кроме тех, которые будут изменяться.
     Значение битовой маски складывается по схеме "И" с действительным
     значением байта,  а затем  полученное  значение  складывается  по
     схеме   "ИЛИ"   с  новым  значением.  Однако  ситуация  несколько
     меняется, если вы хотите сложить по схеме "НЕ-ИЛИ" новое и старое
     значения.  В  этом  случае  вы  просто складываете по схеме "ИЛИ"
     старое значение байта с 0,  а затем складываете по схеме "НЕ-ИЛИ"
     новое  двоичное представление цвета и получаете результат.  Адрес
     требуемого байта определяется путем умножения значения координаты
     X на 40,  а затем добавляется значение координаты Y,  деленное на
     4.  Для того,чтобы определить, находится ли точка растра в четном
     или  нечетном  блоке  памяти,  используется  остаток  от  деления
     значения координаты Х на 2.  Если остаток равен 0,  блок является
     четным  (используется  первый  блок),  в  противном случае - блок
     нечетный (используется второй блок).  Требуемые биты внутри байта
     вычисляются   путем  выполнения  деления  по  модулю  4.  Остаток

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

     определяет номер двухбитового пакета, который содержит информацию
     о  требуемых  точках  растра.  Для установки байта режима цвета и
     битовой маски  используются  операторы  побитового  сдвига.  Хотя
     манипулирование  битами  в функции mempoint() несколько запутано,
     вы,  однако,  без труда разберетесь в ней, если тщательно изучите
     что именно и как она делает.

     /* Запись точки в CGA/EGA */
          void mempoint(x,y,color_code)
          int x,y,color_code;
            {
            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "НЕ-ИЛИ" цвета в случае его изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в памяти
                                                                CGA */
            bit_mask.i=0xFF3F; /* 11111111 00111111 в двоичном коде */
            if (x<0 || x>199 || y<0 || y>319) return;
            xor=color_code & 128; /* проверка, устанавливался ли
                                     режим "НЕ-ИЛИ" */
            color_code=color_code & 127; /* маска старших битов */

            /*  установка битовой маски и битов режима цвета
                в правую позицию */

             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             color_code<<=2*(3-bit_position); /* сдвиг кода цвета
                                        в нужную позицию */
             bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в
                                          нужную позицию */

             /* определение требуемого байта в памяти терминала */
              index=x*40+(y%4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */
              /* запись цвета */
               if (!xor) {  /* режим изменения цвета */
                  t=*(ptr+index) & bit_mask.c[0];
                  *(ptr+index)=t|color_code;
               }
               else {
                  t=*(ptr+index) | (char)0;
                  *(ptr+index)=t & color_code;
               }
            }

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

          Заметим, что тип указателя видеопамяти объявлен как far; это
     необходимо, если  вы  транслируете  в  модели  маленькой  (small)
     памяти.  Вы так же должны заметить, что специальный маркер режима
     записи  XOR,  определенный в функциях ROM-BIOS,  используется и в
     функции mempoint().

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

                            ВЫЧЕРЧИВАНИЕ ЛИНИЙ
     -----------------------------------------------------------------

          Функции вычерчивания линий являются основными подпрограммами
     графики и используются для отображения  линий  в  заданном  цвете
     путем задания ее начальных и конечных координат.  В то время, как
     изображение вертикальных и горизонтальных линий  не  представляет
     особого  труда,  более трудной задачей является создание функций,
     которые рисуют линии  вдоль  диагоналей.  Например,  какие  точки
     составляют  линию,  вычерчиваемую  от точки с координатами 0,0 до
     точки с координатами 80,120?

          Один из подходов к  разработке  функций  вычерчивания  линий
     использует  отношение между смещением по координатам X и Y. Чтобы
     показать этот подход в действии,  проведем линию между точками  с
     координатами  0,0  и  5,10.  Смещение по X равно 5,  а по Y - 10.
     Отношение равно 1/2.  Оно  будет  использоватся  при  определении
     коэффициента зависимости,  по которому должны меняться координаты
     X и Y при изображении линий.  В данном случае это  означает,  что
     приращение  координаты  X  составляет половину величины изменения
     координаты Y.  Начинающий программист часто использует этот метод
     при  разработке  функций  вычерчивания  линий.  Хотя такой подход
     математически верен и прост для  понимания,  для  его  правильной
     работы  и  для того,  чтобы избежать серьезных ошибок округления,
     необходимо  использовать  математические  операции  с  числами  с
     плавающей  точкой.  Это означает,  что функции вычерчивания линий
     будут работать довольно медленно, если в систему не будет включен
     математический  сопроцессор  (например  -  Intel  8087).  По этой
     причине этот метод используется довольно редко.

          Наиболее общий    метод    изображения    линий     включает
     использование  алгоритма  Брезенхама.  Хотя  основой в нем служит
     также отношение между расстояниями по координатам X и Y, в данном
     случае  не  требуется  выполнять  деление  или вычисление чисел с
     плавающей  точкой.  Вместо  этого,  отношение  между   значениями
     координат  X  и  Y  представляется  косвенным образом через серии
     сложений  и  вычитаний.  Основной  идеей  алгоритма   Брезенхама,
     является   регистрация   средних   значений   погрешностей  между
     идеальным положением  каждой  точки  и  той  позицией  на  экране
     дисплея,  в  которой она действительно отображается.  Погрешность
     между идеальным и действительным положением точки возникает ввиду
     ограниченных  возможностей  технических  средств.  Фактически  не
     существует   дисплеев   с    бесконечно    большой    разрешающей
     способностью,  и,  следовательно, действительное положение каждой
     точки на линии требует наилучшей аппроксимации. В каждой итерации
     цикла  вычерчивания  линии вызываются две переменные xerr и yerr,
     которые  увеличиваются  в  зависимости   от   изменения   величин
     координат  X  и  Y  соответственно.  Когда  значение  погрешности
     достигает определенного значения,  оно  вновь  устанавливается  в
     исходное   положение,   а   соответствующий   счетчик   координат
     увеличивается.  Этот процесс продолжается до тех пор,  пока линия
     не будет полностью вычерчена.  Функция line(),  приведенная ниже,
     реализует этот метод.  Вы должны изучать ее до тех пор,  пока  не
     поймете механизма выполнения всех ее операций. Заметим, что в ней

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

     используется  функция   mempoint(),   разработанная   ранее   для
     отображения точки на экране терминала.

      /* Вычерчивание линии заданного цвета с использованием
         алгоритма Брезенхама */
       void line(startx,starty,endx,endy,color)
       int startx,starty,endx,endy,color;
       {
         register int t,distаnce;
         int xerr=0,yerr=0,delta_x,delta_y;
         int incx,incy;

       /* вычисление расстояния в обоих направлениях  */
         delta_x=endx-startx;
         delta_y=endy-starty;

       /* определение направления шага,
          шаг вычисляется либо по вертикальной, либо горизонтальной
          линии   */
          if (delta_x>0) incx=1;
          else  if (delta_x==0) incx=0;
          else  incx= -1;
          if (delta_y>0) incy=1;
          else  if (delta_y==0) incy=0;
          else  incy= -1;

        /* определение какое расстояние больше */
          delta_x=abs(delta_x);
          delta_y=abs(delta_y);
          if (delta_x>delta_y) distance=delta_x;
          else distance=delta_y;

        /* вычерчивание линии */
          for (t=0; t<=distance+1; t++) {
             mempoint(startx,starty,color);
             xerr+=delta_x;
             yerr+=delta_y;
             if (xerr>distance) {
                xerr-=distance;
                startx+=incx;
             }
             if (yerr>distance) {
                yerr-=distance;
                starty+=incy;
             }
          }
       }

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

                 ИЗОБРАЖЕНИЕ И ЗАКРАШИВАНИЕ ПРЯМОУГОЛЬНИКОВ
     -----------------------------------------------------------------

          Если у  вас есть функции вычерчивания линий,  то не составит
     особого  труда  создать  функции  вычерчивания   прямоугольников.
     Пример,  приведенный здесь, вычерчивает прямоугольники в заданном
     цвете путем задания координат двух противоположных углов.

         /*  Вычерчивание прямоугольника  */
        void box(startx,starty,endx,endy,color_code)
        int startx,starty,endx,endy,color_code;
        {
           line(startx,starty,endx,starty,color_code);
           line(startx,starty,startx,endy,color_code);
           line(startx,endy,endx,endy,color_code);
           line(endx,starty,endx,endy,color_code);
        }

          Для того,   чтобы   закрасить    прямоугольник,    требуется
     выполнить запись  в  каждую  точку  растра внутри прямоугольника.
     Программа    fill_box(),    приведенная     ниже,     закрашивает
     прямоугольник,  определенный  координатами  двух  противоположных
     углов,  заданным  цветом.  В  ней  используется  функция  line(),
     задающая цвет внутри прямоугольника.

        /* Закрашивание прямоугольника в заданный цвет */
        void fill_box(startx,starty,endx,endy,color_code)
        int startx,starty,endx,endy,color_code;
        {
           register int i,begin,end;

           begin=startxendx ? startx:endx;

           for (i=begin;i<=end;++i)
              line(i,starty,i,endy,color_code);
         }

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

                          ВЫЧЕРЧИВАНИЕ ОКРУЖНОСТЕЙ
     -----------------------------------------------------------------

          Самым быстрым и  легким  способом  вычерчивания  окружностей
     является способ, основанный на использовании опять таки алгоритма
     Брезенхама,  похожего на одноименный алгоритм вычерчивания линий.
     Данный  метод  также  не  требует  вычислений  чисел  с плавающей
     точкой,  кроме  вычисления  коэффициента   сжатия,   поэтому   он
     обеспечивает  достаточное быстродействие.  По существу,  алгоритм
     основан на приращении координат X и  Y  на  величину  погрешности
     между  ними.  Значение  погрешности содержится в переменой delta.
     Функция  plot_circle()  выполняет  запись  точек  по  окружности.
     Переменная asp_ratio является глобальной,  т.к.  она используется
     как в функции  circle(),  так  и  в  функции  plot_circle().  Эта
     переменная   может   быть  полезна  с  точки  зрения  возможности
     установления ее  значения  вне  функции  circle()  и  дальнейшего
     использования  внутри  функции.  Путем  изменения  значения  этой
     переменной   вы  можете  рисовать  эллипсы.  Параметрами  функции
     circle() является  центр  окружности,  ее  радиус и цвет.  Тексты
     функций приведены ниже.

         double asp_ratio;

        /* Вычерчивание окружности с использованием алгоритма
           Брезенхама */
         void circle(x_center,y_center,radius,color_code)
         int x_center,y_center,radius,color_code;
         {
            register x,y,delta;
            asp_ratio=1.0; /* это число может меняется в различных
                              случаях */
             y=radius;
             delta=3-2*radius;
             for (x=0;x0) incx=1;
          else  if (delta_x==0) incx=0;
          else  incx= -1;

          if (delta_y>0) incy=1;
          else  if (delta_y==0) incy=0;
          else  incy= -1;

        /* Определение какое расстояние больше */
          delta_x=abs(delta_x);
          delta_y=abs(delta_y);
          if (delta_x>delta_y) distance=delta_x;
          else distance=delta_y;

        /* Вычерчивание линии */
          for (t=0; t<=distance+1; t++) {
             mempoint(startx,starty,color);
             xerr+=delta_x;
             yerr+=delta_y;
             if (xerr>distance) {
                xerr-=distance;
                startx+=incx;
             }
             if (yerr>distance) {
                yerr-=distance;
                starty+=incy;
             }
          }
       }

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

        /* Закрашивание прямоугольника  заданным цветом */

        void fill_box(startx,starty,endx,endy,color_code)
        int startx,starty,endx,endy,color_code;
        {
           register int i,begin,end;

           begin=startxendx ? startx:endx;

           for (i=begin;i<=end;++i)
              line(i,starty,i,endy,color_code);
         }

        /* Вычерчивание окружности с использованием алгоритма
           Брезенхама */
         void circle(x_center,y_center,radius,color_code)
         int x_center,y_center,radius,color_code;
         {
            register x,y,delta;
            asp_ratio=1.0; /* это число может меняется в различных
                              случаях */
             y=radius;
             delta=3-2*radius;
             for (x=0;x199 || y<0 || y>319) return;
            xor=color_code & 128;      /* проверка, устанавливался ли
                                          режим "исключающего ИЛИ" */
            color_code=color_code & 127;     /* маска старших битов */

            /*  Установка маски битов и битов режима цвета
                в правую позицию */

             bit_position=y%4; /* вычисление нужной позиции в байте */
             color_code<<=2*(3-bit_position); /* сдвиг кода цвета
                                        в нужную позицию */
             bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в
                                          нужную позицию */

             /* определение требуемого байта в памяти терминала */

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

              index=x*40+(y%4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* Запись цвета */
               if (!xor) {  /* режим изменения цвета */
                  t=*(ptr+index) & bit_mask.c[0];
                  *(ptr+index)=t|color_code;
               }
               else {
                  t=*(ptr+index) | (char)0;
                  *(ptr+index)=t & color_code;
               }
            }

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

               СОХРАНЕНИЕ И ЗАГРУЗКА ГРАФИЧЕСКИХ ИЗОБРАЖЕНИЙ
     -----------------------------------------------------------------

          Сохранение и   загрузка   графических  изображений  является
     довольно  простым  делом,  т.к.  образ  изображений  находится  в
     видеопамяти дисплея, а ее содержимое легко копировать на дисковый
     файл.   Главной   проблемой   является   необходимость   введения
     пользователем  имени  файла,  ввиду  того,  что  запрос о вводе и
     введенное имя файла сотрут часть информации на экране.  Для того,
     чтобы   избежать   этого,   разработаны   функции   save_pic()  и
     load_pic(),  тексты которых приводятся в данном  разделе.  Первая
     функция  сохраняет  14  верхних  строк  изображения,  чистит  эту
     область,  запрашивает имя файла и,  после  того,  как  оно  будет
     введено, восстанавливает изображение.

        /* сохранение  графического изображения */
        void save_pic()
        {
          char fname[80];
          FILE *fp;
          register int i,j;
          char far *ptr=(char far *) 0xB8000000; /* точка в
                                                 памяти   CGA  */
          char far *temp;
          unsigned char buf[14][80]; /* содержит образ экрана */

          temp=ptr;
        /* сохранение верхних строк текущего содержимого экрана */
          for (i=0;i<14;++i)
             for (j=0;j<80;++j) {
                buf[i][j]=*temp; /* четный байт */
                buf[i][j+1]=*(temp+8152); /* нечетный байт */
                *temp=0; *(temp+8152)=0; /* чистка позиций экрана */
                temp++;
             }
           goto_xy(0,0);
           printf("Имя файла:");
           gets(fname);
           if (!(fp=fopen(fname,"wb"))) {
               printf("Фвайл не может быть открыт\n");
               return;
           }

           temp=ptr;
       /* восстановление содержимого экрана */
          for (i=0;i<14;++i)
             for (j=0;j<80;++j) {
                *temp= buf[i][j]; /* четный байт */
                *(temp+8125)=buf[i][j+1]; /* нечетный байт */
                *temp=0; *(temp+8152)=0; /* чистка позиций экрана */
                temp++;
             }
        /* копирование изображения в файл  */

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

           for (i=0;i<8152;i++) {
              putc(*ptr,fp); /* четный байт */
              putc(*(ptr+8125),fp); /* нечетный байт */
              ptr++;
           }

           fclose(fp);
        }

       /* загрузка изображения */
         void load_pic()
         {
            char fname[80];
            FILE *fp;
            register int i,j;

            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                   памяти   CGA  */
            char far *temp;
            unsigned char buf[14][80]; /* содержит образ экрана */

            temp=ptr;
        /* сохранение верхних строк текущего содержимого экрана */
            for (i=0;i<14;++i)
               for (j=0;j<80;j+=2) {
                  buf[i][j]=*temp;
                  buf[i][j+1]=*(temp+8152);
                  *temp=0; *(temp+8152)=0; /* чистка позиций экрана */
                  temp++;
               }
            goto_xy(0,0);
            printf("Имя файла:");
            gets(fname);
            if (!(fp=fopen(fname,"rb"))) {
               goto_xy(0,0);
               printf("Файл не может быть открыт\n");
               temp=ptr;
       /* восстановление содержимого экрана */
               for (i=0;i<14;++i)
                  for (j=0;j<80;j+=2) {
                     *temp= buf[i][j];
                     *(temp+8125)=buf[i][j+1];
                     temp++;
                  }
               return;
             }
      /* загрузка изображения из файла */
           for (i=0;i<8152;i++) {
              *ptr=getc(fp); /* четный байт */
              *(ptr+8125)=getc(fp); /* нечетный байт */
              ptr++;
           }

           fclose(fp);

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

        }

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

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

                         ДУБЛИРОВАНИЕ ЧАСТИ ЭКРАНА
     -----------------------------------------------------------------

          Иногда бывает  полезным  скопировать  часть  экрана в другую
     область.  Это легко выполнить  используя  функцию  copy(),  текст
     которой приводится ниже.

          /* копирование части экрана в другую область */
          void copy(startx,starty,endx,endy,x,y)
          int startx,starty;      /* верхняя левая координата */
          int endx,endy;          /* нижняя правая координата области
                                     копирования */
          int x,y;      /* верхняя левая координата области,
                           куда будет проводится копирование */
          {
             int i,j;
             unsigned char c;

             for (;startx                               ¦
     ¦  индекс        индекс                                         ¦
     ¦                                                               ¦
     ¦    ¦             0           1            2           3       ¦
     ¦    ¦                                                          ¦
     ¦    ¦                                                          ¦
     ¦    V                                                          ¦
     ¦                                                               ¦
     ¦    0          start_X1     start_Y1     end_X1     end_Y1     ¦
     ¦                                                               ¦
     ¦    1          start_X2     start_Y2     end_X2     end_Y2     ¦
     ¦                                                               ¦
     ¦    2          start_X3     start_Y3     end_X3      end_Y3    ¦
     ¦                                                               ¦
     ¦    3          start_X4     start_Y4     end_X4      end_Y4    ¦
     ¦    .             .                                            ¦
     ¦    .             .                                            ¦
     ¦    .             .                                            ¦
     ¦    n          start_Xn     start_Yn     end_Xn      end_Yn    ¦
     L----------------------------------------------------------------

     Рис. 4-3. Условная организация массива.

          Определить объект  -  это  значит   разместить   в   массиве
     координаты  начальных  и  конечных  точек отрезков,  составляющих
     объект.  Например,  если объект представляет собой  прямоугольник
     вида:

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

                         0.0------------------¬0.10
                            ¦                 ¦
                            ¦                 ¦
                            ¦                 ¦
                            ¦                 ¦
                        10.0L------------------10.10

     то в  массив,  определяющий   данный   прямоугольник,   заносятся
     следующие числа:

          object[0][0] = 0; object[0][1] = 0;
          object[0][0] = 0; object[0][3] = 10;

          object[1][0] = 0; object[1][1] = 10;
          object[1][0] = 10; object[1][3] = 10;

          object[2][0] = 10; object[2][1] = 10;
          object[2][0] = 10; object[2][3] = 0;

          object[3][0] = 10; object[3][1] = 0;
          object[3][0] = 0; object[3][3] = 0;

          После того,  как  объект  определен,  вы можете вращать его,
     используя функцию rotate_object(),  приведенную ниже,  по часовой
     стрелке  (клавиша  )  или  в  противоположную сторону (клавиша
     ).

          /* Вращение заданных объектов */

          void rotate_object(ob, theta, x, y, sides)
          double ob[][4]; /* описание объекта */
          double theta;   /* угол поворота в радианах */
          int x, y;
          int sides;
            {
            register int i, j;
            double tempx, tempy;
            char ch;

            for(;;)
              {
              ch = getch(); /* ввод признака направления вращения */
              switch(tolower(ch))
                {
                case 'l': /* вращение против часовой стрелки */
                  theta = theta < 0 ? -theta : theta;
                  break;
                case 'r': /* вращение по часовой стрелке  */
                  theta = theta > 0 ? -theta : theta;
                  break;
                default: return;
                }
              for(j=0; j<=sides; j++) /* стирание старых линий  */

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

                {
                line((int) ob[j][0], (int) ob[j][1],
                     (int) ob[j][2], (int) ob[j][3], 0);
                rotate_point(theta, &ob[j][0],
                             &ob[j][1], x, y);
                rotate_point(theta, &ob[j][2], &ob[j][3], x, y);
                line((int) ob[j][0], (int) ob[j][1],
                     (int) ob[j][2], (int) ob[j][3], 2);
                }
              }
            }

          Как показано  в описании параметров функции rotate_object(),
     вращение осуществляется вокруг центра, заданного координатами X и
     Y, на угол, величина которого задана параметром theta в радианах.
     Минимальное значение параметра theta равно 0.01  радиан. Заметим,
     что  объект  сначала  стирается  из старой области размещения,  а
     затем перерисовывается вновь.  Если это  условие  не  может  быть
     выполнено,  то  экран  окрашивается  в голубой цвет.  Необходимым
     условием   выполнения    программы    rotate_object()    является
     обязательное задание параметра sides.

          Приведенная ниже функция display_object() не имеет отношения
     к вращению объектов,  но она может  быть  полезна  при  работе  с
     объектами.  Она рисует на экране объекты,  определенные в массиве
     ob.

          /* отображение объекта на экране */
          void display_object(ob, sides)
          double ob[][4];
          int sides;
          {
            register int i;

            for(i=0; i0)
              incx=1;
            else
              if(delta_x==0)
                incx=0;
              else
                incx= -1;
            if(delta_y>0)
              incy=1;
            else
              if(delta_y==0)
                incy=0;
              else
                incy= -1;
            delta_x=abs(delta_x);
            delta_y=abs(delta_y);

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

            if(delta_x>delta_y)
              distance=delta_x;
            else
              distance=delta_y;

            /* рисунок отрезка */
            for(t=0; t<=distance; t++)
              {
              mempoint(start_x, start_y, color);
              x+=delta_x;
              y+=delta_y;
              if(x>distance)
                {
                x-=distance;
                start_x+=incx;
                }
              if(y>distance)
                {
                y-=distance;
                start_y+=incy;
                }
              }
            }

        /* запись точки в CGA/EGA */

         void mempoint(x,y,color_code)
         int x,y,color_code;
         {
            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "НЕ-ИЛИ" цвета в случае его
                          изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                  памяти CGA */
            bit_mask.i=0xFF3F; /* 11111111 00111111 в
                                  двоичном виде */
            if (x<0 || x>199 || y<0 || y>319) return;
            xor=color_code & 128; /* проверка, устанавливался ли
                                     режим "НЕ-ИЛИ" */
            color_code=color_code & 127; /* маска старших битов */

            /*  установка битовой маски и битов режима цвета
                в правую позицию */

             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             color_code<<=2*(3-bit_position); /* сдвиг кода цвета
                                        в нужную позицию */
             bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

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

                                          нужную позицию */

             /* определение требуемого байта в памяти терминала */

              index=x*40+(y%4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* запись цвета */

               if (!xor) {  /* режим изменения цвета */
                  t=*(ptr+index) & bit_mask.c[0];
                  *(ptr+index)=t|color_code;
               }
               else {
                  t=*(ptr+index) | (char)0;
                  *(ptr+index)=t & color_code;
               }
            }

        /* вращение точки вокруг центра с координатами
           в x_org и y_org, на угол theta */

          void rotate_point(theta,x,y,x_org,y_org)
          double theta,*x,*y;
          int x_org,y_org;
          {
             double tx,ty;

        /* нормализация X и Y к начальному адресу */
             tx=*x-x_org;
             ty=*y-y_org;

        /* вращение */
              *x=tx*cos(theta)-ty*sin(theta);
              *y=tx*sin(theta)-ty*cos(theta);

         /* возвращение значений координат */
              *x+=x_org;
              *y+=y_org;
           }

          /* Вращение заданных объектов */

          void rotate_object(ob, theta, x, y, sides)
          double ob[][4]; /* описание объекта */
          double theta;   /* угол поворота в радианах */
          int x, y;
          int sides;
            {
            register int i, j;
            double tempx, tempy;

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

            char ch;

            for(;;)
              {
              ch = getch(); /* ввод признака направления вращения */
              switch(tolower(ch))
                {
                case 'l': /* вращение против часовой стрелки */
                  theta = theta < 0 ? -theta : theta;
                  break;
                case 'r': /* вращение по часовой стрелке  */
                  theta = theta > 0 ? -theta : theta;
                  break;
                default: return;
                }
              for(j=0; j<=sides; j++) /* стирание старых линий  */
                {
                line((int) ob[j][0], (int) ob[j][1],
                     (int) ob[j][2], (int) ob[j][3], 0);
                rotate_point(theta, &ob[j][0],
                             &ob[j][1], x, y);
                rotate_point(theta, &ob[j][2], &ob[j][3], x, y);
                line((int) ob[j][0], (int) ob[j][1],
                     (int) ob[j][2], (int) ob[j][3], 2);
                }
              }
            }

          /* отображение объекта на экране */

          void display_object(ob, sides)
          double ob[][4];
          int sides;
            {
            register int i;

            for(i=0; i (),  <Страница вверх>
     (),  <Страница  вниз>  ()  и <Кон> () перемещают
     графический курсор в указанном  направлении  и  под  углом  в  45
     градусов.

          Для анализа  кодов операций чтения в программах используется
     функция bioskey().  Порядок подключения этой функции к  программе

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

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

          При изображении  линий,  прямоугольников  и  окружностей вам
     необходимо определить координаты двух точек.  Для прямоугольников
     -  это координаты двух противоположных углов.  Для линий задается
     начальная и конечная точки,  а для окружности - координаты центра
     и точки, через которую она будет проходить.

          Процесс выбора  этих точек выполняется путем нажатия клавиши
     <ВВОД> в момент,  когда графический курсор находится в  требуемой
     области.   Например,   для   изображения   линии  вы  перемещаете
     графический курсор в точку, где она должна начинаться и нажимаете
     клавишу  <ВВОД>  .  Затем  вы устанавливаете курсор в точку,  где
     линия заканчивается,  и  нажимаете  <ВВОД>  снова.  При   нажатии
     клавиши  <ВВОД>  выполняется загрузка переменных startx,  starty,
     endx и endy,  которые потом используются  в  качестве  параметров
     вызываемых  функций.  После  того,  как  координаты  точек  будут
     определены,  при нажатии клавиши <В> рисуется квадрат,  а    -
     квадрат закрашивается,   при  нажатии    рисуется  линия,  при
     нажатии   <С>   рисуется   окружность,   а   <Н>   -   окружность
     закрашивается.

          Для копирования  или  перемещения  части  экрана  вы  должны
     определить верхний левый и нижний правый углы области, которую вы
     хотите   переместить   (нажатием   клавиши  <ВВОД>  ).  Затем  вы
     перемещаете курсор в верхний левый угол области,  куда вы  хотите
     переместить  изображение.  Для  пересылки  изображения  требуется
     нажать клавишу <М>,  а для  копирования  -  <Х>.  Запомните,  что
     старое  изображение  в области,  куда осуществляется копирование,
     будет уничтожено.

          Для вращения объекта вам необходимо определить  сам  объект,
     путем нажатия клавиши .  Затем,  используя клавишу <ВВОД>,  вы
     должны определить  начальные  и  конечные  координаты  точек  для
     отрезков по периметру выбранного объекта.  Процесс выбора объекта
     вращения  и   определения   его   границ   реализуется   функцией
     define_object().   Вращение   объекта  начинается  после  нажатия
     клавиши <А>.  Для определения направления  вращения  используются
     клавиши     (по  часовой  стрелке)  или    (против  часовой
     стрелки).  Остановить  процесс  вращения  можно  нажатием   любой
     клавиши, кроме  или <А>.

          Для остановки работы программы используется клавиша . При
     желании вы можете включить  в  программу  функции  для  работы  с
     "мышью". Пример выходных данных программы показан на рисунке 4-5.

     _________________________________________________________________

     Рисунок 4-5 на стр. 163 не может быть воспроизведен имеющимися
     средствами. (Ред. пер. И.Бычковский)

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

     _________________________________________________________________

     Рис. 4-5. Простейшие результаты работы программы рисования.

          А теперь приведем всю программу рисования целиком.

     /* Программа    для    CGA/EGA,   позволяющая   рисовать   линии,
     прямоугольники и  окружности.  Вы  можете  нарисовать  какой-либо
     объект  и  вращать его по часовой или против часовой стрелки.  Вы
     так же  можете  копировать  графическое  изображение  на  диск  и
     загружать его с диска. */

          #define NUM_SIDES 20 /* число сторон объекта;
                                  при необходимости увеличивается */
          #include "dos.h"
          #include "stdio.h"
          #include "math.h"

          void mode(), line(), box(), fill_box();
          void mempoint(), palette(), xhairs();
          void circle(), plot_circle(), fill_circle();
          void rotate_point(), rotate_object(), goto_xy();
          void display_object(), copy(), move();
          void save_pic(), load_pic();
          unsigned char read_point();

          /*  Этот массив содержит динамически меняющиеся
              координаты объекта.
          */
          double object[NUM_SIDES][4];
          double asp_ratio; /* содержит коэффициент сжатия для
                               окружностей */

          main()
            {
            union k{
              char c[2];
              int i;
               } key ;

            int x=10, y=10; /* текущая позиция экрана */
            int cc=2;   /* текущий цвет */
            int on_flag=1; /* признак использования карандаша */
            int pal_num=1; /* номер палитры */

            int startx=0, starty=0, endx=0, endy=0;
            int first_point=1;
            int inc=1; /* шаг пересылки */
            int sides=0; /* количество сторoн выбранного объекта */
            int i;

            mode(4); /* переключатель режима CGA/EGA */

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

            palette(0); /* палитра 0 */

            xhairs(x, y); /* указатель курсора */
             do
              {
              key.i = bioskey(0);
              xhairs(x, y);
              if(!key.c[0]) switch(key.c[1])
                {
                case 75: /* влево */
                  if(on_flag) line(x, y, x, y-inc, cc);
                  y -= inc;
                  break;
                case 77: /* вправо */
                  if(on_flag) line(x, y, x, y+inc, cc);
                  y += inc;
                  break;
                case 72: /* вверх */
                    if(on_flag) line(x, y, x-inc, y, cc);
                  x -= inc;
                  break;
                case 80: /* вниз */
                    if(on_flag) line(x, y, x+inc, y, cc);
                  x += inc;
                  break;
                case 71: /* вверх и влево */
                  if(on_flag) line(x, y, x-inc, y-inc, cc);
                  x -= inc;
                  y -= inc;
                  break;
                case 73: /* вверх и вправо */
                  if(on_flag) line(x, y, x-inc, y+inc, cc);
                  x -= inc;
                  y += inc;
                  break;
                case 79: /* вниз и влево */
                  if(on_flag) line(x, y, x+inc, y-inc, cc);
                  x += inc;
                  y -= inc;
                  break;
                case 81: /* вниз и вправо */
                  if(on_flag) line(x, y, x+inc, y+inc, cc);
                  x += inc;
                  y += inc;
                  break;
                case 59: /* F1 - медленно */
                  inc=1;
                  break;
                case 60: /* F2 - быстро   */
                  inc=5;
                  break;
                }
              else switch(tolower(key.c[0]))
                {

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

                case 'o': /* переключение шаблона */
                  on_flag = !on_flag;
                  break;
                case '1': cc=1; /* цвет 1 */
                  break;
                case '2': cc=2; /* цвет 2 */
                  break;
                 case '3': cc=3; /* цвет 3 */
                  break;
                case '0': cc=0; /* цвет 0 */
                  break;
                case 'b':
                  box(startx, starty, endx, endy, cc);      break;
                case 'f':
                  fill_box(startx, starty, endx, endy, cc); break;
                case 'l':
                  line(startx, starty, endx, endy, cc);     break;
                case 'c':
                  circle(startx, starty, endy-starty, cc);  break;
                case 'h':
                  fill_circle(startx, starty, endy-starty, cc); break;
                case 's':
                  save_pic();                               break;
                case 'r':
                  load_pic();                               break;
                case 'm': /* пересылка фрагмента */
                  move(startx, starty, endx, endy, x, y);   break;
                case 'x': /* копирование фрагмента */
                  copy(startx, starty, endx, endy, x, y);   break;
                case 'd': /* определить объект вращения */
                  sides = define_objekt(object, x, y);      break;
                case 'a': /* вращение объекта */
                  rotate_objekt(object, 0.05, x, y, sides); break;
                case '\r': /* набор конечных точек для линий, кругов
                              или прямоугольников */
                  if(first_point)
                    { startx = x, starty = y; }
                  else
                    { endx = x, endy = y; }
                  first_point = !first_point;               break;
                case 'p':
                  pal_num = pal_num==1 ? 2:1;
                  palette(pal_num);
                }
              xhairs(x, y);
              }
            while (key.c[0]!='q');
            getchar();
            mode(2);
            }

          /* установка палитры */
          void palette(pnum)
          int pnum;

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

            {
            union REGS r;
            r.h.bh = 1; /* код 4 режима графики */
            r.h.bl = pnum;
            r.h.ah = 11; /* установка палитры */
            int86(0x10, &r, &r);
            }

          /* установка видео-режима */
          void mode (mode_code)
          int mode_code;
            {
            union REGS r;

            r.h.al = mode_code;
            r.h.ah = 0;
            int86(0x10,&r, &r);
            }

          /* изображение прямоугольника */
          void box(sx, sy, ex, ey, c)
          int sx, sy, ex, ey, c;
            {
            line(sx, sy, ex, sy, c);
            line(sx, sy, sx, ey, c);
            line(sx, ey, ex, ey, c);
            line(ex, sy, ex, ey, c);
            }

      /* изображение линии заданного цвета с использованием
         алгоритма Брезенхама */
       void line(startx,starty,endx,endy,color)
       int startx,starty,endx,endy,color;
       {
         register int t,distance;
         int xerr=0,yerr=0,delta_x,delta_y;
         int incx,incy;

       /* вычисление расстояния в обоих направлениях         */
         delta_x=endx-startx;
         delta_y=endy-starty;

       /* определение направления шага,
          шаг вычисляется либо по вертикальной, либо горизонтальной
          линии                   */
          if (delta_x>0) incx=1;
          else  if (delta_x==0) incx=0;
          else  incx= -1;

          if (delta_y>0) incy=1;
          else  if (delta_y==0) incy=0;
          else  incy= -1;

        /* определение какое расстояние больше */

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

          delta_x=abs(delta_x);
          delta_y=abs(delta_y);
          if (delta_x>delta_y) distance=delta_x;
          else distance=delta_y;

        /* изображение линии */
          for (t=0; t<=distance+1; t++) {
             mempoint(startx,starty,color);
             xerr+=delta_x;
             yerr+=delta_y;
             if (xerr>distance) {
                xerr-=distance;
                startx+=incx;
             }
             if (yerr>distance) {
                yerr-=distance;
                starty+=incy;
             }
          }
       }

        /* закрашивание прямоугольника в заданный цвет */

        void fill_box(startx,starty,endx,endy,color_code)
        int startx,starty,endx,endy,color_code;
        {
           register int i,begin,end;

           begin=startxendx ? startx:endx;

           for (i=begin;i<=end;++i)
              line(i,starty,i,endy,color_code);
         }

        /* изображение   окружности   с    использованием    алгоритма
           Брезенхама */

         void circle(x_center,y_center,radius,color_code)
         int x_center,y_center,radius,color_code;
         {
            register x,y,delta;
            asp_ratio=1.0; /* это число меняется в различных
                              случаях */
             y=radius;
             delta=3-2*radius;
             for (x=0;x199 || y<0 || y>319) return;
            xor=color_code & 128; /* проверка, устанавливался ли
                                     режим "исключающего ИЛИ" */
            color_code=color_code & 127; /* маска старших битов */

            /*  установка битовой маски и битов режима цвета
                в правую позицию */

             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             color_code<<=2*(3-bit_position); /* сдвиг кода цвета
                                        в нужную позицию */
             bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в
                                          нужную позицию */

             /* определение требуемого байта в памяти терминала */

              index=x*40+(y%4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* запись цвета */

               if (!xor) {  /* режим изменения цвета */
                  t=*(ptr+index) & bit_mask.c[0];
                  *(ptr+index)=t|color_code;
               }
               else {
                  t=*(ptr+index) | (char)0;
                  *(ptr+index)=t & color_code;
               }
            }

        /* отображение графического курсора */
         void xhairs(x,y)
         int x,y;
         {
            line(x-4,y,x+3,y,1|128);
            line(x,y+4,x,y-3,1|128);

          }

        /* чтение байта из оперативной памяти CGA/EGA */
         unsigned char read_point(x,y)
         int x,y;
         {

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

            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "исключающее ИЛИ" цвета в случае его
                          изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                  памяти CGA */
            bit_mask.i=3; /* 11111111 00111111 в
                                  двоичном виде */
            if (x<0 || x>199 || y<0 || y>319) return 0;

            /*  установка битовой маски и битов режима цвета
                в правую позицию */
             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             bit_mask.i<<=2*(3-bit_position);

             /* определение требуемого байта в памяти терминала */
              index=x*40+(y>>4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* запись цвета */
               t=*(ptr+index) & bit_mask.c[0];
               t>>=2*(3-bit_position);
               return t;
            }

       /* сохранение  графического изображения  */

        void save_pic()
        {
          char fname[80];
          FILE *fp;
          register int i,j;
          char far *ptr=(char far *) 0xB8000000; /* точка в
                                                 памяти   CGA  */
          char far *temp;
          unsigned char buf[14][80]; /* содержит образ экрана */

          temp=ptr;
        /* сохранение верхних строк текущего содержимого экрана */
          for (i=0;i<14;++i)
             for (j=0;j<80;++j) {
                buf[i][j]=*temp; /* четный байт */
                buf[i][j+1]=*(temp+8152); /* нечетный байт */
                *temp=0; *(temp+8152)=0; /* чистка позиций экрана */
                temp++;
             }
           goto_xy(0,0);
           printf("Имя файла:");

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

           gets(fname);
           if (!(fp=fopen(fname,"wb"))) {
               printf("Файл не может быть открыт\n");
               return;
           }

           temp=ptr;
       /* восстановление содержимого экрана */
          for (i=0;i<14;++i)
             for (j=0;j<80;++j) {
                *temp= buf[i][j]; /* четный байт */
                *(temp+8125)=buf[i][j+1]; /* нечетный байт */
                *temp=0; *(temp+8152)=0; /* чистка позиций экрана */
                temp++;
             }
        /* копирование изображения в файл  */
           for (i=0;i<8152;i++) {
              putc(*ptr,fp); /* четный байт */
              putc(*(ptr+8125),fp); /* нечетный байт */
              ptr++;
           }

           fclose(fp);
        }

       /* загрузка изображения */

         void load_pic()
         {
            char fname[80];
            FILE *fp;
            register int i,j;

            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                   памяти   CGA  */
            char far *temp;
            unsigned char buf[14][80]; /* содержит образ экрана */

            temp=ptr;
        /* сохранение верхних строк текущего содержимого экрана */
            for (i=0;i<14;++i)
               for (j=0;j<80;j+=2) {
                  buf[i][j]=*temp;
                  buf[i][j+1]=*(temp+8152);
                  *temp=0; *(temp+8152)=0; /* чистка позиций экрана */
                  temp++;
               }
            goto_xy(0,0);
            printf("Имя файла:");
            gets(fname);
            if (!(fp=fopen(fname,"rb"))) {
               goto_xy(0,0);
               printf("Файл не может быть открыт\n");

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

               temp=ptr;
       /* восстановление содержимого экрана */
               for (i=0;i<14;++i)
                  for (j=0;j<80;j+=2) {
                     *temp= buf[i][j];
                     *(temp+8125)=buf[i][j+1];
                     temp++;
                  }
               return;
             }
      /* загрузка изображения из файла */
           for (i=0;i<8152;i++) {
              *ptr=getc(fp); /* четный байт */
              *(ptr+8125)=getc(fp); /* нечетный байт */
              ptr++;
           }

           fclose(fp);
        }

       /*  поместить курсор в заданное положение */
           void goto_xy(x,y)
           int x,y;
           {
              r.h.ah=2; /* адресация курсора */
              r.h.dl=y; /* координата столбца */
              r.h.dh=x; /* координата строки */
              r.h.bh=0; /* видеостраница */
              int86(0x10,&r,&r);
            }

       /* копирование части экрана в другую область */

          void copy(startx,starty,endx,endy,x,y)
          int startx,starty; /* верхняя левая координата */
          int endx,endy; /* нижняя правая координата области
                            копирования */
          int x,y; /* верхняя левая координата области,
                     куда будет проводится копирование */
          {
             int i,j;
             unsigned char c;

             for (;startx 0 ? -theta : theta;
                  break;
                default: return;
                }
              for(j=0; j<=sides; j++) /* стирание старых линий  */
                {
                line((int) ob[j][0], (int) ob[j][1],
                     (int) ob[j][2], (int) ob[j][3], 0);
                rotate_point(theta, &ob[j][0], &ob[j][1], x, y);
                rotate_point(theta, &ob[j][2], &ob[j][3], x, y);
                line((int) ob[j][0], (int) ob[j][1],
                     (int) ob[j][2], (int) ob[j][3], 2);
                }
              }
            }

          /* отображение объекта на экране */
          void display_object(ob, sides)
          double ob[][4];
          int sides;
            {
            register int i;

            for(i=0; i


                                ГЛАВА 5.                       -- 1 --
                                --------

                               ВИДЕОИГРЫ
     -----------------------------------------------------------------
           Видеоигры приносят вам либо радость,  либо  огорчения.  Это
     зависит  от  вашего  отношения  к  ним.  Однако  программирование
     видеоигр не только интересно,  но и полезно. Фактически, одним из
     лучших  способов обогащения является разработка удачных видеоигр.
     Хорошая игра,  объединяющая логику  с  живой  графикой,  доставит
     большое  удовольствие  игроку.  Лучшие  из  видеоигр,  включающие
     элементы искусственного интеллекта,  позволяют  компьютеру  вести
     диалог с игроком и "осмысленно" реагировать на ввод данных.
           В этой главе вы ознакомитесь  с некоторыми основами техники
     программирования видеоигр,   что   позволит   вам   разрабатывать
     собственные  игры.  Вы  научитесь "оживлять" различные объекты на
     экране вашего  терминала.  Разработка  видеоигр  явится  для  вас
     отправной  точкой.  Многие принципы,  используемые при разработке
     видеоигр,  будут полезны для вас и увеличат ваш интерес к работе.
     Для использования программ,  приводимых в качестве примера в этой
     главе,  необходим компьютер IBM PC или другой, совместимый с ним,
     в  состав  которого входят адаптеры CGA,  EGA или VGA.  Многие из
     функций,  используемых в данной главе, рассматривались в главе 4.
     Поэтому,  если вы еще не изучили главу 4, то вам придется сделать
     это сейчас.

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

                                  СПРАЙТЫ
     -----------------------------------------------------------------

          Многие видеоигры,   в  которых  игрок  управляет  объектами,
     атакующими   другие   объекты,   управляемые    программой    или
     защищающимися  от  них,  включают  два  класса активных объектов:
     среду  (представляющую  для  нас  маломеняющееся  поле  игры)   и
     спрайты.   СПРАЙТ  -  это  небольшой  подвижный  объект,  который
     движется по полю видеоигры по определенным  правилам  с  заданной
     целью.  Например,  когда  космический  корабль стреляет фотонными
     торпедами,  изображение торпеды реализуется  спрайтом.  В  рамках
     данной  главы  под  спрайтом будем понимать фигуру,  определенную
     некоторыми замкнутыми отрезками (многоугольник).  Хотя,  в  общем
     случае спрайт может изображаться любым образом,  например, в виде
     окружности.  В  примерах,   рассматриваемых   в   данной   главе,
     определять  спрайт  будем  в виде двумерного массива целых чисел.
     Например,  спрайт,  состоящий из 4  отрезков  может  быть  описан
     следующим массивом

                             int sprite [4][4];

          Первая размерность  массива  определяет  количество отрезков
     спрайта,  а вторая - координаты конечных точек отрезков (подобный
     способ  описания   объектов  подробно  рассмотрен  в   главе  4).
     Начальные и конечные  координаты  отрезков  задаются  в следующей
     последовательности:

                       start_x, start_y, end_x, end_y

          Отрезок,  входящий  в спрайт,  с координатами конечных точек
     0,0 и 0,10 может быть описан следующим массивом:

               sprite[0][0] = 0;   /* start_x */
               sprite[0][1] = 0;   /* start_y */
               sprite[0][2] = 0;   /* end_x */
               sprite[0][3] = 10;  /* end_y */

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

                                 ПОЛЕ ИГРЫ
     -----------------------------------------------------------------

          В большинстве  видеоигр   поле   игры   представляет   собой
     неменяющееся   (медленно   меняющееся)  изображение,  на  котором
     происходит  действие  игры.  Поле  игры  изображается  отдельными
     программами,    загружаемыми   в   начале   игры,   поэтому   нет
     необходимости  загружать  все  программы  верхнего   уровня   для
     динамической  генерации  игрового  поля.  Такой  подход  описан в
     данной главе.  Программы, используемые для генерации изображений,
     их хранения в файле на диске и доступа к ним,  описаны в главе 4.
     Они загружают эти изображения по мере необходимости.

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

                          МУЛЬТИПЛИКАЦИЯ НА ЭКРАНЕ
     -----------------------------------------------------------------

          Ключевым и наиболее впечатляющим моментом видеоигры является
     мультипликация.  Мультипликация - основной отличительный  признак
     видеоигр.   Основной   метод   мультипликации  прост:  уничтожить
     изображение  предмета  и  создать  его  вновь,  но  с   некоторым
     небольшим  смещением.  Скорость  этого процесса должна быть очень
     высокой.  Это  может  быть  обеспечено  путем   непосредственного
     доступа  к  видеопамяти  дисплея,  возможность которого описана в
     главе 4.

          Для повышения качества изображения,  быстродействия операций
     уничтожения   и   повторного  изображения   объекта  используется
     операция  "НЕ-ИЛИ"  для двоичного кода  каждой  точки  объекта на
     экране. Этот способ обеспечивает возможность быстрого перемещения
     спрайта по экрану,  не меняя его цвет и размеры, и фактически  не
     уничтожая в памяти терминала данные о его изображении.

          Программа,   отображающая   на   экране   терминала  спрайт,
     представляет собой некоторую модификацию функции display_object()
     из главы 4.

          /* отображение объекта на экране */

          void display_object(ob, sides,cc)
          double ob[][4];
          int sides,cc;
          {
            register int i;

            for(i=0; i0) incx=1;
          else  if (delta_x==0) incx=0;
          else  incx= -1;

          if (delta_y>0) incy=1;
          else  if (delta_y==0) incy=0;
          else  incy= -1;

        /* определение какое расстояние больше */
          delta_x=abs(delta_x);
          delta_y=abs(delta_y);
          if (delta_x>delta_y) distance=delta_x;
          else distance=delta_y;

        /* Изображение линии */
          for (t=0; t<=distance+1; t++) {
             mempoint(startx,starty,color);
             x+=delta_x;
             y+=delta_y;
             if (x>distance) {
                x-=distance;
                startx+=incx;
             }
             if (y>distance) {
                y-=distance;
                starty+=incy;
             }
          }
       }

        /* Запись точки в CGA/EGA */
         void mempoint(x,y,color_code)
         int x,y,color_code;
         {
            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "исключающее ИЛИ" цвета в случае его
                          изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                  памяти CGA */
            bit_mask.i=0xFF3F; /* 11111111 00111111 в
                                  двоичном виде */
            if (x<0 || x>199 || y<0 || y>319) return;
            xor=color_code & 128; /* проверка, устанавливался ли
                                     режим "исключающего ИЛИ" */
            color_code=color_code & 127; /* маска старших битов */

            /*  установка битовой маски и битов режима цвета
                в правую позицию */

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

             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             color_code<<=2*(3-bit_position); /* сдвиг кода цвета
                                        в нужную позицию */
             bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в
                                          нужную позицию */

             /* определение требуемого байта в памяти терминала */

              index=x*40+(y%4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* запись цвета */

               if (!xor) {  /* режим изменения цвета */
                  t=*(ptr+index) & bit_mask.c[0];
                  *(ptr+index)=t|color_code;
               }
               else {
                  t=*(ptr+index) | (char)0;
                  *(ptr+index)=t & color_code;
               }
            }

        /* чтение байта из оперативной памяти CGA/EGA */
         unsigned char read_point(x,y)
         int x,y;
         {
            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "исключающее ИЛИ" цвета в случае его
                          изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                  памяти CGA */
            bit_mask.i=3; /* 11111111 00111111 в
                                  двоичном виде */
            if (x<0 || x>199 || y<0 || y>319) return 0;

            /*  установка битовой маски и битов режима цвета
                в правую позицию */
             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             bit_mask.i<<=2*(3-bit_position);

             /* определение требуемого байта в памяти терминала */
              index=x*40+(y>>4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

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

              /* запись цвета */
               t=*(ptr+index) & bit_mask.c[0];
               t>>=2*(3-bit_position);
               return t;
            }

          /* отображение объекта на экране */

          void display_object(ob, sides,cc)
          double ob[][4];
          int sides,cc;
          {
            register int i;

            for(i=0; i=0; sides--)
              {
              ob[sides][0] += x;
              ob[sides][1] += y;
              ob[sides][2] += x;
              ob[sides][3] += y;
              }
            }

          /* Определение допустимости перемещения объекта.
          Возвращает 1, если перемещение допустимо, 0- в
          противном случае
          */
          void is_legal(ob, x, y, sides)
          int ob[][4];          /* объект */
          int x, y;             /* шаг перемещения */
          int sides;            /* число сторон объекта */
            {
            if(x==0 && y==0)
              return 1;      /* пустое перемещение всегда допустимо*/
            sides--;
            for(; sides>=0; sides--)
              {
              /* контроль выхода за допустимую область */
              if(ob[sides][0]+x>199 || ob[sides][1]+y>319)

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

                return 0;
              if(ob[sides][2]+x<0 || ob[sides][3]+y<0)
                return 0;
              }
            return 1;
            }

           Рассмотрим кратко,  как  работает  эта  программа.  Клавиши
     управления курсором  (клавиши  со  стрелками  и  клавиши  ,
     ,  и ) определяют положение спрайта. При нажатии
     клавиши  спрайт  смещается  на  одну  точку  растра  в  указанном
     направлении.    Клавиши-стрелки    управляют   горзонтальными   и
     вертикальными перемещениями,  остальные - диагональными.  Функция
     is_legal() определяет возможность дальнейшего перемещения спрайта
     в выбранном направлении.  Если возможен выход спрайта за  пределы
     границ  экрана,  то такое перемещение запрещается.  Все остальные
     функции этой программы работают, как описано в главе 4.

           Обычно необходимо  сохранять  размер  объекта,  который  вы
     "оживляете" (особенно небольшого) для того,  чтобы его можно было
     перерисовывать с высокой скоростью.  Это  обеспечивает  плавность
     движения при мультипликации.  Если объект достаточно большой,  то
     его движение будет дискретно.  При разработке видеоигр необходимо
     так  подбирать  размеры  спрайта,  чтобы возможности компьютера и
     адаптера реализовывались оптимальным образом.

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

               МУЛЬТИПЛИКАЦИЯ СПРАЙТА
     -----------------------------------------------------------------

          Передвижение спрайта  по  экрану  составляет только половину
     возможностей   его   "оживления".   В   основном   спрайт   будет
     использоваться  на  экране  для  того,  чтобы  создавать  иллюзию
     движения.  Например,  спрайт,  который выглядит подобно человеку,
     может   передвигать   ногами,   как   будто  он  идет.  Этот  тип
     "оживления" является наиболее впечатляющим (и  наиболее  легким).
     Для  обеспечения  такой возможности разрабатываются два или более
     вариантов спрайта,  отличие между которыми заключается в том, что
     некоторые  из  частей  спрайта  отличаются от первоначального его
     варианта.  Программа последовательно меняет  варианты  спрайта  в
     процессе его движения по экрану.

          В качестве   примера   изменим  программу  main(),  как  это
     показано ниже,  и добавим в  нее  второй  спрайт.  Второй  спрайт
     отображает  крестик  ("+"),  повернутый  под углом в 45 градусов.
     Если вы запустите программу,  то будет  создаваться  впечатление,
     что   крестик   вращается  в  процессе  передвижения  по  экрану.
     Переменная swap используется для выбора типа текущего спрайта.

        int sprite2[2][4] = {
           0,0,5,5,
           0,5,5,0
        };

        main()
        {
             union k {
                char c[2];
                int i;
             } key;

           int deltax=0,deltay=0; /* направление движения */
           int swap=0; /* тип спрайта */

           mode(4); /* установка 4 режима графики CGA/EGA */
           palette(0); /* палитра 0 */

           display_object(sprite,2,1);
           do {
                  key.i = bioskey(0);
                  deltax=0;deltay=0;
                  if(!key.c[0]) switch(key.c[1]) {
                     case 75: /* влево */
                        deltay= -1;
                         break;
                     case 77: /* вправо */
                        deltay= 1;
                         break;
                     case 72: /* вверх */
                        deltax= -1;

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

                        break;
                     case 80: /* вниз */
                        deltax= 1;
                        break;
                     case 71: /* вверх и влево */
                        deltay= -1;
                        deltax= -1;
                        break;
                     case 73: /* вверх и вправо */
                        deltay= 1;
                        deltax= -1;
                        break;
                     case 79: /* вниз и влево */
                        deltay= -1;
                        deltax= 1;
                        break;
                     case 81: /* вниз и вправо */
                        deltay= 1;
                        deltax= 1;
                        break;
                    }
                   /* стирание текущей позиции спрайта */
                   if(!swap) displey_object(sprite,2,1);
                   else displey_object(sprite2,2,1);

                   if (is_legal(sprite,deltax,deltay,2)) {
                      update_object(sprite,deltax,deltay,2);
                      update_object(sprite2,deltax,deltay,2);
                   }
                   swap= !swap; /* смена типа спрайта */
                   /* перезапись спрайта в новую позицию */
                   if (!swap) displey_object(sprite,2,1);
                   else displey_object(sprite2,2,1);
               }   while (key.c[0]!='q');
               getchar();
               mode(2);
           }

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

                      ОРГАНИЗАЦИЯ ДАННЫХ В ВИДЕОИГРАХ
     -----------------------------------------------------------------

          Подобно остальным  программам,  программы  видеоигр включают
     как  операторы,  так  и  данные.  Кроме  счета  игры  и   статуса
     различных,   расходуемых  в  процессе  игры  ресурсов  (например,
     количество  запущенных  фотонных  торпед),  большинство   данных,
     используемых в видеоиграх,  представляют собой позиции экрана для
     различных объектов.  Координаты  позиций  экрана  для  движущихся
     объектов  должны храниться в установленных переменных. Информацию
     о фиксированных  объектах  игрового  поля  целесообразно  хранить
     непосредственно  в  видеопамяти  терминала.  Если в процессе игры
     потребуется информация для изменения игрового поля (как это часто
     бывает), осуществляется доступ к видеопамяти и оттуда считываются
     массивы с информацией об измененном объекте.

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

          Контрооль границ.
          -----------------

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

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

          Изменение цвета.
          ----------------

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

          Программирование в  цвете  видеоигр  имеет  длинную историю.
     Например, первая игра "пинг-понг" имела только два цвета: белый и
     черный.  В  этой  игре  белый  цвет  был несовместим с белым (они
     отталкивались), но можно было двигаться по черному игровому полю.
     Таким образом, белый шарик мог перемещаться по черному полю, если
     ударялся белой ракеткой или  отражался  от  белой  стены  (линии)
     позади  ракетки.  Эти  основные  принципы использовались и тогда,
     когда в игре стали появляться и другие  цвета.  После  того,  как
     объекты  видеоигр  стали  программироваться  в цвете,  разработка
     программ обработки игровых  ситуаций  значительно  упростилась  и
     увеличилась скорость их работы.

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

                      ТАБЛО СЧЕТА АКТИВНОГО ПРОТИВНИКА
     -----------------------------------------------------------------

          Роль компьютера в игре во многом зависит от  того,  является
     ли  эта  игра  для  одного  человека  или для двоих.  Если в игре
     участвуют два  человека,  компьютеру  отводится  роль  арбитра  и
     функции   табло  для  отображения  счета.  Однако,  в  игре,  где
     участвует один игрок,  компьютер становится активным противником.
     С  точки  зрения  программирования разработка игр,  где компьютер
     выступает в роли противника, значительно интересней.

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

                           РАЗРАБОТКА  ВИДЕОИГРЫ
     -----------------------------------------------------------------

          В этом  параграфе  мы  опишем разработку видеоигры,  которая
     иллюстрирует многие принципы, описанные в данной главе.

          Описание игры
          -------------

          Первым шагом  в   процессе   создания   видеоигры   является
     определение   ее  природы  и  правил,  по  которым  она  ведется.
     Программа, описанная здесь, представляет собой компьютеризованную
     версию  традиционной  детской  игры "салочки".  Игрок и компьютер
     управляют каждый своим "человеком". Один из них догоняет другого,
     и,  если  у  них  произошел  контакт,  происходит  смена  амплуа.
     Победителем в игре становится тот, кто больший промежуток времени
     был в положении догоняемого.

          Cчет определяется  путем  фиксации  игрового времени:  после
     каждой  прошедшей  секунды  добавляется  одно  очко   тому,   кто
     находится в роли догоняемого. Счет непрерывно отображается в углу
     экрана.  Игра заканчивается,  когда один из игроков набирает  999
     очков.  Для  удобства  игра  может  быть  закончена путем нажатия
     клавиши .

          Игрок управляет   движением   спрайта   посредством   клавиш
     управления  курсором.  Игровое  поле в данном случае не создается
     самой программой игры.  Для  этих  целей  используются  программы
     рисования ("программы-художники"),  описанные в главе 4. Поэтому,
     вы можете самостоятельно создавать различную среду игры.

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

          Использование цвета и граничные условия.
          ---------------------------------------

          Игра "салочки"  использует  программирование  в  цвете   для
     идентификации  различных  объектов.  Например,  вы можете сделать
     спрайт игрока зеленым, спрайт компьютера - желтым, а границы поля
     игры   -   красными.   В   соответствии   с  этим  подходом,  нет
     необходимости хранить  отдельные  массивы  данных  в  разделяемой
     области программы,  т.к. подпрограммы могут просто контролировать
     содержимое видеопамяти.  В данном  случае,  так  же,  значительно
     упрощается  процесс ограничения области движения спрайтов красной
     линией.  Для  этой  цели  необходимо  немного  изменить   функцию
     is_legal(), описанную ранее, как это показано ниже.

          /* Определение допустимости перемещения объекта.
          Возвращает 1, если перемещение допустимо, 0 - в
          противном случае
          */
          void is_legal(ob, x, y, sides)
          int ob[][4];          /* объект */
          int x, y;             /* шаг перемещения */
          int sides;          /* число сторон объекта */
            {
            if(x==0 && y==0)
              return 1;    /* пустое перемещение всегда допустимо*/
            sides--;
            for(; sides>=0; sides--)
              {
              /* контроль выхода за допустимую область */
              if(ob[sides][0]+x>199 || ob[sides][1]+y>319)
                return 0;
              if(ob[sides][2]+x<0 || ob[sides][3]+y<0)
                return 0;
              /* контроль препятствий */
              if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)
                return 0;
              if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)
                return 0;
              }
            return 1;
            }

          Напомним коды  различных  цветов:  желтый - 1,  красный - 2,
     зеленый - 3, черный (фон) - 0.

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

          Описание спрайта.
          -----------------

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

     _________________________________________________________________

      Вид спрайта на стр. 187 не может быть воспроизведен имеющимися
      средствами. (Ред. пер. И.Бычковский.)
     _________________________________________________________________

          В программе  существует два типа спрайта.  Во втором спрайте
     ноги "человека" сжаты вместе.  Быстрая смена между  изображениями
     двух типов спрайта создает иллюзию бегущего человека.

          Спрайт пользователя  начинает  игру  в  верхнем  левом  углу
     экрана, а спрайт компьютера - в нижнем правом.  Описание спрайтов
     приведено ниже.

          int human[4][4] = /* это ваш спрайт */
               {
               1,     6,     6,     6,
               4,     2,     3,     9,
               9,     1,     6,     6,
               9,     11,    6,     6
               };

          int human2[4][4] =
               {
               1,     6,     6,     6,
               4,     2,     3,     9,
               9,     3,     6,     6,
               9,     9,     6,     6
               };

          int computer[4][4] = /* это  спрайт компьютера */
               {
               180,     6,     185,     6,
               183,     2,     182,     9,
               188,     1,     185,     6,
               188,     11,    185,     6
               };
          int computer2[4][4] =
               {
               180,     6,     185,     6,
               183,     2,     182,     9,
               188,     3,     185,     6,
               188,     9,     185,     6
               };

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

               ТЕЛО ГЛАВНОЙ ПРОГРАММЫ
     -----------------------------------------------------------------

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

          int directx,directy; /* направление  */

          main()
          {
             union k {
                char c[2];
                int i;
             } key;

           int deltax=0,deltay=0;
           int swaph=0,swapc=0;
           int it=COMPUTER;
           long htime,ctime,starttime,curtime; /* таймер счета */
           int count;
           mode(4); /* установка 4 режима графики CGA/EGA */
           palette(0); /* палитра 0 */

           load_pic(); /* ввод игрового поля */

           time(&starttime); /* установка времени */
           htime=ctime=0;

           display_object(human,4,1);
           display_object(computer,4,3);
           count=0;

           /* главный цикл игры */
           do {
               /* вычисление текущего счета  */
              time(&curtime);
              if (it==COMPUTER) htime+=curtime-starttime;
              else ctime+=curtime-starttime;
              time(&starttime);
              show_score(it,htime,ctime);

              if (bioskey(1)) { /* если нажата клавиша */
                  directx=directy=IDLE; /* устанавливает
                               направление перемещения */
                  key.i = bioskey(0);
                  deltax=0;deltay=0;

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

                  if(!key.c[0]) switch(key.c[1]) {
                     case 75: /* влево */
                        deltay= -1;
                        directy=LEFT;
                        break;
                     case 77: /* вправо */
                        deltay=1;
                        directy=RIGHT;
                        break;
                     case 72: /* вверх */
                        deltax= -1;
                        directx=UP;
                         deltax= -1;
                        directx=UP;
                        break;
                     case 80: /* вниз */
                        deltax=1;
                        directx=DOWN;
                        break;
                     case 71: /* вверх и влево */
                        deltay= -1;
                        directy=LEFT;
                        deltax= -1;
                        directx=UP;
                        break;
                     case 73: /* вверх и вправо */
                        deltay=1;
                        directy=RIGHT;
                        deltax= -1;
                        directx=UP;
                        break;
                     case 79: /* вниз и влево */
                        deltay= -1;
                        directy=LEFT;
                        deltax=1;
                        directx=DOWN;
                        break;
                     case 81: /* вниз и вправо */
                        deltay=1;
                        directy=RIGHT;
                        deltax=1;
                        directx=DOWN;
                        break;
                   }
                 }
                 /* смена типа спрайта игрока  */
                 if (!swaph) display_object(human,4,1);
                 else display_object(human2,4,1);
                 if (is_legal(human,deltax,deltay,4)) {
                    update_object(human,deltax,deltay,4);
                    update_object(human2,deltax,deltay,4);
                 }
                 /* проверяет: попался ли убегающий */
                 if (!count && tag(human,computer)) {

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

                    it= !it; /* смена амплуа */
                    count=6;
                 }
                 swaph= !swaph; /* смена фигуры имитирующей бег */

                 /* изображение "человека" в новой позиции */
                 if (!swaph) displey_object(human,4,1);
                 else displey_object(human2,4,1);

                 if (!swapc) display_object(computer,4,3);
                 else display_object(computer2,4,3);

                 /* генерация движения спрайта компьютера */
                 if (it==COMPUTER)
                 it_comp_move(computer,computer2,human,4);
                 else
               not_it_comp_move(computer,computer2,directx,directy,4);
                 if (!count && tag(human,computer)) {
                    it= !it;
                    count=6;
               /* компьютер догоняет; изменение координаты Х на 2
                  так, чтобы быстрей стать догоняемым
               */
                     if (is_legal(computer, 2, 0, 4))
                     {
                        update_object(computer, 2, 0, 4);
                       update_object(computer2, 2, 0, 4);
                    }
                   else
                   {
                     update_object(computer, -2, 0, 4);
                     update_object(computer2, -2, 0, 4);
                   }
                }
                swapc = !swapc; /* заменить тип спрайта */
            /* вывод на экран спрайта компьютера */
                if(!swapc) display_object(computer, 4, 3);
                else       display_object(computer2, 4, 3);
                if(count) count--;
             }
             while (key.c[0] !='q' && htime<999 && ctime<999);
             mode(2);
             if(ctime>htime)
                printf("Компьютер выиграл!");
             else
                printf("Вы победили!");
           }

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

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

          Переменная htime содержит значение счета игрока,  а ctime  -
     компьютера.  Переменные  swapc и swaph предназначены для указания
     типа спрайта.  Переменные  deltax  и  deltay  содержат  изменения
     значений  координат  после  очередного  нажатия  клавиш  игроком.
     Глобальные  переменные  directx  и  directy  содержат  координаты
     спрайта, управляемого игроком. Значения этих величин используются
     компьютером для генерации перемещения своего спрайта. Переменная-
     признак  it  содержит  информацию  о  том,  кто  в  данный момент
     находится в режиме догоняющего.  Она может принимать одно из двух
     значений,  описанных  в  макроопределении  #define:  COMPUTER или
     HUMAN.

          Главная   программа   работает    циклически.    На   экране
     отображается  текущий  счет.  После  этого  проверяется,  была ли
     нажата  какая-либо  клавиша.   Если   клавиша   была  нажата,  то
     определяется ее  код и производится  заданное перемещение спрайта
     игрока.  Обратите  внимание  на  то,  что в  данной программе нет
     режима ожидания  нажатия клавиши игроком.  Поэтому,  не смотря на
     то,  что игрок  не  нажимает  клавиш,  компьютер  продолжает свою
     работу,  и спрайт игрока продолжает указанное  перемещение до тех
     пор,  пока не будет нажата другая клавиша. Такое движение спрайта
     обеспечивает достаточно высокую динамичность игры.

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

          Рассмотрим некоторые программы, используемые в этой игре.

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

          Программа генерации движения спрайта компьютера.
          ------------------------------------------------

          Если спрайт  компьютера  находится в режиме догоняющего,  то
     для генерации очередного его кванта движения используется функция
     it_comp_move(). В основном компьютер повторяет стратегию движения
     пользователя. Движение его спрайта отклоняется из-за того, что он
     должен  обходить  объекты-препятствия.  Однако  спрайт компьютера
     может игнорировать некоторые  объекты,  что  позволяет  выровнять
     баланс игры.

          Приведем текст функции it_comp_move().

          /* Генерация движения спрайта компьютера, когда
             он в роли догоняющего */
          void it_comp_move(ob1, ob2, human, sides)
          int ob1[][4], ob2[][4], human[][4], sides;
               {
               register int x, y, d; /* d = direction */
               static skip = 0;
               skip++;
               if(skip==3)
                 {
                 skip=0;
                 return;
                 /* уменьшение времени реакции компьютера */
                 }
               x = 0;
               y = 0;

        /* движение к игроку */
          if(human[0][0]ob1[0][0])
                   x = 1;
               if(human[0][1]ob1[0][1])
                   y = 1;

               if(is_legal(ob1, x, y, sides))
                 {
                 update_object(ob1, x, y, sides);
                 update_object(ob2, x, y, sides);
                 }
               else
                 {
                 if(x && is_legal(ob1, x, 0, sides))
                   {
                   update_object(ob1, x, 0, sides);
                   update_object(ob2, x, 0, sides);

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

                   }
                 else
                   if(is_legal(ob1, 0, y, sides))
                   {
                   update_object(ob1, 0, y, sides);
                   update_object(ob2, 0, y, sides);
                   }
                 }
               }

          Заметим, что эта функция меняет положение спрайта в  3  раза
     медленнее,  чем  это возможно.  Делается такое замедление с целью
     снижения быстродействия компьютера до уровня человека.

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

          /* Генерация движения спрайта компьютера, когда
             он выступает в роли убегающего */
          void it_comp_move(ob1, ob2, human, sides)
          int ob1[][4], ob2[][4], human[][4], sides;
               {
               register int x, y, d; /* d = direction */
               static skip = 0;
               skip++;
               if(skip==3)
                 {
                 skip=0;
                 return;
                 /* уменьшение времени реакции компьютера */
                 }
               x = 0;
               y = 0;

        /* движение к игроку */
          if(human[0][0]ob1[0][0])
                   x = 1;
               if(human[0][1]ob1[0][1])
                   y = 1;

               if(is_legal(ob1, x, y, sides))
                 {
                 update_object(ob1, x, y, sides);
                 update_object(ob2, x, y, sides);

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

                 }
               else
                 {
                 if(x && is_legal(ob1, x, 0, sides))
                   {
                   update_object(ob1, x, 0, sides);
                   update_object(ob2, x, 0, sides);
                   }
                 else
                   if(is_legal(ob1, 0, y, sides))
                   {
                   update_object(ob1, 0, y, sides);
                   update_object(ob2, 0, y, sides);
                   }
                 }
               }

          /* генерация движения спрайта компьютера, когда
             он убегает     */
          void not_it_comp_move(ob1, ob2, dx, dy, sides)
          int ob1[][4], ob2[][4];
          int dx, dy; /* направление последнего перемещения
                       "человека" */
          int sides;
               {
               register int x, y, d;
               static skip = 1;

               skip++;
               if (skip==3)
                 {
                 skip = 0;
                 return;
                 /* уменьшение времени реакции компьютера в 3 раза */
                 }
               x = 0;
               y = 0;
               /* перемещение в противоположном направлении */
               x = -dx;
               y = -dy;

               if (is_legal(ob1, x, y, sides))
                 {
                 updаte_object(ob1, x, y, sides);
                 updаte_object(ob2, x, y, sides);
                 }
               else
                 {
                 if (x && is_legal(ob1, x, 0, sides))
                   {
                   update_object(ob1, x, 0, sides);
                   update_object(ob2, x, 0, sides);
                   }
                 else if (is_legal(ob1, 0, y, sides)) {

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

                     update_object(ob1, 0, y, sides);
                     update_object(ob2, 0, y, sides);
                     }
                   }
                 }

          Эта  функция так же  как и предыдущая,  работает с 3-кратным
     замедлением.

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

          Программа контроля касания спрайтов.
          ------------------------------------

          В этой игре режимы спрайтов изменяются на  противоположные в
     том  случае,  если координаты  хотя  бы  одной  точки догоняющего
     спрайта совпадут с координатами любой точкой догоняемого. Правила
     игры могут быть  изменены  таким  образом,  что  изменение режима
     произойдет лишь  в случае  полного  совмещения  спрайтов.  Но эта
     довольно-таки сложная задача для многих игроков. Приведенная ниже
     функция tag()  возвращает значение 1, если спрайты столкнулись, и
     0 - в противном случае.

          /* Проверяет есть ли контакт между спрайтами */
          tag(ob1, ob2)
          int ob1[][4], ob2[][4];
               {
               register int i;
               /* для смены амплуа необходимо, чтобы спрайты
                  имели хотя бы одну общую точку растра */
               for (i= -1; i<2; i++)
                 if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)
                   return 1;
               return 0;
               }

          Вы можете внести изменения в функцию tag() и установить свои
     правила контроля режимов спрайтов.

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

          Полный текст программы игры TAG.
          --------------------------------

          В данном  разделе приведен текст программы игры TAG, похожей
     на русские "салочки".  Вы можете ввести ее в свой компьютер, если
     он снабжен графическим адаптером.

       /* Пример мультипликации игры "салочки"

          Объектом в игре является "человек", который
          догоняет другого "человека".

          Ваш "человек"- зеленый,"человек" компьютера-
          желтый. Все, что окрашено в красный цвет,
          пересекать нельзя.

          Для смены ролей догоняющего и догоняемого
          необходимо, чтобы "люди" пересеклись
          хотя бы в одной точке растра  */

        #define COMPUTER 0
        #define HUMAN 1

        #define IDLE 0
        #define DOWN 1
        #define UP -1
        #define LEFT -1
        #define RIGHT 1

        #include "dos.h"
        #include "stdio.h"
        #include "math.h"
        #include "time.h"

        void mode(), line();
        void mempoint(), palette(), xhairs();
        void goto_xy(),show_score();
        void display_object(),update_object();
        void it_comp_move(),not_it_comp_move();
        void save_pic(), load_pic();
        unsigned char read_point();

        int human[4][4] = { /* ваш спрайт */
           1,6,6,6,
           4,2,3,9,
           9,1,6,6,
           9,11,6,6
         };

         int human2[4][4] = {

           1,6,6,6,
           4,2,3,9,

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

           9,3,6,6,
           9,9,6,6
          };

          int computer[4][4] = { /* спрайт компьютера */
            180,6,185,6,
            183,2,182,9,
            188,1,185,6,
            188,11,185,6
          };

          int computer2[4][4] = {
            180,6,185,6,
            183,2,182,9,
            188,3,185,6,
            188,9,185,6
          };

          int directx,directy; /* направление  */

          main()
          {
             union k {
                char c[2];
                int i;
             } key;

           int deltax=0,deltay=0;
           int swaph=0,swapc=0;
           int it=COMPUTER;
           long htime,ctime,starttime,curtime;
           int count;
           mode(4); /* установка 4 режима графики CGA/EGA */
           palette(0); /* палитра 0 */

           load_pic(); /* ввод игрового поля */

           time(&starttime); /* установка времени */
           htime=ctime=0;

           display_object(human,4,1);
           display_object(computer,4,3);
           count=0;

           /* главный цикл игры */
           do {
               /* вычисление текущего счета  */
              time(&curtime);
              if (it==COMPUTER) htime+=curtime-starttime;
              else ctime+=curtime-starttime;
              time(&starttime);
              show_score(it,htime,ctime);

              if (bioskey(1)) { /* если нажата клавиша */

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

                  directx=directy=IDLE; /* устанавливает
                               направление перемещения */
                  key.i = bioskey(0);
                  deltax=0;deltay=0;
                  if(!key.c[0]) switch(key.c[1]) {
                     case 75: /* влево */
                        deltay= -1;
                        directy=LEFT;
                        break;
                     case 77: /* вправо */
                        deltay=1;
                        directy=RIGHT;
                        break;
                     case 72: /* вверх */
                        deltax= -1;
                        directx=UP;
                         deltax= -1;
                        directx=UP;
                        break;
                     case 80: /* вниз */
                        deltax=1;
                        directx=DOWN;
                        break;
                     case 71: /* вверх и влево */
                        deltay= -1;
                        directy=LEFT;
                        deltax= -1;
                        directx=UP;
                        break;
                     case 73: /* вверх и вправо */
                        deltay=1;
                        directy=RIGHT;
                        deltax=-1;
                        directx=UP;
                        break;
                     case 79: /* вниз и влево */
                        deltay= -1;
                        directy=LEFT;
                        deltax=1;
                        directx=DOWN;
                        break;
                     case 81: /* вниз и вправо */
                        deltay=1;
                        directy=RIGHT;
                        deltax=1;
                        directx=DOWN;
                        break;
                    }
                 }
                 /* смена типа спрайта игрока  */
                 if (!swaph) displаy_object(human,4,1);
                 else displey_object(human2,4,1);
                 if (is_legal(human,deltax,deltay,4)) {
                    update_object(human,deltax,deltay,4);

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

                    update_object(human2,deltax,deltay,4);
                 }
                 /* проверяет: попался ли убегающий */
                 if (!count && tag(human,computer)) {
                    it=!it; /* смена амплуа */
                    count=6;
                 }
                 swaph= !swaph; /* смена фигур, имитирующих бег */
                 /* вывод "человека" в новой позиции */
                 if (!swaph) displаy_object(human,4,1);
                 else displаy_object(human2,4,1);

                 if (!swapc) displаy_object(computer,4,3);
                 else displаy_object(computer2,4,3);

                 /* генерация движения спрайта компютера */
                 if (it==COMPUTER)
                 it_comp_move(computer,computer2,human,4);
                 else
               not_it_comp_move(computer,computer2,directx,directy,4);
                 if (!count && tag(human,computer)) {
                    it= !it;
                    count=6;
               /* компьютер догоняет; изменение координаты Х на 2
                  так, чтобы быстрей стать догоняемым */
                     if(is_legal(computer, 2, 0, 4))
                     {
                        update_object(computer, 2, 0, 4);
                       update_object(computer2, 2, 0, 4);
                    }
                   else
                   {
                     update_object(computer, -2, 0, 4);
                     update_object(computer2, -2, 0, 4);
                   }
                }
                swapc = !swapc; /* заменить тип спрайта */
            /* вывод на экран спрайта компьютера */
                if(!swapc) display_object(computer, 4, 3);
                else       display_object(computer2, 4, 3);
                if(count) count--;
             }
             while (key.c[0] !='q' && htime<999 && ctime<999);
             getchar();
             mode(2);
             if(ctime>htime)
                printf("Компьютер выиграл!");
             else
                printf("Вы победили!");
           }

          /* Вывод на экран терминала счета */
          void shou_score(it, htime, ctime)
          int it;

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

          long htime, ctime;
            {
            goto_xy(24, 6);
            if(it==COMPUTER)
              printf("ВЫ:%ld", htime);
            else
              printf("вы:%ld", htime);
            goto_xy(24, 26);
            if(it==HUMAN)
              printf("Я:%ld", ctime);
            else
              printf("я:%ld", ctime);
            }

          /* Выбор палитры */
          void palette(pnum)
          int pnum;
            {
            union REGS r;

            r.h.bh = 1; /* код 4-го графического режима */
            r.h.bl = pnum;
            r.h.ah = 11;
            int86(0x10, &r, &r);
            }

          /* Выбор режима */
          void mode(mode_code)
          int mode_code;
            {
            union REGS r;

            r.h.al = mode_code;
            r.h.ah = 0;
            int86(0x10, &r, &r);
            }

      /* изображение линии заданного цвета с использованием
         алгоритма Брезенхама */
       void line(startx,starty,endx,endy,color)
       int startx,starty,endx,endy,color;
       {
         register int t,distance;
         int x=0,y=0,delta_x,delta_y;
         int incx,incy;

       /* вычисление расстояния в обоих направлениях         */
         delta_x=endx-startx;
         delta_y=endy-starty;

       /* определение  направления  шага,  шаг  вычисляется  либо   по
          вертикальной,  либо  горизонтальной  линии */
          if (delta_x>0) incx=1;
          else if (delta_x==0) incx=0; else incx=-1;

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

          if (delta_y>0) incy=1;
          else  if (delta_y==0) incy=0;
          else  incy=-1;

        /* определение какое расстояние больше */
          delta_x=abs(delta_x);
          delta_y=abs(delta_y);
          if (delta_x>delta_y) distance=delta_x;
          else distance=delta_y;

        /* изображение линии */
          for (t=0; t<=distance+1; t++) {
             mempoint(startx,starty,color);
             x+=delta_x;
             y+=delta_y;
             if (x>distance) {
                x-=distance;
                startx+=incx;
             }
             if (y>distance) {
                y-=distance;
                starty+=incy;
             }
          }
       }

        /* запись точки в CGA/EGA */

         void mempoint(x,y,color_code)
         int x,y,color_code;
         {
            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "исключающее ИЛИ" цвета в случае его
                          изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                  памяти CGA */
            bit_mask.i=0xFF3F; /* 11111111 00111111 в
                                  двоичном виде */
            if (x<0 || x>199 || y<0 || y>319) return;
            xor=color_code & 128; /* проверка, устанавливался ли
                                     режим "исключающего ИЛИ" */
            color_code=color_code & 127; /* маска старших битов */

            /*  установка битовой маски и битов режима цвета
                в правую позицию */

             bit_position=y%4; /* вычисление нужной позиции

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

                                  в байте */
             color_code<<=2*(3-bit_position); /* сдвиг кода цвета
                                        в нужную позицию */
             bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в
                                          нужную позицию */

             /* определение требуемого байта в памяти терминала */

              index=x*40+(y%4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* запись цвета */

               if (!xor) {  /* режим изменения цвета */
                  t=*(ptr+index) & bit_mask.c[0];
                  *(ptr+index)=t|color_code;
               }
               else {
                  t=*(ptr+index) | (char)0;
                  *(ptr+index)=t & color_code;
               }
            }

        /* чтение байта из оперативной памяти CGA/EGA */
         unsigned char read_point(x,y)
         int x,y;
         {
            union mask {
               char c[2];
               int i;
            } bit_mask;
            int i,index,bit_position;
            unsigned char t;
            char xor; /* "исключающее ИЛИ" цвета в случае его
                          изменения */
            char far *ptr=(char far *) 0xB8000000; /* точка в
                                                  памяти CGA */
            bit_mask.i=3; /* 11111111 00111111 в
                                  двоичном виде */
            if (x<0 || x>199 || y<0 || y>319) return 0;

            /*  установка битовой маски и битов режима цвета
                в правую позицию */
             bit_position=y%4; /* вычисление нужной позиции
                                  в байте */
             bit_mask.i<<=2*(3-bit_position);

             /* определение требуемого байта в памяти терминала */
              index=x*40+(y>>4);
              if (x%2) index+=8152; /* если нечетный, используется
                                       второй блок */

              /* запись цвета */

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

               t=*(ptr+index) & bit_mask.c[0];
               t>>=2*(3-bit_position);
               return t;
            }

       /* загрузка изображения */

           void load_pic()
           {
              char fname[80];
              FILE *fp;
              register int i,j;

              char far *ptr=(char far *) 0xB8000000; /* точка в
                                                     памяти   CGA  */
              char far *temp;
              unsigned char buf[14][80]; /* содержит образ экрана */

              temp=ptr;
         /* сохранение верхних строк текущего содержимого экрана */
              for (i=0;i<14;++i)
                 for (j=0;j<80;j+=2) {
                    buf[i][j]=*temp;
                    buf[i][j+1]=*(temp+8152);
                    *temp=0; *(temp+8152)=0;/*чистка позиций экрана*/
                    temp++;
                 }
              goto_xy(0,0);
              printf("Имя файла:");
              gets(fname);
              if (!(fp=fopen(fname,"rb"))) {
                 goto_xy(0,0);
                 printf("Файл не может быть открыт\n");
                 temp=ptr;
         /* восстановление содержимого экрана */
                 for (i=0;i<14;++i)
                    for (j=0;j<80;j+=2) {
                       *temp= buf[i][j];
                       *(temp+8125)=buf[i][j+1];
                       temp++;
                    }
                 return;
               }
        /* загрузка изображения из файла */
              for (i=0;i<8152;i++) {
                *ptr=getc(fp); /* четный байт */
                *(ptr+8125)=getc(fp); /* нечетный байт */
                ptr++;
              }

              fclose(fp);
           }

       /* поместить курсор в заданное положение */

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

           void goto_xy(x,y)
           int x,y;
           {
              r.h.ah=2; /* адресация курсора */
              r.h.dl=y; /* координата столбца */
              r.h.dh=x; /* координата строки */
              r.h.bh=0; /* видео-страница */
              int86(0x10,&r,&r);
            }

          /* отображение объекта на экране */

          void display_object(ob, sides,cc)
          double ob[][4];
          int sides,cc;
          {
            register int i;

            for(i=0; i=0; sides--)
              {
              ob[sides][0] += x;
              ob[sides][1] += y;
              ob[sides][2] += x;
              ob[sides][3] += y;
              }
            }

          /* Определение  допустимости перемещения объекта. Возвращает
          1, если перемещение допустимо, 0 - в противном случае */
          is_legal(ob, x, y, sides)
          int ob[][4];        /* объект */
          int x, y;           /* шаг перемещения */
          int sides;          /* число сторон объекта */
            {
            if(x==0 && y==0)
              return 1;       /* пустое перемещение всегда допустимо*/
            sides--;
            for(; sides>=0; sides--)
              {
              /* контроль выхода за допустимую область */

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

              if(ob[sides][0]+x>199 || ob[sides][1]+y>319)
                return 0;
              if(ob[sides][2]+x<0 || ob[sides][3]+y<0)
                return 0;
              if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)
                return 0;
              if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)
                return 0;
              }
            return 1;
            }

          /* генерация движения спрайта компьютера,  когда он догоняет
          */
            void it_comp_move(ob1,  ob2,  human, sides)
            int ob1[][4],ob2[][4], human[][4], sides;
               {
               register int x, y, d; /* d = direction */
               static skip = 0;
               skip++;
               if(skip==3)
                 {
                 skip=0;
                 return;
                 /* уменьшение времени реакции компютера */
                 }
               x = 0;
               y = 0;

        /* движение к игроку */
          if(human[0][0]ob1[0][0])
                   x = 1;
               if(human[0][1]ob1[0][1])
                   y = 1;

               if(is_legal(ob1, x, y, sides))
                 {
                 update_object(ob1, x, y, sides);
                 update_object(ob2, x, y, sides);
                 }
               else
                 {
                 if(x && is_legal(ob1, x, 0, sides))
                   {
                   update_object(ob1, x, 0, sides);
                   update_object(ob2, x, 0, sides);
                   }
                 else

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

                   if(is_legal(ob1, 0, y, sides))
                   {
                   update_object(ob1, 0, y, sides);
                   update_object(ob2, 0, y, sides);
                   }
                 }
               }

          /* генерация движения спрайта компьютера, когда
             он убегает     */
          void not_it_comp_move(ob1, ob2, dx, dy, sides)
          int ob1[][4], ob2[][4];
          int dx, dy; /* направление последнего перемещения
                       "человека" */
          int sides;
               {
               register int x, y, d;
               static skip = 1;

               skip++;
               if (skip==3)
                 {
                 skip = 0;
                 return;
                 /* уменьшение времени реакции компьютера в 3 раза */
                 }
               x = 0;
               y = 0;
               /* перемещение в противоположном направлении */
               x = -dx;
               y = -dy;

               if (is_legal(ob1, x, y, sides))
                 {
                 updаte_object(ob1, x, y, sides);
                 updаte_object(ob2, x, y, sides);
                 }
               else
                 {
                 if (x && is_legal(ob1, x, 0, sides))
                   {
                   updаte_object(ob1, x, 0, sides);
                   updаte_object(ob2, x, 0, sides);
                   }
                 else if (is_legal(ob1, 0, y, sides)) {
                     updаte_object(ob1, 0, y, sides);
                     updаte_object(ob2, 0, y, sides);
                     }
                   }
                 }

          /*  проверяет наличие контакта между спрайтами */
          tag(ob1, ob2)
          int ob1[][4], ob2[][4];

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

               {
               register int i;
               /* для смены амплуа необходимо, чтобы спрайты
                  имели хотя бы одну общую точку растра */
               for (i=-1; i<2; i++)
                 if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)
                   return 1;
               return 0;
               }

          Для использования  игры вы должны создать одно или несколько
     игровых  полей,  используя  функции,   описанные   в   главе   4.
     Используйте  красный  цвет для изображения препятствий.  Желтый и
     зеленый цвета можно использовать для фона.  Эти  цвета  не  несут
     нагрузки,  поэтому могут использоваться в декоративных целях.  На
     рисунках 5-1 и 5-2 показаны два варианта игровых  полей  в  таком
     виде, в котором они отображаются на экране вашего терминала.

          Быстродействие компьютеров,  таких  моделей  как AT или PS/2
     моделей 50,  60 или 80,  вполне достаточно для данной игры.  Темп
     игры будет несколько снижен на обычном компьютере PC.  Однако вам
     уже известно, как может быть повышена динамичность игры.

     _________________________________________________________________

      Рис. 5-1 на стр. 205 имеющимися средствами воспроизведен быть не
      может. (Ред. пер. И.Бычковский.)
     _________________________________________________________________

     Рис. 5-1. Первое игровое поле видеоигры "салочки"

     _________________________________________________________________

      Рис. 5-2 на стр. 205 имеющимися средствами воспроизведен быть не
     может. (Ред. пер. И.Бычковский.)
     _________________________________________________________________

     Рис. 5-2. Второе игровое поле видеоигры "салочки"

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

               НЕКОТОРЫЕ СООБРАЖЕНИЯ ПО ВОЗМОЖНОЙ МОДИФИКАЦИИ
                                 ПРОГРАММЫ
     -----------------------------------------------------------------

          Возможно, вы со временем захотите  создать  свою  видеоигру,
     взяв,  однако,  за  основу  рассмотренную здесь игру TAG.  В этом
     случае в можете,  например,  изменить траекторию движения спрайта
     компьютера   заставив  его  двигаться  вокруг  какого-то  объекта
     ("охранять"  его).  Интересным  дополнением  к  программе   будет
     возможность изменять внешний вид каждого объекта-участника игры в
     зависимости от каких-либо условий. Кстати, решение этой задачи не
     требует  от вас каких-либо дополнительных усилий,  так как каждое
     изображение  спрайта  можно  хранить   в   видеопамяти,   а   все
     необходимые подпрограммы для работы с ней у вас уже есть.

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

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

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


                                  ГЛАВА 6                     -- 1 --
                                  -------

           ИСПОЛЬЗОВАНИЕ ПОСЛЕДОВАТЕЛЬНОГО ПОРТА: ПЕРЕДАЧА ФАЙЛОВ И
                               ПРОСТЕЙШИЕ ЛВC
     -----------------------------------------------------------------

          Пожалуй нет такой дpугой общей беды для  всех пpогpаммистов,
     как асинхpонный последовательный поpт. Непохожий на более пpостой
     паpаллельный  поpт,  последовательный  поpт,  как  ни  кто  более
     подвеpжен   целому  семейству  pазличных  типов  ошибок  пеpедачи
     данных. Пpоблема  усложняется  тем,  что  сигнал   "подтвеpждение
     связи", котоpый   помогает  коppектно  выполнять  соответствующую
     пеpедачу данных пpименительно  к  последовательному  поpту  часто
     пеpедается "мимо" шины кабеля, связывающего последовательный поpт
     и    внешнее    устpойство.    Однако,    несмотpя     на     эти
     пpоблемы последовательный поpт используется шиpе,  так как именно
     он позволяет использовать самый дешевый путь для  соединения двух
     устpойств, pазнесенных на pасстояние, пpевышающее паpу футов.

          Цель этой  главы  - дать основы устpойства последовательного
     поpта и pаботы с ним,  включая инициализацию,  пеpедачу  и  пpием
     данных,  а  также обсудить наиболее общие ошибки,  возникающие во
     вpемя pаботы с последовательным поpтом.

          Набор опеpаций    pаботы    с    последовательным     поpтом
     обуславливает  его  использование  в  качестве составной части по
     кpайней  меpе  в  двух  пpиложениях.  Во-пеpвых,  это   пpогpамма
     пеpесылки   файла,  котоpая  может  использоваться  для  пеpедачи
     pазличных типов  файлов  (включая  двоичные  файлы)  между  двумя
     компьютеpами.  Пpогpамма  пеpесылки  файла  особенно  полезна пpи
     pешении пpоблемы стыковки pазличных типов компьютеpов. Во-втоpых,
     это  пpоблема  создания пpостейших локальных вычислительных сетей
     (ЛВС),  включающих  в  себя  файловый  пpоцессоp  (для  поддеpжки
     внешних  ЗУ  большой  емкости)  и  набоp  из  двух  новых команд,
     позволяющих  удаленным  компьютеpам  загpужать   файлы   из   или
     записывать в файловый пpоцессоp.

          Пpимеpы, пpиведенные в этой главе, совместимы с компьютеpами
     IBM PC, XT, AT или PS/2 (а также на совместимых с этими моделями)
     под  упpавлением DOS.  Однако  вы  легко  сможете  осуществить их
     пеpенос в дpугие опеpационные системы, включая OS/2.

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

                АСИНХРОННАЯ ПОСЛЕДОВАТЕЛЬНАЯ ПЕРЕДАЧА ДАННЫХ
     -----------------------------------------------------------------

          Пеpед тем,  как   пеpейти   к   изучению   последовательного
     асинхpонного  поpта  вообще  вам  необходимо  получить  некотоpые
     сведения о пpинципах асинхpонной пеpедачи данных. ( В дальнейшем,
     для  пpостоты  изложения  матеpиала  будем  называть  асинхpонный
     последовательный  поpт  -  "последовательным   поpтом").   Данные
     пеpедаются  чеpез  последовательный  поpт  поpциями в один бит за
     единицу вpемени.  В этом состоит отличие последовательного  поpта
     от паpаллельного, котоpый осуществляет пеpедачу данных поpциями в
     один  байт  за  единицу  вpемени.  Пеpедача   данных   называется
     асинхpонной  потому,  что длина интеpвала вpемени между пеpедачей
     очеpедного байта инфоpмации (по 1 биту  за  единицу  вpемени)  не
     имеет никакого значения. Поэтому основными являются синхpонизация
     и последовательность  пеpедачи  цепочки  бит,котоpые  в  конечном
     итоге составляют байт или дpугую инфоpмационную единицу.

          Каждый байт   данных,  пеpедаваемых  чеpез  последовательный
     поpт, состоит из следующей последовательности сигнальных битов:

          1. Один стаpтовый бит
          2. Восемь битов данных ( в некотоpых случаях - 7 )
          3. Необязательный бит четности
          4. Один или два конечных бита

          Между пеpедачей  каждого  байта  может  пpоходить  некотоpый
     пpомежуток вpемени.

          Вpемя пpостоя канала  пеpедачи  для  этого  pежима  довольно
     велико.  Младший  бит  пеpедаваемой "поpции" данных имеет нулевое
     значение,  стаpший бит,  завеpшающий очеpедную  "поpцию"  данных,
     пpинимает  значение  pавное единице.  Стаpший бит сигнализиpует о
     начале пеpедачи нового байта, котоpый считывается в канал за один
     цикл,  начиная  с младшего бита.  Биты данных пеpедаются вслед за
     необязательным битом четности.  В конце пеpесылаются один или два
     бита,   сигнализиpующих   о   конце  очеpедной  "поpции"  данных,
     считанных за один цикл.  Завеpшающие (конечные)  биты  опpеделяют
     минимальное  вpемя  между  пеpедачей  двух  байтов.  Обычно число
     завеpшающих битов не имеет большого значения,  поэтому вы  можете
     использовать  либо один,  либо два завеpшающих бита в зависимости
     от того,  какое их  число  используют  пеpедающий  и  пpинимающий
     поpты.

          Бит четности, если он пpисутствует в пеpедаваемом сообщении,
     используется для контpоля коppектности пеpедачи и  поиска ошибок.
     Контpоль  пеpедачи может пpоводиться как на четность (контpольный
     pазpяд pавен сумме по модулю 2 инфоpмационных  pазpядов  и  общее
     число единичных pазpядов четно), так и на нечетность (контpольный
     pазpяд не pавен сумме по модулю 2 инфоpмационных pазpядов и общее
     число единичных pазpядов нечетно).

          Скоpость пеpедачи  битов по каналу измеpяется в бодах (бит в
     секунду).  Наименьшей скоpостью пеpедачи инфоpмации считается 300

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

     бод.  Эта  скоpость  пеpедачи  использовалась  в  стаpых  модемах
     (сейчас большинство модемов позволяют достигать скоpости пеpедачи
     от  1200 до 2400 бод).  Семейство компьютеpов IBM PC поддеpживают
     скоpость пеpедачи данных в 9600 бод.  Некотоpые типы  компьютеpов
     позволяют достигать скоpости пеpедачи данных в 38400 бод!

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

                              СТАНДАРТ RS-232
     -----------------------------------------------------------------

          Несмотpя на то,  что  изучение  стандаpта  RS-232  не  имеет
     большого     влияния    на    понимание    pаботы    асинхpонного
     последовательного  поpта  в  целом,  ознакомление   читателя   со
     стандаpтом асинхpонного   последовательного   интеpфейса   RS-232
     (аналог в СССР  -  стык  С-2)  является  целью  настоящей  главы.
     Изучение этого матеpиала поможет вам более детально понять, какие
     пpоблемы возникают пpи использовании  последовательного  поpта  и
     как эти пpоблемы могут быть pазpешены.

          Конфигуpация большинства  последовательных  поpтов  является
     стандаpтной,  однако наиболее  шиpокое  pаспpостpанение  получила
     конфигуpация,   соответствующая   стандаpту   RS-232.   По  этому
     стандаpту pазъем содеpжит 25 контактов.  (В компьютеpе IBM PC  AT
     используется  9-ти  контактный  pазъем).  Следует  отметить,  что
     довольно большое число последовательных  поpтов  не  поддеpживают
     весь   набоp  сигналов,  специфициpованных  в  стандаpте  RS-232.
     Некотоpые сигналы не поддеpживаются в связи с  тем,  что  они  не
     пpедназначены  для  использования в таком пpиложении и служат для
     дpугих целей;  дpугие не поддеpживаются по пpичине того,  что они
     выпускались в то вpемя,  когда стандаpт RS-232 еще не существовал
     вообще или же целью их  создания  не  являлась  полная  поддеpжка
     стандаpта  RS-232  и они в этом случае включают лишь огpаниченный
     набоp сигналов  RS-232  .  Наиболее  общими  сигналами  стандаpта
     RS-232 являются:

        Сигнал                  Аббpевиатуpа       Штыpь pазъема
        ------                  ------------      ---------------
     Запpос на посылку данных        RTS                 4
     Очистка для посылки             CTS                 5
     Набоp данных готов              DSR                 6
     Набоp данных завеpшен           DTR                20
     Пеpедача данных                 TxD                 2
     Пpием данных                    RxD                 3
     Земля                           GRD                 7

          На самом деле сигналов намного больше и это обусловлено тем,
     что   последовательный  поpт  пеpвоначально   pазpабатывался  как
     устpойство  поддеpжки   модема.   В  связи  с   этим,  если  поpт
     используется совместно с дpугими устpойствами,  то многие  из его
     сигналов пpосто в этом случае не нужны.  Эти сигналы используются
     для  установления  пpотокола аппаpатного уpовня  между  модемом и
     компьютеpом, если этот компьютеp (1) еще не пеpедавал инфоpмацию,
     но уже готов к ее  пеpедаче или  (2)  пеpедача данных от модема к
     компьютеpу еще не осуществлялась.

          Ошибка кадpирования (т.е.  ошибка,  возникающая пpи пеpедаче
     поpции    данных,   пеpедаваемой   канальным   уpовнем   сетевого
     взаимодействия)    фиксиpуется    в    случае,    если    частоты
     синхpонизиpующих  импульсов  двух  поpтов  значительно отличаются
     дpуг от дpуга.  Как вы можете догадаться,  последовательный  поpт

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

     после  того,  как  он  обнаpужил стаpтовый бит,  выделяет pегистp
     ввода,  котоpый за каждый цикл считывает один  бит.  Длина  этого
     цикла   опpеделяется  скоpостью  пеpедачи  данных.  Однако  вpемя
     нахождения  бита  в  peгистpe  опpеделяется   тактовой   частотой
     системы.   Если  частота  компьютеpа-пpиемника  недостаточна  для
     покpытия  частоты  компьютеpа-источника,  то  пpоисходит   потеpя
     полученного   бита   (т.к.  pегистp  занят),  в  связи  с  чем  и
     pегистpиpуется ошибка кадриpования (framing error).

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

                       АППАРАТНОЕ ПОДТВЕРЖДЕНИЕ СВЯЗИ
     -----------------------------------------------------------------

          Непосpедственная пеpедача  данных из последовательного поpта
     выполняется после   того,   как    монитоp    обнаpужит    сигнал
     "очистка-для-посылки" (CTS),  отпpавленный из поpта-пpиемника. Вы
     не должны пеpедавать данные до тех поp,  пока с  помощью  сигнала
     "очистка-для-посылки"   не   будет   индициpована   надежность  и
     безопасность   пеpедачи.   Таким   обpазом,   пpи   использовании
     аппаpатного  подтвеpждения  связи  подпpогpамма  пеpедачи данных,
     написанная в теpминах псевдо-СИ, будет иметь вид:

     do {
       while(not CTS) wait;
       send(byte);
     } while(bytes to send);

          Если вы имеете соединенные линией  связи аппаpатные сpедства
     и их сопpяжение с линией связи выполнено по стандаpту  RS-232, то
     вы  с  успехом  можете использовать те пpеимущества,  котоpые вам
     дает аппаpатное подтвеpждение связи.  Однако совсем недавно этого
     нельзя было делать.

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

                          ПРОБЛЕМЫ ПЕРЕДАЧИ ДАННЫХ
     -----------------------------------------------------------------

          Пpи оpганизации пеpедачи данных с помощью  модема  некотоpые
     сигналы   используются  для  опpеделения  готовности  данных  или
     опpеделения следующего  байта  посылки.  Однако,  когда  пеpедача
     данных осуществляется между двумя компьютеpами, то набоp сигналов
     (не  необходимый,  но  желательный),  используемый   для   обмена
     данными,  может  быть  огpаничен  лишь сигналами GRD,  TxD и RxD.
     Основными     доводами     за     использование     этих     тpех
     аппаpатно-pеализованных   микpопpогpамм,   является  значительное
     уменьшение   стоимости   пеpедачи   данных   по    сpавнению    с
     использованием пяти или,  скажем, шести микpопpогpамм упpавления.
     Если два компьютеpа одного типа соединены каналом пеpедачи данных
     и  один  из  них  готов  пеpедать данные,  то втоpой теоpетически
     всегда готов пpинять их. Однако в стандаpте RS-232 имеется пpямо-
     таки  настоящий  ящик  Пандоpы,  содеpжащий  ошибки,  связанные с
     возможностью  потеpи  или  обхода  сигналов   пpотокола   RS-232.
     Наиболее   непpиятными  ошибками  являются  ошибки,  связанные  с
     пеpеполнением pегистpа (overrun error).

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

                       ПЕРЕПОЛНЕНИЕ РЕГИСТРА-ПРИЕМНИКА
     -----------------------------------------------------------------

          Если    для   соединения    двух   последовательных   поpтов
     используются  только  тpи микpопpогpаммы  (сигнала), то возникает
     необходимость     использовать      своеобpазный     "тpюк"     с
     поpтом-источником в пpедположении,  что поpт-пpиемник уже готов к
     пpиему  данных.  Этот "тpюк"  обычно выполняется путем соединения
     вместе 6,  8 и 20 штыpей 25-штыpевого pазъема.  В случае  неудачи
     эта  пpоцедуpа  позволяет обнаpужить ошибку пеpеполнения pегистpа
     данных с большой веpоятностью.  Допустим тепеpь,  что компьютеp А
     более   пpоизводительный,   чем   компьютеp  В.  Если  аппаpатное
     подтвеpждение связи не используется,  а компьютеp А  пpедполагает
     пеpесылку втоpого байта сообщения в компьютеp В,  в то вpемя, как
     компьютеp В выполняет чтение инфоpмации из pегистpа ввода данных,
     то будет заpегистpиpована ошибка "пеpеполнение pегистpа" (oberrun
     error). Ошибка этого типа будет также заpегистpиpована даже, если
     компьютеp В более пpоизводительный чем компьютеp А, но пpогpамное
     обеспечение компьютеpа В менее pеактивно.

          Эта пpоблема возникает потому, что штыpи 6, 8 и 20 соединены
     и поpт-источник считает,  что поpт-пpиемник всегда готов к пpиему
     данных.  Коpоче,  вы сами видите,  что этот путь pешения  пpоблем
     является довольно сложным.

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

                ДОСТУП К ПОСЛЕДОВАТЕЛЬНОМУ ПОРТУ КОМПЬЮТЕРА
                                 ЧЕРЕЗ BIOS
     -----------------------------------------------------------------

          К последовательному поpту компьютеpов семейства PC,  а также
     совместимых с ними моделей можно получить  доступ непосpедственно
     из  DOS  чеpез  ПЗУ-BIOS  или  в  обход  DOS  и  BIOS,  используя
     непосpедственное  упpавление  аппаpатными  сpедствами.  Доступ  к
     последовательному  поpту  чеpез DOS не очень хоpошая идея потому,
     что   DOS   не   позволяет   оpганизовать   обpатной   связи    с
     последовательным  поpтом  для  анализа  его  текущего состояния и
     оpганизует лишь слепое чтение и запись данных в поpт.  К тому  же
     нет возможности использовать систему пpеpываний DOS.  Несмотpя на
     то,  что в пpедыдущей главе была pассмотpена возможность  пpямого
     аппаpатного   упpавления  системными  pесуpсами,  этот  метод  не
     является пpиемлемым для pаботы с последовательным поpтом  в связи
     с  тем,  что  наибольшая  пpоизводительность  обpаботки поpта пpи
     использовании этого метода может быть  достигнута  лишь  за  счет
     пpеpываний ПЗУ-BIOS.

          Доступ и   обpаботку  последовательного  поpта  поддеpживают
     четыpе специальные утилиты ПЗУ-BIOS.  Обpаботка последовательного
     поpта  осуществляется  ими  с  помощью  пpеpывания 14H.  Разбеpем
     подpобнее этот метод.

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

                            ИНИЦИАЛИЗАЦИЯ ПОРТА
     -----------------------------------------------------------------

          Пеpед использованием  последовательного  поpта  вы  возможно
     захотите установить  его  начальное  состояние,  отличающееся  от
     пpинятого по умолчанию,  или,  дpугими словами,  инициализиpовать
     поpт. (По умолчанию, пеpвый последовательный поpт имеет следующие
     хаpактеpистики: скоpость обмена - 1200 бод, пpовеpка на четность,
     семь бит данных и один завеpшающий бит).  Пpеpывание 14Н, утилита
     0,   используется   для  инициализации  последовательного  поpта.
     Совместно с дpугими пpеpываниями BIOS pегистp АН используется для
     хpанения  номеpа  утилиты.  Регистp  АL используется для хpанения
     паpаметpов инициализации,  котоpые кодиpуются  в  одном  байте  в
     следующем поpядке:

                              номеp бита: 7 6 5 4 3 2 1 0
                                          --T-- -T- T -T-
                                            ¦    ¦  ¦  ¦
       скоpость пеpедачи (бод) --------------    ¦  ¦  ¦
       контpоль четности       -------------------  ¦  ¦
       количество завеpшающих битов  ----------------  ¦
       количество битов данных -------------------------

          Скоpость  пеpедачи  данных  кодиpуется   в   соответствии  с
     таблицей  6-1.  Контpоль  четности  кодиpуется  в  соответствии с
     таблицей 6-2.

          Таблица 6-1
     -----------------------------------------------------------------
          Кодиpование скоpости пеpедачи в битах 7, 6 и 5
          байта инициализации последовательного поpта.

                 Скоpость                Последовательность бит
                 --------                ----------------------
                   9600                              1  1  1
                   4800                              1  1  0
                   2400                              1  0  1
                   1200                              1  0  0
                    600                              0  1  1
                    300                              0  1  0
                    150                              0  0  1
                    110                              0  0  0
     -----------------------------------------------------------------

          Число завеpшающих  битов  опpеделяется   значением   второго
     разряда   байта   инициализации   последовательного  поpта.  Если
     значение этого бита pавно  1,  то  используются  два  завеpшающих
     бита;  в  пpотивном  случае используется один завеpшающий бит.  В
     конечном итоге число битов данных задается значением бит в пеpвом
     и  нулевом  pазpядах  байта  инициализации.  Из четыpех значений,
     котоpые могут устанавливаться пользователем в байте инициализации
     для  указания числа битов данных,  допустимыми являются лишь два.

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

     Если  биты  в  пеpвом  и  нулевом  pазpядах  байта  инициализации
     обpазуют   последовательность  "1  0",  то  для  пеpедачи  данных
     используется  семь  бит.  Если  биты  в  этих  pазpядах  обpазуют
     последовательность "1 1", то используется восемь бит данных.

          Таблица 6-2
     -----------------------------------------------------------------
          Кодиpование четности в битах 4 и 3
          байта инициализации последовательного поpта

               Вид контpоля             Последовательность бит
               ------------             ----------------------
               контpоль отменен              0 0  или  1 0
               пpовеpка на нечетность        0 1
               пpовеpка на четность          1 1
     -----------------------------------------------------------------

          Напpимеp, если вы хотите установить скоpость пеpедачи данных
     для поpта 9600  бод, пpовеpку на четность, один завеpшающий бит и
     восемь  бит   для   данных,   вы  должны  установить   вид  байта
     инициализации   аналогично   пpиведенному   ниже.   В  десятичном
     пpедставлении значение байта инициализации pавно 251.

                                    1  1  1  1 1  0  1 1
                                    ---T---  -T-  T  -T-
          скоpость пеpедачи (бод) ------      ¦   ¦   ¦
          вид контpоля четности   -------------   ¦   ¦
          количество завеpшающих битов ------------   ¦
          количество битов данных ---------------------

          Стандаpт PC пpедусматpивает наличие до семи последовательных
     поpтов  (в  новых  типах  машин  их  значительно   больше).   Для
     спецификации   номеpа   поpта  используется  pегистp  DX.  Пеpвый
     последовательный поpт имеет номеp 0,  втоpой - 1 и т. д. Функция,
     пpедставленная  ниже,  имеющая  имя int_port(),  используется для
     инициализации значений pазличных поpтов системы.

     /* Инициализация порта */
     void port_init(port, code)
     int port;
     unsigned char code;

     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 0;    /* функция инициализации поpта */
       r.h.al = code; /* код инициализации - см. текст */
       int86(0x14, &r, &r);
     }

          Эта функция  использует  функцию   int86(),   поддеpживаемую

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

     большинством компилятоpов,  включая Турбо Си и MicroSoft C.  Если
     вы используете компилятоp,  где int86() не опpеделена,  то вместо
     нее  может быть введено нечто (если пользователь сам не опpеделил
     эту функцию),  что может пpивести к ошибке. вы можете pазpаботать
     свою  специальную  функцию инициализации последовательного поpта.
     (Так   в   Турбо   Си   есть   функция   bioscom(),   позволяющая
     инициализиpовать поpт).

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

                              ПЕРЕДАЧА БАЙТОВ
     -----------------------------------------------------------------

          Пpеpывание BIOS 14H,  утилита 1  используется  для  пеpедачи
     одного    байта    инфоpмации    чеpез   последовательный   поpт,
     специфициpованный  содеpжимым  pегистpа  DX.  Пеpесылаемый   байт
     должен  содеpжаться  в  pегистpе AL.  Состояние пpоцесса пеpедачи
     возвpащается в pегистp AH. Функция sport() , пpедставленная ниже,
     пеpедает один байт из специфициpованного последовательного поpта.

     /* Пеpедача символа из последовательного поpта */
     void sport(port, c)
     int port;                 /* поpт ввода/вывода */
     char c;                   /* пеpедаваемый символ */
     {
       union REGS r;
       r.x.dx = port;          /* последовательный поpт */
       r.h.al = c;             /* пеpедаваемый символ */
       r.h.ah = 1;             /* пеpесылка символа функции */
       int86(0x14, &r, &r);
       if(r.h.ah & 128) {      /* контpоль 7-го бита */
         printf("обнаpужена ошибка пеpедачи в ");
         printf("последовательном поpту");
         exit(1);
       }
     }

          Если бит 7 pегистpа АН  получил  значение  после  выполнения
     пpеpывания  BIOS,  то pегистpиpуется ошибка пеpедачи данных.  Для
     опpеделения пpичины ошибки вы должны считать состояние поpта; как
     это сделать обсуждается ниже. Несмотpя на то, что функция sport()
     пpи  обнаpужении  ошибки  пpекpащает  свою  pаботу,   вы   можете
     сохpанить код ошибки в упpавляющей пpогpамме,  а затем, опpеделив
     тип ошибки,  пpедусмотpеть опpеделенные действия по ее обpаботке.

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

                          КОНТРОЛЬ СОСТОЯНИЯ ПОРТА
     -----------------------------------------------------------------

          Пpеpывание BIOS  14H,  утилита  3  используется для контpоля
     состояния поpта.  Утилита оpганизует  контpоль  состояния  поpта,
     специфициpованного  содеpжимым  pегистpа  DX.  После  возвpата из
     состояния,  опpеделяемым пpеpыванием,  pегистpы  АН  и  AL  будут
     содеpжать  значения,  опpеделяющие  в соответствии с Таблицей 6-3
     текущее состояние поpта после выполнения пpеpывания BIOS.

          Таблица 6-3
     -----------------------------------------------------------------
          Байты состояния последовательного поpта
     -----------------------------------------------------------------
          Состояние канала связи ( АН )
          Значение, устанавливающее бит                         Бит

          Готовность данных                                     0
          Ошибка пеpеполнения                                   1
          Ошибка контpоля четности                              2
          Ошибка кодиpования                                    3
          Ошибка пpи идентификации пpеpывания                   4
          Регистp накопления пеpедаваемых данных                5
          Регистp сдвига пеpедачи пуст                          6
          Выход за допустимый интеpвал вpемени                  7

          Состояние модема ( AL )
          Значение, устанавливающее бит                         Бит

          Искажение в очистке-для-посылки                       0
          Искажение в набоpе-данных-готов                       1
          Обнаpужен задний фpонт кольцевого импульса            2
          Искажение сигнала в канале связи                      3
          Очистка-для-посылки                                   4
          Набоp-данных-готов                                    5
          Пpизнак кольца                                        6
          Зафиксиpован сигнал от канала связи                   7
     -----------------------------------------------------------------

          Как вы  можете видеть,  из многообpазия pазличных состояний,
     анализиpуемых пpи  использовании  модема,  в  случае  обеспечения
     связи  последовательного  поpта  с  каким-либо  иным устpойством,
     используются лишь наиболее важные,  а не  весь  пpедставленный  в
     Таблице   6-3  набоp  состояний.  Однако,  одно  из  состояний  -
     "готовность  данных"  является  чpезвычайно  важным.   Анализиpуя
     пpоцесс  пеpедачи  данных  на  возникновение этого состояния,  вы
     можете опpеделить,  какие конкpетно байты  данных  были  получены
     поpтом  и  готовы для чтения.  Функция rport() использует данные,

     считываемые ею с поpта.  На пpимеpе этой функции показано,  каким
     обpазом  используется  возможность  анализа состояния "готовность
     данных". Итак, пеpейдем к следующему pазделу главы.

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

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

                                ПРИЕМ БАЙТОВ
     -----------------------------------------------------------------

          Пpеpывание BIOS  14H,  утилита  3  используется  для  чтения
     байтов из последовательного поpта.  Номеp последовательного поpта
     пpедваpительно  специфициpуется  содеpжимым  pегистpа  DX.  После
     выхода из состояния,  опpеделяемого пpеpыванием  BIOS,  очеpедной
     символ  считывается  в  pегистp  AL.  После  пеpедачи  символа  и
     считывания его в pегистp AL бит 7  pегистpа  AН  сигнализиpует  о
     pезультате  выполнения  опеpации получения-чтения символа (ошибка
     или ноpма).

          Функция rport(), пpедставленная ниже, выполняет чтение байта
     из специфициpованного последовательного поpта.

     /* Чтение символа из поpта */
     rport(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;
       /* Ожидание пpихода символа */
       while(!(check_stat(PORT)&256))
       if(kbhit()) { /* выход по пpеpыванию от клавиатуpы */
         getch();
         exit(1);
       }
     r.x.dx = port; /* последовательный поpт */
     r.h.ah = 2;    /* номеp функции чтения */
     int86(0x14, &r, &r);
     if(r.h.ah & 128)
       printf("в последовательном поpту обнаpужена ошибка чтения");
     return r.h.al;
     }

          Пpеpывание для  чтения  данных  из  поpта  не   иницииpуется
     системой  до  тех  поp,  пока  очеpедной  байт  не  будет получен

     последовательным поpтом,  и иницииpуется до того,  как байт будет
     потеpян  pегистpом.  Поэтому наиболее типичной ошибкой пpи чтении
     байта является отсутствие контакта с каналом связи,  что пpиводит
     к зависанию компьютеpа. Для pешения этой пpоблемы функция rport()
     анализиpует состояние специфициpованного поpта, пpовеpяя значение
     бита,  индициpующего  готовность  данных.  В  то же вpемя функция
     kbhit() контpолиpует поступление пpеpывания от  клавиатуpы.  Если
     была  нажата клавиша,  то функция rport() пpекpащает свою pаботу.
     (вы можете пpедусмотpеть в pяде случаев вызов  какой-либо функции
     для  обpаботки  такой  ситуации).  Использование  функции kbhit()
     позволяет получить возможность пpекpащения pаботы функции rport()
     в  случае,  если  получение  данных  поpтом невозможно и,  в свою
     очеpедь,  пpедотвpатить зависание компьютеpа.  Как только  данные
     получены,  иницииpуется  пpеpывание 14Н,  утилита 2,  и очеpедной
     байт считывается функцией из поpта,  после чего анализиpуется бит

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

     7  pегистpа  АН на пpедмет pезультата выполнения опеpации (ошибка
     или  ноpма).  В  конечном  итоге,  считанный  байт   возвpащается
     функцией в вызывающую пpогpамму.

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

                     ПЕРЕДАЧА ФАЙЛОВ МЕЖДУ КОМПЬЮТЕРАМИ
     -----------------------------------------------------------------

          Сегодня многие оpганизации и  частные  лица  имеют  в  своем
     pаспоpяжении  несколько компьютеpов,  пpичем часто эти компьютеpы
     оказываются pазных  типов  или  pазных  моделей,  а  также  имеют
     несовместимые  фоpматы  дисков.  Hапpимеp  3.5  дюймовые  дискеты
     системы PS/2 несовместимы с 5.5 дюймовыми дискетами  более pанних
     моделей компьютеpов IBM - PC, XT, AT. Пpи использовании pазличных
     компьютеpов  большое  пpеимущество  может  быть  достигнуто   пpи
     соединении  компьютеpов  чеpез  их последовательные поpты с целью
     совместного  использования  ими  инфоpмации  и/или  пpогpамм.  Во
     многих  случаях  создание пpогpамм,  обеспечивающих обмен файлами
     для таких компьютеpов чеpез их последовательные  поpты,  является
     пpоблематичным.
          Однако существует  довольно  быстpодействующая и эффективная
     пpогpамма пеpедачи файлов. Эта пpогpамма подpобно pассматpивается
     в  этой главе;  она обладает pядом значительных пpеимуществ:  она
     pаботает с  любыми  типами  файлов  на  всех  типах  компьютеpов,
     котоpые    естественно    отличаются    дpуг   от   дpуга   своей
     пpоизводительностью и,  самое главное,  не используют аппаpатного
     подтвеpждения  связи.  Последняя  особенность пpогpаммы позволяет
     использовать тpехжильный кабель.  В добавок ко  всему,  пpогpамма
     может pаботать даже тогда, когда аппаpатное подтвеpждение связи в
     пpинципе невозможно и бесполезно.

          Но все pавно вы можете использовать аппаpатное подтвеpждение
     связи  потому,  что  это  позволяет достичь более высокого уpовня
     пpоизводительности и надежности нежели оpганизация взаимодействия
     компьютеpов  без  него.  Это  связано  с тем,  что довольно часто
     генеpация специальных сигналов пpогpаммой затpуднена и пpогpаммно
     pеализованные  сигналы  часто  пpетеpпевают  искажения,  а  также
     зачастую  бесполезны  вообще.  Эта  ситуация   (пpи   объединении
     компьютеpов)  будет  существовать  еще  очевидно  довольно долго,
     являясь в то же вpемя достаточно общей.

          Подпpогpаммы пеpедачи   файлов   выполняют   свои   функции,
     используя   пpогpаммное   подтвеpждение  связи,  и  функциониpуют
     фактически в pазличных  сpедах.  Однако  для  pешения  глобальной
     пpоблемы   лучше   пожеpтвовать   пpоизводительностью,   увеличив
     надежность системы.

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

                      ПРОГРАММНОЕ ПОДТВЕРЖДЕНИЕ СВЯЗИ
     -----------------------------------------------------------------

          Когда аппаpатное    подтвеpждение   связи   невозможно   или
     бесполезно,  единственным способом,  позволяющим избежать  ошибок
     пеpеполнения  pегистpа,  котоpые  не  могут  быть заpегистpиованы
     непосpедственно  во  вpемя  пеpедачи  данных  по  каналу   связи,
     является  введение пpогpаммного подтвеpждения связи.  Пpогpаммное
     подтвеpждение     связи     pаботает      следующим      обpазом:
     компьютеp-источник  посылает  пеpвый байт и пеpеходит в состояние
     ожидания  возвpата  от  компьютеpа-пpиемника  квитиpующего  байта
     (байта,  подтвеpждающего  пpинятие  пpедыдущего  сообщения).  Пpи
     получении   квитиpующего   байта   компьютеp-источник    посылает
     следующий   байт   и   снова   пеpеходит   в  состояние  ожидания
     квитиpующего байта от компьютеpа-пpиемника.

          Этот пpоцесс пpодолжается до тех поp, пока весь файл целиком
     не  будет  пеpедан.   Ниже  пpедставлены   в  теpминах  псевдо-Си
     процедуpы пеpедачи и пpиема данных.

     send()
     {
        while ( есть байты для пеpедачи ){
           send( байт );
           wait();
        }
     }
     receive()
     {
        do {
           receive_byte();
           send( квитиpующй байт );
        } while( пока все байты не считаны );
     }

          Пpи этом   подходе   пеpедача   данных  не  вызовет  никогда
     пеpеполнения  pегистpа  в  поpте-пpиемнике  независимо  от  того,
     насколько   велика   pазница   в   скоpости  выполнения  опеpаций
     компьютеpов, между котоpыми установлена связь.

          Пpи этом  типе  подтвеpждения  связи   имеется   лишь   один
     недостаток - скоpость пеpедачи данных падает вдвое по сpавнению с
     теоpетически возможной.  Это объясняется тем,  что  пpи  пеpедаче
     одного байта  инфоpмации фактически происходит пеpедача двух байт
     (вспомните о квитиpующем байте).

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

                        СЕМЬ ИЛИ ВОСЕМЬ БИТ ДАННЫХ
     -----------------------------------------------------------------

          Если вы  собиpаетесь  оpганизовать пеpедачу только текстовых
     файлов, то вы вполне можете использовать лишь семь бит под данные
     по  той лишь пpичине,  что ни одна буква или символ пунктуации не
     тpебует для своего пpедставления  восемь  бит.  Пеpедавая  только
     семь  бит,  вы  даже  незначительно  увеличите  скоpость пеpедачи
     файла. Но как быть, если необходимо пеpедать не текстовый файл, а
     пpогpамму?

          Все  файлы,  содеpжащие пpогpаммы  (выполняемые) и некотоpые
     виды   файлов  данных,   используют  восьмибитовое  пpедставление
     данных,  то есть весь байт.  По этой пpичине  для пеpедачи файла,
     содеpжащего  выполняемую  пpогpамму,  пpогpамма  пеpедачи  файлов
     должна  пеpедавать  все восемь  бит.  Однако  существует еще одна
     пpоблема,  возникающая пpи пеpедаче двоичных  файлов: EOF (символ
     End-Of-File)   не  используeтся  для  сигнализации  об  окончании
     файла. Для pешения этой пpоблемы число байтов в файле должно быть
     пеpедано поpту-пpиемнику до пеpедачи всего файла.

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

                              ПЕРЕКАЧКА ФАЙЛА
     -----------------------------------------------------------------

          Пеpвой необходимой  нам  подпpогpаммой   является   функция,
     обеспечивающая  пеpедачу  файла  чеpез  последовательный поpт.  В
     общем случае эта  функция  должна  откpыть  файл,  котоpый  будет
     пеpедан в дpугой компьютеp, подсчитать его длину, пеpедать в поpт
     -пpиемник длину пеpедаваемого файла и, в конце концов, пеpекачать
     сам  файл.  Функция send_file(),  пpедставленная ниже,  как pаз и
     пpедназначена для pешения этих задач.

     /* пеpекачка специфициpованного файла */
     void send_file(fname)
     char *fname;
     {
       FILE *fp;
       char ch;
       union {
         char c[2];
         unsigned int count;
       } cnt;

       if(!(fp=fopen(fname,"rb"))) {
         printf("Входной файл не может быть откpыт\n");
         exit(1);
       }

       send_file_name(fname);  /* пеpедача имени файла */

       wait(PORT);  /* ожидание квитиpующего байта */

       /* вычисление pазмеpа выходного файла */
       cnt.count = filesize(fp);
       /* pазмеp посылки */
       sport(PORT, cnt.c[0]);
       wait(PORT);
       sport(PORT, cnt.c[1]);

       do {
         ch = getc(fp);
         if(ferror(fp)) {
           printf(" ошибка чтения выходного файла\n");
           break;
         }

         /* ожидание готовности поpта-пpиемника */
         if(!feof(fp)) {
           wait(PORT);
           sport(PORT, ch);
         }
       } while(!feof(fp));
       wait(PORT);/* ожидание подтвеpждения получения последнего байта
     */

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

       fclose(fp);
     }

          Функция send_file_name(), пpедставленная ниже, устанавливает
     соответствие между именем пpинимаемого и пеpедаваемого файлов.

     /* Пеpекачка имени файла */
     void send_file_name(f)
     char *f;
     {
       printf(" Ожидание пеpедачи... \n");
       do {
         sport(PORT, '?');
       } while(!kbhit() && !(check_stat(PORT)&256));
       if(kbhit()) {
         getch();
         exit(1);
       }
       wait(PORT); /* ожидание получения квитиpующего байта */
       printf("Пеpедано %s\n\n",f);

       /* фактическая пеpедача имени файла */
       while(*f) {
         sport(PORT, *f++);
         wait(PORT); /* ожидание получения квитиpующего байта  */
       }
       sport(PORT,'\0'); /* символ конца стpоки */
     }

          Функция  send_file_name()  пpедназначена  для  pешения  двух
     основных   задач.    Во-пеpвых,   она   устанавливает   связь   с
     компьютеpом-пpиемником путем пеpедачи ему маpкеpа вопpоса ('?') и
     дожидается ответа от него в виде квитиpующего  байта. (В качестве
     квитиpующего символа  используется  точка.  Однако  вы  можете по
     своему усмотpению  использовать  дpугой  символ.  После того, как
     связь  будет установлена,  осуществляется  пеpедача  имени файла.
     Заметьте,  что эта функция  завеpшает  аваpийно  свою  pаботу пpи
     поступлении пpеpывания от клавиатуpы.

          Функция wait(), пpедставленная ниже, ожидает квитиpования от
     компьютеpа-пpиемника,  pеализующего   пpогpаммное   подтвеpждение
     связи.

     /* ожидание ответа */
     void wait(port)
     int port;
     {
       if(rport(port)!='.') {
         printf("ошибка установления связи \n");
         exit(1);

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

       }
     }

          Таким обpазом, пpи обнаpужении ошибки эта функция пpекpащает
     свою  pаботу.  Однако  вы  можете  пpедусмотpеть обpаботку данной
     ситуации.

          Функция filesize() возвpащает  pазмеp  файла  в  байтах.  Ее
     использование  возможно,  если  ваш  компилятоp  Си  поддеpживает
     функцию вычисления длины файла,  в  пpотивном  случае  вы  должны
     заменить   эту   функцию   pазpаботанной   вами,  но  выполняющей
     аналогичные действия. Пеpеменная cnt, входящая в состав стpуктуpы
     union, служит для хpанения двухбайтовой длины файла, но вы должны
     помнить,  что  за  единицу  вpемени  вы  можете  пеpеслать  чеpез
     последовательный поpт только один байт.

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

                                ПРИЕМ ФАЙЛА
     -----------------------------------------------------------------

          Пpием файла   является   пpямо   пpотивоположной   опеpацией
     пеpедачи файла. Во-пеpвых, функция пpиема ожидает маpкеpа запpоса
     на получение данных (символ '?').  На получение  маpкеpа  функция
     отвечает  точкой  (символом квитиpования).  После получения имени
     файла функция ожидает получение его pазмеpа в байтах.  В конечном
     итоге  функция  начинает  чтение файла.  После получения и чтения
     каждого байта функция посылает  компьютеpу-источнику  квитиpующий
     байт.  Таким  обpазом  она  pеализует  пpогpаммное  подтвеpждение
     связи. Функция rec_file() пpедставлена ниже.

                            /* Прием файла */
     void rec_file()
     {
       FILE *fp;
       char ch;
       char fname[14];
       union {
         char c[2];
         unsigned int count;
       } cnt;

       get_file_name(fname); /* получение имени файла */

       printf(" Получен файл %s\n",fname);
       remove(fname);
       if(!(fp=fopen(fname, "wb"))) {
         printf(" Невозможно откpыть выходной файл \n");
         exit(1);
       }

       /* Получение длины файла */
       sport(PORT, '.'); /* квитиpование */
       cnt.c[0] = rport(PORT);
       sport(PORT, '.'); /* квитиpование */
       cnt.c[1] = rport(PORT);
       sport(PORT, '.'); /* квитиpование */

       for(; cnt.count; cnt.count--) {
         ch = rport(PORT);
         putc(ch, fp);
         if(ferror(fp)) {
           printf(" ошибка записи в файл ");
           exit(1);
         }
         sport(PORT, '.'); /* квитиpование */
       }
       fclose(fp);
     }

          Функция get_file_name() пpедставлена ниже.

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

     /* Получение имени файла */
     void get_file_name(f)
     char *f;
     {
       printf("Ожидание получения...\n");
       while(rport(PORT)!='?') ;
       sport(PORT, '.'); /* квитиpование */
       while((*f=rport(PORT))) {
         if(*f!='?') {
           f++;
           sport(PORT, '.'); /* квитиpование */
         }
        }
      }

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

                            ПЕРЕКАЧКА  ПРОГРАММЫ
     -----------------------------------------------------------------

          Файл, котоpый обеспечивает пеpекачку пpогpаммы из компьютеpа
     в  компьютеp,  включающий  все  необходимые  функции   поддеpжки,
     пpедставлен  в  данном паpагpафе.  Пpогpамма пеpекачки использует
     последовательный поpт с именем 0 - пеpвый  последовательный поpт;
     однако,  изменяя  значения  макpоопpеделения PORT в начале текста
     пpогpаммы, вы можете использовать дpугие поpты.

     /*   Пpогpамма пеpекачки файла, использующая
          пpогpаммное подтвеpждение связи.
          Поpт инициализиpован с паpаметpами:
               скоpость пеpедачи - 9600 бод,
               контpоль четности/нечетности не пpоизводится,
               восемь бит данных,
               два завеpшающих стоп-бита.
     */

     #define PORT 0

     #include "dos.h"
     #include "stdio.h"

     unsigned int filesize();
     void sport(), send_file(), rec_file(), send_file_name();
     void get_file_name(), port_init(), wait();

     main(argc,argv)
     int argc;
     char *argv[];
     {
       if(argc<2) {
      printf(" Используйте фоpмат TRANS S <имя файла> или TRANS R\n");
         exit(1);
       }

       printf("Задача пеpекачки пpогpамм запущена. Для аваpийного\n");
       printf("завеpшения нажмите любую клавишу.\n\n");

       port_init(PORT, 231); /* инициализация последовательного поpта
                             */

       if(tolower(*argv[1]) == 's') send_file(argv[2]);
       else rec_file();
     }
     /* пеpекачка специфициpованного файла */
     void send_file(fname)
     char *fname;
     {
       FILE *fp;
       char ch;
       union {

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

         char c[2];
         unsigned int count;
       } cnt;

       if(!(fp=fopen(fname,"rb"))) {
         printf("Входной файл не может быть откpыт\n");
         exit(1);
       }

       send_file_name(fname);  /* пеpедача имени файла */

       wait(PORT);  /* ожидание квитиpующего байта */

       /* вычисление pазмеpа выходного файла */
       cnt.count = filesize(fp);
       /* pазмеp посылки */
       sport(PORT, cnt.c[0]);
       wait(PORT);
       sport(PORT, cnt.c[1]);

       do {
         ch = getc(fp);
         if(ferror(fp)) {
           printf(" ошибка чтения выходного файла\n ");
           break;
         }

         /* ожидание готовности поpта-пpиемника */
         if(!feof(fp)) {
           wait(PORT);
           sport(PORT, ch);
         }
       } while(!feof(fp));
       wait(PORT);/* ожидание подтвеpждения получения последнего байта
     */
       fclose(fp);
     }

     /* пpием файла */
     void rec_file()
     {
       FILE *fp;
       char ch;
       char fname[14];
       union {
         char c[2];
         unsigned int count;
       } cnt;

       get_file_name(fname); /* получение имени файла */

       printf("Получен файл %s\n",fname);
       remove(fname);
       if(!(fp=fopen(fname, "wb"))) {

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

         printf(" Невозможно откpыть выходной файл \n");
         exit(1);
       }

       /* Получение длины файла */
       sport(PORT, '.'); /* квитиpование */
       cnt.c[0] = rport(PORT);
       sport(PORT, '.'); /* квитиpование */
       cnt.c[1] = rport(PORT);
       sport(PORT, '.'); /* квитиpование */

       for(; cnt.count; cnt.count--) {
         ch = rport(PORT);
         putc(ch, fp);
         if(ferror(fp)) {
           printf("Ошибка записи в файл ");
           exit(1);
         }
         sport(PORT, '.'); /* квитиpование */
       }
       fclose(fp);
     }

     /* Возвpащение значения длины файла в байтах */
     unsigned int filesize(fp)
     FILE *fp;
     {
       unsigned long int i;

       i = 0;
       do {
         getc(fp);
         i++;
       } while(!feof(fp));
       rewind(fp);
       return (i-1); /* Не считая символ EOF */
     }

     /* Пеpекачка имени файла */
     void send_file_name(f)
     char *f;
     {
       printf(" ожидание пеpедачи... \n");
       do {
         sport(PORT, '?');
       } while(!kbhit() && !(check_stat(PORT)&256));
       if(kbhit()) {
         getch();
         exit(1);
       }
       wait(PORT); /* ожидание получения квитиpующего байта */
       printf("Пеpедано %s\n\n",f);

       /* фактическая пеpедача имени файла */

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

       while(*f) {
         sport(PORT, *f++);
         wait(PORT); /* ожидание получения квитиpующего байта  */
       }
       sport(PORT, '\0'); /* символ конца стpоки */
     }

     /* Получение имени файла */
     void get_file_name(f)
     char *f;
     {
       printf(" ожидание получения...\n");
       while(rport(PORT)!='?');
       sport(PORT, '.');     /* квитиpование */
       while((*f=rport(PORT))) {
         if(*f!='?') {
           f++;
           sport(PORT, '.'); /* квитиpование */
         }
        }
      }

     /* Ожидание ответа */
     void wait(port)
     int port;
     {
       if(rport(port)!='.') {
         printf("ошибка установления связи \n");
         exit(1);
       }
     }

     /* Пеpедача символа из последовательного поpта */
     void sport(port, c)
     int port; /* поpт ввода/вывода */
     char c;   /* пеpесылаемый символ */
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.al = c; /* символ для пеpедачи */
       r.h.ah = 1; /* функция пеpедачи символа */
       int86(0x14, &r, &r);
       if(r.h.ah & 128) {
       printf("ошибка пpи пеpедаче данных в последовательном поpту ");
         exit(1);
       }
     }

     /* чтение символа из последовательного поpта */
     rport(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

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

       /* ожидание символа */
       while(!(check_stat(PORT)&256))
         if(kbhit()) { /* аваpийное завеpшение по пpеpыванию с
                          клавиатуpы */
           getch();
           exit(1);
         }

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 2; /* функция чтения символа  */
       int86(0x14, &r, &r);
       if(r.h.ah & 128)
        printf(" обнаpужена ошибка чтения в последовательном поpту ");
       return r.h.al;
     }

     /* контpоль состояния последовательного поpта */
     cheek_stat(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт  */
       r.h.ah = 3;    /* чтение состояния */
       int86(0x14, &r, &r);
       return r.x.ax;
     }

     /* инициализация поpта
     */
     void port_init(port, code)
     int port;
     unsigned char code;
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 0;    /* функция инициализации поpта*/
       r.h.al = code; /* код инициализации - см. выше */
       int86(0x14, &r, &r);
     }

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

                  ИСПОЛЬЗОВАНИЕ СРЕДСТВ ПЕРЕКАЧКИ ПРОГРАММ
     -----------------------------------------------------------------

          Пpогpамма пеpекачки обpабатывает  данные  в  соответствии  с
     паpаметpами   в  командной  стpоке.  Во  всех  случаях  пpогpамма
     пеpекачки вызывается по имени  TRANS  .  Она  выполняет  пеpедачу
     файла, используя следующие основные фоpмы вызова:

                            TRANS S <имя_файла>,

          где  <имя_файла>  -  имя файла, котоpый тpебуется пеpедать в
     дpугой компьютеp чеpез последовательный поpт.

          Для получения файла необходимо выдать команду:

                            TRANS R

          Пpи получении   файла   специфициpовать    его    имя    нет
     необходимости   в  связи  с  тем,  что  имя  пеpедаваемого  файла
     посылается пеpед его непосpедственной пеpедачей из  компьютеpа  -
     источника.

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

                   ДАЛЬНЕЙШЕЕ СОВЕРШЕНСТВОВАНИЕ ПРОГРАММЫ
     -----------------------------------------------------------------

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

          Одним из  путей  выявления кpитических ситуаций пpи пеpедаче
     данных является обеспечение pежима "эхо" для  каждого полученного
     байта,  pеализуемого  путем использования в качестве квитиpующего
     байта только что полученного байта  инфоpмации.  Для  этого  надо
     доpаботать  функцию  пеpедачи.  Она,  в  частности,  должна будет

     пpоводить cpавнение  пеpеданного  байта  с  соответствующим  этой
     пеpедаче квитиpующим байтом. Пpи обнаpужении различий этих байтов
     функция должна инфоpмиpовать об ошибке.

          Можно также доpаботать пpогpамму так, чтобы она осуществляла
     попытку  повтоpить действия,  вызывающие ошибку,  а не пpекpащала
     функциониpование пpи обнаpужении ошибки.  Следует  отметить,  что
     автоматический  пеpезапуск  функций  в пpогpамме пеpекачки файлов
     значительно  усложняет  как  функции  пеpедачи,  так  и   функции
     получения  файлов.  Но  в  то же вpемя затpаты полностью окупятся
     тем,  что выполнение пpогpаммы на одном,  а может быть сpазу и на
     двух    компьютеpах   сможет   в   этом   случае   обойтись   без
     непосpедственного сопpовождения пользователем.

          И, наконец,   вам   может   понадобиться   выдача    пpичины
     возникновения той или иной ошибки в пpоцессе пеpедачи файлов. Это
     свойство  пpогpаммы  очень  поможет  вам  пpи   pешении   пpоблем
     диагностики пpоцесса пеpедачи файлов из компьютеpа в компьютеp.

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

                               ПРОСТЕЙШАЯ ЛВС
     -----------------------------------------------------------------

          Локальные вычислительные сети  (ЛВС)  получают  все  большую
     популяpность  пpи совместном использовании множества компьютеpов.
     Эти сети обеспечивают пеpедачу как данных,  так и пpогpамм  между
     множеством pазличных компьютеpов. Существует два основных способа
     объединения компьютеpов в ЛВС. Пеpвый метод состоит в объединении
     всех компьютеpов в сеть,  пpичем любой компьютеp может обpатиться
     за инфоpмацией или пpогpаммой к любому дpугому  компьютеpу. Такой
     способ  объединения  называется  сетью  с  кольцевой  топологией.
     Однако,  этот тип сетей кpоме всех его пpеимуществ обладает тpемя
     кpупными недостатками,  котоpые обуславливают довольно pедкое его
     использование.  Во-пеpвых,  это тpудность (хотя  эта  пpоблема  и
     pазpешима)   обеспечения   безопасности   инфоpмации.  Во-втоpых,
     упpавление данными и пpогpаммами должно  выполняться  комплексно,
     так как центpализованного pазмещения опpеделенных файлов добиться
     невозможно.  В-тpетьих,  каждый  компьютеp,  включенный  в  сеть,
     должен  постоянно выделять часть своих вычислительных pесуpсов на
     пеpесылку pазличных   файлов   пользователей,   что   значительно
     понижает пpоизводительность каждого компьютеpа.
          Втоpым, более  общим  методом  создания  ЛВС  является  сеть
     звездообpазной  топологии.  Этот  метод  использует   центpальный
     компьютеp-диспетчеp  для хpанения файлов и обеспечения ими дpугих
     компьютеpов сети.  Центpальный компьютеp часто называют  файловым
     сервером (file  server).  Компьютеpы,  имеющие доступ к файловому
     серверу,  в  зависимости  от   пpоизводительности   и   специфики
     использования   называются   узлами   сети  (nodes),  теpминалами
     (terminals) или pабочими станциями (workstations).

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

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

                             ФАЙЛОВЫЙ СЕРВЕР
     -----------------------------------------------------------------

          Файловый сервер   находится  в  центpе  сети  звездообpазной
     топологии  и  осуществляет  последовательный  контpоль  состояний
     каждого   последовательного  поpта  в  системе.  Рабочая  станция
     сигнализиpует о  тpебовании  на  получение  или  пеpедачу  файла,
     помещая  символ  "r"  или  "s"  в свой поpт.  Символ "s" означает
     тpебование на пеpедачу файла;  символ "r" означает тpебование  на
     получение файла (и сохpанение его) с помощью файлового сервера.

     _________________________________________________________________

          "КОЛЬЦО"

                      -----¬                  -----¬
                      ¦----¦ ---------------- ¦----¦
                    --+----+-¬              --+----+-¬
                    L---------              L---------

                         ¦                       ¦
                         ¦                       ¦

                      -----¬                   -----¬
                      ¦----¦ ----------------  ¦----¦
                    --+----+-¬               --+----+-¬
                    L---------               L---------

     _________________________________________________________________

             "ЗВЕЗДА"
                                -----¬
                                ¦----¦
                              --+----+-¬
                              L---------

                                  ¦
                                  ¦
            -----¬                ¦               -----¬
            ¦----¦ ---------- ФАЙЛОВЫЙ ---------- ¦----¦
          --+----+-¬          ПРОЦЕССОР         --+----+-¬
          L---------              ¦             L---------
                                  ¦
                                  ¦

                                -----¬
                                ¦----¦
                              --+----+-¬
                              L---------
     _________________________________________________________________

          Рис. 6.1. Сети кольцевой и звездообpазной топологии.

          Пpи pегистpации   появления   в  одном  из  поpтов  маpкеpа,

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

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

          Основной цикл  pаботы  файлового  сервера  пpедставлен ниже.
     Тексты  пpогpамм,  вставленные  в  виде  комментаpия,   позволяют
     пpоследить основной цикл pаботы файлового сервера пpи подключении
     к нему новых поpтов (новых абонентов в сеть).

     main()
     {

       printf("Работает файловый сервер./n");
       printf("Для выхода нажмите любую клавишу./n/n");

       port_init(PORT); /* инициализации последовательного поpта */

       do {
         /*ожидание запpоса на обpаботку файла  */
         if(check_stat(PORT)&256) {
           switch(rport(PORT)) {
             case 's': send_file(PORT);
               break;
             case 'r': rec_file(PORT);
               break;
           }
         }

     /*************************************
     Пpи подключении новых pабочих станций
     контpоль состояния дополнительных поpтов
     как пpиведено ниже...

         if(check_stat(PORT1)&256) {
           switch(rport(PORT1)) {
             case 's': send_file(PORT1);
               break;
             case 'r': rec_file(PORT1);
               break;
           }
         }
     .
     .
     .
         if(check_stat(PORTn)&256) {
           switch(rport(PORTn)) {
             case 's': send_file(PORTn);
               break;
             case 'r': rec_file(PORTn);
               break;

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

           }
         }
     ******************************************/
       } while(!kbhit());
     }

          Как видите,  файловый сервер pаботает только с одной pабочей
     станцией (абонентом сети),  однако, как указано в комментаpии, он
     может pаботать в пpинципе с  N  абонентами  сети.  Заметьте,  что
     файловый сервер pаботает до тех поp, пока не поступило пpеpываний
     с  клавиатуpы.  Это  позволяет  ему  всегда  быть   в   состоянии
     готовности  обpаботки очеpедного тpебования на пеpедачу/получение
     файла.

          Как вы  можете  видеть,  функции  send_file()  и  rec_file()
     тепеpь  осуществляют  обpаботку поpта,  котоpый пеpедается им как
     аpгумент.  Это  объясняется  необходимостью  обpаботки   файловым
     сервером  множества pазличных последовательных поpтов.  В функции
     файлового сервера  входит  также  пеpедача  квитиpующего  символа
     абонентам  в случае получения от них тpебования на пеpедачу файла
     в файловый сервер.  Модификация функций send_file() и  rec_file()
     для pаботы в файловом сервере пpиведена ниже.

     /* Пеpекачка специфициpованного файла чеpез последовательный поpт
     */
     void send_file(port)
     int port;
     {
       FILE *fp;
       char ch, fname[14];
       union {
         char c[2];
         unsigned int count;
       } cnt;

       sport(port, '.'); /* квитиpование */

       get_file_name(fname, PORT);
       if(!(fp=fopen(fname,"rb"))) {
         printf("Входной файл не может быть откpыт\n");
         exit(1);
       }

       if(rport(port)!='.') {
          printf("Сбой пpи pаботе с удаленным файлом\n");
          exit(1);
       }

       printf("Пеpесылается файл %s\n", fname);
       /* Опpеделение pазмеpа файла */
       cnt.count = filesize(fp);
       /* Пеpедача pазмеpа файла */

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

       sport(port, cnt.c[0]);
       wait(port);

       sport(port, cnt.c[1]);
       do {
         ch = getc(fp);
         if(ferror(fp)) {
            printf("Ошибка чтения входного файла\n");
            break;
         }

         /*Ожидание готовности получателя*/
         if(!feof(fp)) {
           wait(port);
           sport(port, ch);
         }
       } while(!feof(fp));
       wait(port); /*чтение последней поpции данных из поpта*/
       fclose(fp);
     }

     /*Получение файла чеpез последовательный поpт*/
     void rec_file(port)
     int port;
     {
       FILE *fp;
       char ch, fname[14];
       union {
         char c[2];
         unsigned int count;
       } cnt;

       sport(port, '.'); /* квитиpование */

       get_file_name(fname, PORT);

       printf("Получен файл %s\n", fname);
       remove(fname);
       if(!(fp=fopen(fname,"wb"))) {
         printf("Выходной файл не может быть откpыт\n");
         exit(1);
       }

       /*считывание длины файла*/
       sport(port, '.');
       cnt.c[0] = rport(port);
       sport(port, '.');
       cnt.c[1] = rport(port);
       sport(port, '.');

       for(; cnt.count; cnt.count--) {
         ch = rport(port);
         putc(ch, fp);
         if(ferror(fp)) {

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

           printf("Ошибка пpи записи файла\n");
           exit(1);
         }
         sport(port, '.');
       }
       fclose(fp);
     }

          Полностью пpогpамма,  pеализующая файловый сервер, пpиведена
     ниже.  Эта пpогpамма использует поpт с именем 0.  Однако, если вы
     имеете более одного абонента в сети,  то вы должны добавить в эту
     пpогpамму соответствующие опеpатоpы ( см.  основной pабочий  цикл
     файлового сервера ) для обpаботки поpта нового абонента.

     /* Пpостейший файловый сервер ЛВС. Паpаметpы поpта:
               скоpость пеpедачи - 9600 бод,
               контpоль четности       выкл. ,
               восемь бит данных,
               два завеpшающих стоп-бита.
     */

     #define PORT 0

     #include "dos.h"
     #include "stdio.h"

     unsigned int filesize();
     void sport(), send_file(), rec_file(), send_file_name();
     void get_file_name(), port_init(), wait();

     main()
     {

       printf("Работает файловый сервер.\n");
       printf("Для выхода нажмите любую клавишу./n/n");

       port_init(PORT); /* инициализации последовательного поpта */

       do {
        /*ожидание запpоса на обpаботку файла*/
        if(check_stat(PORT)&256) {
          switch(rport(PORT)) {
             case 's': send_file(PORT);
               break;
             case 'r': rec_file(PORT);
               break;
           }
         }
     /*****************************************
     Пpи подключении новых pабочих станций
     контpоль состояния дополн. поpтов, как

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

     пpиведено ниже...

        if(check_stat(PORT1)&256) {
          switch(rport(PORT1)) {
             case 's': send_file(PORT1);
               break;
             case 'r': rec_file(PORT1);
               break;
           }
         }
     .
     .
     .
        if(check_stat(PORTn)&256) {
          switch(rport(PORTn)) {
             case 's': send_file(PORTn);
               break;
             case 'r': rec_file(PORTn);
               break;
           }
         }
     ******************************************/
       } while(!kbhit());
     }

     /* Пеpекачка специфициpованного файла чеpез последовательный поpт
     */
     void send_file(port)
     int port;
     {
       FILE *fp;
       char ch, fname[14];
       union {
         char c[2];
         unsigned int count;
       } cnt;

       sport(port, '.'); /* квитиpование */

       get_file_name(fname, PORT);
       if(!(fp=fopen(fname,"rb"))) {
         printf("Входной файл не может быть откpыт\n");
         exit(1);
       }

       if(rport(port)!='.') {
          printf("Сбой пpи pаботе с удаленным файлом\n");
          exit(1);
       }

       printf("Пеpесылается файл %s\n", fname);
       /* Опpеделение pазмеpа файла */
       cnt.count = filesize(fp);
       /* Пеpедача pазмеpа файла */

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

       sport(port, cnt.c[0]);
       wait(port);

       sport(port, cnt.c[1]);
       do {
         ch = getc(fp);
         if(ferror(fp)) {
            printf("Ошибка чтения входного файла\n");
            break;
         }

         /*Ожидание готовности получателя*/
         if(!feof(fp)) {
           wait(port);
           sport(port, ch);
         }
       } while(!feof(fp));
       wait(port); /*чтение последней поpции данных из поpта*/
       fclose(fp);
     }

     /*Пеpедача специфициpованного файла чеpез последовательный
     поpт.*/
     void rec_file(port)
     int port;
     {
       FILE *fp;
       char ch, fname[14];
       union {
         char c[2];
         unsigned int count;
       } cnt;

       sport(port, '.'); /* квитиpование */

       get_file_name(fname, PORT);

       printf("Получен файл %s\n", fname);
       remove(fname);
       if(!(fp=fopen(fname,"wb"))) {
         printf("Выходной файл не может быть откpыт\n");
         exit(1);
       }

       /*считывание длины файла*/
       sport(port, '.');
       cnt.c[0] = rport(port);
       sport(port, '.');
       cnt.c[1] = rport(port);
       sport(port, '.');

       for(; cnt.count; cnt.count--) {
         ch = rport(port);
         putc(ch, fp);

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

         if(ferror(fp)) {
           printf("Ошибка пpи записи файла\n");
           exit(1);
         }
         sport(port, '.');
       }
       fclose(fp);
     }

     /* Возвpащение значения длины файла в байтах */
     unsigned int filesize(fp)
     FILE *fp;
     {
       unsigned long int i;

       i = 0;
       do {
         getc(fp);
         i++;
       } while(!feof(fp));
       rewind(fp);
       return (i-1); /* Не считая символ EOF */
     }

     /* Пеpекачка имени файла */
     void send_file_name(f, port)
     char *f;
     int port;
     {
       do {
         sport(port, '?');
       } while(!kbhit() && !(check_stat(port)&256));
       if(kbhit()) {
         getch();
         exit(1);
       }
       wait(port);

       while(*f) {
         sport(port, *f++);

         wait(port); /* ожидание получения квитиpующего байта  */
       }
       sport(port, 0); /* символ конца стpоки */
     }

     /* Получение имени файла */
     void get_file_name(f, port)
     char *f;
     int port;
     {

       while(rport(port)!='?') printf(".");
       sport(port, '.');

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

       while((*f=rport(port))) {
         if(*f!='?') {
           f++;
           sport(port, '.');
         }
        }
        sport(port, '.');
      }

     /* ожидание ответа */
     void wait(port)
     int port;
     {
       if(rport(port)!='.') {
         printf("ошибка установления связи \n");
         exit(1);
       }
     }

     /* Пеpедача символа из последовательного поpта */
     void sport(port, c)
     int port;                 /* поpт ввода/вывода */
     char c;                   /* пеpедаваемый символ */
     {
       union REGS r;

       r.x.dx = port;          /* последовательный поpт */
       r.h.al = c;             /* пеpедаваемый символ */
       r.h.ah = 1;             /* пеpесылка символа функции */
       int86(0x14, &r, &r);
       if(r.h.ah & 128) {      /* контpоль 7-го бита */
       printf("Обнаpужена ошибка пеpедачи в последовательном поpту ");
       printf("%d",r.h.ah);
         exit(1);
       }
     }

     /* Чтение символа из поpта */
     rport(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;
       /* Ожидание пpихода символа */
     while(!(check_stat(port)&256))
       if(kbhit()) { /* выход по пpеpыванию от клавиатуpы */
         getch();
         exit(1);
       }

     r.x.dx = port; /* последовательный поpт */
     r.h.ah = 2;    /* функция чтения символа */
     int86(0x14, &r, &r);
     if(r.h.ah & 128)
       printf("В последовательном поpту обнаpужена ошибка чтения");

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

     return r.h.al;
     }

     /* Пpовеpка состояния последовательного поpта */
     check_stat(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт  */
       r.h.ah = 3;    /* чтение состояния */
       int86(0x14, &r, &r);
       return r.x.ax;
     }

     /* инициализация поpта с паpаметpами:
     скоpость пеpедачи 9600 бод, два стоп-бита,
     контpоль на четность  выкл., 8 бит данных.
     */
     void port_init(port)
     int port;
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 0;    /* функция инициализации поpта*/
       r.h.al = 231; /* код инициализации - см. выше */
       int86(0x14, &r, &r);
     }

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

                    ЗАГРУЗКА УДАЛЕННЫХ ФАЙЛОВ В УЗЕЛ СЕТИ
     -----------------------------------------------------------------

          Для того,  чтобы  pабочая станция иницииpовала тpебования на
     получение файла из файлового сервера и  его  загpузку,  тpебуется
     вызов  специальной  пpогpаммы.  Эта пpогpамма вызывается по имени
     GET и выполняется pабочей станцией,  котоpая нуждается в  данных.
     Вы   можете   оpганизовать   вызов  этой  пpогpаммы  как  команды
     pасшиpенного набоpа команд DOS.  Основной фоpмой вызова пpогpаммы
     GET является следующий:

                              GET <имя_файла>

          где <имя_файла> - имя загpужаемого файла.

          Пpоцесс функциониpования  функции  GET  имеет два отличия от
     пpоцесса функциониpования дpугих функций,  использующих  файловый
     сервер.

          Во-пеpвых функция rec_file() пеpесылает имя файла компьютеpу
     -получателю.

          Во-втоpых, имя поpта жестко кодиpуется в подпpогpаммах, а не
     пеpедается подпpогpаммам в качестве аpгумента, как это делается в
     файловом сервере.

          Полный текст пpогpаммы GET пpедставлен ниже.

     /* Загpузка файла из файлового сервера. */

     #define PORT 0

     #include "dos.h"
     #include "stdio.h"

     void sport(), send_file(), rec_file(), send_file_name();
     void get_file_name(), port_init(), wait();

     main(argc,argv)
     int argc;
     char *argv[];
     {
       if(argc!=2) {
         printf(" Используйте фоpмат: GET <имя файла>\n");
         exit(1);
       }

       port_init(PORT); /* инициализация последовательного поpта */

         rec_file(argv[1]);
       }
       /*Получение файла*/
     void rec_file(fname)

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

     char *fname;
     {
       FILE *fp;
       char ch;
       union {
         char c[2];
         unsigned int count;
       } cnt;

       printf("Загpужается файл %s\n", fname);
       remove(fname);
       if(!(fp=fopen(fname,"wb"))) {
         printf("Выходной файл не может быть откpыт\n");
         exit(1);
       }

       sport(PORT, 's');     /*Пеpедача серверу маpкеpа
                               "готов к пpиему файла"*/
       wait(PORT);         /* Ожидание готовности сервера */

       /* Получение длины файла */
       send_file_name(fname);

       sport(PORT, '.'); /* квитиpование */
       cnt.c[0] = rport(PORT);
       sport(PORT, '.'); /* квитиpование */
       cnt.c[1] = rport(PORT);
       sport(PORT, '.'); /* квитиpование */

       for(; cnt.count; cnt.count--) {
         ch = rport(PORT);
         putc(ch, fp);
         if(ferror(fp)) {
           printf("ошибка записи в файл ");
           exit(1);
         }
         sport(PORT, '.'); /* квитиpование */
       }
       fclose(fp);
     }

     /* Пеpекачка имени файла */
     void send_file_name(f)
     char *f;
     {
       do {
         sport(PORT, '?');
       } while(!kbhit() && !(check_stat(PORT)&256));
       if(kbhit()) {
         getch();
         exit(1);
       }
       wait(PORT);

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

       while(*f) {
         sport(PORT, *f++);
         wait(PORT);
       }
       sport(PORT, '\0'); /* символ конца стpоки */
       wait(PORT);
     }
     /*Ожидание ответа (квитиpования)*/
     void wait(port)
     int port;
     {
       if(rport(port)!='.') {
         printf("ошибка установления связи \n");
         exit(1);
       }
     }

     /* Пеpедача символа из последовательного поpта */
     void sport(port, c)
     int port;                 /* поpт ввода/вывода */
     char c;                   /* пеpедаваемый символ */
     {
       union REGS r;

       r.x.dx = port;          /* последовательный поpт */
       r.h.al = c;             /* пеpедаваемый символ */
       r.h.ah = 1;             /* пеpесылка символа функции */
       int86(0x14, &r, &r);
       if(r.h.ah & 128) {      /* контpоль 7-го бита */
       printf("Обнаpужена ошибка пеpедачи в последовательном поpту ");
       printf("%d",r.h.ah);
         exit(1);
       }
     }

     /* Чтение символа из поpта */
     rport(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

       /* Ожидание пpихода символа */
     while(!(check_stat(port)&256))
       if(kbhit()) {
         getch();
         exit(1);
       }

     r.x.dx = port; /* последовательный поpт */
     r.h.ah = 2;    /* функция чтения символа */
     int86(0x14, &r, &r);
     if(r.h.ah & 128)
       printf("в последовательном поpту обнаpужена ошибка чтения");
     return r.h.al;

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

     }

     /* Пpовеpка состояния последовательного поpта */
     check_stat(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт  */
       r.h.ah = 3;    /* чтение состояния */
       int86(0x14, &r, &r);
       return r.x.ax;
     }

     /* инициализация поpта с паpаметpами:
     скоpость пеpедачи 9600 бод, два стоп-бита,
     контpоль на четность  выкл., 8 бит данных.
     */
     void port_init(port)
     int port;
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 0;    /* функция инициализации поpта*/
       r.h.al = 231; /* код инициализации - см. выше */
       int86(0x14, &r, &r);
     }

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

                            ХРАНЕНИЕ ФАЙЛОВ
     -----------------------------------------------------------------

          В большинстве сетей файлы могут  не  только  пеpесылаться  в
     узел сети от файлового сервера для обpаботки, но и пеpесылаться в
     обpатном поpядке - от абонента сети в сервер  для  хpанения.  Для
     поддеpжки  этих  возможностей  была  pазpаботана  пpогpамма  PUT.
     Пpогpамма PUT выполняется  в  узле  сети  на  pабочей  станции  и
     осуществляет  пеpекачку  файлов  из  узла сети в файловый сервер.
     Использование этой пpогpаммы аналогично  использованию  пpогpаммы
     GET  (за  исключением  того,  что  выполняемые  ими функции пpямо
     пpотивоположны). Вот основной фоpмат вызова пpогpаммы:

                              PUT <имя_файла>

          Пpоцесс выполнения  пpогpаммы   PUT   совеpшенно   идентичен
     пpоцессу   выполнения   пpогpаммы,   pешающей   задачу  пеpекачки
     пpогpаммных файлов.

          Полный текст пpогpаммы PUT пpиведен ниже.

     #define PORT 0

     #include "dos.h"
     #include "stdio.h"

     unsigned int filesize();
     void sport(), send_file(), send_file_name();
     void wait(), port_init(), wait();

     main(argc,argv)
     int argc;
     char *argv[];
     {
       if(argc!=2) {
         printf(" Используйте фоpмат GET <имя файла>\n");
         exit(1);
       }

       port_init(PORT); /* инициализация последовательного поpта */

       send_file(argv[1]);
       }

     /* пеpекачка специфициpованного файла */
     void send_file(fname)
     char *fname;
     {
       FILE *fp;
       char ch;
       union {
         char c[2];
         unsigned int count;

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

       } cnt;

       if(!(fp=fopen(fname,"rb"))) {
         printf("Входной файл не может быть откpыт\n");
         exit(1);
       }
       printf("Пеpесылается файл %s\n", fname);

     /* Тpебуется файловый сервер.*/
       sport(PORT, 'r'); /* маpкеp готовности к пеpесылке файла
                            из узла */
       wait(PORT);/*ожидание готовности файлового сервера.*/

       send_file_name(fname);  /* пеpедача имени файла */

       if(rport(PORT)!='.') {
         printf("Сбой пpи pаботе с удаленным файлом\n");
         exit(1);
       }

      /* вычисление pазмеpа выходного файла */
       cnt.count = filesize(fp);

       /* пеpедача pазмеpа файла*/
       sport(PORT, cnt.c[0]);
       wait(PORT);
       sport(PORT, cnt.c[1]);

       do {
         ch = getc(fp);
         if(ferror(fp)) {
           printf(" Ошибка чтения выходного файла\n");
           break;
         }

         /* ожидание готовности поpта-пpиемника */
         if(!feof(fp)) {
           wait(PORT);
           sport(PORT, ch);
         }
       } while(!feof(fp));
       wait(PORT);/* чтение последней поpции из поpта*/
       fclose(fp);
     }

     /* Возвpащение значения длины файла в байтах */
     unsigned int filesize(fp)
     FILE *fp;
     {
       unsigned long int i;

       i = 0;
       do {
         getc(fp);

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

         i++;
       } while(!feof(fp));
       rewind(fp);
       return (i-1); /* Не считая символ EOF */
     }

     /* Пеpекачка имени файла */
     void send_file_name(f)
     char *f;
     {
       do {
          sport(PORT, '?');
       } while(!kbhit() && !(check_stat(PORT)&256));
       if(kbhit()) {
         getch();
         exit(1);
       }
       wait(PORT);

       while(*f) {
         sport(PORT, *f++);
         wait(PORT);
       }
       sport(PORT, '\0'); /* символ конца стpоки */
       wait(PORT);
     }

     /* ожидание ответа */
     void wait(port)
     int port;
     {
       if(rport(port)!='.') {
         printf("Ошибка установления связи \n");
         exit(1);
       }
     }

     /* Пеpедача символа из последовательного поpта */
     void sport(port, c)
     int port; /* поpт ввода/вывода */
     char c;   /* пеpесылаемый символ */
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.al = c; /* символ для пеpедачи */
       r.h.ah = 1; /* функция пеpедачи символа */
       int86(0x14, &r, &r);
       if(r.h.ah & 128) {
       printf("Ошибка пеpедачи в последовательном поpту %d",r.h.ah);
         exit(1);
       }
     }

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

     /* чтение символа из последовательного поpта */
     rport(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

       /* ожидание символа */
       while(!(check_stat(PORT)&256))
         if(kbhit()) {
           getch();
           exit(1);
         }

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 2; /* функция чтения символа  */
       int86(0x14, &r, &r);
       if(r.h.ah & 128)
         printf(" ошибка чтения в последовательном поpту ");
       return r.h.al;
     }

     /* контpоль состояния последовательного поpта */
     cheek_stat(port)
     int port; /* поpт ввода/вывода */
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт  */
       r.h.ah = 3;    /* чтение состояния */
       int86(0x14, &r, &r);
       return r.x.ax;
     }

     /* инициализация поpта паpаметpами:
               скоpость пеpедачи - 9600 бод,
               контpоль четности       выкл. ,
               восемь бит данных,
               два завеpшающих стоп-бита.
     */
     void port_init(port)
     int port;
     {
       union REGS r;

       r.x.dx = port; /* последовательный поpт */
       r.h.ah = 0;    /* функция инициализации поpта*/
       r.h.al = 231; /* код инициализации - см. выше */
       int86(0x14, &r, &r);
     }

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

                             ИСПОЛЬЗОВАНИЕ ЛВС
     -----------------------------------------------------------------

          Для обеспечения функциониpования  ЛВС  необходимо  запустить
     файловый сервер на центpальном компьютеpе. Каждая pабочая станция
     -  абонент  сети  должна  иметь  в  составе  своего   пpогpамного
     обеспечения  файлы GET.EXE и PUT.EXE.  Пpи необходимости получить
     файл,  вводится команда GET,  пpи необходимости сохpанить файл во
     внешней памяти файлового сервера вводится команда PUT.

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

                           СОВЕРШЕНСТВОВАНИЕ ЛВС
     -----------------------------------------------------------------

          Одним из   пеpвых   усовеpшенствований    описанной    здесь
     пpостейшей  ЛВС  является  обеспечение дополнительной возможности
     для pабочих станций сети опеpиpовать с каталогом файловой системы
     центpального  компьютеpа.  Для  этой  цели  может  быть добавлена
     команда 'd' (directory) в  набоp  командных  пpимитивов  сети.  В
     пpостейшем случае обpаботка каталога сводится к его выдаче в виде
     пеpечня   файлов   текущей   диpектоpии.   Поэтому,   исходя   из
     вышепpиведенного  положения,  добавление  команды  'd'  потpебует
     соответствующего  дополнения  основного  цикла  pаботы  файлового
     сервера   с   целью  обеспечения  выдачи  каталога  пpи  пеpедаче
     абонентом этой команды. Результат выполнения команды отобpажается
     обычным способом на экpане так, будто вы выполнили команду dir на
     своем компьютеpе.

          Довольно пpивлекательно выглядит pасшиpение набоpа командных
     пpимитивов  сети за счет внесения в него команды RUN, позволяющей
     автономно пеpесылать  из  файлового  сервера  выполняемый   файл,
     pазмещать его в памяти pабочей станции и запускать.

          Электpонная почта,  с  помощью  котоpой  пользователи  могут
     обмениваться  дpуг  с  дpугом  сообщениями,  является  одним   из
     пеpспективных напpавлений совеpшенствования сети.

          В конечном  итоге  вы  можете  обеспечить  защиту всей вашей
     системы путем pазpешения загpузки для каждого узла  сети (pабочей
     станции) лишь опpеделенных файлов для защиты всей совокупности.

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


                                  ГЛАВА 7                     -- 1 --
                                  -------

                           ИНТЕРПРЕТАТОРЫ ЯЗЫКА.
     -----------------------------------------------------------------

           Вы когда-нибудь  хотели создать свой язык программирования?
     Большинство  программистов  призывают  к  поиску  идеи  создания,
     управления  и модификации своих языков программирования.  Однако,
     лишь немногие программисты могут легко  и  непринужденно  создать
     язык  программирования.  Создание  полного  компилятора  является
     многообязывающей задачей,  но гораздо проще создать интерпретатор
     языка.  Методы,  используемые  для создания итерпретаторов языка,
     изучаются редко или изучаются довольно абстрактно.  В этой  главе
     на  практических примерах вы будете изучать секреты интерпретации
     языка и грамматического разбора выражений.

          Интерпретаторы важны  по   трем   очень   важным   причинам.
     Во-первых,  они  могут  обеспечивать  удобную интерактивную среду
     (как доказательство -  интерпретатор  стандартного  языка  BASIC,
     которыми  снабжаются большинство персональных компьтеров). Многие
     пользователи-новички  находят,  что  интерактивная  среда   более
     удобна,   чем   компилятор.   Во-вторых,   интерпретаторы   языка
     обеспечивают превосходные интерактивные  отладочные  возможности.
     Даже ветераны-программисты при отладке трудных программ прибегают
     к помощи интерпретатора языка,  потому что он позволяет динамично
     устанавливать   значения   переменных   и   условий.   В-третьих,
     большинство языков запросов  к  базе  данных  работают  в  режиме
     интерпретации.

          В этой главе будет разработан интерпретатор для подмножества
     языка BASIC,  который еще называют "SMALL  BASIC".  BASIC  выбран
     вместо  Cи,  потому что BASIC легче интерпретируется,  чем Cи или
     другой структурный язык.  Интерпретатор для  структурного  языка,
     такого  как  Cи  более  труден,  чем  для  BASIC из-за автономных
     функций   с   локальными   переменными,   которые    обеспечивают
     интерпретатору    многозначность.    Принципы,   используемые   в
     интерпретаторе BASIC,  применимы и для других языков, и вы можете
     использовать  написанные  программы  в  качестве отправной точки.
     Прежде,  чем начать работу,  необходимо изучить сущность языковой
     интерпретации,  особенно  перед тем,  как браться за интерпетацию
     такого сложного языка,  как Cи.  Если  вы  не  знаете  BASIC,  не
     беспокойтесь, команды используемые в SMALL BASIC очень легкие для
     понимания.

          Мы начинаем с сердца любого  интерпретатора: синтаксического
     анализатора выражений.

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

                      СИНТАКСИЧЕСКИЙ РАЗБОР ВЫРАЖЕНИЙ
     -----------------------------------------------------------------

          Наиболее важной   частью   интерпретатора   языка   является
     синтактический анализатор выражений, который преобразует числовые
     выражения,  такие как (10-X)/23,  в такую форму,  чтобы компьютер
     мог  понять  ее  и вычислить.  В книге по языку Cи:  The Complete
     Reference   (Osborne/McGraw-uill,   1987)   вступительная   глава
     посвящена  синтаксическому  анализу выражений.  Подобного же рода
     синтаксический анализ,  основанный  на  принципах,  изложенных  в
     вышеупомянутой  книге,  (правда,  с небольшими изменениями) будет
     использоваться для построения интерпретатора SMALL BASIC в данной
     главе  нашей  книги.  (Так  как эта глава содержит только краткие
     сведения  о  синтаксическом  анализе  выражений,  то  для   более
     детального  изучения  этой  проблемы  советуем  вам  обратиться к
     источнику: The Compelete Reference.

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

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

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

                                 ВЫРАЖЕНИЯ
     -----------------------------------------------------------------

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

           - числа
           - операторы  + - / * ^ % = () <> ; ,
           - скобки
           - переменные

          Символ '^'  означает экспоненту,  а  символ '=' используется
     как оператор присваивания, а также как знак равенства в операциях
     сравнения.    Элементы  выражения  можно  комбинировать  согласно
     правилам алгебры.

          Вот некоторые примеры выражений:

      7-8 (100-5)*14/6
      a+b-c
      10^5
      a=7-b

          Символы '=', '>', '<', ',', ';' являются операторами, они не
     могут использоватся в выражениях функций и конструкциях  типа IF,
     PRINT и операторах присваивания.  (Заметим,  что анализатор языка
     Cи  должен  обрабатывать  и  эти   операторы   в   различных   их
     комбинациях).

          Что касается   языка   BASIC,   старшинство   операторов  не
     определено.   В   процессе   работы   синтаксический   анализатор
     присваивает операторам следующие приоритеты:

         высший      ()
                     ^
                     *  /  %
                     +  -
         низший      =

          Операторы равного приоритета выполняются слева направо.

          Синтаксис языка SMALL BASIC предполагает, что все переменные
     обозначаются одной буквой.  Это позволяет оперировать в программе
     двадцати шестью   переменными   (буквы   от   A   до   Z).   Хотя
     интерпретаторы языка  BASIC  поддерживают  обычно  большее  число
     символов  в  определении переменной,  (например,  переменные типа
     Х27),  но для простоты изложения  основных  принципов  построения
     интерпретаторов  наш интерпретатор языка SMALL BASIC этого делать
     не будет. Будем считать также, что переменные разных регистров не
     отличаются друг от друга и,  поэтому,  переменные "a" и "A" будут
     трактоваться как одна и та  же  переменная.  Условимся,  что  все
     числа являются целыми,  хотя вы без особого труда можете написать

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

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

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

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

                              ЛЕКСЕМЫ
     -----------------------------------------------------------------

          Перед тем,   как   построить   синтаксический    анализатор,
     разбирающий   значения   выражений,  вы  должны  иметь  несколько
     вариантов разбиения строки, содержащей выражение, на составляющие
     части. Например, выражение

                                 А*В-(W+10)

     содержит  компоненты  "А",  "*",  "В", "-", "(", "W", "+", "10" и
     ")". Каждый  компонент  представляет  собой   неделимый   элемент
     выражения.   Такой  компонент  или  независимая  часть  выражения
     называется лексемой.  Функция, разбивающая выражение на составные
     части,  должна  решать четыре задачи:  (1) игнорировать пробелы и
     символы табуляции,  (2) извлекать каждую лексему из  текста,  (3)
     если  необходимо,  преобразовывать  лексему во внутренний формат,
     (4) определять тип лексемы.

          Каждая лексема имеет  два  формата:  внешний  и  внутренний.
     Внешний  формат  -  это  символьная строка,  с помощью которой вы
     пишите программы на каком-либо языке  программирования. Например,
     "PRINT"  -  это  внешняя  форма команды PRINT языка BASIC.  Можно
     построить  интерпретатор   из   расчета,   что   каждая   лексема
     используется во внешнем формате, но это типичное решение проблемы
     программистом-непрофессионалом,  который  лишь  два  часа   назад
     оторвался   от  материной  юбки  и  час  назад  увидел  настоящий
     компьютер.  Настоящие мужчины ориентируются на внутренний  формат
     лексемы, который   является   просто   числом,   и  разрабатывают
     интерпретаторы исходя из этой профессиональной  точки  зрения  на
     проблему.  Поясним  этот  подход.  Например,  команда PRINT может
     иметь порядковый внутренний номер 1,  команда INPUT -  2  и  т.д.
     Преимущество   внутреннего   формата   заключается   в  том,  что
     программы,  обрабатывающие числа,  более  быстродействующие,  чем
     программы,  обрабатывающие строки.  Для реализации такого подхода
     необходима функция,  которая  берет  из  входного  потока  данных
     очередную  лексему  и  преобразует  ее  из  внешнего  формата  во
     внутренний.  Помните,  что не все лексемы имеют  разные  форматы.
     Например,  операторы  не подлежат преобразованию потому,  что они
     могут  трактоваться  как  символы  или  числа  в  своих   внешних
     форматах.

          Очень важно  знать,  какой тип лексемы возвращен.  Например,
     анализатору выражений необходимо  знать,  является  ли  следующая
     лексема числом,  оператором или переменной. Значение типа лексемы
     для  процесса  анализа  в  целом  станет  очевидным,   когда   вы
     приступите непосредственно к разработке интерпретатора.

          Функция, которая  возвращает  следующую лексему в выражении,
     называется get_token( ).  Она работает из  расчета  того,  что  в
     языке   SMALL   BASIC,   программа   хранится  как  одна  строка,
     ограниченная в конце  символом завершения  строки  (\0).  Функция
     get_token() сканирует   текст  программы,  анализируя  по  одному
     символу,  при этом  глобальный  указатель  анализатора  принимает

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

     значение адреса очередной считаной лексемы. В версии get_token(),
     приведенной ниже,  этот указатель называется prog.  Так как  prog
     является  глобальной  переменной,  то его значение между вызовами
     get_token сохраняется и позволяет  другим  функциям  использовать
     его.

          Анализатор, разрабатываемый  в этой главе,  использует шесть
     типов лексем:  DELIMITER,  VARIABLE,  NUMBER,  COMMAND,  STRING и
     QUOTE   (разделитель,   переменная,   число,  команда,  строка  и
     кавычки). Тип VARIABLE приписывается  переменным.  Тип  DELIMITER
     приписывается операторам и скобкам.  Тип NUMBER - для чисел.  Тип
     COMMAND - для команд  языка  SMALL  BASIC.  Тип  STRING  временно
     используется  внутри  get_token()  пока идет разбор лексемы.  Тип
     QUOTE  используется  при  определении   кавычек,   ограничивающих
     строку.  Глобальная  переменная  token_type содержит тип лексемы.
     Внутреннее  представление   лексемы   помещается   в   глобальную
     переменную tok.

          Ниже   приведена   функция    get_token().   Все   остальные
     необходимые вспомогательные функции для  полного  синтаксического
     анилизатора будут приведены в этой главе немного позже.

          #define DELIMITER  1
          #define VARIABLE   2
          #define NUMBER     3
          #define COMMAND    4
          #define STRING     5
          #define QUOTE      6
          #define FINISHED   10
          #define EOL        9

          extern char token[80];
          extern int tok, token_type;
          extern char *prog;  /* Содержит анализируемое выражение */
          /* Получить лексему */
          get_token()
          {
            register char *temp;

            token_type=0; tok=0;
            temp=token;

            if(*prog=='\0')  { /* Конец файла */
              *token=0;
              tok=FINISHED;
              return(token_type=DELIMITER);
             }

             while(iswhite(*prog)) ++prog;  /* пропуск пробелов */

             if(*prog=='\r') { /* crtl */
               ++prog; ++prog;
               tok= EOL; *token='\r';

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

               token[1]='\n';token[2]=0;
               return (token_type = DELIMITER);
             }

             if(strchr("+-*^/%=;(),><", *prog)) { /* разделитель */
               *temp=*prog;
               prog++; /* переход на слкдующую позицию */
               temp++;
               *temp=0;
               return (token_type=DELIMITER);
             }

             if(*prog=='"')  { /* строка в кавычках */
              prog++;
               while(*prog != '"' && *prog!='\r') *temp++=*prog++;
               if(*prog=='\r') serror(1);
               prog++;*temp=0;
               return(token_type=QUOTE);
             }

             if(isdigit(*prog)) { /* число */
               while(!isdelim(*prog)) *temp++=*prog++;
               *temp = '\0';
               return(token_type = NUMBER);
             }

             if(isalpha(*prog))  { /* переменная или команда */
               while(!isdelim(*prog)) *temp++=*prog++;
               token_type=STRING;
             }

             *temp = '\0';
      /* Просматривается, если строка есть команда или переменная */
             if(token_type==STRING) {
               tok=look_up(token); /* преобразование во внутренний
                                      формат */
               if(!tok) token_type = VARIABLE;
               else token_type = COMMAND; /* это команда */
             }
             return token_type;
           }

          Посмотрите внимательно на get_token().  Многие  программисты
     любят   помещать   пробелы   перед   выражениями   для  улучшения
     удобочитаемости и наглядности своей программы. Лидирующие пробелы
     пропускаются  с  помошью  функции is_white(),  которая возвращает
     значение "истина" ("TRUE"),  если ее аргумент  является  пробелом
     или   символом   табуляции.   Псле   пропуска  пробелов,  сканер,
     реализуемый с помощью программы prog,  указывает на каждое число,
     переменную,  команду,  символ  "возврат  каретки" или ноль,  если
     достигнут   конец   выражения   (программы).    Если    очередным
     анализируемым символом является символ "возврат каретки" (\r), то
     возвращается значение  "конец  строки  программы"  ("EOL").  Если

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

     очередной  символ  является  оператором,  то  в качестве значения
     глобальной переменной token возвращается  соответствующая строка,
     при этом в переменную token_type помещается значение DELIMITER. В
     противном случае проверяется наличие  кавычек.  Затем  происходит
     проверка  является  ли  лексема  числом.  Если  лексема  является
     символом,  то она,  следовательно,  является или  переменной  или
     командой.  Функция  look_up() сравнивает внешний формат лексемы с
     таблицей лексем,  определенной при разработке анализатора и, если
     находит  соответствующе  значение  в  ней,  возвращает внутреннее
     представление  лексемы  (команды).  В  противном  случае  лексема
     трактуется   как   переменная.   И,   наконец,   если  символ  не
     удовлетворяет ни одному  из  условий,  приведенных  выше,  то  он
     трактуется  как  символ конца выражения.  При этом значение token
     обнуляется.

          Для лучшего  понимания  работы  get_token()  изучите   типы,
     которые возвращает функция для каждой лексемы:

                            PRINT A+100-(B*C)/2

     --------------------------------
     Лексема         Тип лексемы.

          PRINT           COMMAND
          A               VARIABLE
          +               DELIMITER
          100             NUMBER
          -               DELIMITER
          (               DELIMITER
          B               VARIABLE
          *               DELIMITER
          C               VARIABLE
          )               DELIMITER
          /               DELIMITER
          2               NUMBER
          null            DELIMITER
     --------------------------------

          Помните, что  значение  переменной  token  равно нулю,  если
     лексема состоит из одного символа.

          Некоторые функции  интерпретатора  нуждаются   в   повторном
     просмотре  лексемы.  В этом случае лексема должна быть возвращена
     во входной поток. Функция putback() решает эту задачу.

      /*  Возвращает лексему обратно во входной поток */
          void putback()
          {

            char *t;

            t = token;
            for(; *t; t++) prog--;
          }

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

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

                        ПОРЯДОК ПОСТРОЕНИЯ ВЫРАЖЕНИЙ
     -----------------------------------------------------------------

          Имеется много  вариантов анализа и вычисления выражений. Для
     использования полного  синтаксического  анализатора  рекурсивного
     спуска   мы  должны  представить  выражение  в  виде  рекурсивной
     структуры данных.  Это означает,  что  выражение  определяется  в
     термах   самого   себя.   Если   выражение   можно  определить  с
     использованием только символов "+" ,"-" ,"*" ,"/"  и  скобок,  то
     все  выражения  могут  быть определены с использованием следующих
     правил:

                  Выражение = > Терм [+Терм][-Терм]
                  Терм      = > Фактор [*Фактор][/Фактор]
                  Фактор    = > Переменная, Число или (Выражение)

          Очевидно,    что   некоторые   части   в   выражении   могут
     отсутствовать вообще.  Квадратные  скобки  означают  именно такие
     необязательные   элементы  выражения.   Символ   =>  имеет  смысл
     "продуцирует".

          Фактически,   выше  перечислены   правила,   которые  обычно
     называют    правилами вывода  выражения.  В соответствии  с этими
     правилами     терм   можно   определить   так:   "Терм   является
     произведением или отношением факторов".

          Вы вероятно заметили,  что приоритет операторов безусловен в
     описанных  выражениях,   то  есть  вложенные   элементы  включают
     операторы с более высоким приоритетом.

          В связи с этим рассмотрим ряд примеров. Выражение

                                   10+5*B

     содержит два терма: "10" и "5*B". Они, в свою очередь, состоят из
     трех  факторов:  "10",  "5"  и  "B",  содержащих два числа и одну
     переменную.

          В другом случае выражение

                                  14*(7-C)

     содержит  два фактора "14"  и "(7-C)",  которые,  в свою очередь,
     состоят из  числа и  выражения  в  скобках.  Выражение  в скобках
     вычисляется как разность числа и переменной.

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

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

          Пусть на вход анализатора поступает следующее выражение:

                                9/3-(100+56)

          Анализатор в этом случае будет работать по такой схеме:

     1.  Берем первый терм: "9/3".
     2.  Берем   каждый  фактор  и  выполняем  деление чисел, получаем
         результат "3".
     3.  Берем   второй  терм:  "(100+56)".  В  этой   точке  стартует
         рекурсивный анализ второго выражения.
     4.  Берем  каждый  фактор  и  суммируем  их между собой, получаем
         результат 156
     5.  Берем  число,  вернувшееся  из  рекурсии,  и  вычитаем его из
         первого: 3-156. Получаем итоговый результат "-153".

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

          Вы должны  помнить  две  основные  идеи рекурсивного разбора
     выражений:  (1)  приоритет  операторов  является  безусловным   в
     продукционных   правилах  и  определен  в  них;  (2)  этот  метод
     синтаксического анализа и вычисления  выражений  очень  похож  на
     тот,   который  вы  сами  используете  для  выполнения  таких  же
     операций.

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

                    СИНТАКСИЧЕСКИЙ АНАЛИЗАТОР ВЫРАЖЕНИЙ
     -----------------------------------------------------------------

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

          Исходный   текст   простейшего  синтаксического  анализатора
     рекурсивного спуска для целочисленных выражений приведен ниже.

          /* Синтаксический анализатор рекурсивного спуска
             для целочисленных выражений, который содержит
             ряд включаемых переменных
          */
             #include "setjmp.h"
             #include "math.h"
             #include "ctype.h"
             #include "stdlib.h"
             #define  DELIMITER 1
             #define  VARIABLE  2
             #define  NUMBER    3
             #define  COMMAND   4
             #define  STRING    5
             #define  QUOTE     6
             #define  EOL       9
             #define  FINISHED  10

             extern char *prog;  /* буфер анализируемого выражения */
             extern jmp_buf e_buf; /* буфер среды функции longjmp() */
             extern int variables[26]; /* переменные */
               extern struct commands {
               char command[20];
               char tok;
             } table[];

           extern char token[80]; /* внешнее представление лексемы */
           extern char token_type; /* тип лексемы */
           extern char tok; /* внутреннее представление лексемы */
             void get_exp(),level2(),level3(),level4(),level5();
             void level6(),primitive(),arith(),unary();
             void serror(), putback();

             /* Точка входа в анализатор. */
             void get_exp(result)
             int *result;
             {
               get_token();
               if(!*token) {
                 serror(2);

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

                 return;
               }
               level2(result);
               putback(); /* возвращает последнюю считаную
                             лексему обратно во входной поток */
             }

             /* Сложение или вычитание двух термов */
             void level2(result)
             int *result;
             {
               register char op;
               int hold;

               level3(result);
               while((op=*token) == '+' || op == '-') {
                 get_token();
                 level3(&hold);
                 arith(op,result,&hold);
               }
             }

            /* Вычисление произведения или частного двух фвкторов */
             void level3(result)
             int *result;
             {
               register char op;
               int hold;

               level4(result);

               while((op = *token) == '+' || op == '/' || op == '%') {
                 get_token();
                 level4(&hold);
                 arith(op,result,&hold);
               }
             }

             /* Обработка степени числа (целочисленной) */
             void level4(result)
             int *result;
             {
               int hold;

               level5(result);
               if(*token== '^') {
                 get_token();
                 level4(&hold);
                 arith('^', result, &hold);
               }
             }

             /* Унарный + или - */
             void level5(result)

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

             int *result;
             {
               register char op;

               op = 0;
           if((token_type==DELIMITER) && *token=='+' || *token=='-') {
                 op = *token;
                 get_token();
               }
               level6(result);
               if(op)
                 unary(op, result);
             }

             /* Обработка выражения в круглых скобках */
             void level6(result)
             int *result;
             {
               if((*token == '(') && (token_type == DELIMITER)) {
                 get_token();
                 level2(result);
                 if(*token != ')')
                   serror(1);
                 get_token();
               }
               else
                 primitive(result);
             }

             /* Определение значения переменной по ее имени */
             void primitive(result)
             int *result;
             {
               switch(token_type) {
               case VARIABLE:
                 *result = find_var(token);
                 get_token();
                 return;
               case NUMBER:
                 *result  = atoi(token);
                 get_token();
                 return;
               default:
                 serror(0);
               }
             }

             /* Выполнение специфицированной арифметики */
             void arith(o, r, h)
             char o;
             int *r, *h;
             {register int t, ex;

             switch(o) {

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

               case '-':
                 *r = *r-*h;
                 break;
               case '+':
                 *r = *r+*h;
                 break;
               case '*':
                 *r = *r * *h;
                 break;
               case '/':
                 *r = (*r)/(*h);
                 break;
               case '%':
                 t = (*r)/(*h);
                 *r = *r-(t*(*h));
                 break;
               case '^':
                 ex =*r;
                 if(*h==0) {
                   *r = 1;
                   break;
                 }
                 for(t=*h-1; t>0; --t) *r = (*r) * ex;
                 break;
               }
             }

             /* Изменение знака */
             void unary(o, r)
             char o;
             int *r;
             {
               if(o=='-') *r = -(*r);
             }

             /* Поиск значения переменной */
             int find_var(s)
             char *s;
             {
               if(!isalpha(*s)){
                 serror(4); /* не переменная */
                 return 0;
               }
               return variables[toupper(*token)-'^'];
             }

             /* выдать сообщение об ошибке */
             void serror(error)
             int error;
             {
               static char *e[]= {
                 "Синтаксическая ошибка",
                 "Непарные круглые скобки",
                 "Это не выражениеt",

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

                 "Предполагается символ равенства",
                 "Не переменная",
                 "Таблица меток переполнена",
                 "Дублирование меток",
                 "Неопределенная метка",
                 "Необходим оператор THEN",
                 "Необходим оператор TO",
                 "Уровень вложенности цикла FOR слишком велик",
                 "NEXT не соответствует FOR",
                 "Уровень вложенности GOSUB слишком велик",
                 "RETURN не соответствует GOSUB"
               };
               printf("&4%s\n",e[error]);
               longjmp(e_buf, 1); /* возврат в точку сохранения */
             }

             /* Чтение лексемы. */
             get_token()
             {

               register char *temp;
               token_type=0; tok=0;
               temp=token;
               if(*prog=='\0') { /* Конец файла */
                 *token=0;
                 tok = FINISHED;
                 return(token_type=DELIMITER);
               }
               while(iswhite(*prog)) ++prog; /* пропуск пробелов */

               if(*prog=='\r') { /* коней строки программы */
                 ++prog; ++prog;
                 tok = EOL; *token='\r';
                 token[1]='\n'; token[2]=0;
                 return (token_type = DELIMITER);
               }

               if(strchr("+-^/%=;(),><", *prog)){ /* разделитель */
                 *temp=*prog;
                 prog++; /* переход на следующую позицию */
                 temp++;
                 *temp=0;
                 return (token_type=DELIMITER);
               }

               if(*prog=='"') { /* строка кавычек */
                 prog++;
                 while(*prog != '"' && *prog!='\r') *temp++=*prog++;
                 if(*prog=='\r') serror(1);
                 prog++;*temp=0;
                 return(token_type=QUOTE);
               }

               if(isdigit(*prog)) { /* число */

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

                 while(!isdelim(*prog)) *temp++=*prog++;
                 *temp = '\0';
                 return(token_type = NUMBER);
               }

               if(isalpha(*prog)) { /* переменная или команда */
                 while(!isdelim(*prog)) *temp++=*prog++;
                 token_type=STRING;
               }

               *temp = '\0';

        /* просматривается, если строка - переменная или команда */
               if (token_type==STRING) {
       tok=look_up(token); /* Преобразование во внутренний формат */
                 if (!tok) token_type = VARIABLE;
                 else token_type = COMMAND; /* это команда */
               }
               return token_type;
             }

             /* Возврат лексемы во входной поток */
            void putback()
             {

               char *t;

               t = token;
               for(; *t; t++) prog--;
             }

             /* Поиск соответствия внутреннего формата для
                текущей лексемы в таблице лексем.
             */
             look_up(s)
             char *s;
             {
               register int i,j;
               char *p;

               /* преобразование к нижнему регистру */
               p = s;
               while(*p){ *p = tolower(*p); p++; }

               /* просматривается, если лексема обнаружена в
                  таблице */
               for(i=0; *table[i].command; i++)
                 if(!strcmp(table[i].command, s)) return table[i].tok;
               return 0; /* нераспознанная команда */
             }

             /* Возвращает "истину", если "c" разделитель */
             isdelim(c)
             char c;

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

             {
             if(strchr(" ;,+-<>/*%^=()",c) || c==9 || c=='\r' || c==0)
                 return 1;
               return 0;
             }

             /* Возвращает 1, если "с" пробел или табуляция */
             iswhite(c)
             char c;
             {
               if(c==' ' || c=='\t') return 1;
               else return 0;
             }

          Анализатор поддерживает следующие операторы:  "+", "-", "*",
     "/",  "%",  целочисленный показатель степени (^) и унарный минус.
     Все  эти  операции  могут  использоваться  в сочетании с круглыми
     скобками. Вы, вероятно, заметили, что в программе имеются функции
     шести  уровней,   которые  работают  точно   также  как   функция
     primitive(), вращающая  значение  числа.  Помимо  этих   функций,
     реализующих арифметические операции,  в состав программы включены
     функции arith() и unary(), а также get_token().

          При вычислении выражения prog указывает  на  начало  строки,
     содержащей выражение,  и вызывает get_exp() с адресом переменной,
     в которую вы хотите поместить результат.

          Обратите особое  внимание  на  функцию   serror(),   которая
     используется  для  выдачи  сообщений об ошибках.  При обнаружении
     синтаксической ошибки serror() вызывается с номером этой ошибки в
     качестве  аргумента.  Ошибка  с  кодом  0,  которой соответствует
     сообщение "синтаксическая ошибка",  выдается в том случае,  когда
     тип  ошибки  нельзя определить более конкретно.  В других случаях
     ошибка уточняется.  Заметьте,  что serror() заканчивается вызовом
     функции longjmp().

          Функция logjmp()  выполняет нелокальный переход, возвращаясь
     в  точку,  определенную  с  помощью  функции  setjmp().   Функция
     setjmp()   включена   в  исходный  текст  интерпретатора.  Первый
     аргумент функции logjmp() является буфером  среды, инициированной
     с  помощью  setjmp().  Второй  аргумент  - это значение,  которое
     определяется при передаче управления из setjmp() обратно  в точку
     ее вызова. Как это делается вы, увидите позже.

          Использование ljgjmp()  упрощает  обработку ошибок,  так как
     программы-анализаторы  не   должны   аварийно   завершаться   при
     обнаружении  ошибки.  Если  ваш  компилятор  Си  не  поддерживает
     функции setjmp() и logjmp(),  то каждая функция  при  обнаружении
     ошибок должна  выполнять  возврат  в  точку  возникновения ошибки
     самостоятельно.

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

                   КАК АНАЛИЗАТОР ОБРАБАТЫВАЕТ ПЕРЕМЕННЫЕ
     -----------------------------------------------------------------

          Как было  сказано  раньше,  интерпретатор  языка SMALL BASIC
     распознает переменные с именами только  от  "A"  до  "Z".  Каждой
     переменной соответствует элемент массива variables, состоящего из
     26 элементов.  Этот массив определен в тексте интерпретатора, как
     показано ниже, и инициализируется нулевыми значениями.

        int variables[26]= {   /* 26 переменных пользователя, A-Z */
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0
        };
          Так как именами переменных являются буквы от "A" до  "Z", то
     индексирование  массива  variables  можно легко осуществить путем
     вычитания из соответствующих  значений  имен  переменных  в  коде
     ASCII кода символа 'A'. Функция find_var(), определяющая значение
     переменной в зависимости от ее имени, представлена ниже.

         /* Определение значения переменной по ее имени*/
         int find_var(s)
         char *s;
         {
           if(!isalpha(*s)){
             serror(4); /* это не переменная */
             return 0;
           }
           return variables[toupper(*token)-'A'];
         }

          Эта функция  допускает использование более длинных имен,  но
     только первая буква имени переменной является значащей.

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

                      ИНТЕРПРЕТАТОР ЯЗЫКА  SMALL BASIC
     -----------------------------------------------------------------

          Разрабатываемый интерпретатор  будет  распознавать следующие
     ключевые слова языка программирования BASIC:

            PRINT
            INPUT
            IF
            THEN
            FOR
            NEXT
            TO
            GOTO
            GOSUB
            RETURN
            END

          Внутреннее представление этих команд (плюс значение  EOL для
     конца  строки  и  FINISHED  для  сигнализации  о конце программы)
     определяется так:

          #define PRINT   1
          #define INPUT   2
          #define IF      3
          #define THEN    4
          #define FOR     5
          #define NEXT    6
          #define TO      7
          #define GOTO    8
          #define EOL     9
          #define FINISHED 10
          #define GOSUB    11
          #define RETURN   12
          #define END      13

          Для преобразования   внешнего   представления   лексем    во
     внутренний формат используется вспомагательная структура table.

          struct commands { /* Вспомогательная структура ключевых
                               слов анализатора                  */
            char command[20];
            char tok;
          } table[] = { /* Таблица обрабатывает команды, введенные */
            "print",PRINT, /* на нижнем регистре */
            "input",INPUT,
            "if",IF,
            "then",THEN,
            "goto",GOTO,
            "for",FOR,
            "next",NEXT,
            "to",TO,
            "gosub",GOSUB,
            "return",RETURN,

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

            "end",END,
            "",END  /* mark end of table */
          };

          Обратите внимание на то,  что признак конца  файла  (нулевая
     строка) помещен в конец таблицы.

          Функция look_up() возвращает внутреннее представление каждой
     лексемы или символа '\0', если таковая не обнаружена.

          /* Преобразование каждой лексемы из таблицы лексем
             во внутреннее представление.
          */
          look_up(s)
          char *s;
          {
            register int i,j;
            char *p;
            /* преобразование в символы нижнего регистра */
            p =s;
            while(*p){ *p = tolower(*p); p++; }

            /* если лексема обнаружена в таблице */
            for(i=0; *table[i].command; i++)
                if(!strcmp(table[i].command, s)) return table[i].tok;
            return 0; /* команда не распознана */
          }

          Интерпретатор языка SMALL  BASIC  не  поддерживает  редактор
     текстов,  поэтому  вы  должны создавать программы на языке BASIC,
     используя стандартный текстовый редактор.

          Каждая программа  считывается  и   выполняется   с   помощью
     интерпретатора.  Функция, которая загружает программу, называется
     load_program().

         /* Загрузка программы */
         load_program(p, fname)
         char *p;
         char *fname;
         {
           FILE *fp;
           int i=0;
           if(!(fp=fopen(fname, "rb"))) return 0;

           i = 0;
             do {
                *p = getc(fp);
                p++; i++;
             } while(!feof(fp) && i=<выражение>

          Функция assignment() поддерживает этот тип присваивания.

           /* Присвоить значение переменной */
           assignment()
           {
             int var, value;
             /* получить имя переменной */
             get_token();
               if(!isalpha(*token)) {
                 serror(4); /* это не переменная */
                 return;
               }

               /* поиск индекса переменной в массиве */
               var = toupper(*token)-'A';

               /* считать символ равенства*/
               get_token();
               if(*token!='=') {}
                 serror(3);
                 return;
               }

               /* считать присваемое переменной значение */
               get_exp(&value);

               /* присвоить значение*/
               variables[var] = value;
             }

          Сначала assignment()  считывает лексему из программы.  Это -
     лексема-переменная,  которой должна быть присвоена величина. Если
     лексема  не является переменной,  то сообщается об ошибке.  Затем
     считывается знак равенства (очередная лексема).  Далее вызывается
     функция get_exp(), которая вычисляет значение переменной. Функция
     получается удивительно простой потому, что анализатор выражений и
     get_token делают большую часть рутинной работы.

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

               КОМАНДА PRINT
     -----------------------------------------------------------------

          Команда PRINT  стандарта  языка  BASIC  достаточно  мощная и
     гибкая,  особенно когда применяется ее формат PRINT  USING.  Хотя
     создание функции, которая реализует все возможности этой команды,
     выходит за рамки этой  главы,  разрабатываемая  функция  является
     наиболее важной.  Вот  основная  форма  команды PRINT языка SMALL
     BASIC:

                         PRINT <список аргумеентов>

     где <список аргументов> представляет собой  перечень  переменных,
     заключенных  в  кавычки  и  разделенных  запятыми  или  точкой  с
     запятой.  Функция print(), представленная ниже, является аналогом
     команды   PRINT   языка  BASIC.  Обратите  внимание,  что  печать
     выполняется в теле цикла do-while,  условием завершения  которого
     является вывод на печать всего списка аргументов команды.

        /* Простейшая версия оператора PRINT */
        void print()
        {
          int answer;
          int len=0, spaces;
          char last_delim;
          do {
               get_token(); /* получить следующий элемент списка */
               if (tok == EOL || tok == FINISHED) break;
               if (token_tipe == QUOTE) { /* это строка */
                   printf(token);
                     len += strlen(token);
                    get_token();
              }
               else { /* это выражение */
                  putback();
                  get_exp(&answer);
                  get_token();
                  len+= printf("%d", answer);
               }
               last_delim = *token;
               if (*token == ';') {
          /* Вычисление числа пробелов при переходе к следующей
             табуляции */
               spaces = 8-(len % 8);
               len += spaces; /* смещение на одну табуляцию */
               while (spaces) {
                 printf(" ");
                 spaces--;
               }
               }
               else if (*token == ','); /* ничего не делать */;
               else if (tok != EOL && tok != FINESHED) serror(0);
               } while (*token == ';' || *token == ',');

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

               if (tok == EOL || tok == FINESHED) {
                  if (last_delim != ';' && last_delim != ',')
                                                        printf("\n");
                  else serror(0); /* Отсутствует разделитель */
              }

          Команда PRINT может  быть  использована  для  вывода  списка
     переменных  и  строчных  констант  на  экран.  Если  два элемента
     разделены  запятой,  то  их  значения  выводятся  на  печать  без
     пробелов (конкатенируются). Если же два элемента разделены точкой
     с запятой,  то второй элемент  выводится  начиная,  со  следующей
     позиции  табуляции.  Если  список элементов заканчивается запятой
     или точкой с запятой,  то переход на новую строку не выполняется.

          Приведенные ниже  примеры  вызовут  печать  данных  с  новой
     строки:

     PRINT X; Y; "ЭТО СТРОКА"
     PRINT 10 / 4
     PRINT

          Функция print()  использует  функцию  putback() для возврата
     лексемы во входной поток. Причиной этого является то, что прежде,
     чем  начать  печать строки,  заключенной в скобки,  или числового
     выражения,  функция  print()  должна  проанализировать  следующий
     элемент   списка  аргументов.  Если  следующий  элемент  является
     выражением,  то первый терм выражения должен быть помещен обратно
     во  входной  поток,  так  как  в  противном случае анализатору не
     удастся корректно обработать это выражение.

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

          КОМАНДА INPUT.
     -----------------------------------------------------------------

          В языке   BASIC   команда   INPUT  используется  для  чтения
     информации с клавиатуры и сохранения ее в переменных.  Она  имеет
     два  основных  формата.  Первый  формат  команды  выводит  маркер
     ожидания ввода данных ('?') и переводит всю программу  в ожидание
     ввода данных:

                           INPUT <имя переменной>

          Второй формат   приводит  к  отображению  на  экране  строки
     символов, после чего ожидается ввод данных:

               INPUT "<символьная строка>" <имя переменной>

          Функция input (),  приведенная ниже, реализует команду языка
     BASIC INPUT.

         /* Простейшая версия оператора INPUT */
         void input()
         {
           char str[80], var;
           int i;

           get_token(); /*Анализ наличия символьной строки */
           if (token_type == QUOTE) {
             printf(token); /*Если строка есть, проверка запятой */
             get_token();
             if (*token != ',') serror(1);
             get_token();
           }
           else printf("? "); /* В противном случае отображение "? "*/
      var = toupper(*token)-'A'; /* Вычисление индекса массива имен */
           scanf("%d",&i);   /* Чтение входных данных */
           variables[var] = i;  /* Сохранение их */
         }

          Работа  функции  станет  ясной   и   понятной  после  чтения
     комментариев.

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

               КОМАНДА GOTO
     -----------------------------------------------------------------

          Сейчас вы увидите реализацию одной из самых  простых команд,
     но в то же время довольно сложной для разработчика. В языке BASIC
     основной формой программного управления является команда  GOTO. В
     стандарте языка BASIC объектом, на который должен указывать GOTO,
     является номер строки.  Этот традиционный подход сохраняется и  в
     языке  SMALL BASIC.  Однако,  SMALL BASIC не требуется нумеровать
     каждую строку.  Номер строки необходим только в том случае,  если
     на  нее  ссылается  команда  GOTO.  Основной  формат команды GOTO
     представлен ниже:

                          GOTO <номер строки>

          Основной сложностью реализации оператора GOTO  является  то,
     что он должен позволять совершать переходы как вниз,  так и вверх
     по  программе.  Для  удовлетворения  этого  требования  необходим
     механизм, который бы просматривал программу, выбирал каждую метку
     и помещал ее в таблицу, содержащую как имя метки так, и указатель
     ее размещения в программе. Такая таблица определена ниже.

          struct label {
            char name[LAB_LEN]; /* имя метки */
            char *p; /* указатель на место размещения в программе */
          };
          struct label label_table[NUM_LAB];

          Функция, которая  просматривает  программу и выбирает каждую
     метку для размещения ее в таблице,  называется scan_labels(). Она
     и основные поддерживающие ее функции приведены ниже.

         /* Поиск всех меток */
         void scan_labels()
         {
           register int loc;
           char *temp;

           label_init();  /* обнуление всех меток */
           temp = prog;   /* запись указателя на начало программы */
           /* Если первая лексема файла является меткой */
           get_token();
           if(token_type==NUMBER) {
             strcpy(label_table[0].name,token);
             label_table[0].p=prog;
           }

           find_eol();
           do {
             get_token();
             if(token_type==NUMBER) {
               loc=get_next_label(token);
               if(loc == -1 || loc == -2) {
                   (loc == -1) ? serror(5):serror(6);

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

               }
               strcpy(label_table[loc].name, token);
           label_table[loc].p = prog; /* текущий указатель программы*/
             }
             /* Если строка не помечена, переход к следующей */
             if(tok!=EOL) find_eol();
           } while(tok != FINISHED);
           prog = temp; /* восстановить начальное значение*/
         }

         /* Инициализация массива хранения меток.
            По соглашению имя нулевой метки указывает, что
            данная позиция массива не используется  */
         void label_init()
         {
           register int t;
           for(t=0;t<оператор><выражение> THEN <оператор>

          Оператор, стоящий  после  THEN,  выполняется  только  в  том
     случае,   если  значение  сравнения  является  истинным.  Функция
     exec_if(),  приведенная ниже,  обеспечивает выполнение этой формы
     оператора IF.

         /* Простейшая реализация оператора IF  */
         void exec_if()
         {
           int x , y, cond;
           char op;
           get_exp(&x); /* получить левое выражение */
           get_token(); /* получить оператор  */
           if (!strchr("=<>", *token)) {
           serror(0);      /* недопустимый оператор  */
           return;
         }
         op=*token;
         get_exp(&y);  /* получить правое выражение */
         /* Определение результата */
         cond=0;
         switch(op) {
             case '=':
           if(x==y) cond=1;
           break;
             case '<':
           if(x':
           if(x>y) cond=1;
           break;
         }
         if(cond) {  /* если истина, то поиск нужного IF  */
           get_token();
           if(tok!=THEN) {
              serror(8);
              return;
           }  /* В противном случае программа продолжается со
                   следующей строки  */
         }
         else find_eol(); /* поиск строки продолжения программы */
       }

          Функция exec_if() выполняется следующим образом:

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

         1. Вычисляется значение левого выражения.
         2. Считывается оператор сравнения.
         3. Вычисляется величина правого выраженния.
         4. Выполняется операция сравнения.
         5. Если условие является истиной, то выполняется поиск  THEN;
            в  противном случае, find_eol  выполняет переход на начало
            следующей строки.

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

               ЦИКЛ FOR
     -----------------------------------------------------------------

          Проблема обработки  интерпретатором  оператора  цикла   FOR,
     входящего  в  состав  оператора  BASIC,  решена  в  нашем  случае
     довольно  оригинально.  Основной  формат  оператора   цикла   FOR
     следующий:

      FOR<имя управляющей переменной>=<нач. значение>TO<кон. значение>
          .
          .
          .
        последовательность операторов
          .
          .
          .
       NEXT

          Версия оператора  FOR,  поддерживаемая интерпретатором SMALL
     BASIC,  реализует цикл с положительным проращением  равным  1  на
     каждую итерацию цикла. Параметр STEP не поддерживается.

          В языке   BASIC,   точно  также  как  и  в  Cи,  допускается
     вложенность цикла FOR.  Основной изюминкой,  при реализации этого
     оператора,  с  точки зрения программиста-профессионала,  является
     сохранение информации о каждом  вложенном  цикле  со  ссылкой  на
     внешний   цикл.   Для  реализации  этой  маленькой,  но  все-таки
     заковырки      (трудности      всегда      радуют       настоящих
     программистов-мужчин),  используется стековая структура,  которая
     работает следующим образом:  Начало цикла,  информация о значении
     управляющей  переменной  цикла  и  ее конечном значении,  а также
     место расположения цикла  в  теле  программы  заносятся  в  стек.
     Каждый раз,  при достижении оператора NEXT,  из стека извлекается
     информация о значении управляющей переменной,  затем ее  значение
     пересчитывается  и сравнивается с конечным значением цикла.  Если
     значение управляющей переменной цикла достигло  своего  конечного
     значения,  выполнение  цикла  прекращается и выполняется оператор
     программы следующий за оператором NEXT.  В  противном  случае,  в
     стек  заносится  новая информация и выполнение цикла начинается с
     его начала.  Таким  же  образом  обеспечивается  интерпретация  и
     выполнение вложенных циклов.  В стекоподобной структуре вложенных
     циклов каждый FOR должен быть закрыт  соответствующим  оператором
     NEXT.

          Для  реализации  оператора  цикла  FOR   стек  должен  иметь
     следующую структуру:

          struct for_stack {
               int var; /* счетчик цикла */
               int target; /* конечное значение */
               char *loc;
          } fstack[FOR_NEST]; /* стек для цикла FOR/NEXT */
          int ftos; /* индекс начала стека FOR */

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

          Значение макроса  FOR_NEST  ограничивает уровень вложенности
     цикла.  (По  умолчанию  допускается  25   уровней   вложенности).
     Переменная ftos всегда имеет значение индекса начала стека.

          Для обработки  стека  вам  понадобятся две функции fpush() и
     fpop(), которые приведены ниже.

                /* Поместить элемент в стек */
          void fpush(i)
          struct for_stack i;
          {
               if (ftos > FOR_NEST)
                    serror(10);

               fstack[ftos]=i;
               ftos++;
          }

          struct for_stack fpop()
          {
               ftos--;
               if (ftos < 0) serror(11);
               return(fstack[ftos]);
          }

          Итак, после  того,  как вы получили возможность ознакомиться
     со всеми необходимыми вспомогательными функцмями, приведем полный
     текст функции, реализующей оператры FOR и NEXT.

          /* Простейшая реализация оператора цикла FOR */
          void exec_for()
          {
               struct for_stack i;
               int value;

               get_token(); /* чтение управляющей переменной */
               if (!isalfa(*token)) {
                    serror(4);
                    return;
               }

               i.var=toupper(token) - 'A'; /* сохраним индекс */

               get_token(); /* чтение символа равенства */
               if (*token != '=') {
               serror(3);
               return;
               }

               get_exp(&value); /* получить начальное значение */

               variables[i.var] = value;

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

               get_token();
               if (tok != TO) serror(9); /* чтение и анализ TO */

               get_exp(&i.target); /* получить конечное значение */

     /* Если цикл выполняется последний раз, поместить информацию
        в стек*/
               if (value >= variables[i.var]) {
                    i.loc = prog;
                    fpush(i);
               }
               else { /* пропустить весь цикл целиком */
                    while(tok != NEXT) get_token();
               }
             }

      /* Выполнение оператора NEXT */
          void next()
          {
              struct for_stack i;

              i = fpop(); /* чтение информации о цикле */

              variables[i.var]++; /* увеличение переменной цикла */
              if (variables[i.var] > i.target) return; /* конец */
              fpush(i); /* в противном случае запомнить информацию */
              prog = i.loc; /* цикл */
          }

          Как именно  работает  эта  подпрограмма,  вполне   ясно   из
     комментариев к ней.  Следует заметить,  что анализ на возможность
     выхода  из  цикла  по  оператору  GOTO   в   данном   случае   не
     выполняется. Поэтому   использование  GOTO  в  теле  цикла  может
     привести  к  искажению  содержимого  стека,  что   вобщем-то   не
     желательно.

          Решение проблемы  реализации  цикла FOR с помощью применения
     стековых  структур  является,  в  общем  случае,   типичным   для
     реализации  циклических конструкций.  Так как интерпретатор SMALL
     BASIC не поддерживает другие типы циклических конструкций,  то вы
     можете  самостоятельно разработать подпрограммы реализации циклов
     WHILE и DO-WHILE.  Как вы сможете увидеть в следующем  параграфе,
     стековые   структуры   ипользуются   при   реализации   и  других
     конструкций программирования, допускающих вложенность.

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

               ОПЕРАТОР GOSUB.
     -----------------------------------------------------------------

          Хотя BASIC   не   поддерживает  отдельные  подпрограммы,  но
     имеется возможность вызвать отдельные части программы  с  помощью
     оператора  GOSUB  и  вернутся  из нее с помощью оператора RETURN.
     Основной формой GOSUB-RETURN является следующая:

     GOSUB <номер строки>
             .
             .
             .
             <номер строки>
             .
             тело подпрограммы
             .
             .
     RETURN

          Вызов подпрограммы  требует  использования стека.  Очевидно,
     что это реализовать в данном случае проще,  чем для оператора FOR
     потому,  что  каждому  оператору RETURN соответствует свой GOSUB.
     Описание стека GOSUB показано ниже.

         char *gstack[SUB_NEST]; /* стек подпрограмм */
         int gtos; /* индекс верхней части стека  */

          Функция gosub() и вспомогательные программы приведены ниже.

         /* Простейшая реализация оператора GOSUB  */
         void gosub()
         {
           char *loc;

           get_token();
           /* поиск метки обращения */
           loc = find_label(token);
           if(loc=='\0')
             serror(7); /* метка не найдена */
           else {
             gpush(prog); /* запомним место, куда надо вернуться */
             prog = loc; /* старт программы с точки loc */
           }
         }

         /*  Возврат из подпрограммы */
         void greturn()
         {
           prog = gpop();
         }

         /* Поместить элемент в стек операторов GOSUB (подпрограмм) */
         void gpush(s)
         char *s;

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

         {
           gtos++;
           if(gtos == SUB_NEST) {
             serror(12);
             return;
           }
           gstack[gtos]=s;
         }

      /* Вытолкнуть элемент из стека операторов GOSUB (подпрограмм) */
         char *gpop()
         {
           if(gtos==0) {
             serror(13);
             return;
           }
           return(gstack[gtos--]);
         }

          Команда GOSUB  работает следующим образом.  Текущее значение
     prog помещается в стек  GOSUB.  Затем  prog  присваивается  адрес
     строки,   с   которой   начинается   подпрограмма,  и  начинается
     выполнение подпрограммы. Когда встречается RETURN, из стека GOSUB
     выталкивается  очередное  значение  и  присваивается prog.  Далее
     выполнение  продолжается  со  следующей  строки  после  оператора
     GOSUB. Оператор GOSUB допускает вложенность.

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

                        ПОЛНЫЙ ФАЙЛ ИНТЕРПРЕТАТОРА.
     -----------------------------------------------------------------

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

        /* Минимальный BASIC-интепретатор */
        #include "stdio.h"
        #include "setjmp.h"
        #include "math.h"
        #include "ctype.h"
        #include "stdlib.h"

        #define NUM_LAB 100
        #define LAB_LEN 10
        #define FOR_NEST 25
        #define SUB_NEST 25
        #define PROG_SIZE 10000

        #define DELIMITER 1
        #define VARIABLE  2
        #define NUMBER    3
        #define COMMAND   4
        #define STRING    5
        #define QUOTE     6

        #define PRINT 1
        #define INPUT 2
        #define IF    3
        #define THEN  4
        #define FOR   5
        #define NEXT  6
        #define TO    7
        #define GOTO  8
        #define EOL   9
        #define FINISHED 10
        #define GOSUB 11
        #define RETURN 12
        #define END   13

        char *prog; /* содержит выражение для анализа */
        jmp_buf e_buf; /* содержит сртеду для longjmp() */

        int variables[26]= {   /* 26 переменных пользователя A - Z */
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0
        };

          struct commands { /* Просмотр таблицы ключевых слов */
            char command[20];
            char tok;

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

          } table[] = { /* Команда должна вводится прописными */
            "print",PRINT, /* буквами в эту таблицу */
            "input",INPUT,
            "if",IF,
            "then",THEN,
            "goto",GOTO,
            "for",FOR,
            "next",NEXT,
            "to",TO,
            "gosub",GOSUB,
            "return",RETURN,
            "end",END,
            "",END  /* Маркер конца таблицы */
          };

          char token[80];
          char token_type, tok;

          struct label {
            char name[LAB_LEN];
            char *p; /*      */
          };
          struct label label_table[NUM_LAB];

          char *find_label(), *gpop();

          struct for_stack {
            int var; /* переменная счетчика */
            int target; /* конечное значение */
            char *loc;
          } fstack[FOR_NEST]; /* стек цикла FOR/NEXT */
          struct for_stack fpop();

          char *gstack[SUB_NEST]; /* стек оператора GOSUB */
          int ftos; /* индекс начала стека FOR */
          int gtos; /* индекс начала стека GOSUB */

          void print(), scan_labels(), find_eol(),exec_goto();
          void exec_if(), exec_for(), next(), fpush(), input();
          void gosub(), greturn(), gpush(), label_init();

          main(argc, argv)
          int argc;
          char *argv[];
          {
            char in[80];
            int answer;
            char *p_buf;
            char *t;

            if(argc!=2) {
              printf("Используйте формат: run \n");
              exit(1);
            }

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

            /* Выделение памяти для программы */
            if (!(p_buf=(char *) malloc(PROG_SIZE))) {
              printf("Ошибка при выделении памяти ");
              exit(1);
            }

            /* Загрузка программы для выполнения */
            if(!load_program(p_buf,argv[1])) exit(1);
            if(setjmp(e_buf)) exit(1); /* инициализация буфера
                                          нелокальных переходов */
            prog = p_buf;
            scan_labels(); /* поиск метки в программе */
            ftos = 0; /* инициализация индеса стека FOR */
            gtos = 0; /* инициализация индеса стека GOSUB */
           do {
             token_type = get_token();
             /* проверка на оператор присваивания */
             if(token_type==VARIABLE) {
               putback(); /* возврат пер. обратно во входной поток */
               assignment(); /* должен быть оператор присваивания */
             }
             else /* это команда */
               switch(tok) {
                 case PRINT:
                   print();
                   break;
                 case GOTO:
                   exec_if();
                   break;
                 case FOR:
                   exec_for();
                   break;
                 case NEXT:
                   next();
                   break;
                 case INPUT:
                   input();
                   break;
                 case GOSUB:
                   gosub();
                   break;
                 case RETURN:
                   greturn();
                   break;
                 case END:
                   exit(0);
               }
           } while (tok != FINISHED);
         }

         /* Загрузка программы. */
         load_program(p, fname)
         char *p;

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

         char *fname;
         {
           FILE *fp;
           int i=0;
           if(!(fp=fopen(fname, "rb"))) return 0;

           i = 0;
             do {
                *p = getc(fp);
                p++; i++;
             } while(!feof(fp) && i", *token)) {
               serror(0);      /* недопустимый оператор */
               return;
             }
             op=*token;
             get_exp(&y);  /* получить правое выражение */
             /* Определение результата */
             cond=0;
             switch(op) {
                 case '=':
               if(x==y) cond=1;
               break;
                 case '<':
               if(x':
               if(x>y) cond=1;
               break;
             }
             if(cond) {  /* если значение IF "истина"  */
               get_token();
               if(tok!=THEN) {
                  serror(8);
                  return;
              } /* иначе, программа выполняется со следующей строки */
             }
             else find_eol(); /* поиск точки старта программы */
           }

           /* Реализация цикла FOR */
           void exec_for()

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

           {
             struct for_stack i;
             int value;

             get_token(); /* получить управляющую переменную */
             if(!isalpha(*token)); {
               serror(4);
               return;
             }
             i.var=toupper(*token)-'A'; /* сохранить ее индекс */
             get_token(); /* получить знак равенства */
             if(*token!='=') {
               serror(3);
               return;
             }
             get_exp(&value); /* получить начальное значение  */
             variables[i.var]=value;

             get_token();
             if(tok!=TO) serror(9); /* если нее нашли TO */
             get_exp(&i.target); /* получить конечное значение */

             /* Если цикл выполняется последний раз, поместить
                информацию в стек  */
             if(value>=variables[i.var]) {
               i.loc = prog;
               fpush(i);
             }
             else /* пропустить весь цикл */
               while(tok!=NEXT) get_token();
           }

           /* Реализация оператора NEXT */
           void next()
           {
             struct for_stack i;
             i = fpop(); /* чтение информации о цикле */

            variables[i.var]++; /* увеличение управляющей переменной*/
             if(variables[i.var]>i.target) return; /* конец цикла */
             fpush(i); /* иначе, сохранить информацию в стеке */
             prog = i.loc; /* цикл */
           }

           /* Поместить информацию в стек FOR */
           void fpush(i)
           struct for_stack i;
           {
             if(ftos>FOR_NEST)
             serror(10);
           fstack[ftos]=i;
           ftos++;
         }

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

         struct for_stack fpop()
         {
           ftos--;
           if(ftos<0) serror(11);
           return(fstack[ftos]);
         }

         /* Реализация оператора INPUT */
         void input()
         {
           char str[80], var;
           int i;

      get_token(); /*просматривается если существует строка символов*/
           if(token_type==QUOTE) {
          printf(token); /* если да, то ее печать и контроль ',' */
             get_token();
             if(*token!=',') serror(1);
             get_token();
           }
           else printf("? "); /* выдача строки по умолчанию */
       var = toupper(*token)-'A'; /* получить индекс имени переменной*
           scanf("%d",&i);   /* чтение ввода данных */
           variables[var] = i;  /* сохранение данных */
         }

         /* Реализация оператора GOSUB */
         void gosub()
         {
           char *loc;

           get_token();
           /* поиск метки вызова */
           loc = find_label(token);
           if(loc=='\0')
             serror(7); /* метка не определена */
           else {
             gpush(prog); /* запомним место, куда вернемся */
             prog = loc; /* старт программы с указанной точки */
           }
         }

         /* Возврат из подпрограммы, вызвынной по GOSUB */
         void greturn()
         {
           prog = gpop();
         }

         /* Помещает данные в стек GOSUB */
         void gpush(s)
         char *s;
         {
           gtos++;
           if(gtos==SUB_NEST) {

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

             serror(12);
             return;
           }
           gstack[gtos]=s;
         }

         /*   */
         char *gpop()
         {
           if(gtos==0) {
             serror(13);
             return;
           }
           return(gstack[gtos--]);
         }

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

              ПРИМЕР ИСПОЛЬЗОВАНИЯ ИНТЕРПРЕТАТОРА  SMALL BASIC
     -----------------------------------------------------------------

          Теперь вы  можете  запускать   простейшие   BASIC-программы.

         PRINT "Эта программа демонстрирует все команды."
         FOR X = 1 TO 100
         PRINT X, X/2; X, X*X
         NEXT
         GOSUB 300
         PRINT "Привет"
         INPUT H
         IF H<11 THEN GOTO 200
         PRINT 12-4/2
         PRINT 100
         200 A = 100/2
         IF A>10 THEN PRINT "Все нормально"
         PRINT A
         PRINT A+34
         INPUT H
         PRINT H
         INPUT "Это тест",y
         PRINT H+Y
         END
         300 PRINT "Это подпрограмма"
             RETURN

         PRINT "Эта подпрограмма демонстрирует вложенный GOSUB"
         INPUT "Введите число: ", i
         GOSUB 100

         END

         100 FOR T = 1 TO I
           X = X+I
           GOSUB 150
         NEXT
         RETURN

         150 PRINT X;
             RETURN

         print "Эта подпрограмма вычисляет объем куба "
         input "Введите длину парвой стороны: ", l
         input "Введите длину второй стороны: ", w
         input "Введите длину третей стороны: ", d
         t = l * w * d
         print "Обьем равен: ", t

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

         PRINT "Эта программа демонстрирует вложенные циклы"
         FOR X = 1 TO 100
          FOR Y = 1 TO 10
            PRINT X; Y; X*Y
          NEXT
         NEXT

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

                  РАСШИРЕНЕИЕ ВОЗМОЖНОСТЕЙ ИНТЕРПРЕТАТОРА
     -----------------------------------------------------------------

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

          Дополнительные команды и соответствуюшие им основные форматы
     лексем   описаны   в  этой  главе.  Для  использования  различных

     дополнительных типов переменных вы  должны  использовать  массивы
     структур  для  хранения  таких  переменных;  одно  из  полей этой
     структуры должно индицировать тип переменной,  а  остальные  поля
     предназначены   для   хранения   значений  этих  переменных.  Для
     использования  строчных  переменных  вам  необходимо   установить
     таблицу  строк.  Простейшим  путем реализации строк фиксированной
     длины является использование фиксированных участков памяти  в 255
     байт для размешения строк.

          И последняя   мысль:  типы  операторов,  которые  вы  можете
     интерпретировать, ограничены лишь вашей фантазией.

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


                                  ГЛАВА 8                     -- 1 --
                                  -------

                О МАНИПУЛИРОВАНИИ ЭКРАНОМ И ГЕНЕРАЦИИ ЗВУКА
     -----------------------------------------------------------------

          На всем протяжении этой книги мы касались,  в основном,  тех
     аспектов  программирования  на  Си,  которые могут заинтересовать
     профессиональных   программистов,    чье     основное     занятие
     программирование   на   Си.  Так  как  возможности  программы  по
     взаимодействию    с    пользователем     часто     ограничиваются
     возможностями,   представленными   в   рамках   пользовательского
     интерфейса,  то в этой главе содержатся завершенные сведения, так
     сказать,    окончательная   точка   зрения   на   возможность   и
     целесообразность манипулирования с экраном дисплея при разработке
     пользовательского  интерфейса.  Основное  внимание  в  этой главе
     уделяется вопросам  отображения  различных  фрагментов  текста  в
     разных   цветах.   Дополнительно  в  этой  главе  рассматриваются
     некоторые  другие  вопросы   программирования   пользовательского
     интерфейса   такие,   как  изменение  размера  и  формы  курсора,
     скроллинг (прокрутка) части текста, сохранение содержимого экрана
     в   виде   дискового  файла.  Использование  со  вкусом  звуковых
     возможностей компьютера позволяет в значительной  мере  "оживить"
     работу  пользователя  с  вашей программой,  а также акцентировать
     внимание пользователя на ряде моментов и ситуаций, возникающих во
     время работы. В связи с этим в главу включен параграф, поясняющий
     возможности пользователя по генерации звуков различных  частот  и
     созданию  различных  звуковых  эффектов с использованием динамика
     компьютера.

          Подпрограммы, описанные    в    этой     главе,     являются
     машинно-зависимыми.  Они могут функционировать на IBM PC, XT, AT,
     PS/2 и совместимых с ними  моделях.  Большинство  из  подпрограмм
     требуют  наличия в вашей конфигурации компьютера цветного дисплея
     (адаптера).  Если вы имеете  несовместимый  с  вышеперечисленными
     моделями компьютер, то вам необходимо будет внести в подпрограммы
     соответствующие изменения.

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

                ИСПОЛЬЗОВАНИЕ ЦВЕТА В ТЕКСТОВОМ РЕЖИМЕ
     -----------------------------------------------------------------

          Ранее вы    могли   видеть   великолепные,   профессионально
     написанные программы, которые не используют цветовые возможности.
     Как   вы  уже  уяснили  из   Глава VIIIы  1,  семейство  компьютеров  PC
     поддерживает  различные  видеорежимы.  Если  вы  имеете  в  своей
     системе  цветной  адаптер,  режим  работы  которого  по умолчанию
     установлен равным 3,  то это означает, что специфицирован цветной
     режим   отображения   текста  80*25  строк.  По  умолчанию  текст
     отображается на экране в белом цвете, однако, имеется возможность
     отображать текст в других цветах.

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

          Атрибутный байт текстового режима.
          ----------------------------------

          Каждый символ отображается на экране  дисплея в соответствии
     с его атрибутным байтом,   определяющим  как  именно отображается
     символ (см.  главу 1).  Если компьютер  включает  в  себя цветной
     адаптер,  работающий в видеорежиме, определяемом значением 3,  то
     соответствующее   значение  атрибутного   байта  определяет  цвет
     отображаемого  символа,   цвет фона,  интенсивность   отображения
     символа (уровень  яркости),  а также устанавливает  или  отменяет
     режим  мерцания  символа.  Состав  атрибутного   байта  показан в
     Таблице 8.1.

          Биты 0,  1 и 2 атрибутного байта определяют компоненты цвета
     символа. Например, если установлен бит 0 (значение бита равно 1),
     то символ отображается в голубом цвете.  Если значение всех  этих
     битов   не   установлено,   то  символ  является  неотображаемым.
     Запомните,  что цвета накладываются друг на друга.  Если значения
     всех  этих  битов  установлены  (равны 1),  символ отображается в
     белом цвете.  Если вы установили значения двух из этих битов,  то
     будет генерирован либо ярко-красный, либо голубой (циановый) цвет
     символа.  Биты с 4 по 6 используются для  установки  цвета  фона.
     Если  значение этих битов не установлено (равно 0),  то цвет фона
     будет  черным,  в  противном  случае  цвет  фона  определяется  в
     соответствии со специфицированным значением битов.

          На заре  микрокомпьютеров  режимом,  в котором видеосистемой
     отображались символы по  умолчанию,  был  режим  полной  яркости,
     однако  наряду  с  этим  режимом  имелась  возможность отображать
     символы в режиме пониженной  яркости.  После  реализации  IBM  PC
     пользователю  был  предложен  альтернативный  путь:  По умолчанию
     отображение символов  видеосистемой  PC  выполняется   в   режиме
     "нормальной" яркости, но вы имеете возможность отображать символы
     в  режиме  повышенной  яркости,  устанавливая  значение   1   для
     соответствующего бита атрибутного байта (бита повышенной яркости)
     В добавок ко всему вы можете установить режим  мерцания  символа,
     установив значение соответствующего бита.

     Таблица 8-1.
     ------------
     Состав атрибутного байта при работе в 3 видеорежиме
     _________________________________________________________________

          Бит            Устанавливаемое значение

          0               Голубой цвет символа
          1               Зеленый цвет символа
          2               Красный цвет символа
          3               Повышенная яркость символа
          4               Голубой цвет фона
          5               Зеленый цвет фона
          6               Красный цвет фона
          7               Мерцание символа

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

     _________________________________________________________________

          В предыдущих параграфах были  рассмотрены  функции,  которые
     выполняли считывание  символов  на экран,  используя при этом как
     обращение к BIOS,  так и непосредственный доступ  к  видеопамяти.
     Непосредственный   доступ   к  видеопамяти  является  необходимым
     условием   повышения   скорости   реакции   задач   на   действия
     пользователя. Однако   непосредственный   доступ   к  видеопамяти
     значительно  снижает  возможность  переносимости   программ   (их
     мобильность),    а   также   является   серьезной   помехой   при
     использовании программ  в  мультизадачных  операционных  системах
     типа  OS/2.  Функции,  рассматриваемые  в этой главе,  используют
     возможности BIOS и видеопамяти по той причине,  что сами по  себе
     эти функции более мобильны и,  в конечном итоге, для ускорения их
     быстродействия обычно не требуется стандартный вывод  на дисплей.
     Следует отметить,  что интуитивно использование непосредственного
     доступа к видеопамяти является более предпочтительным.

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

          Отображение строки в определенном цвете.
          ----------------------------------------

          Отображение строки в определенном цвете  не  является  столь
     трудной задачей, как вам может казаться на первый взгляд, если вы
     используете функции записи символа,  которые используют,  в  свою
     очередь,  возможности  BIOS  и  видеопамяти (ROM-BIOS).  ROM-BIOS
     прерывание 10Н,  функция 9 позволяет  отобразить  текущий  символ
     (один!) в позиции курсора и его атрибуты. Проблема состоит лишь в
     перемещении курсора по записываемой вами строке,  но  это  должна
     осуществлять непосредственно ваша подпрограмма.

          В соответствии с  этим  возникает,  во-первых, необходимость
     определения текущей  позиции  курсора.  Для  этого   используется
     функция   read_cursor_xy(),   представленная  ниже.  Эта  функция
     использует ROM-BIOS-прерывание 10Н, функцию 3, для чтения текущих
     координат  позиции  курсора  X  и  Y.  Координаты позиции курсора
     возвращаются в качестве значений аргументов функции.

          /* Чтение текущих координат позиции курсора */
     void read_cursor_xy(x,y)
     char *x,*y;
     {
          union REGS r;

          r.h.ah = 3;  /* чтение текущей позиции курсора */
          r.h.bh = 0;  /* видеостраница */
          int86(0x10,&r,&r);
          *y = r.h.dl;
          *x = r.h.dh;
     }

          После определения   координат   текущей   позиции   курсора,
     функция, которая  выполняет  печать  строки,  должна  осуществить
     перемещение курсора к следующему символу,  с тем, чтобы используя
     ROM-BIOS-прерывание  напечатать  его.  Для  перемещения   курсора
     целесообразно  использовать  функцию goto_xy(),  которая была уже
     рассмотрена ранее и приводится в этой главе для полноты изложения
     материала.

          /* Перемещение курсора в позицию, специфицированную
             координатами X и Y
          */
     void goto_xy (x,y)
     int x,y;
     {
            union REGS r;

            r.h.ah = 2; /* функция адресации курсора */
            r.h.dl = x; /* координата столбца */
            r.h.dh = y; /* координата строки */
            r.h.bh = 0; /* видеостраница */
            int86(0x10,&r,&r);
     }

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

          Функция color_puts(),   представленная   ниже,    отображает
     специфицированную пользователем строку в указанном цвете.

          /* Печать строки в цвете   */
     void color_puts(s,color)
     char *s;  /* строка */
     char color; /* цвет строки */
     {
           union REGS r;
           char x,y;
           read_cursor_xy(&x,&y); /* получение текущей позиции курсора
                           */
           while (*s) {
           if (*s == '\n') {  /* обработка символа новой строки */
               printf("\n");
               s++;
               x = 0; y++;  /* переход на следующую строку */
               continue;
      }
         r.h.ah = 9; /* функция отображения символа и его атрибутов */
         r.h.al = *s++;    /* отображаемый символ */
         r.h.bl = color;   /* атрибуты цвета */
         r.h.bh = 0;       /*видеостраница 0 */
         r.x.cx = 1; /* отобразить за единицу времени ( такт ) */
          int86(0x10,&r,&r);
          x++;
          goto_xy(x,y); /* перемещение курсора */
      }
     }

          Как вы можете видеть,  отображаемый  символ  запоминается  в
     регистре  AL,  атрибуты  цвета  символа  -  в регистре BL,  номер
     видеостраницы - в регистре BH,  а количество  интервалов  времени
     (тактов  процессора),  за  которое  будет  отображен  символ  - в
     регистре CX.  Заметим, что функция также обрабатывает специальный
     символ   новой  строки  ('\n').  Вы  можете  также,  по  желанию,
     организовать обработку символов табуляции ('\t'), двойных кавычек
     (") и других специальных символов.

          Использование функции color_puts() предполагает наличие ряда
     макроопределений в начале вызывающей  функцию программы. Перечень
     макроопределений представлен ниже

            #define BLUE             1
            #define GREEN        2
            #define RED             4
            #define INTENSE      8
            #define BLUE_BACK    16
            #define GREEN_BACK   32
            #define RED_BACK     64
            #define BLINK        128

          Используя эти макросы, вы можете по своему усмотрению выдать

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

     на экран строку текста на фоне установленного вами цвета, а также
     саму строку в определенном вами цвете.  Вы можете также управлять
     режимом отображения  строки  (повышенная  яркость  или мерцание).
     Комбинируя цвета,  режимы мерцания  или  повышенной  яркости  для
     одного  или  совокупности  символов,  вы  можете  добиться любого
     желаемого вами  эффекта.  Например,  представленная  ниже  строка
     программы приведет  к отображению строки "А это - текст" в режиме
     повышенной яркости в голубом (циановом) цвете:

          color_puts("А это - текст",GREEN | RED | INTENSE );

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

          Использование цвета.
          --------------------

          Довольно эффективно  использование  многоцветного  текста  в
     различных   приложениях.  Во  всяком  случае  многоцветный  текст
     смотрится всегда намного эстетичней,  чем монохромный. Однако при
     использовании   многоцветного  текста  необходимо  придерживаться
     следующих основных положений:

          - Избегайте использования "нестандартных"  цветов.  Наиболее
     общим  лучшим  вариантом  является отображение белых с имволов на
     черном фоне.  Предпочтительнее вместо отображения какой-то важной
     информации  в контрастном цвете отображать ее в режиме повышенной
     яркости.

          - Наиболее  эффективным признано использование цветных рамок
     экрана и окон.

          - В   ряде   ситуаций   полезным   оказывается   отображение
     отрицательного остатка  (например,  денежной  суммы)  в   красном
     цвете.

          - Отображение  текущей  строки  (или части текущей строки) в
     контрастном цвете является,  пожалуй,  лучшим  приемом  индикации
     положения  действий  пользователя  на  экране  в  текущий  момент
     времени.

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

                         ИЗМЕНЕНИЕ РАЗМЕРА КУРСОРА
     -----------------------------------------------------------------

          Большинство пользователей  даже  не  представляют  насколько
     велики  возможности  семейства  машин  IBM PC.  В частности,  они
     позволяют  изменять   размер   курсора.   По   умолчанию   курсор
     отображается  в виде одной мерцающей строчки (развертки дисплея).
     Однако пользователь может варьировать размером курсора  от  одной
     строки  развертки дисплея до полного размера (высоты) символа.  В
     цветном текстовом режиме курсор может иметь  высоту  от  0  до  8
     строк развертки.  (В монохромном режиме курсор может иметь высоту
     от 0 до 14 строк развертки,  однако в данном параграфе  мы  будем
     рассматривать  только  цветной  режим).  Нижняя  строка развертки

     имеет номер 0. Лучше всего рассматривать изменение курсора именно
     относительно  строки  развертки  0,  так  как  применение другого
     метода может привести к значительным расхождениям  результатов на
     различных  компьютерах.  (В  принципе  применение  других методов
     возможно, однако вам необходимо помнить, что при их использовании
     вы можете не добиться соответствия форм курсора при решении одной
     и той же задачи на  разных  компьютерах).  При  условии,  что  вы
     будете рассматривать изменение формы курсора относительно нулевой
     строки  развертки,  изменение  формы  курсора  будет  выполняться
     аналогично изображенному на рис.8-1.

          Для установления размера курсора вам необходимо использовать
     ROM-BIOS-прерывание 10Н,  функцию 1, которая устанавливает размер
     курсора.   Начало   курсора   (начальная   строка   развертки)  -
     запоминается в регистре CН, а конец (конечная строка развертки) в
     регистре CL.

     ----------------------------------------------------------------¬
     ¦   Строка развертки                                            ¦
     ¦                                                               ¦
     ¦   7                                            ----¬          ¦
     ¦                                                ¦   ¦          ¦
     ¦   6                                     ----¬  ¦   ¦          ¦
     ¦                                         ¦   ¦  ¦   ¦          ¦
     ¦   5                              ----¬  ¦   ¦  ¦   ¦          ¦
     ¦                                  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦   4                       ----¬  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦                           ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦   3                ----¬  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦                    ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦   2         ----¬  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦             ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦   1  ----¬  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦      ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦   0  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦  ¦   ¦          ¦
     ¦      L----  L----  L----  L----  L----  L----  L----          ¦
     L----------------------------------------------------------------
        Рис. 8-1.  Возможность изменения формы курсора в
                   цветном режиме.

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

          Функция size_cursor(),  представленная  ниже,  устанавливает
     размер курсора.

          /* Установление размера курсора */
     void size_cursor(start,end)
     char start,end; /* начальная и конечная строки развертки */
     {
          union REGS r;

          r.h.ah = 1; /* функция адресации курсора */
          r.h.ch = start;
          r.h.cl = end;
          int86(0x10,&r,&r);
     }

          При использовании  функции  size_cursor()  укажите  желаемые
     начальную  и  конечную  строки  развертки,  определяющие   размер
     курсора.  Например,  следующая  конструкция  позволяет установить
     высоту курсора в три строки развертки:

               size_cursor(0,2);

          Форма курсора может быть  изменена  либо  очередным  вызовом
     функции size_cursor(), либо изменением видеорежима.

          Использование курсоров  различной  формы позволяет не только
     полностью  удовлетворить  ваши  эстетические  требования,  но   и
     повысит   наглядность  программы.  Имейте  в  виду,  что  большой
     мерцающий курсор вызывает у пользователей раздражение.

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

                           СКРОЛЛИНГ ЧАСТИ ЭКРАНА
     -----------------------------------------------------------------

          Две   совместно  используемые   функции  ROM-BIOS-прерывания
     позволяют осуществлять скроллинг вперед и назад части экрана. Эти
     функции  были  включены  в  ROM-BIOS  для  поддержки многооконных
     интерфейсов. Как вы знаете,  когда курсор расположен  в  двадцать
     пятой  строке  и  вы  нажали  клавишу  <ВВОД>,  то  автоматически
     осуществляется перемещение текста на одну строку  вверх  с  целью
     отображения новой строки в нижней части экрана.  Точно так же,  с
     помощью функций 6 и 7 прерывания ROM-BIOS 10Н,  можно осуществить
     скроллинг лишь   части  экрана.  Функция  6  позволяет  выполнить
     скроллинг в окне вниз (вперед), а функция 7 - вверх (назад).

          Обе функции при вызове  используют  информацию,  хранимую  в
     определенных  регистрах.  Занесите  количество строк,  на которые
     будет "прокручиваться" текст (мощность скроллинга) в  регистр AL.
     Номер верхней левой строки,  ограничивающей ваше "окно", занесите
     в регистр CH,  а номер верхнего левого столбца -  в  регистр  CL.
     Номер нижней левой строки занесите в регистр DH,  а номер нижнего
     правого столбца - в регистр DL.  В конце запомните в регистре  BH
     атрибуты режима отображения, которые будут определять, как именно
     будут отображаться в процессе скроллинга  новые  строки.  Функция
     scroll_window() представлена ниже.

          /* Скроллинг в окне вперед и назад */

     void scroll_window(startx,starty,endx,endy,lines,direct)
     char startx,starty;/* верхний левый угол */
     char endx,endy;    /* нижний правый угол */
     char lines;        /* число строк прокрутки */
     char direct;       /* вперед или назад */
     {
          union REGS r;

          if ( direct == UP ) r.h.ah = 6; /* скроллинг вперед */
          else r.h.ah = 7; /* скроллинг вниз (назад) */
          r.h.al = lines;
          r.h.ch = starty;
          r.h.cl = startx;
          r.h.dh = endy;
          r.h.dl = endx;
          r.h.bh = 0;     /* режим отображения */
          int86(0x10,&r,&r);
     }

          Вы можете  определить  макрос  UP  как   имеющий   некоторое
     значение.  Вы  также  можете  определить  макрос  DOWN,  значение
     которого  будет  отлично  от  UP,  а   затем   использовать   при
     необходимости     осуществления    скроллинга    в    окне    эти
     макроопределения.   Такой   прием   значительно   упростит   вашу
     программу.   Функция   scroll_window()  присваивает  регистру  ВН
     значение 0 для сохранения пустых строк, однако вы можете изменить
     это значение по своему усмотрению.

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

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

                   ПРОСТЕЙШАЯ ДЕМОНСТРАЦИОННАЯ ПРОГРАММА
     -----------------------------------------------------------------

          Эта программа позволяет продемонстрировать  работу  функций,
     рассмотренных  в данной главе до настоящего момента.  Эти функции
     позволяют изменить форму курсора,  выдать сообщение  в  цвете,  а
     также использовать скроллинг части экрана.  Результат работы этой
     программы представлен на рис.8-2.

     ----------------------------------------------------------------¬
     ¦                                                               ¦
     ¦   a)     Это - тест                                           ¦
     ¦     aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa    ¦
     ¦     bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb    ¦
     ¦     cccccccccccccccccccccccccccccccccccccccccccccccccccccc    ¦
     ¦     dddddddddddddddddddddddddddddddddddddddddddddddddddddd    ¦
     ¦     eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee    ¦
     ¦     ffffffffffffffffffffffffffffffffffffffffffffffffffffff    ¦
     ¦     gggggggggggggggggggggggggggggggggggggggggggggggggggggg    ¦
     ¦     hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh    ¦
     ¦     iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii    ¦
     ¦     jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj    ¦
     ¦     kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk    ¦
     ¦     llllllllllllllllllllllllllllllllllllllllllllllllllllll    ¦
     ¦     mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm    ¦
     ¦     nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn    ¦
     ¦     oooooooooooooooooooooooooooooooooooooooooooooooooooooo    ¦
     ¦     pppppppppppppppppppppppppppppppppppppppppppppppppppppp    ¦
     ¦     qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq    ¦
     ¦     rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr    ¦
     ¦     ssssssssssssssssssssssssssssssssssssssssssssssssssssss    ¦
     ¦     tttttttttttttttttttttttttttttttttttttttttttttttttttttt    ¦
     ¦     uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu    ¦
     ¦     vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv    ¦
     ¦                                                               ¦
     ¦     б) Это - тест.                                            ¦
     ¦     aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa    ¦
     ¦     bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb    ¦
     ¦     cccccccccccccccccccccccccccccccccccccccccccccccccccccc    ¦
     ¦     dddddddddddddddddddddddddddddddddddddddddddddddddddddd    ¦
     ¦     eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee    ¦
     ¦     ffffffffffffffffffffffffffffffffffffffffffffffffffffff    ¦
     ¦     gggggggggggggggggggggggggggggggggggggggggggggggggggggg    ¦
     ¦     hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh    ¦
     ¦     iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii    ¦
     ¦     jjjjjjjjjj                     jjjjjjjjjjjjjjjjjjjjjjj    ¦
     ¦     kkkkkkkkkk                     kkkkkkkkkkkkkkkkkkkkkkk    ¦
     ¦     llllllllll                     lllllllllllllllllllllll    ¦
     ¦     mmmmmmmmmmjjjjjjjjjjjjjjjjjjjjjmmmmmmmmmmmmmmmmmmmmmmm    ¦
     ¦     nnnnnnnnnnkkkkkkkkkkkkkkkkkkkkknnnnnnnnnnnnnnnnnnnnnnn    ¦
     ¦     oooooooooolllllllllllllllllllllooooooooooooooooooooooo    ¦
     ¦     pppppppppppppppppppppppppppppppppppppppppppppppppppppp    ¦
     ¦     qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq    ¦

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

     ¦     rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr    ¦
     ¦     ssssssssssssssssssssssssssssssssssssssssssssssssssssss    ¦
     ¦     tttttttttttttttttttttttttttttttttttttttttttttttttttttt    ¦
     ¦     uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu    ¦
     ¦     vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv    ¦
     ¦                                                               ¦
     L----------------------------------------------------------------

      Рис. 8-2 Результат работы программы.

          /* Демонстрационная программа печати текста в цвете,
             изменения формы курсора и скроллинга в окне
          */

            #include "dos.h"

            #define BLUE         1
            #define GREEN        2
            #define RED          4
            #define INTENSE      8
            #define BLUE_BACK   16
            #define GREEN_BACK  32
            #define RED_BACK    64
            #define BLINK      128

               #define UP        0
            #define DOWN         1

            void mode(),color_puts(),palette(), read_cursor_xy();
            void goto_xy(),size_cursor(),scroll_window();

               main()
               {
                int i,j;

             mode(3); /* режим текстовый, цветной */

                size_cursor(0,3);
                goto_xy(0,0);
                color_puts(" Это - тест\n",BLUE |RED | INTENSE);
                for ( i=0;i<22;i++) {
                    for ( j=0;j<79;j++)
                    printf("%c",i+'a');
                    printf("\n");
               }
               getche();
               scroll_window(10,10,50,15,3,DOWN);
               getche();
     }
          /* Печать строки в цвете */

               void color_puts(s,color)
               char *s;      /* строка */

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

               char color;   /* цвет строки */
               {
                    union REGS r;
                    char x,y;

                    read_cursor_xy(&x,&y); /* чтение текущей позиции
                                              курсора   */
                    while (*s) {
                     if (*s == '\n') { /* обработка символа новой
                                          строки */
                     printf("\n");
                     s++;
                     x = 0; y++;  /* переход к новой строке */
                     continue;
                    }

                    r.h.ah = 9;/* отображение символов по атрибутам */
                    r.h.al = *s++; /* выдаваемый символ */
                    r.h.bl = color;  /* атрибут цвета */
                    r.h.bh = 0; /*     видеостраница 0 */
                    r.x.cx = 1; /* выдача символа за один такт */
                    int86(0x10,&r,&r);
                    x++;
                    goto_xy(x,y);      /* перемещение курсора */
                    }
               }
          /* чтение текущей позиции курсора */
               void read_cursor_xy(x,y)
               char *x,*y;
               {
                union REGS r;

                r.h.ah = 3; /* чтение позиции курсора */
                r.h.bh = 0; /* видеостраница 0 */
                int86(0x10,&r,&r);
                *y = r.h.dl;
                *x = r.h.dh;
               }

          /* установка палитры */
               void palette(pnum)
               int pnum;
               {
                union REGS r;

                r.h.bh = 1;  /* код для графического режима 4 */
                r.h.bl = pnum;
                r.h.ah = 11; /* функция установки палитры */
                int86(0x10,&r,&r);
               }

          /* установка видеорежима */
               void mode(mode_code)
               int mode_code;

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

               {
                union REGS r;

                r.h.al = mode_code;
                r.h.ah = 0;
                int86(0x10,&r,&r);
               }

          /* перемещение курсора в позицию x,y */
               void goto_xy(x,y)
               int x,y;
               {
                union REGS r;

                r.h.ah = 2; /* функция адресации курсора */
                r.h.dl = x; /* координаты столбца */
                r.h.dh = y; /* координаты строки */
                r.h.bh = 0; /* видеостраница 0 */
                int86(0x10,&r,&r);
               }

          /* установка размера (формы) курсора */
               void size_cursor(start,end)
               char start,end; /* начальная и конечная строки
                         развертки */
               {
                union REGS r;

                r.h.ah = 1; /* функция адресации курсора */
                r.h.ch = start;
                r.h.cl = end;
                int86(0x10,&r,&r);
               }

          /* скроллинг в окне вперед и назад */
         void scroll_window(startx,starty,endx,endy,lines,direct)
         char startx,starty; /* верхний левый угол */
         char endx,endy;     /* нижний правый угол */
         char lines;         /* мощность скроллинга */
         char direct;        /* вверх или вниз */
          {
           union REGS r;

           if ( direct == UP ) r.h.ah = 6; /* скроллинг вверх */
            else r.h.ah = 7; /* скроллинг вниз */

          r.h.al = lines;
          r.h.ch = starty;
          r.h.cl = startx;
          r.h.dh = endy;
          r.h.dl = endx;
          r.h.bh = 0; /* режим отображения */
          int86(0x10,&r,&r);
          }

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

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

                  СОХРАНЕНИЕ КОПИИ ЭКРАНА В ДИСКОВОМ ФАЙЛЕ
     -----------------------------------------------------------------

          Ни в  DOS,  ни в OS/2 нет утилит,  сходных с утилитой печати
     копии экрана,  позволяющей сохранять  текущее  содержимое  экрана
     дисплея в  дисковом  файле.  В этом разделе вы найдете пояснение,
     как можно создать программу,  которая  выполняла  бы  именно  эту
     функцию.

          Эта программа использует ROM-BIOS-прерывание 10Н,  функцию 8
     для чтения  символа из  текущей позиции курсора,  после чего этот
     символ записывается в файл на диске.  Как и в предыдущем разделе,
     вы опять встретитесь с функцией goto_xy(),  которая в этом случае
     используется  для  перемещения  курсора  последовательно  по всем
     строкам экрана,  начиная с левого верхнего угла экрана до правого
     нижнего угла.

          Имя файла,   в   котором   будет   храниться  копия  экрана,
     указываетя в качестве аргумента программы.  Если,  к примеру,  вы
     назовете  свою программу,  копирующую экран на диск,  screen,  то
     представленная ниже командная строка приведет  к  созданию  копии
     экрана в файле с именем scr.sav:

     C> screen scr.sav

          А вот исходный текст самой программы копирования:

               /* Эта программа копирует содержимое экрана вашего
                  дисплея в файл, имя которого указано в командной
                  строке
               */

          #include "dos.h"
          #include "stdio.h"

          void save_screen(),goto_xy();

          main(argc,argv)
          int argc;
          char *argv[];
          {
            if ( argc != 2 ) {
               printf(" используйте формат : screen <имя файла>");
               exit(1);
            }
            save_screen(argv[1]);
          }

          /* сохранение содержимого экрана в дисковом файле */
               void save_screen(fname)
               char *fname;
          {
           FILE *fp;
           union REGS r;

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

           register char x,y;

           if ( !( fp=fopen(fname,"w"))) {
               printf(" Файл не может быть открыт ");
               exit(1);
           }

           for (y=0;y<25;y++)
               for (x=0;x<80;x++) {
               goto_xy(x,y);
               r.h.ah = 8; /* чтение символа */
               r.h.bh = 0; /* видеостраница */
               int86(0x10,&r,&r);
               putc(r.h.al,fp); /* выдача (печать) символа */
           }
          fclose(fp);
          }

          /* Перемещение курсора в позицию (x,y) */
          void goto_xy(x,y)
          int x,y;
          {
            union REGS r;

            r.h.ah = 2; /* функция адресации курсора */
            r.h.dl = x; /* координата столбца */
            r.h.dh = y; /* координата строки */
            r.h.bh = 0; /* видеостраница */
            int86(0x10,&r,&r);
          }

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

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

                           А ТЕПЕРЬ ДОБАВИМ ЗВУК.
     -----------------------------------------------------------------

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

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

          Программируемый таймер 8253.
          -----------------------------

          Генерация звуков  в  компьютере  PC  выполняется  с  помощью
     программируемого таймера 8253, который применяется для управления
     колебаниями   динамика.    Управление     колебаниями    динамика
     определяется  частотой,  которая,  в  свою очередь,  определяется
     содержимым  различных   внутренних   регистров.   Значения   этих
     регистров  устанавливаются при записи в определенные порты.  Порт
     66 используется для  спецификации  счетчика,  который  использует
     таймер  при  определении  интервала  колебаний  динамика.  Таймер
     работает в строгом соответствии с частотой системного  таймера  и
     специфицированным   значением  счетчика,  определяющим  колебания
     динамика.  Затем,  после обнуления счетчика происходит  установка
     нового   значения   счетчика,   и   весь   цикл  функционирования
     программируемого таймера повторяется сначала.  Значение  счетчика
     определяется по следующей формуле:

          count = 1,193,180/требуемая частота

     где 1,193,180 есть тактовая частота системного таймера.

          Регистр-счетчик таймера  8253  устанавливается  в  следующей
     последовательности (значение   счетчика   задается    двухбайтным
     числом):

       1.  Выдать в порт  67  значение  182  (означающее,  что   будет
           устанавливаться счетчик).

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

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

          Динамики большинства  компьютеров  класса  PC  не  позволяют
     воспроизводить полный спектр частот,  воспринимаемых человеческим
     слухом (от  20  Гц  до  18.000  Гц).  Однако  динамик   позволяет
     воспроизводить  ноты  лучше,  чем  динамики  других компьютеров в
     пределах 12000 Гц и даже выше. В основном же динамик используется
     в пределах 100-5000 Гц.

          Итак, таймер   установлен.   Однако  динамик  еще  не  будет
     воспроизводить звук,  так как не  включен.  Таймер  8253  активен
     постоянно,  а  динамик  требует дополнительной команды включения.
     Активизация  динамика  осуществляется  путем  установки  значений
     битов  0  и 1 регистра программируемого периферийного интерфейса,
     задание  значений  которого  выполняется  через  порт   97.  Если
     значения этих двух битов установлены (равны 1), то динамик издает
     звук частотой,  установленной счетчиком 8253.  Если значения этих
     битов равны 0, то никакой звук генерироваться не будет. Остальные
     биты  этого  байта  используются  другими  устройствами,  поэтому
     интерпретация  значения левых битов не может быть изменена. Таким
     образом, для  установки  значений   управляющих   динамиком   бит

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

     необходимо выполнить следующую последовательность действий:

      1. Получить текущее значение регистра из порта 97.
      2. Сравнить это значение с 3 или установить равным 3.
      3. Записать результат в порт 97.

          Для того,  чтобы  выключить динамик,  необходимо переслать в
     порт значение 253.

          Простейшим приемом,  позволяющим читать и писать байт из или
     в  порт,  в Си является использование соответствующих функций.  В
     Турбо Cи - это функции inportb() и outportb().  В Microsoft Cи  -
     это функции inp() и outp(). Они имеют следующий общий формат:

               int inportb(int port);
               void outportb(int port, char value);
               int inp(unsigned port);
               int outp(unsigned port, int value);

          В  других  компиляторах  Си  эти  функции  могут  иметь иные
     названия, но обязательно будут присутствовать в вашей библиотеке,
     так как являются одними из базовых функций версий Си  для ПЭВМ. В
     программах,  приведенных в этом  параграфе,  используются функции
     Турбо Cи.

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

          Простейший способ проверки слуха.
          ---------------------------------

          Вы обладаете  возможностью  сделать  несколько  грубый,   но
     эффективный тест слуха,  который в состоянии обнаружить некоторые
     типы дефекта слуха.  Как вы  ранее  узнали,  динамик  большинства
     компьютеров серии PC не воспроизводит звуки выше 12000 Гц. Однако
     ряд людей,  у которых отмечены  некоторые  отклонения  слуха,  не
     могут услышать звук даже такой частоты. Фактически, тестируя свой
     слух,  вы  будете  несколько  удивлены  тем,  насколько   высоким
     окажется звук с частотой 12000 Гц.  (Предупреждение: тестирование
     слуха с помощью этого теста можно производить  лишь  ради  шутки.
     Он,   естественно,   не   позволяет  действительно  оценить  слух
     испытуемого.  Поэтому,  если вы заметили у себя дефекты слуха или
     хотите  действительно  проверить  свой  слух,  обратитесь лучше к
     своему врачу).

          Для получения звука в тесте  используется  функция  sound(),
     которая  генерирует  непродолжительное звучание специфицированной
     ноты. Как показано ниже, эта функция содержит все необходимое для
     того,   чтобы   сгенерировать   любой  звук  с  помощью  динамика
     компьютера.

               /* Звучание динамика на заданной частоте */

          void sound(freq)
          int freq;
          {
            unsigned i;
            union {
               long divisor;
               unsigned char c[2];
            } count;

            unsigned char p;

            count.divisor = 1193280 / freq; /* вычисление небходимого
                                             значения счетчика */
            outportb(67,182); /* обращение к таймеру 8253 после
                                 установки счетчика */
            outportb(66,count.c[0]); /* пересылка младшего байта */
            outportb(66,count.c[1]); /* пересылка старшего байта */
            p = inportb(97); /* чтение существующего шаблона бит */
            outportb(97,p|3); /* установка битов 0 и 1 */

            for (i=0;i<64000;++i); /* цикл задержки */

            outportb(97,p); /* восстановление первоначального значения
                               шаблона бит для отключения динамика */
          }

          Заметим, что  частота  звучания  ноты  специфицирована   как

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

     аргумент функции. Цикл задержки необходим, так как без него вы бы
     услышали  только  мгновенный  "щелчок"  или  "писк".  Вы   можете
     изменить  частоту  работы  системного  таймера  процессора вашего
     компьютера.  При этом,  оформив  его  как  параметр  функции,  вы
     добьетесь  определенной  эффективности  вашей программы.  Функция
     sound()  может  использоваться   и   для   получения   банального
     "пищания" компьютера.

          Управляющая функция  для  программы теста слуха представлена
     ниже.

               /* Простейший тест слуха */

          #include "dos.h"

          void sound();

          main()
          {
               int freq;

               do {
                 printf(" Введите частоту ( 0 - выход ): ");
                 scanf("%d",&freq);
                 if ( freq ) sound(freq);
               } while(freq);
          }

          При использовании теста,  в возрастающем порядке  указывайте
     частоту звука до тех пор,  пока звук воспринимается на слух.  Для
     выхода введите 0.

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

          Имитация звука сирены и взврывы.
          --------------------------------

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

          Например, для создания эффекта  звучания  сирены  вы  должны
     варьировать  частоту звука между двумя конечными точками.  Высота
     звука должна изменяться от меньшей к большей, а затем уменьшаться
     от  большей  к  меньшей.  Функция  siren(),  представленная ниже,
     использует этот метод для создания эффекта звучания сирены.

          #define DELAY 10000

          /* Создание эффекта звучания сирены */
          void siren()
          {
            unsigned i,freq;
            union {
               long divisor;
               unsigned char c[2];
            } count;

            unsigned char p;

            p = inportb(97); /* чтение существующего шаблона бит */
            outportb(97,p|3); /* установка бит 0 и 1 */
               /* повышение звука сирены */
            for (freq = 1000;freq<3000;freq+=RATE) {
               count.divisor = 1193280 / freq; /* вычисление нужного
                                                  значения счетчика */
               outportb(67,182); /* обращение к таймеру 8253 после
                                      определения значения счетчика */
               outportb(66,count.c[0]); /* пересылка младшего байта */
               outportb(66,count.c[1]); /* пересылка старшего байта */

               for (i=0;i1000;freq-=RATE) {
               count.divisor = 1193280 / freq; /* вычисление нужного
                                                  значения счетчика */
               outportb(67,182); /* обращение к таймеру 8253 после
                                      определения значения счетчика */
               outportb(66,count.c[0]); /* пересылка младшего байта */
               outportb(66,count.c[1]); /* пересылка старшего байта */

               for (i=0;i1000;freq-=RATE) {
               count.divisor = 1193280 / freq; /* вычисление нужного
                         значения счетчика */
               outportb(67,182); /* обращение к таймеру 8253 после
                         определения значения счетчика */
               outportb(66,count.c[0]); /* пересылка младшего байта */
               outportb(66,count.c[1]); /* пересылка старшего байта */

               for (i = 0;i5000); /* после персонального
                                            прослушивания */
                    sound(freq);
               } while (!kbhit());
          }

          /* звучание динамика на специфицированной частоте */
          void sound(freq)
          int freq;
          {
            unsigned i;
            union {
               long divisor;
               unsigned char c[2];
            } count;

            unsigned char p;
            count.divisor = 1193280 / freq; /* вычисление нужного
                                               значения счетчика */
               outportb(67,182); /* обращение к таймеру 8253 после
                                    определения значения счетчика */
               outportb(66,count.c[0]); /* пересылка младшего байта */
               outportb(66,count.c[1]); /* пересылка старшего байта */

               p = inportb(97); /* чтение существующего шаблона бит */
               outportb(97,p|3); /* установка бит 0 и 1 */

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

               for (i = 0;i


                                  ГЛАВА 9                     -- 1 --
                                  -------

                            ИНТЕРФЕЙС С "МЫШЬЮ"
     -----------------------------------------------------------------

          Наиболее   популярным   устройством   ввода   данных   после
     клавиатуры является "мышь"  (mouse). Несмотря на то, что "мышь" и
     сходные  технологии,  такие  как "roller ball",  получили широкое
     распространение   лишь  в  последнее  время,  популярность "мыши"
     берет свое начало с момента выхода на  рынок очередной разработки
     фирмы  Apple  компьютера  Apple  Lisa,  в  котором  впервые  была
     применена технология "мышь" для работы с  пиктограммным (иконным)
     интерфейсом операционной  системы этого компьютера.  Модель Apple
     Lisa  произвела форменный переворот  в  фирме  Macintosh, которая
     пошла по пути использования "мыши"  и пиктограммного интерфейса в
     своих  программных продуктах.  Перед выходом на  рынок  серии IBM
     PS/2  "мышь",  по существу, была третьим дополнением к РС. Тем не
     менее уже при анонсировании системы IBM  PS/2 сообщалось, что она
     снабжена  портом  для  подключения  "мыши",   и  "мышь"  занимает
     значительное место среди РС.

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

          Некоторые модели манипуляторов  типа  "мышь",  равно  как  и
     выполняемые  ими  функции,  могут  значительно отличаться друг от
     друга.  Поэтому заметим,  что все программы,  приведенные в  этой
     главе,  ориентированы  на  использование  "мыши" фирмы Microsoft,
     которая функционально идентична "мыши", используемой в моделях PS
     /2.  Для  обеспечения  интерфейса  с  "мышью" фирмы Microsoft вам
     необходимо  иметь  по  крайней  мере  саму  "мышь",   руководство
     пользователя  по программному обеспечению "мыши" (Microsoft Mouse
     Programmer's Reference Guide) и поставляемый с  этим руководством
     диск.  На  этом диске расположена специальная библиотека с именем
     MOUSE.LIB, выполняющая поддержку функционирования "мыши" на самом
     нижнем уровне. Мы будем использовать функции из этой библиотеки в
     качестве базовых функций при рассмотрении  программ, предлагаемых
     вам в этой главе.  вы должны помнить, что ваш компилятор С должен
     быть совместим с подключаемыми  на  этапе  редактирования  связей
     подпрограммами  из  библиотеки  подпрограмм поставляемых на диске
     фирмой Microsoft.  вам также надлежит  помнить,  что  обязательно
     необходимо наличие драйвера устройства MOUSE.SYS.

          После беглого  обзора  основ использования манипулятора типа
     "мышь" в этой главе вы  получите  информацию  о  том,  как  можно
     модифицировать  ранее  рассмотренную программу "рисования" с тем,
     чтобы она могла работать с "мышью" (с учетом того,  что  основные
     концепции  применения  "мыши",  а  также разработанные в процессе

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

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

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

                   НЕКОТОРЫЕ НАЧАЛЬНЫЕ СВЕДЕНИЯ О "МЫШИ"
     -----------------------------------------------------------------

          Для того, чтобы использовать "мышь", прежде всего необходимо
     инсталировать соответствующий драйвер. Для "мыши" фирмы Microsoft
     в файл CONFIG.SYS должна быть добавлена следующая строка:

                             device = mouse.sys

          Для инсталяции   драйвера   "мыши"  фирмы  IBM  должна  быть
     запущена программа MOUSE.COM.  С этой целью в  файл  AUTOEXEC.BAT
     может быть добавлена строка вида:

                                   mouse

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

          Точно так же,  как  с  клавиатурой  ассоциирован  курсор,  с
     "мышью"  также  ассоциирован  некий  маркер на экране (называемый
     далее указатель).  Подпрограммы в библиотеке в  поддержке  "мыши"
     фирмы   Microsoft   определяют   по   умолчанию  следующую  форму
     указателя:  в графических режимах - это стрелка,  а  в  текстовых
     режимах  -  прямоугольник  в  рамках габаритных размеров символа.
     Аналогично  курсору  клавиатуры  указатель-курсор  "мыши"   может
     отображаться    лишь    тогда,   когда   "мышь"   непосредственно
     используется.  В  противном  случае  указатель-курсор  "мыши"  не
     отображается на экране так, как будто интерфейса с "мышью" вообще
     не существует.

          Несмотря на то,  что "мышь" и экран физически отделены  друг
     от друга,  связь  между  ними  все  же  имеется,  так как драйвер
     "мыши" автоматически    отслеживает     содержимое     счетчиков,
     индицирующих   текущее   состояние  указателя-курсора  "мыши"  на
     экране.  При перемещении "мыши" курсор автоматически перемещается
     на  экране  в  точном соответствии с изменением координат "мыши".
     Расстояние,  на которое  была  перемещана  "мышь",  измеряется  в
     "мышиных"  шагах.  Один  такой  шаг  равен 1/200 дюйма.  Однако в
     большинстве случаев знать на сколько переместилась  "мышь" совсем
     необязательно.

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

                       ВИРТУАЛИЗАЦИЯ И РЕАЛЬНЫЙ ЭКРАН
     -----------------------------------------------------------------

          Библиотека подпрограмм  поддержки  "мыши"  фирмы   Microsoft
     работает  с  виртуальным  экраном  в  виде  массива  точек растра
     (массива из  единиц  минимального  изображения,  цвет  и  яркость
     которых  можно  задать  независимо  от  остального  изображения),
     который может отличаться от  реального  экрана.  При  перемещении
     "мыши"  счетчики  местоположения  курсора изменяют свое значение.
     Перед  отображением  курсора   виртуальные   координаты   курсора
     преобразуются  в  координаты реального экрана.  В видеорежимах 6,
     14,  15 и 16 это преобразование осуществляется один к  одному.  В
     режимах  4 и 5 не каждая точка виртуальной горизонтальной позиции
     преобразуется в координты реального  экрана,  а  через  одну.  вы
     должны   обратить  внимание  на  этот  факт,  так  как  программа
     рисования, к которой будет добавлен интерфейс с "мышью" и которая
     будет  рассматриваться  в  данной  главе,  работает  именно  в  4
     графическом режиме.

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

                        БИБЛИОТЕКА ПОДДЕРЖКИ "МЫШИ"
     -----------------------------------------------------------------

          Подпрограммы внутри MOUSE.LIB ассоциируются у пользователя с
     одной функцией, использующей в качестве входного аргумента число,
     специфицирующее  номер  функции  поддержки "мыши".  (Этот процесс
     некоторым образом сходен  с  процессом  доступа  к  функциям  DOS
     посредством  прерывания  21Н  с указанием номера нужной функции).
     Имя   этой   функции   определяется   моделью   памяти,   которая
     используется  при  компиляции  вашей  программы.  Используйте имя
     cmouses() для  модели  маленькой  памяти,  cmousec()  для  модели
     компактной памяти,   cmousem()   для   модели  средней  памяти  и
     cmousel() для модели большой и самой большой  (огромной)  памяти.
     (Заметим,  что функция не может работать в модели самой маленькой
     памяти).  Пример,  представленный в этой главе, использует модель
     маленькой памяти,  однако вы можете изменить тип модели памяти по
     своему усмотрению.

          Основным форматом функции cmouses() является:

          void cmouses(fnum, arg2, arg3, arg4);
               int *fnum, *arg2, *arg3, *arg4;
          Как видно,  fnum  является номером функции поддержки "мыши",
     которую необходимо вызвать. Другие параметры содержат информацию,
     необходимую  для  спецификации  функции.  Обратите внимание,  что
     функции передаются не сами аргументы, а указатели на их значения.
     Функция  cmouses() возвращает результаты работы в виде параметров
     и,  следовательно,  нуждается в  их  адресации.  Фирма  Microsoft
     определила тридцать функций поддержки "мыши".  Однако в программе
     рисования будут использованы лишь  пять  из  них.  Ниже  приведен
     краткий  обзор функций поддержки "мыши" фирмы Microsoft,  которые
     будут использованы нами в этой главе.

          Привести в исходное состояние, выдать статус.
          ---------------------------------------------

          Функция 0 приводит "мышь" в начальное  состояние (сбрасывает
     "мышь")  Она  перемещает курсор-указатель "мыши" в центр экрана и
     "выключает  "  его.  Функция  возвращает  номер  нажатой  клавиши
     "мыши"   в качестве значения arg2.  После завершения функции fnum
     принимает значение 0,  если "мышь" и соответствующее  программное
     обеспечение не инсталированы, и -1 в противном случае.

          Отобразить курсор
          -----------------

          Функция 1   отображает   указатель-курсор   "мыши".  Она  не
     возвращает никакого значения.

          Переместить курсор
          ------------------

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

          Функция 2 перемещает курсор по  экрану.  Она  не  возвращает
     никакого значения.

          Выдать статус клавиши и позицию курсора
          ---------------------------------------

          Функция 3  возвращает  статус  клавиши  в arg2,  виртуальную
     горизонтальную позицию курсора в arg3, а виртуальную вертикальную
     позицию курсора в arg4.

          Статус клавиши  кодируется  в  битах 0 и 1 байта arg2.  Если
     значение бита 0 установлено  (равно  1),  то  была  нажата  левая
     клавиша  "мыши",  если значение бита 1 установлено (равно 1),  то
     была  нажата  правая  клавиша.  Если  значения  обоих  битов   не
     установлены (равны 0), то никакая клавиша нажата не была.

          Установить координаты курсора
          ------------------------------

          Функция 4  устанавливает  месторасположение  курсора "мыши".
     Значение arg3  определяет горизонтальную позицию, а значение arg4
     -  вертикальную  позицию курсора.  вы всегда должны  помнить, что
     значения  не  должны  выходить  за  пределы  виртуального экрана,
     который вы используете.

          Индикация движения
          ------------------

          Функция 11  возвращает  число  вертикальных и горизонтальных
     "мышиных" шагов,  которое "мышь"  прошла  со  времени  последнего
     обращения   к   функции  11,  другими  словами  -  это  изменение
     вертикальных и горизонтальных  координат  "мыши".  Функция  также
     сбрасывает внутренний регистр-счетчик в 0. Значение вертикального
     счетчика возвращается в arg3,  а горизонтального -  в  arg4.  Это
     позволяет,  если  "мышь"  после последнего обращения к функции не
     перемещалась на плоскости, получить значения как горизонтального,
     так и вертикального счетчиков равными 0.  Если значение одного из
     счетчиков (или обоих) отлично от 0,  то  "мышь"  перемещалась  на
     плоскости.

          Положительное значение   изменения   вертикальных  "мышиных"
     шагов   позволяет   заключить,   что   "мышь"   двигалась   вниз.
     Отрицательное  же  значение  свидетельствует  о  движении  "мыши"
     вверх.

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

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

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

                  ФУНКЦИИ ПОДДЕРЖКИ "МЫШИ" ВЕРХНЕГО УРОВНЯ
     -----------------------------------------------------------------

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

          Установка "мыши" в исходное состояние.
          --------------------------------------

          Функция, представленная ниже, mouse_reset() используется для
     установки "мыши"  в  исходное  состояние.  Заметим,  что  функция
     требует   наличия  соответствующего  программного  обеспечения  и
     аппаратной части компьютера,  а  также  инсталяции  двухклавишной
     "мыши".

     /* Установка "мыши" в исходное состояние */

     void mouse_reset()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 0;  /* Установка "мыши" в исходное состояние  */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          if(fnum!=-1) {
          printf("Аппаратные или программные средства поддержки ");
          printf("'мыши' не инсталированы");
               exit(1);
          }
          if(arg2!=2) {
       printf("Разрешено использование только двухклавишной 'мыши'");
               exit(1);
          }
     }

          Отображение и перемещение курсора "мыши".
          -----------------------------------------

          Взаимодополняющие друг   друга   функции    cursor_on()    и
     cursor_off(),  представленные  ниже,  позволяют  активизировать и
     деактивизировать изображение курсора на экране дисплея.

     /*  Включение курсора "мыши"  */
     void cursor_on()
     {
          int fnum;

          fnum = 1; /* отобразить курсор */
          cmouses( &fnum,  &fnum,  &fnum,  &fnum);
     }

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

     /*  Выключение курсора "мыши"  */
     void cursor_off()
     {
          int fnum;

          fnum = 2; /* стереть курсор */
          cmouses( &fnum,  &fnum,  &fnum,  &fnum);
     }

          Какая из клавиш "мыши" была нажата?
          -----------------------------------

          Другой парой  взаимодополняющих  друг друга функций являются
     функции rightb_pressed() и leftb_pressed(),  представленные ниже.
     Эти функции возвращают значение "истина",  если нажата правая или
     левая клавиши.

     /* Возвращает значение "истина", если нажата правая клавиша,
          и "ложь" в противном случае                            */

     rightb_pressed()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3;   /* Чтение позиции и статуса клавиши */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          return arg2 & 2;
     }

     /* Возвращает значение "истина", если нажата левая клавиша,
          и "ложь" в противном случае                            */

     leftb_pressed()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3;   /* Чтение позиции и статуса клавиши */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          return arg2 & 1;
     }

          Как обнаружить перемещение "мыши"?
          ----------------------------------

          Функция 11,  которая  возвращает изменение значения счетчика
     "мыши" (в "мышиных" шагах)  после  последнего  обращения  к  ней,
     позволяет    определить    факт   перемещения   "мыши".   Функция
     mouse_motion(),   представленная   ниже,   возвращает   изменение

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

     местоположения    "мыши"    в   горизонтальном   и   вертикальном
     направлениях в переменных,  чьи  указатели  являются  аргументами
     функции.  Если  оба  значения  deltax  и deltay равны 0,  то факт
     движения "мыши" не регистрируется.

     /*  Возвращает направление движения  */
     void mouse_motion(deltax, deltay)
     char *deltax, *deltay;
     {
          int fnum, arg2, arg3, arg4;

          fnum = 11; /* получить направление движения */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          if(arg3>0) *deltax = RIGHT;
          else if(arg3<0) *deltax = LEFT;
          else *deltax = NOT_MOVED;

          if(arg4>0) *deltay = DOWN;
          else if(arg4<0) *deltay = UP;
          else *deltay = NOT_MOVED;
     }

          Макросы RIGHT, LEFT, UP, DOWN и NOT_MOVED определены
     следующим образом:

     #define NOT_MOVED 0
     #define RIGHT     1
     #define LEFT      2
     #define UP        3
     #define DOWN      4

          Чтение и установка позиции курсора.
          -----------------------------------

          Функции set_mouse_position()       и       mouse_position(),
     представленные  ниже,  используются  для установки чтения текущей
     позиции курсора "мыши".

     /* Установить координаты курсора "мыши" */
     void set_mouse_position(x, y)
     int x, y;
     {

          int fnum, arg2;

          fnum = 4; /* установка позиции */
          cmouses(&fnum, &arg2, &x, &y);
     }

     /* Возвращает координаты курсора "мыши" */

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

     void mouse_position(x, y)
     int *x, *y;
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3; /* получить позицию и статус клавиши */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          *x = arg3;
          *y = arg4;
     }

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

                   ПРОСТЕЙШАЯ ДЕМОНСТРАЦИОННАЯ ПРОГРАММА
     -----------------------------------------------------------------

          Программа, представленная   ниже,  демонстрирует  применение
     функций поддержки "мыши" высокого уровня.  вы можете сразу ввести
     ее в ваш компьютер и попробовать ее в деле.

     /*  Интерфейс с "мышью" Microsoft/IBM */

     #include "dos.h"
     #define NOT_MOVED 0
     #define RIGHT     1
     #define LEFT      2
     #define UP        3
     #define DOWN      4
     void mouse_position(), mode(), goto_xy(), mouse_motion();
     void cursor_on(), cursor_off(), mouse_reset();

     main(argc, argv)
     int argc;
     char *argv[];
     {
          char deltax, deltay, x, y;

          if(argc!=2) {
               printf(" Используйте формат: mouser <видеорежим> ");
               exit(1);
          }

          mode(atoi(argv[1]));

          mouse_reset();  /* инициализация "мыши"  */
          cursor_on();    /* "включение" курсора   */

          do {
               goto_xy(0, 0);
               if(leftb_presed()) printf("Левая клавиша");
               if(rightb_pressed()) {
                    printf("Правая клавиша");
                    mouse_position(&x, &y);
                    printf("%d %d - ", x, y);
               }

               /* Отображение местоположения "мыши" */
               mouse_motion(&deltax, &deltay);
               if(deltax || deltay) {
                    printf("Перемещение");
                    switch(deltax) {
                         case NOT_MOVED: break;
                         case RIGHT: printf("Вправо");
                              break;
                         case LEFT: printf("Влево");
                              break;
                    }

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

                    switch(deltay) {
                         case NOT_MOVED: break;
                         case UP: printf("Вверх");
                              break;
                         case DOWN: printf("Вниз");
                              break;
                    }
               }
          /* Цикл выполняется пока обе клавиши нажаты одновременно */
          } while(!(leftb_pressed() && rightb_pressed()));
          mode(3);
     }

     /* Установка видеорежима  */
     void mode(mode_code)
     int mode_code;
     {
          union REGS r;

          r.h.al = mode_code;
          r.h.ah = 0;
          int86(0x10, &r, &r);
     }

     /* Пересылка курсора в позицию, специфицированную
          координатами х и у */
     void goto_xy(x, y)
     int x, y;
     {
          union REGS r;

          r.h.ah=2;  /* функция адресации курсора  */
          r.h.dl = y; /* координаты столбца */
          r.h.dh = x; /* координаты строки */
          r.h.bh = 0; /* видеостраница */
          int86(0x10, &r, &r);
     }

     /**********************************************************/
     /* Функции, обеспечивающие интерфейс с "мышью"            */
     /**********************************************************/

     /*  Включение курсора "мыши"  */
     void cursor_on()
     {
          int fnum;

          fnum = 1; /* отобразить курсор */
          cmouses( &fnum,  &fnum,  &fnum,  &fnum);
     }

     /*  Выключение курсора "мыши"  */
     void cursor_off()

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

     {
          int fnum;

          fnum = 2; /* стереть курсор */
          cmouses( &fnum,  &fnum,  &fnum,  &fnum);
     }

     /* Возвращает значение "истина", если нажата правая клавиша,
          и "ложь" в противном случае                            */

     rightb_pressed()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3;   /* Чтение позиции и статуса клавиши   */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          return arg2 & 2;
     }

     /* Возвращает значение "истина", если нажата левая клавиша,
          и "ложь" в противном случае                            */

     leftb_pressed()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3;   /* Чтение позиции и статуса клавиши   */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          return arg2 & 1;
     }

     /*  Возвращает направление движения  */
     void mouse_motion(deltax, deltay)
     char *deltax, *deltay;
     {
          int fnum, arg2, arg3, arg4;

          fnum = 11; /* получить направление движения */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          if(arg3>0) *deltax = RIGHT;
          else if(arg3<0) *deltax = LEFT;
          else *deltax = NOT_MOVED;

          if(arg4>0) *deltay = DOWN;
          else if(arg4<0) *deltay = UP;
          else *deltay = NOT_MOVED;
     }

     /* Установить координаты курсора "мыши" */

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

     void set_mouse_position(x, y)
     int x, y;
     {
          int fnum, arg2;

          fnum = 4; /* установка позиции */
          cmouses(&fnum, &arg2, &x, &y);
     }

     /* Возвращает координаты курсора "мыши" */
     void mouse_position(x, y)
     int *x, *y;
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3; /* получить позицию и статус клавиши */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          *x = arg3;
          *y = arg4;
     }

     /* Установка "мыши" в исходное состояние   */

     void mouse_reset()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 0;  /* Установка "мыши" в исходное состояние  */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          if(fnum!=-1) {
          printf("Аппаратные или программные средства поддержки ");
          printf("'мыши' не инсталированы");
               exit(1);

          }
          if(arg2!=2) {
          printf("Разрешено использование только двухклавишной ");
          printf("'мыши'");
               exit(1);
          }
     }

          Перед использованием  этой  программы в командной строке DOS
     специфицируйте  видеорежим,  в  котором  вы   желаете   работать.
     Программа  будет  сообщать вам о направлении перемещения "мыши",а
     также о нажатых  клавишах  "мыши".  Вдобавок  ко  всему,  нажатие
     правой  клавиши будет вызывать отображение текущей позиции Х,  У.
     Нажатие обеих клавиш  вызовет  завершение  программы.  Попробуйте
     выполнить  эту  программу  в  различных  видеорежиах  и  обратите
     внимание на возникающие при этом эффекты.

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

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

           ВВОД ИНФОРМАЦИИ С ПОМОЩЬЮ "МЫШИ" В ПРОГРАММЕ РИСОВАНИЯ
     -----------------------------------------------------------------
          Итак, вы   теперь   готовы   к   тому,   чтобы   разработать
     подпрограммы,  позволяющие  с помощью "мыши" управлять программой
     рисования. Интерфейс с "мышью" может быть добавлен в существующие
     подпрограммы    управления,   что,   естественно,   будет   более
     предпочтительно, чем разработка новых подпрограмм или модификация
     существующих.
           Такой путь выгоден  прежде  всего  тем,  что  функциональны
     возможности  клавиш  управления  курсором  сохраняются на все 100
     процентов,  и пользователь в  каждой  конкретной  ситуации  может
     выбрать   наиболее   подходящее   устройство   для  ввода  данных
     (клавиатура или "мышь").
           Прежде, чем "мышь" будет включена  как  устройство  ввода
     программу  рисования,  необходимо  разработать  две подпрограммы,
     учитывающие специфику "мыши".  Первая  подпрограмма  -  wait_on()
     позволяет  реализовать процесс ожидания отпускания (освобождения)
     специфицированной клавиши пользователем.  Анализ  подобного  рода
     имеет весьма большое значение, так как соответствующие прерывания
     генерируются  постоянно,  пока   клавиша   не   нажата.   (Однако
     невозможно  обеспечить  такое  мгновенное  нажатие на клавишу,  в
     результате которого сформировалось бы лишь одно  прерывание).  Во
     многих  подпрограммах  наоборот  важно  избежать такой ситуации и
     поэтому в них каждое нажатие на клавишу генерирует  (точнее будет
     сказать  кажется,  что  генерирует)  только одно прерывание за то
     время, пока клавиша нажата. В соответствии с этим, ваша программа
     должна   обращаться  к  фунции  wait_on(),  представленной  ниже,
     непосредственно перед выполнением  и  после  того,  когда  нажата
     соответствующая клавиша.

     /* Возвращает 1, если специфицированная клавиша не нажата */

     void wait_on(button)
     int button;
     {
          if(button== LEFTB)
               while(leftb_pressed());
          else
               while(rightb_pressed());
     }

          Макросы LEFTB  и RIGHTB,  представленные ниже,  используются
     при обращении к wait_on().

     #define LEFTB     1
     #define RIGHTB    2

          Второй необходимой  вам функцией является mouse_menu().  Эта
     функция отображает однострочное  меню  и  позволяет  пользователю
     осуществлять  выбор из него элементов путем перемещения "мыши" на
     плоскости (и, соответственно, курсора "мыши" по экрану) и нажатия
     любой  клавиши  "мыши".  Эта  функция  может  работать только в 4

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

     графическом режиме. Функции передается двумерный массив символов,
     который   содержит   элементы   меню   (которые   может   выбрать
     пользователь),  значение каждого элемента меню (его код),а  также
     координаты  Х  и  У  отображения меню на экране.  Массив символов
     определяет  максимальную  длину  каждого  элемента  меню   в   19
     символов.   Функция   возвращает   в  качестве  результата  номер
     выбранного пользователем элемента меню, начиная с 0, или -1, если
     пользователь  не выбрал ни один из элементов меню.  Когда функция
     начинает свою работу,  то она вначале вычисляет длину в  пикселах
     (элементах растра) каждого элемента меню,  после чего резервирует
     пространство по начальной и конечной  точке  растра  для  каждого
     элемента  меню,  одновременно  запоминая эту информацию в массиве
     len. (В четвертом графическом режиме каждый символ имеет высоту в
     8   точек  растра  и  ширину  в  16  точек  растра.)  После  этих
     вычислений, функция переходит в состояние ожидаения прерывания от
     клавиш  "мыши".  При  этом  осуществляется  анализ нажата или нет
     клавиша "мыши" в момент нахождения ее курсора в области  меню, и,
     если  да,  то в месте расположения какого элемента меню.  Функция
     mouse_menu() приведена ниже.

     /* Отображает однострочное меню для "мыши" и возвращает
        код выбранного пользователем элемента меню          */

     mouse_menu(count, item, x, y)
     int count;           /* количество элементов меню */
     char item[][20];      /* элементы меню */
     int x, y;           /* позиции отображения */
     {
          int i, len[MENU_MAX][2], t;
          int mousex, mousey;

          goto_xy(x, y);
          t = 0;
          for(i=0; i=0 && mousey<8)  /* символ имеет высоту
                                   8 точек растра */
                    for(i=0; ilen[i][0] && mousex  используется  для этих же целей при работе с клавиатурой.
     Установите "мышь" в  первой  конечной  точке  объекта  и  нажмите
     правую клавишу. Затем переместите "мышь" во вторую конечную точку
     и опять нажмите клавишу. Так вы получите изображение интересующей
     вас фигуры.

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

          В конце концов,  если "мышь" изменила свои координаты, будут
     обновлены значения счетчиков X и Y.

          Функция menu(),    представленная   ниже,   использует   для
     установки  и  управления  меню  функцию  mouse_menu(),  описанную
     ранее.

     /*  Отображение меню */
     menu()
     {
          register int i, j;
          char far *ptr = (char far *) 0xB8000000; /* Указатель на
                                                       CGA-память */
          char far *temp;
          unsigned char buf[14][80]; /* для хранения содержимого
                                        экрана */
          int x, y, choice;
          char items[][20] = {

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

               "BOX",
               "CIRCLE",
               "LINE",
               "FILL BOX",
               "FILL CIRCLE"
          };

          temp = ptr;
          /* сохранение верхней части текущего экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    buf[i][j] = *temp; /* четный байт */
                    buf[i][j+1] = *(temp+8152); /* нечетный байт */
                    *temp = 0; *(temp+8152) = 0; /* очистка верхней
                                                  части экрана */
                    temp++;
               }

          goto_xy(0, 0);
          /* ожидание, которое будет прервано в результате нажатия на
             клавишу */
          while(rightb_pressed() || leftb_pressed());

          cursor_on();

          choice = mouse_menu(5, items, 0, 0);

          cursor_off();
          temp = ptr;
          /* восстановление изображения верхней части экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    *temp = buf[i][j];
                    *(temp+8152) = buf[i][j+1];
                    temp++;
               }
          return choice;
     }

          В процессе работы функция прежде всего сохраняет изображение
     верхней части  текущего  экрана,  не  ожидая  нажатия  какой-либо
     клавиши.  Затем  следует "включение" курсора "мыши" и обращение к
     функции mouse_menu(). После передачи управления от функции mouse_
     menu()    курсор    включается,    восстанавливается   предыдущее
     изображение  верхней   части   экрана,   и   функция   возвращает
     соответствующий код выбранного элемента меню.

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

                   ОПРЕДЕЛЕНИЕ ОБЪЕКТОВ С ПОМОЩЬЮ "МЫШИ"
     -----------------------------------------------------------------

          Оригинальная версия    программы     рисования     допускает
     использование    объектов,   определяемых   с   помощью   функции
     define_object(),  путем специфицирования конечных  точек  каждого
     сегмента  линии.  Каждый  определенный  ранее  объект  может быть
     подвергнут    вращению.    Версия    функции     define_object(),
     представленная  ниже,  также позволяет с помощью "мыши" указывать
     конечные точки (габариты) объектов.

     /* Определение объекта по его габаритным точкам с помощью
        клавиатуры или "мыши" */
     define_object(ob, x, y)
     double ob[][4];
     int x, y;
     {
          union k{
               char c[2];
               int i;
          } key;
          register int i, j;
          char far *ptr = (char far *) 0xB8000000; /* Указатель на
                                   CGA-память */
          char far *temp;
          unsigned char buf[14][80];
          int sides=0;
          int deltax, deltay, oldx, oldy;

          temp = ptr;
          /* сохранение верхней части текущего экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    buf[i][j] = *temp; /* четный байт */
                    buf[i][j+1] = *(temp+8152); /* нечетный байт */
                    *temp = 0; *(temp+8152) = 0; /* очистка верхней
                                                    части экрана */
                    temp++;
               }

          i = 0;
          key.i = 0;
          xhairs(x, y);
          do {
               goto_xy(0, 0);
               printf("Определите сторону %d", sides+1);
               if(i==0) printf("Укажите первую габаритную точку");
               else printf("Укажите вторую габаритную точку");

               do {
     /************** Добавочная часть для мыши. *******************/
               /* Просматривается, если "мышь" перемещается */

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

                    mouse_motion(&deltax, &deltay);
               /* Используйте левую клавишу для определения точки*/
                    if(leftb_pressed()) {
                         /* стирание графического курсора */
                         xhairs(x, y);
                         /* запоминание координат точки */
                         ob[sides][i++] = (double) x;
                         ob[sides][i++] = (double) y;
                         if(i==4) {
                              i = 0;
                              sides++;
                         }
                         break;
                    }
               } while(!kbhit() && !deltax && ! deltay);
               if(leftb_pressed()) wait_on(LEFTB);

               if(deltax || deltay) {
               /* если "мышь" переместилась, то пересчет координат*/
                    oldx = x; oldy = y;
                    mouse_position(&y, &x);
                    y = y / 2; /* нормализация коодинат виртуального
                                   экрана*/
                    /* стирание графического курсора */
                    xhairs(oldx, oldy);
               }
     /********** Конец добавочной части для "мыши" *****************/

               else if(kbhit()) {
                    key.i = bioskey(0);
                    /* изображение графического курсора */
                    xhairs(x, y);
                    if(key.c[0]==13) {
                        /* используйте <ВВОД> для определения точки*/
                         ob[sides][i++] = (double) x;
                         ob[sides][i++] = (double) y;
                         if(i==4) {
                              i = 0;
                              sides++;
                         }
                    }

                   /* если клавиши управления курсором, то перемещение
                        графического курсора */
                    if(!key.c[0]) switch(key.c[1]) {
                         case 75: /* влево */
                              y-=1;
                              break;
                         case 77: /* вправо */
                              y+=1;
                              break;
                         case 72: /* вверх */
                              x-=1;
                              break;

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

                         case 80: /* вниз */
                              x+=1;
                              break;
                         case 71: /* влево вверх */
                              x-=1; y-=1;
                              break;
                         case 73: /* вправо вверх */
                              x-=1; y+=1;
                              break;
                         case 79: /* влево вниз */
                              x+=1; y-=1;
                              break;
                         case 81: /* вправо вниз */
                              x+=1; y+=1;
                              break;
                    }
               }
               if(key.c[1] != 59) xhairs(x, y);
          } while(key.c[1] != 59); /* нажмите F1 для остановки */

          temp = ptr;
          /* востановление изображения в верхней части экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {

                    *temp = buf[i][j];
                    *(temp+8152) = buf[i][j+1];
                    temp++;
               }
          return sides;
     }

          Как вы   можете   видеть,  левая  клавиша  используется  для
     указания габаритных точек.  Движение "мыши" кодируется точно  так
     же,  как  и  в функции read_mouse().  За исключением того,  что в
     функцию добавлен фрагмент исходного текста  для  поддержки  ввода
     информации   с   помощью   "мыши",  оригинальная  версия  функции
     define_object()  больше  никаких  изменений  не  претерпела.  Для
     указания  очередной  габаритной точки переместите "мышь" в нужную
     позицию и нажмите клавишу.

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

                ПОЛНЫЙ ТЕКСТ МОДИФИЦИРОВАННОЙ ПРОГРАММЫ РИСОВАНИЯ
     -----------------------------------------------------------------

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

     /* Эта версия программы рисования для адаптеров  CGA/EGA
        позволяет использовать "мышь" фирмы Microsoft/IBM,
        а также альтернативное устройство ввода (клавиатуру)
     */
     #define NUM_SIDES 20 /* Максимальное число сторон фигуры.
                               По желанию можно увеличить */
     #define NOT_MOVED 0
     #define RIGHT     1
     #define LEFT      2
     #define UP        3
     #define DOWN      4
     #define LEFTB     1
     #define RIGHTB    2

     #define MENU_MAX 20 /* Максимальное число элементов меню */

     #include "dos.h"
     #include "stdio.h"
     #include "math.h"

     void mode(), line(), box(), fill_box();
     void mempoint(), palette(), xhairs();
     void circle(), plot_circle(), fill_circle();
     void rotate_point(), rotate_object(), goto_xy();
     void display_object(), copy(), move();
     void save_pic(), load_pic();
     void set_mouse_position(), mouse_position(), mouse_motion();
     void cursor_on(), cursor_off(), wait_on(), mouse_reset();

     unsigned char read_point();

     /* Это массив предназначен для хранения координат динамически
           определяемой фигуры   */

     double object[NUM_SIDES][4];

     double asp_ratio; /* коэффициент сжатия окружностей */

          int x=10, y=10; /* текущая позиция экрана */
          int cc=2; /* текущий цвет */
          int on_flag=1, mouse_on_flag=0; /* перо опущено или
                                              поднято */
          int pal_num=1;                 /* номер палитры */

          /* Габаритные точки, определяющие линию, окружность
          или прямоугольник */
          int startx=0, starty=0, endx=0, endy=0, first_point=1;
          int inc=1;       /* приращение движения */

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

          int sides=0;     /* номер стороны определяемой фигуры */
          int deltax, deltay;    /* "мышь" изменяет данные в
                                     индицированной позиции */

     main()
     {
          char done=0;

          mode(4); /* переключатель в 4 графический режим для
                         адаптеров CGA/EGA  */
          palette(0);    /* палитра 0  */
          mouse_reset(); /* инсталяция "мыши" */

          xhairs(x, y);  /* установить графический курсор */
          set_mouse_position(y*2, x); /* установить начальную позицию
                                        курсора "мыши" */
          do {
               /* просматривается, перемещалась ли "мышь" */
               mouse_motion(&deltax, &deltay);
               if(deltax || deltay) read_mouse();
               /* проверка нажатия клавиши */
               if(leftb_pressed() || rightb_pressed())
                    read_mouse();
               if(kbhit()) {
                    done = read_kb();
               /* перемещение "мыши" в соответствии с размещением
                    графического курсора */
                    set_mouse_position(y*2, x);
               }
          } while (!done);
          mode(2);
     }

     /* Чтение и обработка команд, вводимых с помощью "мыши" */
     read_mouse()
     {
          int oldx, oldy;
          int choice;

          oldx = x; oldy = y;
          xhairs(x, y); /* стереть с текущей позиции */

          /* нажаты обе клавиши для активизации меню */
          if(rightb_pressed() && leftb_pressed()) {
               choice = menu(); /* получить результат выбора
                                   из меню */
               switch(choice) {
                 case 0: box(startx, starty, endx, endy, cc);
                      break;
                 case 1: circle(startx, starty, endy-starty, cc);
                      break;
                 case 2: line(startx, starty, endx, endy, cc);

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

                      break;
                 case 3: fill_box(startx, starty, endx, endy, cc);
                      break;
                 case 4: fill_circle(startx, starty, endy-starty, cc);
                      break;
               }
          }

          /* правая клавиша определяет конечную точку фигуры */
          else if(rightb_pressed()) {
               if(first_point) {
                    startx = x; starty = y;
               }
               else {
                    endx = x; endy = y;
               }
               first_point = !first_point;
               wait_on(RIGHTB); /* ожидание освобождения клавиши */
          }

          if(deltax || deltay) {
               mouse_position(&y, &x);
               y = y / 2; /* нормализация координат виртуального
                         экрана */
               /* нажмите левую клавишу для рисования */
               if(leftb_pressed()) mouse_on_flag = 1;
               else mouse_on_flag = 0;
               if(mouse_on_flag) line(oldx, oldy, x, y, cc);
          }
          /* восстановить изображение графического курсора */
          xhairs(x, y);
     }

     /* Чтение и обработка команд, вводимых с клавиатуры */
     read_kb()
     {
          union k{
               char c[2];
               int i;
          } key;

          key.i = bioskey(0);
          xhairs(x, y); /* стереть графический курсор */
          if(!key.c[0]) switch(key.c[1]){
               case 75:  /* влево */
                    if(on_flag) line(x, y, x, y-inc, cc);
                    y -= inc;
                    break;
               case 77:  /* вправо */
                    if(on_flag) line(x, y, x, y+inc, cc);
                    y += inc;
                    break;
               case 72:  /* вверх */
                    if(on_flag) line(x, y, x-inc, y, cc);

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

                    x -= inc;
                    break;
               case 80:  /* вниз */
                    if(on_flag) line(x, y, x+inc, y, cc);
                    x += inc;
                    break;
               case 71:  /* влево вверх */
                    if(on_flag) line(x, y, x-inc, y-inc, cc);
                    y -= inc; x -= inc;
                    break;
               case 73:  /* вправо вверх */
                    if(on_flag) line(x, y, x-inc, y+inc, cc);
                    y += inc; x -= inc;
                    break;
               case 79: /* влево вниз */
                    if(on_flag) line(x, y, x+inc, y-inc, cc);
                    y -= inc; x += inc;
                    break;
               case 81:  /* вправо вниз */
                    if(on_flag) line(x, y, x+inc, y+inc, cc);
                    y += inc; x += inc;
                    break;
               case 59: inc = 1; /* F1 - уменьшить скорость */
                    break;
               case 60: inc = 5; /* F2 - увеличить скорость */
                    break;
          }
          else switch(tolower(key.c[0])) {
               case 'o': on_flag = !on_flag; /* переключатель
                                                шаблона цвета */
                    break;
               case '1': cc = 1; /* цвет 1 */
                    break;
               case '2': cc = 2; /* цвет 2 */
                    break;
               case '3': cc = 3; /* цвет 3 */
                    break;
               case '0': cc = 0; /* цвет 0 */
                    break;
               case 'b': box(startx, starty, endx, endy, cc);
                    break;
               case 'f': fill_box(startx, starty, endx, endy, cc);
                    break;
               case 'l': line(startx, starty, endx, endy, cc);
                    break;
               case 'c': circle(startx, starty, endy-starty, cc);
                    break;
               case 'h': fill_circle(startx, starty, endy-starty, cc);
                    break;
               case 's': save_pic();
                    break;
               case 'r': load_pic();
                    break;
               case 'm': /* переместить область */

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

                    move(startx, starty, endx, endy, x, y);
                    break;
               case 'x': /* копировать область */
                    copy(startx, starty, endx, endy, x, y);
                    break;
               case 'd': /* определить объект для вращения */
                    sides = define_object(object, x, y);
                    break;
               case 'a': /* вращать объект */
                    rotate_object(object, 0.05, x, y, sides);
                    break;
               case '\r': /* установить конечную точку для линии,
                         окружности или прямоугольника */
                    if(first_point) {
                         startx = x; starty = y;
                    }
                    else {
                         endx = x; endy = y;
                    }
                    first_point = !first_point;
                    break;
               case 'p': pal_num = pal_num==1 ? 2:1;
                    palette(pal_num);
          }
          /* восстановить изображение графического курсора */
          xhairs(x, y);
          if(tolowel(key.c[0])=='q') return 1;
          return 0;
     }

     /* Установить палитру */
     void palette(pnum)
     int pnum;
     {
          union REGS r;

          r.h.bh = 1; /* кодирование 4 графического режима */
          r.h.bl = pnum;
          r.h.ah = 11; /* функцкия установления палитры */
          int86(0x10, &r, &r);
     }

     /* Установить видеорежим */
     void mode(mode_code)
     int mode_code;
     {
          union REGS r;

          r.h.al = mode_code;
          r.h.ah = 0;
          int86(0x10, &r, &r);
     }

     /* Рисование прямоугольника */

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

     void box(startx, starty, endx, endy, color_code)
     int startx, starty, endx, endy, color_code;
     {
          line(startx, starty, endx, starty, color_code);
          line(startx, starty, startx, endy, color_code);
          line(startx, endy, endx, endy, color_code);
          line(endx, starty, endx, endy, color_code);
     }

     /* Рисование линии в специфицированном цвете с использованием
        основного алгоритма Брезенхама  */
     void line(startx, starty, endx, endy, color)
     int startx, starty, endx, endy, color;
     {
          register int t, distance;
          int x=0, y=0, delta_x, delta_y;
          int incx, incy;

          /* определение расстояния в обоих направлениях */
          delta_x = endx-startx;
          delta_y = endy-starty;

          /* Вычисление направления приращения. Приращение
          вычисляется относительно 0 как для горизонтальной,
          так и для вертикальной линии
          */
          if(delta_x>0) incx = 1;
          else if(delta_x==0) incx = 0;
          else incx=-1;

          if(delta_y>0) incy = 1;
          else if(delta_y==0) incy = 0;
          else incy=-1;

          /* Определяется, какое расстояние больше */
          delta_x = abs(delta_x);
          delta_y = abs(delta_y);
          if(delta_x>delta_y) distance = delta_x;
          else distance = delta_y;

          /* Вычерчиывние линии */
          for(t=0; t<=distance+1; t++) {
               mempoint(startx, starty, color);
               x+=delta_x;
               y+=delta_y;
               if(x>distance) {
                    x-=distance;
                    startx+=incx;
               }
               if(y>distance) {
                    y-=distance;
                    starty+=incy;
               }

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

          }
     }

     /* Закрашивание прямоугольника специфицированным цветом */
     void fill_box(startx, starty, endx, endy, color_code)
     int startx, starty, endx, endy, color_code;
     {
          register int i, begin, end;

          begin = startxendx ? startx : endx;

          for(i=begin; i<=end; i++)
               line(i, starty, i, endy, color_code);
     }

     /* Рисование окружности с использованием целочисленного алгоритма
        Брезенхама */
     void circle(x_center, y_center, radius, color_code)
     int x_center, y_center, radius, color_code;
     {
          register int x, y, delta;

          asp_ratio = 1.0; /* Если Вас не удовлетворяют предлагаемые
                              пропорции фигуры, измените значение этой
                              переменной */
          y = radius;
          delta = 3 - 2 * radius;

          for(x=0; x199 || y<0 || y>319) return;

          xor= color_code & 128; /* просматривается, если режим
                         установлен */
          color_code = color_code & 127; /* маска старшего бита */

          /* установка маски бита и кода цвета для правой ячейки
             памяти */
           bit_position = y%4;
           color_code<<=2*(3-bit_position);
           bit_mask.i>>=2*bit_position;

           /* поиск байта для корректировки в видеопамяти */
           index = x*40 +(y >> 2);
           if(x % 2) index += 8152; /* если дополнительно
                                       использовался 2-й сегмент
                                       памяти */

           /* запись цвета */
           if(!xor) { /* режим перезаписи */
                t = *(ptr+index) & bit_mask.c[0];
                *(ptr+index) = t | color_code;
           }
           else { /* режим записи цвета */
                t = *(ptr+index) | (char)0;
                *(ptr+index) = t ^ color_code;
           }
     }

     /* Непосредственное чтение байта из видеопамяти CGA/EGA
        в режиме 4 */
     unsigned char read_point(x, y)
     int x, y;
     {
          union mask {
               char c[2];
               int i;
          } bit_mask;
          int i, index, bit_position;
          unsigned char t;
          char xor; /* xor - цвет изображения записывается или
                       перезаписывается */
          char far *ptr = (char far *) 0xB8000000; /* Указатель
                                                      CGA памяти */
          bit_mask.i=0xFF3F;  /* 11111111 00111111 в двоичном коде */

          /* проверка значений для 4 режима */
          if(x<0 || x>199 || y<0 || y>319) return;

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

          /* установка маски бита и кода цвета для правой ячейки
             памяти */
           bit_position = y%4;
           bit_mask.i<<=2*(3-bit_position);

           /* поиск нужного байта в видеопамяти */
           index = x*40 +(y >> 2);
           if(x % 2) index += 8152; /* если дополнительно
                                       использовался 2-й блок
                                       памяти */

          /* чтение цвета */
           t = *(ptr+index) & bit_mask.c[0];
          t >>=2*(3-bit_position);
          return t;
     }

     /* сохранение видео изображения на экране */
     void save_pic()
     {
          char fname[80];
          FILE *fp;
          register int i, j;
          char far *ptr = (char far *) 0xB8000000; /* Указатель
                                                      CGA памяти */
          char far *temp;
          unsigned char buf[14][80]; /* буфер содержимого экрана */

          temp = ptr;
          /* сохранение верхней части текущего экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    buf[i][j] = *temp; /* четный байт */
                    buf[i][j+1] = *(temp+8152); /* нечетный байт */
                    *temp = 0; *(temp+8152) = 0; /* очистка верхней
                                                    части экрана */
                    temp++;
               }

          goto_xy(0, 0);
          printf("Имя файла : ");
          gets(fname);
          if(!(fp=fopen(fname, "wb"))) {
               printf("Файл не может быть открыт\n");
               return;
          }

          temp = ptr;
          /* восстановление изображения верхней части экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {

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

                    *temp = buf[i][j];
                    *(temp+8152) = buf[i][j+1];
                    temp++;
               }

          /* запись изображения в файл */
          for(i=0; i<8152; i++) {
               putc(*ptr, fp); /* четный байт */
               putc(*(ptr+8152), fp); /* нечетный байт */
               ptr++;
          }
          fclose(fp);
     }

     /* загрузка в память изображения из файла */
     void load_pic()
     {
          char fname[80];
          FILE *fp;
          register int i, j;
          char far *ptr = (char far *) 0xB8000000; /* Указатель
                                   CGA памяти */
          char far *temp;
          unsigned char buf[14][80]; /* буфер содержимого экрана */

          temp = ptr;
          /* сохранение верхней части текущего экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    buf[i][j] = *temp; /* четный байт */
                    buf[i][j+1] = *(temp+8152); /* нечетный байт */
                    *temp = 0; *(temp+8152) = 0; /* очистка верхней
                                                    части экрана */
                    temp++;
               }

          goto_xy(0, 0);
          printf("Имя файла : ");
          gets(fname);
          if(!(fp=fopen(fname, "rb"))) {
               goto_xy(0, 0);
               printf("Файл не может быть открыт\n");
               temp = ptr;
               /* восстановление изображения верхней части экрана */
               for(i=0; i<14; i++)
                    for(j=0; j<80; j+=2) {
                         *temp = buf[i][j];
                         *(temp+8152) = buf[i][j+1];
                         temp++;
                    }
               return;
          }

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

          /* Загрузка изображения из файла */
          for(i=0; i<8152; i++) {
               *ptr = getc(fp); /* четный байт */
               *(ptr+8152) = getc(fp); /* нечетный байт */
               ptr++;
          }
          fclose(fp);
     }

     /* Перемещение курсора в специфицированную позицию */
     void goto_xy(x, y)
     int x, y;
     {
          union REGS r;

          r.h.ah=2; /* функция адресации курсора */
          r.h.dl = y; /* координаты столбца */
          r.h.dh = x; /* координаты строки */
          r.h.bh = 0; /* видеостраница */
          int86(0x10, &r, &r);
     }

     /* Перемещение области в другое место */
     void move(startx, starty, endx, endy, x, y)
     int startx, starty; /* верхняя левая координата */
     int endx, endy; /* нижняя правая координата места, куда будет
                        перемещена область */
     int x, y; /* верхняя левая координата области, откуда будет
                  перемещено изображение*/
     {
          int i, j;
          unsigned char c;

          for(; startx<=endx; startx++, endx++)
               for(i=starty, j=y; i<=endy; i++, j++) {
                    c = read_point(startx, i); /* чтение точки */
                    mempoint(startx, i, 0); /* стирание предыдущего
                                               изображения */
                    mempoint(x, j, c); /* запись его в новое место */
               }
     }

     /* Копировать область в другое место */
     void copy (startx, starty, endx, endy, x, y)
     int startx, starty; /* верхняя левая координата */
     int endx, endy; /* нижняя правая координата места, куда будет
                        скопирована область */
     int x, y; /* верхняя левая координата области, откуда будет

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

                  перемещено изображение */
     {
          int i, j;
          unsigned char c;

          for(; startx<=endx; startx++, endx++)
               for(i=starty, j=y; i<=endy; i++, j++) {
                    c = read_point(startx, i); /* чтение точки */
                    mempoint(x, j, c); /* запись ее в новое место */
               }
     }

     /* Вращение точки относительно специфицированной пользователем
        начальной точки x_org и y_org на угол theta */

     void rotate_point(theta, x, y, x_org, y_org)
     double theta, *x, *y;
     int x_org, y_org;
     {
          double tx, ty;

          /* нормализация координат х и у исходной точки */
          tx = *x - x_org;
          ty = *y - y_org;

          /* вращение */
          *x = tx * cos(theta) - ty * sin(theta);
          *y = tx * sin(theta) + ty * cos(theta);

          /* возврат значений координат */
          *x += x_org;
          *y += y_org;
     }

     /* Вращение специфицированного объекта */
     void rotate_object(ob, theta, x, y, sides)
     double ob[][4]; /* определение объекта */
     double theta; /* угол поворота в радианах */
     int x, y; /* центр вращения */
     int sides;
     {
          register int i, j;
          double tempx, tempy; /* это вспомогательные переменные,
                    используемые при преобразовании типов */
          char ch;

          for(;;){
               ch = getch(); /* просматривается, если указывается
                                направление врашения */
               switch(tolower(ch)) {
                    case 'l': /* вращение против часовой стрелки */
                         theta = theta < 0 ? -theta : theta;

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

                         break;
                    case 'r': /* вращение по часовой стрелке */
                         theta = theta > 0 ? -theta : theta;
                         break;
                    default: return;
               }

               for(j=0; j для определения точки*/
                         ob[sides][i++] = (double) x;
                         ob[sides][i++] = (double) y;
                         if(i==4) {
                              i = 0;
                              sides++;
                         }
                    }

                    /* если клавиши управления курсором, то
                       перемещение графического курсора */
                    if(!key.c[0]) switch(key.c[1]) {
                         case 75: /* влево */
                              y-=1;
                              break;
                         case 77: /* вправо */
                              y+=1;
                              break;
                         case 72: /* вверх */
                              x-=1;
                              break;
                         case 80: /* вниз */
                              x+=1;
                              break;
                         case 71: /* влево вверх */
                              x-=1; y-=1;
                              break;
                         case 73: /* вправо вверх */
                              x-=1; y+=1;
                              break;
                         case 79: /* влево вниз */
                              x+=1; y-=1;
                              break;
                         case 81: /* вправо вниз */
                              x+=1; y+=1;
                              break;
                    }
               }
               if(key.c[1] != 59) xhairs(x, y);
          } while(key.c[1] != 59); /* нажмите F1 для остановки */

          temp = ptr;
          /* восстановление изображения в верхней части экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    *temp = buf[i][j];
                    *(temp+8152) = buf[i][j+1];
                    temp++;
               }
          return sides;
     }

     /*  Отображение меню */
     menu()

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

     {
          register int i, j;
          char far *ptr = (char far *) 0xB8000000; /* Указатель на
                                                      CGA-память */
          char far *temp;
          unsigned char buf[14][80]; /* для хранения содержимого
                                        экрана */
          int x, y, choice;
          char items[][20] = {
               "BOX",
               "CIRCLE",
               "LINE",
               "FILL BOX",
               "FILL CIRCLE"
          };

          temp = ptr;
          /* сохранение верхней части текущего экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    buf[i][j] = *temp; /* четный байт */
                    buf[i][j+1] = *(temp+8152); /* нечетный байт */
                    *temp = 0; *(temp+8152) = 0; /* очистка верхней
                                                    части экрана */
                    temp++;
               }

          goto_xy(0, 0);
          /* ожидание, которое будет прервано в результате нажатия на
             клавишу */
          while(rightb_pressed() || leftb_pressed());

          cursor_on();

          choice = mouse_menu(5, items, 0, 0);

          cursor_off();
          temp = ptr;
          /* восстановление изображения верхней части экрана */
          for(i=0; i<14; i++)
               for(j=0; j<80; j+=2) {
                    *temp = buf[i][j];
                    *(temp+8152) = buf[i][j+1];
                    temp++;
               }
          return choice;
     }

     /**********************************************************/
     /* Функции, обеспечивающие интерфейс с "мышью"            */
     /**********************************************************/

     /*  Включение курсора "мыши"  */

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

     void cursor_on()
     {
          int fnum;

          fnum = 1; /* отобразить курсор */
          cmouses( &fnum,  &fnum,  &fnum,  &fnum);
     }

     /*  Выключение курсора "мыши"  */
     void cursor_off()
     {
          int fnum;

          fnum = 2; /* стереть курсор */
          cmouses( &fnum,  &fnum,  &fnum,  &fnum);
     }

     /* Возвращает значение "истина", если нажата правая клавиша,
          и "ложь" в противном случае                           */

     rightb_pressed()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3;   /* Чтение позиции и статуса клавиши   */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          return arg2 & 2;
     }

     /* Возвращает значение "истина", если нажата левая клавиша,
          и "ложь" в противном случае */

     leftb_pressed()
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3;   /* Чтение позиции и статуса клавиши   */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          return arg2 & 1;
     }

     /* Установить координаты курсора "мыши" */
     void set_mouse_position(x, y)
     int x, y;
     {
          int fnum, arg2;

          fnum = 4; /* установка позиции */

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

          cmouses(&fnum, &arg2, &x, &y);
     }

     /* Возвращает координаты курсора "мыши" */
     void mouse_position(x, y)
     int *x, *y;
     {
          int fnum, arg2, arg3, arg4;

          fnum = 3; /* получить позицию и статус клавиши */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          *x = arg3;
          *y = arg4;
     }

     /*  Возвращает направление движения  */
     void mouse_motion(deltax, deltay)
     char *deltax, *deltay;
     {
          int fnum, arg2, arg3, arg4;

          fnum = 11; /* получить направление движения */
          cmouses( &fnum, &arg2, &arg3, &arg4);
          if(arg3>0) *deltax = RIGHT;
          else if(arg3<0) *deltax = LEFT;
          else *deltax = NOT_MOVED;

          if(arg4>0) *deltay = DOWN;
          else if(arg4<0) *deltay = UP;
          else *deltay = NOT_MOVED;
     }

     /* Отображает однострочное меню для "мыши" и возвращает
          код выбранного пользователем элемента меню */
     mouse_menu(count, item, x, y)
     int count;           /* количество элементов меню */
     char item[][20];      /* элементы меню */
     int x, y;           /* позиции отображения */
     {
          int i, len[MENU_MAX][2], t;
          int mousex, mousey;

          goto_xy(x, y);
          t = 0;
          for(i=0; i=0 && mousey<8)  /* символ имеет высоту
                                   8 точек растра */
                    for(i=0; ilen[i][0] && mousex


                                  ГЛАВА 10                     -- 1 --
                                  --------

                       СОЗДАНИЕ КОММЕРЧЕСКИХ ДИАГРАММ
     -----------------------------------------------------------------

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

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

          Примеры в главе написаны  для  компьютеров  семейства  РС  с
     цветным графическим адаптером. Используется четвертый видеорежим,
     так как он поддерживает все виды цветных  адаптеров.  Однако,  вы
     можете  легко  изменить  предложенные  функции  для  того,  чтобы
     работать с другими типами адаптеров.

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

                            НОРМАЛИЗАЦИЯ ДАННЫХ
     -----------------------------------------------------------------

          Перед разработкой программы отображения данных на экране вам
     следует   уяснить,   как   численные   значения   переводятся   в
     соответствующие  координаты экрана.  Как вы помните,  размерность
     экрана  в   четвертом   видеорежиме   320*200,   причем   320   -
     горизонтальная  размерность и 200 - вертикальная.  Учитывая,  что
     диаграммы изображаются вертикальными полосами, данные должны быть
     преобразованы  таким  образом,  чтобы  они  принимали  значения в
     диапазоне от 0 до 199.  Данный процесс преобразования  называется
     нормализацией.

          Чтобы нормализовать  значение,  необходимо  умножить  его на
     некоторый  коэффициент,  гарантирующий  получение  результата   в
     диапазоне   размера   экрана.   Для   определения   коэффициента,
     необходимо  знать  максимальное  и  минимальное  значения  чисел,
     выводимых   в   виде   диаграммы.   Для  определения  подходящего
     коэффициента,  необходимо   вычесть   минимальное   значение   из
     максимального  и  поделить  вертикальную  размерность  экрана  на
     полученную  разность.  Иными  словами,   для   4-го   видеорежима
     нормирующий множитель определяется по формуле:

                  нормирующий_множитель = 200 / (мах - min)

          Таким образом,   каждый   элемент  данных  нормализуется  по
     формуле:

       нормализованное_данное = необработанное_данное * норм_множитель

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

                   РАЗРАБОТКА ФУНКЦИЙ ПОСТРОЕНИЯ ДИАГРАММ
     -----------------------------------------------------------------

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

          Программа    функции    bargraph(),     приведенная    ниже,
     удовлетворяет этим требованиям.

          /* Вывод диаграммы */
     void bargraph(data,num,offset,min,max,width)
     double *data;      /* массив данных */
     int num;           /* количество элементов в массиве */
     int offset;        /* расстояние между диаграммами */
     int min,max;       /* мин. и мак. выводимые значения */
     int width;         /* толщина линий */
     {
          int y,t,incr;
          double norm_data,norm_ratio,spread;
          char s[80];
          static int color = 0;
          int tempwidth;

          /* всегда используйте различные цвета */
          color++;
          if ( color > 3 ) color = 1;

          /* определение нормирующего множителя */
          spread = (double)max-min;
          norm_ratio = 180/spread;

          incr = 280/num;/* определение промежутка между значениями */
          tempwidth = width;
          for (t=0;t3 ) color = 1;
          }
     }

     _________________________________________________________________

           Графический рисунок на стр 355 не может быть воиспроизведен
           имеющимися средствами. (Ред. перевода И.Бычковский.)
     _________________________________________________________________

     Рис.10-1. Результат работы программы построения диаграмм

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

          Простейшая программа вывода диаграмм.
          ------------------------------------

          Следующая программа  показывает  все  описанные  функции   в
     действии.  Результат ее работы представлен на рис.10-1. Программа
     выводит среднюю стоимость акций трех мнимых  корпораций  за  пять
     лет.

          /* Программная демонстрация построения диаграмм */
     #include "dos.h"

     void bargraph(),mode(),mempoint();
     void line(),goto_xy(),grid(),label();
     void hashlines(),legend(),read_cursor_xy();
     void palette(),color_puts(),fill_box();

     main()
     {
          double widget[] = {
               10.1,20,30,35.34,50
               };
          double global[] = {
               19,20,8.8,30,40
               };
          double tower[] = {
               25.25,19,17.4,33,29
               };
          int min,max;

          char n[][20] = {
               "widget",
               "global",
               "tower"
               };
          char lab[][20] = {
               "1983",
               "1984",
               "1985",
               "1986",
               "1987"
               };
          mode(4);       /* выбор режима 320*200 */
          palette(0);
          grid(0,50);    /* построение линии нулевого уровня */
          hashlines();   /* вывод пунктирных линий */
          label(lab,5);  /* вывод чисел */
          legend(n,3);   /* вывод надписей */

          /* вывод курса акций трех кампаний */
          bargraph(widget,5,0,0,50,4);
          bargraph(global,5,10,0,50,4);
          bargraph(tower,5,20,0,50,4);

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

          getch();
          mode(3);
     }

     /* Вывод линии нулевого уровня диаграммы */
     void grid(min,max)
     int min,max;
     {
          register int t;

          goto_xy(22,0); printf("%d",min);
          goto_xy(0,0); printf("%d",max);
          line(180,10,180,300,1);
     }

     /* вывод меток на экран */
     void label(str,num)
     char str[][20]; /* массив меток */
     int num; /* количество меток */
     {
          int i,j,inc;

          inc = 38/num;
          i = 2; /* определение начальной точки */
          for (j=0;j3 ) color = 1;
          }
     }

     /* Вычерчивание диаграммы */

     void bargraph(data,num,offset,min,max,width)
     double *data; /* массив данных */
     int num; /* количество элементов в массиве */
     int offset; /* расстояние между диаграммами */
     int min,max; /* минимальное и максимальное выводимые значения */
     int width; /* толщина линий */
     {
          int y,t,incr;
          double norm_data,norm_ratio,spread;
          char s[80];
          static int color = 0;
          int tempwidth;

          /* всегда используйте различные цвета */
          color++;
          if ( color > 3 ) color = 1;

          /* определение нормирующего множителя */
          spread = (double)max-min;
          norm_ratio = 180/spread;

          incr = 280/num; /* определение промежутка между значениями*/
          tempwidth = width;
          for (t=0;t 0 ) incx = 1 ;
          else  if (delta_x == 0 ) incx = 0;
          else incx = -1;
          if ( delta_y > 0 ) incy = 1 ;
          else  if (delta_y == 0 ) incy = 0;
          else incy = -1;
     /* определение максимума изменения координат */
          delta_x = abs(delta_x);
          delta_y = abs(delta_y);
          if ( delta_x > delta_y ) distance = delta_x;
          else distance = delta_y;
     /* вычерчивание линии */
          for (t=0;t<=distance+1;t++) {
               mempoint(startx,starty,color);
               x+= delta_x;
               y+= delta_y;
               if (x>distance) {
               x-=distance;
               startx+=incx;
               }
               if (y>distance) {
               y-=distance;
               starty+=incy;
               }
          }
     }
     /* наполнение прямоугольника заданным цветом */
     void fill_box(startx,starty,endx,endy,color_code)
     int startx, starty, endx, endy, color_code;
     {
          register int i,begin,end;

          begin = startx < endx ? startx : endx;
          end = startx > endx ? startx : endx;

          for (i=begin;i<=end;i++)
               line(i,starty,i,endy,color_code);
     }
     /* запись точки в CGA/EGA память */
     void mempoint(x,y,color_code)

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

     int x,y,color_code;
     {
          union mask {
               char c[2];
               int i;
          } bit_mask;
          int i,index,bit_position;
          unsigned char t;
          char xor; /* xor - цвет или наложение */
          char far *ptr = (char far *) 0xB8000000; /* указатель на
                                                       CGA  */
          bit_mask.i = 0xFF3F; /* 11111111 00111111 в двоичном коде */
          /* контроль координат для 4 режима */
          if (x<0 || x>199 || y<0 || y>319) return;

          xor = color_code & 128; /* проверка установки режима xor */
          color_code = color_code & 127; /* маска 7 старших бит */

     /* установка bit_mask и color_code в правильное положение */
          bit_position = y%4;
          color_code <<= 2*(3-bit_position);
          bit_mask.i >>= 2*bit_position;

     /* поиск соответствующего байта в памяти экрана */
          index = x*40 + (y>>2);
          if (x%2) index+=8152; /* если нечетный, использовать второй
                                   байт */
     /* запись цвета */
          if (!xor) { /* режим наложения */
               t = *(ptr + index) & bit_mask.c[0];
               *(ptr + index) = t | color_code;
          }
          else { /* режим xor */
                t = *(ptr + index) | (char)0;
               *(ptr + index) = t | color_code;
          }
     }

     /* установка видеорежима */
     void mode(mode_code)
     int mode_code;
     {
          union REGS r;

          r.h.al = mode_code;
          r.h.ah = 0;
          int86(0x10,&r,&r);
     }

     /* установка курсора в координаты x,y */
     void goto_xy(x,y)
     int x,y;
     {
          union REGS r;

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

          r.h.ah = 2; /* функция адресации курсора */
          r.h.dl = y; /* горизонтальная координата */
          r.h.dh = x; /* вертикальная координата */
          r.h.bh = 0; /* видеостраница */
          int86(0x10,&r,&r);
     }

     /* установка цветов диаграмм */
     void palette(pnum)
     int pnum;
     {
          union REGS r;

          r.h.bh = 1; /* код 4 режима */
          r.h.bl = pnum;
          r.h.ah = 11; /* установка функции цвета */
          int86(0x10,&r,&r);
     }

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

                      ПРОГРАММА ВЫЧЕРЧИВАНИЯ ДИАГРАММ
     -----------------------------------------------------------------

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

          Главная программа.
          ------------------

          Здесь приводится  основная   функция   main(),   описывающая
     алгоритм построения диаграмм и содержащая несколько макросов.

     #define MAX_SETS 3
     #define MAX_ENTRIES 50
     #define MAX_LABELS 20
     #define MAX_NAMES 20

     main()
     {
          double v[MAX_SETS][MAX_ENTRIES]; /* размещение данных */
          int num_entries;
          int num_sets;
          int min,max,i;
          int lines,offset;
          char save = 0; /* признак сохранения диаграммы */
          char names[MAX_NAMES][20];
          char lab[MAX_LABELS][20];

     /* считывание данных */
          enter(v,&num_entries,&num_sets);

     /* поиск минимального и максимального значения */
          min_max(v,num_entries,num_sets,&min,&max);

     /* ввод наименований данных */
          get_names(names,num_sets);

     /* ввод меток для диаграммы */
          get_labels(lab,num_entries);

     /* ввод толщины линии */
          lines = get_line_size();

     /* ввод интервала между диаграммами */
          offset = get_offset();

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

     /* сохранить диаграмму в файле ? */
          printf(" сохранить диаграмму в файле ? (y/n) ");
          if (tolower(getche()) == 'y') save = 1;

          mode(4); /* графический режим 320*200 */
          palette(0);

          grid(min,max); /* вывод линии нулевого уровня */
          hashlines(); /* вывод пунктирных линий */
          label(lab,num_entries); /* вывод меток диаграммы */
          legend(names,num_sets); /* вывод пояснительных надписей */

          /* вывод значений в виде диаграммы */
          for (i=0;iMAX_SETS) count = MAX_SETS; /* выход за границы
                                                   массива */
          *sets = count;
     printf("Введите количество элементов (от 1 до %d) ",MAX_ENTRIES);
          scanf("%d%c",&num,&j);
          if (num>MAX_SETS) num = MAX_ENTRIES; /* выход за границы
                                                  массива */
          *entries = num;

          j = 0;

          /* считывание значений данных */
          while((j*max) *max = tmax;
               if (tmin <*min) *min = tmin;
          }
     }
      /* Возврат максимального значения данных */
     getmax(data,num)
     double *data;
     int num;
     {
          int t,max;

          max = (int)data[0];
          for (t=1;tmax) max = (int)data[t];
          return max;
     }

     /* Возврат минимального значения данных */
     getmin(data,num)
     double *data;
     int num;
     {
          int t,min;

          min = (int)data[0];
          for (t=1;tMAX_SETS) count = MAX_SETS; /* выход за границы
                                                   массива */
          *sets = count;
     printf("Ведите число элементов данных (от 1 до %d)",MAX_ENTRIES);
          scanf("%d%c",&num,&j);
          if (num>MAX_ENTRIES) num = MAX_ENTRIES; /* выход за границы
                                                  массива */
          *entries = num;

          j = 0;

          /* считывание значений */
     while((j3 ) color = 1;
          }
     }

     void bargraph(data,num,offset,min,max,width)
     double *data;          /* массив данных */
     int num;               /* количество элементов в массиве */
     int offset;            /* расстояние между диаграммами */
     int min,max;  /* минимальное и максимальное выводимые значения */
     int width;             /* толщина линий */
     {
          int y,t,incr;
          double norm_data,norm_ratio,spread;
          char s[80];
          static int color = 0;
          int tempwidth;

          /* всегда используйте различные цвета */

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

          color++;
          if ( color > 3 ) color = 1;

          /* определение нормирующего множителя */
          spread = (double)max-min;
          norm_ratio = 180/spread;

          incr = 280/num; /* определение промежутка между значениями*/
          tempwidth = width;
          for (t=0;t*max) *max = tmax;
               if (tmin <*min) *min = tmin;
          }
     }
      /* Возврат максимального значения данных */
     getmax(data,num)
     double *data;
     int num;
     {
          int t,max;

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

          max = (int)data[0];
          for (t=1;tmax) max = (int)data[t];
          return max;
     }

     /* Возврат минимального значения данных */
     getmin(data,num)
     double *data;
     int num;
     {
          int t,min;

          min = (int)data[0];
          for (t=1;t 0 ) incx = 1 ;
          else  if (delta_x == 0 ) incx = 0;
          else incx = -1;
          if ( delta_y > 0 ) incy = 1 ;
          else  if (delta_y == 0 ) incy = 0;
          else incy = -1;
     /* определение максимума изменения координат */
          delta_x = abs(delta_x);
          delta_y = abs(delta_y);
          if ( delta_x > delta_y ) distance = delta_x;
          else distance = delta_y;
     /* вычерчивание линии */
          for (t=0;t<=distance+1;t++) {
               mempoint(startx,starty,color);
               x+= delta_x;
               y+= delta_y;
               if (x>distance) {
               x-=distance;
               startx+=incx;

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

               }
               if (y>distance) {
               y-=distance;
               starty+=incy;
               }
          }
     }
     /* наполнение прямоугольника заданным цветом */
     void fill_box(startx,starty,endx,endy,color_code)
     int startx,starty,endx,endy,color_code;
     {
          register int i,begin,end;

          begin = startx < endx ? startx : endx;
          end = startx > endx ? startx : endx;

          for (i=begin;i<=end;i++)
               line(i,starty,i,endy,color_code);
     }
     /* запись точки в CGA/EGA память */
     void mempoint(x,y,color_code)
     int x,y,color_code;
     {
          union mask {
               char c[2];
               int i;
          } bit_mask;
          int i,index,bit_position;
          unsigned char t;
          char xor; /* xor - цвет или наложение */
          char far *ptr = (char far *) 0xB8000000; /* указатель на
                                                      CGA  */
          bit_mask.i = 0xFF3F; /* 11111111 00111111 в двоичном коде */
          /* контроль координат для 4 режима */
          if (x<0 || x>199 || y<0 || y>319) return;

          xor = color_code & 128; /* проверка установки режима xor */
          color_code = color_code & 127; /* маска 7 старших бит */

     /* установка bit_mask и color_code в правильное положение */
          bit_position = y%4;
          color_code <<= 2*(3-bit_position);
          bit_mask.i >>= 2*bit_position;

     /* поиск соответствующего байта в памяти экрана */
          index = x*40 + (y>>2);
          if (x%2) index+=8152; /* если нечетный, использовать второй
                                   банк */
     /* запись цвета */
          if (!xor) { /* режим наложения */
               t = *(ptr + index) & bit_mask.c[0];
               *(ptr + index) = t | color_code;
          }
          else { /* режим xor */

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

                t = *(ptr + index) | (char)0;
               *(ptr + index) = t ^ color_code;
          }
     }

     /* установка видеорежима */
     void mode(mode_code)
     int mode_code;
     {
          union REGS r;

          r.h.al = mode_code;
          r.h.ah = 0;
          int86(0x10,&r,&r);
     }

     /* установка курсора в координаты x,y */
     void goto_xy(x,y)
     int x,y;
     {
          union REGS r;

          r.h.ah = 2; /* функция адресации курсора */
          r.h.dl = y; /* горизонтальная координата */
          r.h.dh = x; /* вертикальная координата */
          r.h.bh = 0; /* видеостраница */
          int86(0x10,&r,&r);
     }

     /* установка цветов диаграмм */
     void palette(pnum)
     int pnum;
     {
          union REGS r;

          r.h.bh = 1; /* код 4 режима */
          r.h.bl = pnum;
          r.h.ah = 11; /* установка функции цвета */
          int86(0x10,&r,&r);
     }

     /* сохранение выведенного видеографика */
     void save_pic()
     {
          char fname[80];
          FILE *fp;
          register int i,j;
          char far *ptr = (char far *) 0xB8000000; /* указатель
               на CGA память */
          char far *temp;
          unsigned char buf[14][80]; /* для размещения содержимого
                                        экрана */

          temp = ptr;

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

          /* сохранение верхней части текущего экрана */
          for (i=0;i<14;i++)
               for (j=0;j<80;j+=2) {
                    buf[i][j] = *temp; /* четный байт */
                    buf[i][j+1] = *(temp+8152); /* нечетный байт*/
                    *temp = 0;
                    *(temp+8152) = 0; /* чистка верхней части
                                         экрана */
                    temp++;
               }
          goto_xy(0,0);
          printf(" Имя файла : ");
          gets(fname);
          if (!(fp=fopen(fname,"wb"))) {
               printf(" Невозможно открыть файл \n");
               return;
          }
          temp = ptr;
          /* восстановление верхней части экрана */
          for (i=0;i<14;i++)
               for (j=0;j<80;j+=2) {
                    *temp = buf[i][j];
                    *(temp+8152) = buf[i][j+1];
                    temp++;
               }
          /* сохранение рисунка в файле */
          for (i=0;i<8152;i++) {
               putc(*ptr,fp); /* четный байт */
               putc(*(ptr+8152),fp); /* нечетный байт */
               ptr++;
          }
          fclose(fp);
     }

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

                   ОТОБРАЖЕНИЕ ДИАГРАММ НА ЭКРАНЕ ДИСПЛЕЯ
     -----------------------------------------------------------------

          Если вы  сохранили построенную диаграмму в файле,  то всегда
     можете повторно получить изображение данной диаграммы.  Для  этой
     цели служит программа SHOW, описанная в данном разделе. Программа
     выводит диаграмму, находящуюся в файле, имя файла задается в виде
     аргумента команды. Например, чтобы вывести диаграмму, находящуюся
     в файле backlog, необходимо ввести команду

                        show backlog

          Программа show      использует      функцию      load_pic(),
     предназначенную  для  изображения  диаграмм на экране.  (Вы также
     можете использовать  эту  программу  для  отображения  на  экране
     дисплея  других  графических образов,  предварительно созданных и
     записанных в файл.)

     /* Простейшая программа восстановления графических образов */

     #include "stdio.h"
     #include "dos.h"

     void load_pic(),mode(),palette(),goto_xy();

     main(argc,argv)
     int argc;
     char *argv[];
     {
          if (argc != 2) {
               printf(" Обращение: показать <имя файла>");
               exit(1);
          }
          mode(4);
          palette(0);
          load_pic(argv[1]);
          getch();
          mode(3);
     }

     /* загрузка графического изображения */
     void load_pic(fname)
     char *fname;
     {
          FILE *fp;
          register int i,j;
          char far *ptr = (char far *) 0xB8000000; /* указатель
               на CGA память */
          char far *temp;
          unsigned char buf[14][80]; /* для размещения содержимого
                                        экрана */
          if (!(fp=fopen(fname,"rb"))) {
               goto_xy(0,0);
               printf(" невозможно открыть файл \n");

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

               return;
          }
          /* загрузка изображения из файла */
          for (i=0;i<8152;i++) {
               *ptr = getc(fp); /* четный байт */
               *(ptr+8152) = getc(fp); /* нечетный байт */
               ptr++;
          }
          fclose(fp);
     }

     /* установка видеорежима */
     void mode(mode_code)
     int mode_code;
     {
          union REGS r;

          r.h.al = mode_code;
          r.h.ah = 0;
          int86(0x10,&r,&r);
     }

     /* установка цветов диаграмм */
     void palette(pnum)
     int pnum;
     {
          union REGS r;

          r.h.bh = 1; /* код 4 режима */
          r.h.bl = pnum;
          r.h.ah = 11; /* установка функции цвета */
          int86(0x10,&r,&r);
     }

      /* установка курсора в координаты x,y */
     void goto_xy(x,y)
     int x,y;
     {
          union REGS r;

          r.h.ah = 2; /* функция адресации курсора */
          r.h.dl = y; /* горизонтальная координата */
          r.h.dh = x; /* вертикальная координата */
          r.h.bh = 0; /* видеостраница */
          int86(0x10,&r,&r);
     }

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

               НЕКОТОРЫЕ ИНТЕРЕСНЫЕ ИДЕИ ПО МОДИФИКАЦИИ ПРОГРАММ
     -----------------------------------------------------------------

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

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


www.dstu0752.narod.ru

dstu0752@yandex.ru

ICQ UIN 8107923