Книга
Книга
Валерий
beginbegin
begin
begin
Pascal
программирования
Разработка диалоговых систем
Реализация поддержки аппаратных средств
Адаптация программ для Windows
Большое количество примеров
+ Дискета
Валерий Фаронов
Turbo Pascal
Санкт-Петербург«БХВ-Петербург»2004
УДК 681.3.068+800.92 Turbo Pascal
ББК 32.973.26-018.1
Ф24
Фаронов В. В.
Ф24Turbo Pascal. — СПб.: БХВ-Петербург, 2004. — 1056 с.: ил.
ISBN 5-94157-295-6
В книге содержится исчерпывающее руководство по программированию в среде Турбо Паскаль 7.0 корпорации Borland®. Изложение ведется от простого к сложному, позволяя изучить систему программирования начинающим и пополнить свои знания опытным программистам. В заключительной части книги приводятся практические рекомендации по программированию основных устройств персонального компьютера, на сопровождающей книгу дискете содержатся тексты соответствующих программ и программных модулей.
Для программистов
УДК 681.3.068+800.92 Turbo Pascal
ББК 32.973.26-018.1
Группа подготовки издания:
Главный редактор
Екатерина Кондукова
Зав. редакцией
Григорий Добин
Редактор
Леонид Кочин
Компьютерная верстка
Натальи Смирновой
Корректор
Наталия Першакова
Дизайн обложки
Игоря Цырульникова
Зав. производством
Николай Тверских
Лицензия ИД № 02429 от 24.07.00. Подписано в печать 22.09.04.
формат 70хЮ071е. Печать офсетная. Усл. печ. л. 85,14.
Доп. тираж 3000 экз. Заказ № 941"БХВ-Петербург", 190005, Санкт-Петербург, Измайловский пр., 29.
Гигиеническое заключение на продукцию, товар № 77.99.02.953.Д.001537.03.02от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России.
Отпечатано с готовых диапозитивовв ОАО "Техническая книга"
190005, Санкт-Петербург, Измайловский пр., 29
ISBN 5-94157-295-6ISBN 5-94157-295-6Фаронов в. в., 2ооз
ISBN 5-94157-295-6
ISBN 5-94157-295-6
© Оформление, издательство "БХВ-Петербург", 2003
© о мд Групп. 2003
Содержание
От автора1
ЧАСТЬ I. ЯДРО ТУРБО ПАСКАЛЯ3
Глава 1. Знакомство со средой Турбо Паскаля5
Как начать работу с Турбо Паскалем5
Функциональные клавиши:7
Текстовый редактор8
Основные приемы работы в среде Турбо Паскаля11
Работа с файлами11
Прогон и отладка программы12
Справочная служба Турбо Паскаля13
Глава 2. Знакомство с языком Турбо Паскаля15
Ваша первая программа15
Типы данных20
Преобразования типов и действия над ними25
Операторы языка30
Составной оператор и пустой оператор30
Условный оператор31
Операторы повторений34
Оператор выбора38
Метки и операторы перехода40
Массивы41
Процедуры и функции44
Примеры программ51
Вычисление дня недели52
Биоритмы56
Игра "ним"66
Глава 3. Элементыязыка84
Алфавит84
Идентификаторы85
Константы86
Выражения88
Операции88
Структура программы93
Глава 4. Типыданных95
Простые типы96
Порядковые типы96
Целые типы97
Логический тип99
Символьный тип99
Перечисленный тип102
Тип-диапазон104
Вещественные типы105
Структурированные типы109
МассивыПО
Записи112
Множества117
Строки121
Совместимость и преобразование типов124
Глава 5. Файлы129
Доступ к файлам130
Имена файлов131
Логические устройства132
Инициация файла133
Процедуры и функции для работы сфайлами135
Текстовые файлы143
Типизированные файлы149
Нетипизированные файлы150
Глава 6. Указатели и динамическая память152
Динамическая память152
Адреса и указатели153
Объявление указателей154
Выделение и освобождение динамической.памяти156
Использование указателей161
Процедуры и функции для работы сдинамической памятью165
Администратор кучи168
Глава 7. Типизированные константы171
Константы простых типов и типа String171
Константы-массивы172
Константы-записи174
Константы-множества175
Константы-указатели175
Глава 8. Процедуры и функции176
Локализация имен177
Описание подпрограммы Заголовок181
Параметры182
Параметры-массивыипараметры-строки187
типы. Параметры-функции и параметры-процедуры190
Нетипизированныепараметры-переменные193
Рекурсия и опережающее описание195
Расширенный синтаксис вызова функций198
Глава 9. Модули200
Структура модулей201
ч
Заголовок модуля и связь модулей друг с другом201
Интерфейсная часть202
Исполняемая часть203
Инициализирующая часть204
Компиляция модулей205
Доступ к объявленным в модуле объектам207
Стандартные модули.209
Глава 10. Объекты211
Основные принципы ООП212
Инкапсуляция213
Наследование213
Полиморфизм213
Постановка учебной задачи214
Создание объектов215
Использование объектов224
Глава 11. Дополнительные возможности Турбо Паскаля231
Внешние процедуры (функции)231
Использование встроенных машинных кодов233
Обращение к функциям операционной системы235
Поддержка процедуробработкипрерываний238
Запуск внешних программ241
Оверлей244
Прямое обращение к памятии портамввода/вывода250
Длинные строки251
/
Глава 12. Встроенный ассемблер259
Общее описание МП 8086/8088259
Регистры260
Адресация;262
Регистровая263
Непосредственная263
Прямая263
Косвенная регистровая263
Адресация по базе264
Индексная адресация264
Адресация по базе с индексированием264
Система команд265
Специфика встроенного ассемблера277
ОператорЛ^Л/277
Синтаксис ассемблерных команд279
Метки...279
Префиксы280
Коды инструкций281
Операнды*281
Регистры281
Константы281
Имена282
Выражения283
Операция &286
Операция ()287
Операция [] ....287
Операция . (точка) 287
Операции HIGHи LOW.287
Операция (двоеточие).'.288
Операция OFFSET288
Операция SEG288
Операция PTR288
Операции * и /.288
Операция MOD289
Операции SHL и SHR289
Бинарная операция +289
Бинарная операция —289
Побитовые операции NOT, AND, OR, XOR289
Директивы ассемблера289
Ассемблерные подпрограммы291
Глава 13. Работа с библиотекой CRT.294
Программирование клавиатуры294
Текстовый вывод на экран299
Программирование звукового генератора309
Глава 14. Применение библиотеки GRAPH311
Переход в графический режим и возврат в текстовый
Краткая характеристика графических режимов работы
дисплейных адаптеров
Процедуры и функции313
Координаты, окна, страницы320
Линии и точки329
Многоугольники338
Дуги, окружности, эллипсы341
Краски, палитры, заполнения348
Сохранение и выдача изображений368
Вывод текста372
Включение драйвера и шрифтов в тело программы381
ЧАСТЬ П. БИБЛИОТЕКА TURBO VISION385
Глава 15. Введение в Turbo Vision387
Простейшая программа в Turbo Vision387
Формирование строки статуса389
Формирование меню393
Команды396
События и их обработка397
Программирование диалоговых запросов401
Инкапсуляция новых полей и405
Создание и использование групп:408
Вывод текста412
Цветовая палитра415
Использование коллекций.419
Указатель на элемент списка423
Диалоговое окно выбора режима
Обработка команд пользователя433
Редактирование и добавление записей434
Удаление записи438
Режим поиска записи439
Итоги442
Глава 16. Общая характеристика объектов443
Структура объектов444
Абстрактные объекты и методы445
Функциональность объектов446
Обзор видимых элементов447
Группы видимых элементов447
Абстрактная группа447
Панель экрана448
Программы'448
Окна449
Диалоговые окна449
Терминальные видимые объекты449
Рамки449
Кнопки450
Кластеры450
Меню451
Строки ввода451
Протокол ввода452
Скользящие окна (скроллеры)452
Просмотр списков453
Статический текст453
Строки статуса453
Невидимые элементы453
Потоки453
Потоки DOS454
Буферизованные потоки454
Потоки EMS454
Ресурсы454
Коллекции455
Отсортированные коллекции455
Коллекции строк455
Коллекции ресурсов455
Списки строк455
Контролеры456
TValidator.456
TPXPicture Validator456
TFilterValidator.456
TRange Validator:456
TLookup Validator457
TStringLookup Validator457
Глава 17. Видимые элементы458
Территориальность458
Вывод изображения461
Заполнение области461
Цвет изображения462
Группы467
Создание группы и изменение ее состава467
/-упорядочение и дерево видимых элементов469
Активные элементы470
Модальные видимые элементы472
Изменение свойств элемента...472
Поле Options473
ofSelectable473
ofTopSelect.473
ofFirstClick473
ofFramed474
ofPreProce&s474
ofPostProcess474
ofBuffered.474
ofTileable474
ofCenterX475
ofCenterY475
of Centered•475
Поле GrowMode475
gfGrowLoX476
gfGrowLoY.:476
gfGrowHiX476
gfGrowHiY476
gfGrowAU477
gfGrowRel477
Поле DragMode477
dmDragMove477
dmDragGrow477
dmLimitLoX.478
dmLimitLoY.478
dmLimitHiX478
dmLimitHiY478
dmLimitAll478
Поле State478
s/Visible.'478
sfCursorVis479
sfCwsorlns479
sfShadow479
sfActive479
sfSelected479
sfFocused.479
sfDragging479
sfDisable479
sfModal479
sfExposed480
Воздействие на состояние поля State480
Глава 18. События482
Природа событий482
Виды событий,."483
События от мыши...484
События от клавиатуры485
Сообщения485
Пустые события485
Маршрутизация событий485
Позиционированные события487
Общие события488
Фаза событий488
Команды''.490
J. Преобразование активных событий в команды490
Запрещение и разрешение команд493
Модификация и обработка событий494
События, определенные пользователем494
Маскирование и очистка событий495
Перекрытие HandleEvent496
Перекрытие GetEvent497
Неиспользованное время497
Ненужные события499
Взаимодействие видимых элементов499
Контекстная помощь'.501
Глава 19. Коллекции514
Элементы коллекций514
Создание коллекций515
Доступ к элементам коллекций518
Отсортированные коллекции..:523
Коллекции строк526
Полиморфные коллекции530
Коллекции и управление памятью535
Глава 20. Потоки537
Сущность потоков537
Регистрация объектов539
Создание и удаление потока541
Работа с потоком543
Методы Put и Get543
Методы Store и Load544
Обработка ошибок546
Прямой доступ к потокам547
Использование потоков с произвольными данными547
Глава 21. Ресурсы Turbo Vision549
Создание ресурса550
Использование ресурса551
Строковые ресурсы552
Глава 22. Объекты-контролеры556
Типы объектов-контролеров556
TPXPicture Validator556
TFilterValidator.557
TRange Validator558
TLookup Validator558
TStringLookup Validator559
Использование объектов-контролеров559
Контроль текстового ввода559
Проверка других объектов560
Реализация нестандартного контроля560
Глава 23. Практика использования562
Контроль за динамической памятью562
Обработка ошибок инициализации и модальных состояний564
Отладка программ566
23.4. Использование оверлея571
Порядок вызова наследуемого метода574
Конструктор574
Деструктор576
Другие методы576
Примеры программных реализаций577
Строка статуса578
Меню580
Диалоговое окно582
Окно с текстом588
Окно со скроллером592
Просмотр списка файлов596
ЧАСТЬ III. ПРАКТИКА ПРОГРАММИРОВАНИЯ601
Глава 24. Текстовый ввод/вывод в графическом режиме603
Организация видеопамяти в графическом режиме...604
Адаптер CGA604
Адаптеры EGA и VGA605
Структура CHR-файлов для векторных шрифтов615
Модуль поддержки текстового ввода/вывода FGrText620
Шрифты и координаты621
Драйверы текстовых устройств628
Функция Open Func630
Функция InOutFunc630
Функция Flush Func630
Функция CloseFunc631
Подпрограммы модуля F_GrText631
Тестовая программа639
Глава 25. Обработка ошибок периода исполнения программы643
Механизм обработки исключительных ситуаций643
Модуль F_Jump648
Текст модуля F_Jump650
Тестовая программа652
Глава 26. Работа с диском на физическом уровне,...656
Структура дисков ДОС656
Принцип работы дисковых накопителей656
Логическая структура дисков658
Загрузочный сектор659
Таблица распределения файлов667
Структура каталогов669
Средства BIOS и ДОС для работы с диском670
Прерывание BIOS $13670
Сбросить контроллер диска673
Получить статус диска673
Читать/записать сектор674
Проверить сектор674
Форматировать дорожку675
Форматировать одну или несколько дорожек жесткого диска677
Получить параметры диска678
Инициализировать контроллер диска на основе дисковых таблиц678
Читать/записать сектор с кодом коррекции679
Позиционировать головки на нужный цилиндр679
Сбросить контроллер диска679
Читать/записать буфер сектора679
Получить состояние дисковода жесткого диска680
Рекалибровка дисковода жесткого диска680
Проверка памяти контроллера жесткого диска680
Проверка дисковода жесткого диска680
Проверка контроллера жесткого диска681
Получить тип и размер диска681
Проверить флаг дискеты681
Установить тип дискеты681
Установить среду для форматирования дискеты682
Парковка головок жесткого диска682
Форматирование жесткого диска с контроллером типа ESDI683
Поддержка дисков в ДОС683
Получить/установить параметры устройства CL = $60/$40688
Записать/прочитать дорожку CL = $41/$61690
Форматировать/проверить дорожку CL = $42/$62.691
Модуль F_Disk для работы с диском на физическом уровне691
Описание модуля691
Тестовая программа699
Глава 27. Работа с мышью702
Взаимодействие с драйвером мыши702
Сбросить драйвер704
Показать указатель мыши705
Убрать указатель мыши705
Получить текущие координаты мыши и состояние ее кнопок705
Переместить указатель в новое положение706
Получить информацию о нажатии/отпускании кнопки706
Установить область перемещения указателя по горизонтали
(вертикали)...706
Задать форму указателя для графического режима707
Задать форму указателя для текстового режима708
Получить счетчик шагов перемещения708
Задать условия вызова программы пользователя709
Включить (выключить) эмуляцию светового пера710
Установить отношение числа шагов на единицу виртуального
экрана710
Установить порог удвоения скорости смещения указателя мыши710
Заменить процедуру пользователя710
Получить размер буфера, необходимого для сохранения состояния драйвера711
Сохранить текущее состояние драйвера:711
Восстановить состояние драйвера.711
Задать видеостраницу для отображения указателя711
Получить номер видеостраницы, в которой отображается указатель711
Модуль F_Mouse712
Глобальные константы, типыи переменные712
Инициализация мыши713
Координаты715
Состояние кнопок716
Окна717
Форма указателя717
Сохранение/восстановление состояния719
Эмуляция клавиатуры719
Обработка событий от мыши721
Тестовая программа...723
Глава 28. Доступ к отображаемой памяти.735
Что такое отображаемая память735
Функции драйвера отображаемой памяти...739
Дать статус EMM742
Получить сегмент отображаемого окна742
Получить информацию о EMS-памяти....742
Создать дескриптор и распределить память743
Картировать окно743
Уничтожить дескриптор EMM и освободить память744
Дать номер версии EMM744
Сохранить/восстановить картирующий контекст744
Дать количество активных дескрипторов745
Дать количество связанных с дескриптором страниц745
Дать информацию о распределении памяти747
Дать/установить карту всех страниц747
Дать/установить карту части страниц748
Картировать/освободить несколько страниц одновременно748
Изменить количество связанных с дескриптором страниц749
Получить/установить флаг сохранения страниц749
Установить/получить имя дескриптора750
Дать список имен дескрипторов или искать дескриптор по имени750
Картировать память и выполнить инструкцию JUMP.751
Картировать память и выполнить инструкцию CALL752
Переместить/обменять блок памяти752
Получить полную карту EMS-окна753
Модуль F_Ems'.753
Объекты модуля755
TEMSArea755
TEMSVector760
TEMSMatrix.762
TEMSArray763
TEMSType765
TEMSText766
TEMSScreen767
Глобальные ссылки769
Тестовая программа773
Глава 29. Защита программ от компьютерных вирусов782
Что такое компьютерные вирусы-.782
Цикл функционирования вирусов783
"Вакцинация" программ785
Заголовок исполняемых файлов785
Защита вновь создаваемых программ788
Модуль F_Anti790
Защита существующих ЕХЕ-файлов794
Описание программ SetFag.pas и Fag.asm796
Программа AntiVir800
Некоторые рекомендации802
Глава 30. Защита программ от нелегального копирования803
Суть защиты803
Анализ индивидуальных признаков среды805
Проверка типа ПК и версии DOS806
Проверка даты изготовления и контрольной суммы ПЗУ 807
Проверка места расположения файла на диске808
Проверка состава аппаратных средств809
Использование скрытых частей программы и особенностей физических носителей информации817
Защита программ от трассировки826
Модуль F_Prot для защиты программ832
Описание модуля832
Тестовая программа836
Глава 31. Работа в среде Borland Pascal838
Особенности среды.'838
Отличия в меню838
Использование защищенногорежима839
Работа с браузером840
Просмотр объектов840
Просмотр модулей840
Глобальные символы840
Поиск символа841
Особенности защищенного режима841
Адресация памяти841
Многозадачность843
Виртуальная память и перемещение сегментов844
Глава 32. Программирование для защищенного режима845
Расширитель ДОС.845
Модуль WinApi846
Управление памятью•849
Управление селекторами854
Управление модулями854
Управление ресурсами856
Функции разного назначения859
Работа с памятью861
Создание и использование ресурсов865
Создание ресурсов866
Использование ресурсов869
Создание и использование динамических библиотек871
Создание DLL872
Использование DLL...875
Глава 33. Адаптация MS-DOS-программ к работе в среде Windows880
Введение в Windows880
Модуль Win CRT. 881
Переменные модуля WinCRT883
Процедуры и функции884
Модификация WinCRT дляиспользования графики Windows885
ЧАСТЬ IV. ПРИЛОЖЕНИЯ 891
Приложение 1. Среда Турбо Паскаля893
П1.1. Элементы диалоговой среды893
П1.1.1. Работа с окнами.894
П1.1.2. Работа с меню895
П1.1.3. Работа с диалоговым окном896
П1.2. Система меню898
П1.2.1. Меню опции File898
П1.2.3. Меню опции Edit901
П1.2.4. Меню опции Search902
Ш.2.5. Меню опции Run904
Ш.2.6. Меню опции Compile905
П1.2.7. Меню опции Debug907
П1.2.8. Меню опции Tools.912
П1.2.9. Меню опции Options912
Ш.2.10. Меню опции Window.925
П1.2.11. Меню опции Help925
П1.3. Директивы компилятора926
П1.4. Редактор930
П1.4.1. Команды перемещения курсора930
П1.4.2. Команды удаления/вставки931
П1.4.3. Команды работы с блоками931
Ш.4.4. Прочие команды932
П1.4.5. Команды, передаваемые среде из редактора933
П1.5. Вызов Турбо Паскаля935
Приложение 2. Варианты кодировки знакогенераторов ПК937
Приложение 3. Сообщения и коды ошибок939
П3.1. Сообщения об ошибках периода компиляции939
П3.2. Ошибки, возникающие во время выполнения программ957
ПЗ.2.1. Ошибки, обнаруживаемыеДОС957
ПЗ.2.2. Ошибки ввода/вывода959
П3.2.3. Критические ошибки960
ПЗ.2.4. Фатальные ошибки......961
Приложение 4. Стандартные библиотечные модули964
П4.1. Модуль System964
П4.1.1. Константы964
П4.1.2. Переменные '.966
П4.1.3. Стандартные процедуры и функции966
Процедуры управления966
Процедуры динамического распределения памяти967
Функции динамического распределения памяти967
Функции преобразования968
Арифметические функции968
Процедуры порядкового типа969
Функции порядкового типа969
Строковые процедуры;969
Строковые функции970
Функции для работы с указателями и адресами970
Процедуры разного назначения971
Функции разного назначения971
Процедуры ввода/вывода972
Функции ввода/вывода973
Процедуры для текстовых файлов973
Функции для текстовых файлов973
Процедуры для нетипизированных файлов974
П4.2. Модуль Dos974
П4.2.1. Константы974
Константы регистра флагов974
Константы режима доступа к файлу974
Константы атрибутов файла975
П4.2.2. Типы975
П4.2.3. Переменные976
П4.2.4. Процедуры и функции977
Процедуры даты и времени977
Процедуры обслуживания прерываний977
Функции статуса диска978
Процедуры обработки файлов978
Функции обработки файла978
Процедуры обработки процессов978
Функции управления программой979
Функции управления средой979
Дополнительные процедуры979
Дополнительные функции979
П4.3. Модуль Crt979
П4.3.1. Константы980
Константы режима работы980
Константы цветов980
П4.3.2. Переменные981
П4.3.3. Процедуры и функции981
Функции981
Процедуры:982
П4.4. Модуль GRAPH...983
П4.4.1. Константы985
Константы задания драйверов и режимов работы:985
Значения ошибок, возвращаемые функцией Graph Result986
Константы цвета986
Константы типов и толщины линий987
Константы управления шрифтом988
Константы отсечения988
Константы для процедуры Bar3D988
Константы шаблона штриховки988
Битовые константы989
Константа палитры989
П4.4.2. Типы989
П4.4.3. Переменные990
П4.4.4. Процедуры990
П4.4.5. Функции.994
Приложение 5. Тексты программ997
П5.1. Программа определения дня недели997
П5.2. Определение биоритмов998
П5.3. Игра "Ним":1004
П5.4. Программа Notebook1012
Приложение 6. Описание редактора векторных шрифтов1029
П6.1. Интерфейс с пользователем1029
П6.2. Описание программы1032
Приложение 7. Описание содержимого дискеты1034
Список литературы1035
От автора
У вас в руках, уважаемый читатель, книга с удивительной судьбой! Ее первый вариант был написан и передан в издательство еще в августе 1989 г. С тех пор, с точки зрения развития информационных технологий, прошла целая вечность. Если первое издание этой книги я писал на скромной машине "CorData с объемом оперативной памяти 680 Кбайт, частотой процессора 4,7 МГц и объемом диска 20 Мбайт, то это предисловие к почти той же книге я переписываю на куда более мощной машине. Достаточно сказать, что только объем ее дисковой памяти составляет 30 Гбайт, т. е. почти в 1500 раз больше. Частота процессора 1700 МГц и объем оперативной памяти 256 Мбайт дополняют картину (уверен, что многие из вас имеют на своем столе еще более мощные машины).
В чем же загадка Турбо Паскаля, почему эта книга до сих пор еще не забыта, но переиздается из года в год ? Давно канула в Лету операционная система MS-DOS, окончательно вытесненная куда более удобными системами Windows, а ведь Турбо Паскаль работает сам под управлением MS-DOS и создает программы, рассчитанные на эту операционную систему!
Разумеется, за эти годы книга претерпела сильные изменения. В ней описывается другая версия Турбо Паскаля, добавлены многочисленные примеры и описание библиотеки Turbo Vision. Однако дело не в достоинствах или недостатках книги. Дело в том, что программирование в среде MS-DOS намного проще, чем в Windows, которая, к тому же, стремительно развивается, ставя даже перед профессионалами все новые и новые вопросы. Именно эта простота остается той главной причиной, почему тысячи и тысячи людей начинают свой путь в программировании с Турбо Паскаля.
Другая, не менее важная причина, — сравнительная простота в использовании и легкость изучения мощного современного объектно-ориентированного языка Турбо Паскаль. И это не случайно: язык придуман в конце 60-х — начале 70-х гг. XX века швейцарским профессором Никлаусом Виртом как раз для обучения студентов азам программирования (кстати, первый вариант транслятора для Паскаля создан еще в 1970 г. учениками Н. Вирта для машины PDP-11). Свою приставку "Турбо" язык получил в стенах американской корпорации Borland потому, что созданный ее сотрудником
А. Хейлсбергом — одним из учеников Н. Вирта — компилятор языка отличался рекордной по тем временам скоростью.
Книга логически делится на три части.
В первой рассматривается ядро системы программирования — язык Турбо Паскаль и стандартные библиотеки CRT и Graph. Этот материал рассчитан, в основном, на начинающих программистов, желающих овладеть азами искусства программирования и попробовать свои силы в решении относительно не сложных задач.
Во второй части рассматривается объектно-ориентированная библиотека Turbo Vision, существенно упрощающая разработку диалоговых систем средней сложности.
Наконец, третья часть посвящена практике программирования. Поскольку, как уже говорилось, программы, созданные с помощью среды Турбо Паскаль, работают под управлением MS-DOS, в этой части излагаются некоторые сведения об особенностях данной операционной системы, а уже затем приводится программная реализация, учитывающая описанные особенности. Здесь вы познакомитесь с работой различных устройств современного компьютера — экрана, мыши, дисков, научитесь использовать всю память компьютера, а не только ее первый мегабайт, доступный в MS-DOS. Наконец, вы сможете защитить свои программы от вирусных атак и от воровства (нелегального копирования).
Январь 2003 г.
ЧАСТЬ I
Ядро
ТУРБО ПАСКАЛЯ
Глава 1
Знакомство со средой
Турбо Паскаля
Система программирования Турбо Паскаль представляет собой единство двух в известной степени самостоятельных начал: компилятора с языка программирования Паскаль (язык назван в честь выдающегося французского математика и философа Блеза Паскаля (1623-1662)) и некоторой ин-
струментальной программной оболочки, способствующей повышению эффективности создания программ. Для краткости условимся в дальнейшем называть реализуемый компилятором язык программирования Паскаль — языком Турбо Паскаля, а разнообразные сервисные услуги, представляемые программной оболочкой, — средой Турбо Паскаля.
Среда Турбо Паскаля — это первое, с чем сталкивается любой программист, приступающий к практической работе с системой. Если вы по каким-либо причинам не собираетесь писать собственные программы, можно пропустить эту главу, в которой приводится минимум сведений об основных приемах работы в среде Турбо Паскаля. Более полная справочная информация содержится в приложении 1.
Как начать работу с Турбо Паскалем
Система Турбо Паскаль довольно значительна по объему. Она поставляется на нескольких дистрибутивных дискетах (или компакт-диске) и устанавливается на жесткий диск. При развертывании системы на жестком диске обычно создается каталог с именем ТР (или PAS, TURBOPAS, PASCAL и т. п.), в который помещаются все файлы с дистрибутивных дискет. Для вызова Турбо Паскаля необходимо отыскать в древовидной структуре каталогов ПК этот каталог и в нем файл TURBO.EXE. Этот файл содержит готовую к работе диалоговую систему программирования Турбо Паскаль. В него входят минимально необходимые части Турбо Паскаля (текстовый редактор, компилятор, компоновщик, загрузчик). Для нормальной работы в диалоговой среде понадобятся также основная библиотека, располагающаяся
в файле TURBO.TPL, и справочная служба (файл TURBO.HLP). В принципе, этих файлов достаточно для написания, компиляции и исполнения большинства примеров, содержащихся в этой книге.
Пусть перечисленные файлы располагаются в каталоге ТР на диске D:. Тогда для вызова Турбо Паскаля следует дать команду:
D: \TP\TURBO
По этой команде операционная система MS-DOS поставит на исполнение программу из файла TURBO.EXE: загрузит программу в оперативную память и передаст ей управление.
Не рекомендуется работать с системой, назначив в качестве каталога по умолчанию (текущего каталога) тот, в котором хранятся перечисленные выше файлы (этот каталог будем называть системным). Во-первых, в таком случае можно ошибочно стереть какой-либо из файлов системы программирования и тем самым нарушить ее работоспособность, а во-вторых, этот каталог очень скоро заполнится другими файлами, прямо не относящимися к Турбо Паскалю. Существует и еще одна причина, по которой нежелательно работать в системном каталоге. Дело в том, что Турбо Паскаль имеет свойство запоминать свою настройку в двух файлах с именами TURBO.TP и TURBO.РСК. При вызове система начинает поиск этих файлов в текущем каталоге. Если этот каталог — ваш индивидуальный, система всякий раз будет настраиваться так, как вы этого хотите. Если эти файлы не обнаружены в вашем каталоге (а при первом обращении к Турбо Паскалю так оно и будет), система продолжит поиск в системном каталоге, а не найдя их там, настроится стандартным образом. Впоследствии можно сохранить настроечные файлы в своем каталоге и тем самым избавить себя от необходимости перенастройки системы всякий раз при обращении к ней.
После успешного вызова системы экран ПК приобретает вид, показанный на рис. 1.1.
Сразу же скажем, что для выхода из Турбо Паскаля следует нажать клавишу <Alt> и, не отпуская ее, — клавишу с латинской буквой X, после чего можно отпустить обе клавиши.
Верхняя строка содержит "меню" возможных режимов работы Турбо Паскаля, нижняя — краткую справку о назначении основных функциональных клавиш. Вся остальная часть экрана принадлежит окну редактора, очерченному двойной рамкой и предназначенному для ввода и коррекции текстов программ. В его верхней строке приводится имя того дискового файла, откуда был прочитан текст программы (новому файлу присваивается имя NONAME00.PAS), два специальных поля, используемых при работе с устройством ввода "мышь" (эти поля выделены квадратными скобками), и цифра 1 — номер окна. В Турбо Паскале можно работать одновременно с несколькими программами (или частями одной крупной программы), каж-
дая из которых может располагаться в отдельном окне редактора. Среда позволяет использовать до 9 окон редактора одновременно.
Рис. 1.1.Вид экрана после вызова Турбо Паскаля
Кроме окна (окон) редактора в Турбо Паскале используются также окна отладочного режима, вывода результатов работы программы, справочной службы, стека, регистров. По желанию они могут вызываться на экран поочередно или присутствовать на нем одновременно.
Функциональные клавиши
Функциональные клавиши используются для управления средой Турбо Паскаля. Они имеют обозначения <F1>, <F2>, ..., <F12> и располагаются в самом верхнем ряду клавиатуры. С каждой из этих клавиш связывается некоторая команда меню. Действие почти всех функциональных клавиш можно модифицировать тремя особыми клавишами: <Alt> (от ALTernative — дополнительный), <Ctrl> (ConTRoL — управляющий) и <Shift> (SHIFT — сдвиговый). Эти клавиши используются подобно клавише временной смены регистра на пишущей машинке: нужно нажать на одну из них и затем, не отпуская ее, нажать функциональную клавишу. В дальнейшем такое совместное нажатие двух клавиш будем обозначать знаком сложения. Например, <Alt>+<F3> означает, что вместе с клавишей <Alt> необходимо нажать кла-
вишу <F3>, <Ctrl>+<F9> — вместе с <Ctrl> нажимается <F9> и т. д.
Ниже приводятся команды, которые передаются среде Турбо Паскаля функциональными клавишами и некоторыми их комбинациями с клавишами <Ctrl> и
П <F1> — обратиться за справкой к встроенной справочной службе (Help — помощь);
О <F2> — записать редактируемый текст в файл на диск;
F3> — прочитать текст из дискового файла в окно редактора;
П <F4> — используется в отладочном режиме: начать или продолжить исполнение программы и остановиться перед исполнением той ее строки, на которой стоит курсор;
F5>"распахнуть" активное окно на весь экран;
F6> — сделать активным следующее окно;
F7> — применяется в отладочном режиме: выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), войти в эту процедуру и остановиться перед исполнением первого ее оператора;
П <F8> — используется в отладочном режиме: выполнить следующую строку программы; если в строке есть обращение к процедуре (функции), исполнить ее и не прослеживать ее работу;
П <F9> — компилировать программу, но не выполнять ее;
П <F10> — перейти к диалоговому выбору режима работы с помощью главного меню;
П <Ctrl>+<F9> — выполнить прогон программы: компилировать программу, находящуюся в редакторе, загрузить ее в оперативную память и выполнить, после чего вернуться в среду Турбо Паскаля;
П— сменить окно редактора на окно вывода результатов рабо
ты (прогона) программы.
Полное описание функциональных клавиш содержится в приложении 1, а сейчас — самый краткий комментарий.
Во-первых, вам понадобятся<Ctrl>+<F9> для проверки работы
вашей программы и— для выхода из Турбо Паскаля. Клавиши
F2> и <F3> помогут вам в работе с вашим каталогом. Командой вы в любой момент сможете просмотреть данные, выданные на экран в результате прогона программы.
Текстовый редактор
Текстовый редактор среды Турбо Паскаля предоставляет пользователю удобные средства создания и редактирования текстов профамм. Признаком
того, что среда находится в состоянии редактирования, является наличие в окне редактора курсора — небольшого мигающего прямоугольника. Режим редактирования автоматически устанавливается сразу после загрузки Турбо Паскаля. Из режима редактирования можно перейти к любому другому режиму работы Турбо Паскаля с помощью функциональных клавиш или путем выбора нужного режима из главного меню. Если среда находится в состоянии выбора из меню, курсор исчезает, а в строке меню появляется цветной указатель-прямоугольник, выделяющий одно из кодовых слов (опций меню). Для перехода от состояния выбора режима из главного меню в состояние редактирования нужно нажать клавишу <Esc> (ESCape — ускользать, убегать), а для перехода к выбору из главного меню — <F10>.
Рассмотрим основные приемы работы с текстовым редактором.
Для создания текста программы нужно ввести этот текст с помощью клавиатуры ПК подобно тому, как это делается при печатании текста на пишущей машинке. После заполнения очередной строки следует нажать на клавишу <Enter>, чтобы перевести курсор на следующую строку (курсор всегда показывает то место на экране, куда будет помещен очередной вводимый символ программы).
Окно редактора имитирует длинный и достаточно широкий лист бумаги, фрагмент которого виден в окне. Если курсор достиг нижнего края, осуществляется прокрутка окна редактора: его содержимое смещается вверх на одну строку и снизу появляется новая строка листа. Если курсор достиг правой границы экрана, окно начинает по мере ввода символов смещаться вправо, показывая правый край листа. Размеры листа по горизонтали и вертикали ограничиваются только общим числом символов в файле, которых не должно быть больше 64 535, однако компилятор Турбо Паскаля воспринимает строки программы длиной не более 126 символов.
Окно можно смещать относительно листа с помощью следующих клавиш:
til <Page Up> — на страницу вверх;
р <Page Down> — на страницу вниз;
Р <Ноте> — в начало текущей строки;
О <End> — в конец текущей строки;
<Ctrl>+<Page Up> — в начало текста;
<Ctrl>+<Page Down> — в конец текста.
Клавишами перевода курсора (эти клавиши помечены стрелками и располагаются в правой части клавиатуры) его можно смещать по экрану. При достижении границ окна оно смещается на строку или на символ.
Если вы ошиблись при вводе очередного символа, его можно стереть с помощью клавиши со стрелкой (или надписью <Backspace>), расположенной над клавишей <Enter>. Клавиша <Delete> стирает символ, на который в
данный момент указывает курсор, a <Ctrl>+<Y> — всю строку, на которой расположен курсор.
Следует помнить, что редактор Турбо Паскаля вставляет в конце каждой строки невидимый символ-разделитель. Этот символ вставляется клавишей <Enter>, а стирается клавишами <Backspace> или <Delete>. С помощью вставки/стирания разделителя можно "разрезать7"склеить строки. Чтобы разрезать строку, следует подвести курсор к нужному месту и нажать <Enter>, чтобы склеить соседние строки, нужно установить курсор в конец первой строки (для этого удобно использовать клавишу <End>) и нажать <Delete> или установить курсор в начало второй строки (клавишей <Ноте>) и нажать < Backspaced
Нормальный режим работы редактора — режим вставки, в котором каждый вновь вводимый символ как бы "раздвигает" текст на экране, смещая вправо остаток строки. Следует учитывать, что разрезание текста и последующая вставка пропущенных строк возможны только в этом режиме. Редактор может также работать в режиме наложения новых символов на существующий старый текст: в этом режиме новый символ заменяет собой тот символ, на который указывает курсор, а остаток строки не смещается вправо. Для перехода к режиму наложения нужно нажать клавишу <Insert >, а если нажать эту клавишу еще раз, вновь устанавливается режим вставки. Признаком того, в каком режиме работает редактор, является форма курсора: в режиме вставки он похож на мигающий символ подчеркивания, а в режиме наложения он представляет собой крупный мигающий прямоугольник, заслоняющий символ целиком.
И еще об одной возможности редактора. Обычно редактор работает в режиме автоотступа. В этом режиме каждая новая строка начинается в той же позиции на экране, что и предыдущая. Режим автоотступа поддерживает хороший стиль оформления текста программы: отступы от левого края выделяют тело условного или составного оператора и делают программу более наглядной. Отказаться от автоотступа можно клавишами <Ctrl>+<O>, <I> (при нажатой <Ctrl> нажимается сначала клавиша с латинской буквой О, а затем <О> отпускается и нажимается клавиша <I>), повторная команда <Ctrl>+<O>, <I> восстановит режим автоотступа.
Ниже перечислены наиболее часто используемые команды редактора Турбо Паскаля.
Смещение курсора:
О <Page Up> — на страницу вверх;
О <Page Down> — на страницу вниз;
<Home> — в начало текущей строки;
<End> — в конец текущей строки;
П <Ctrl>+<Page Up> — в начало текста;
О <Ctrl>+<Page Down> — в конец текста.
Команды редактирования:
П <Backspace> — стирает символ слева от курсора;
П <Delete> — стирает символ, на который показывает курсор;
П <Ctrl>+<Y> — стирает строку с курсором;
О <Enter> — вставляет новую строку, разрезает старую;
П <Ctrl>+<Q>, <L> — восстанавливает измененную строку (действует, если курсор не покидал строку после ее изменения).
Работа с блоком:
П <Ctrl>+<K>, <B> — начинает выделение блока;
П <Ctrl>+<K>, <K> — заканчивает выделение блока;
П <Ctrl>+<K>, <Y> — уничтожает выделенный блок;
<Ctrl>+<K>, <C> — копирует блок;
О <Ctrl>+<K>, <V> — перемещает блок на новое место;
П <Ctrl>+<K>, <W> — записывает блок в файл;
П <Ctrl>+<K>, <R> — читает блок из файла;
П <Ctrl>+<K>, <P> — печатает блок.
1.4. Основные приемы работы в среде Турбо Паскаля
Работа с файлами
Как уже говорилось, сразу после запуска Турбо Паскаля среда переходит в режим редактирования текста, в котором можно подготовить новую программу или исправить существующую.
Основной формой хранения текстов программ вне среды являются файлы. После завершения работы с Турбо Паскалем можно сохранить текст новой программы в дисковом файле с тем, чтобы использовать его в следующий раз. Для обмена данными между дисковыми файлами и редактором среды предназначены клавиши <F2> (запись в файл) и <F3> (чтение из файла). Если вы создаете новую программу, то среда еще не знает имя того файла, в который вы захотите поместить текст этой программы, и поэтому она присваивает ей стандартное имя NONAMEOO.PAS (NO NAME — нет имени). Для сохранения текста программы в файле нужно нажать клавишу <F2>. В этот момент среда проверит имя программы и, если это стандартное имя
I
NONAME, спросит, нужно ли его изменять: на экране появится небольшое окно запроса с надписью
Save File as
(Сохранить в файле с
Ниже надписи располагается поле для ввода имени файла, в котором можно написать нужное имя и нажать— текст будет сохранен в файле.
Если в имени опущено расширение, среда присвоит файлу стандартное расширение PAS. Если вы захотите завершить работу с Турбо Паскалем, а в редакторе остался не сохраненный втекст, на экране появится окно с
запросом
NONAMEOO.PAS has been modified.
Save?
(Файл NONAMEOO.PAS был изменен.
Сохранить
В ответ следует нажать клавишу <Y> (Yes — да), если необходимо сохранить текст в файле, или <N> (No — нет), если сохранять текст не нужно.
•'•:
Прогон и отладка программы
После подготовки текста программы можно попытаться исполнить ее, т. е. откомпилировать программу, связать ее (если это необходимо) с библиотекой стандартных процедур и функций, загрузить в оперативную память и передать ей управление. Вся эта последовательность действий называется прогоном программы и реализуется комбинацией клавиш <Ctrl>+<F9>.
Если в программе нет синтаксических ошибок, то все действия выполняются последовательно одно за другим, при этом в небольшом окне сообщается о количестве откомпилированных строк и объеме доступной оперативной памяти. Перед передачейзагруженной программе среда очищает
экран (точнее, выводит на экран окно прогона программы), а после завершения работы программы вновь берет управление компьютером на себя и восстанавливает на экране окно редактора.
Если на каком-либо этапе среда обнаружит ошибку, она прекращает дальнейшие действия, восстанавливает окно редактора и помещает курсор на ту строку программы, при компиляции или исполнении которой обнаружена ошибка. При этом в верхней строке редактора появляется диагностическое сообщение о причине ошибки. Все это позволяет очень быстро отладить программу, т. е. устранить в ней синтаксические ошибки и убедиться в правильности ее работы. Если ошибка возникла на этапе прогона программы, простое указание того места, где она обнаружена, может не дать нужной информации, т. к. ошибка может быть следствием неправильной подготовки данных в предыдущих операторах программы. Например, если ошибка возникла в результате извлечения квадратного корня из отрицательного числа,
будет указан оператор, в котором осуществляется извлечение корня, хотя ясно, что первопричину ошибки надо искать где-то раньше, там, где COOT-, ветствующей переменной присваивается отрицательное значение. В таких ситуациях обычно прибегают к пошаговому исполнению программы с помощью команд, связанных с клавишами <F4>, <F7> и <F8>. Пока еще не накоплен достаточный опыт отладки, можно воспользоваться одной клавишей <F7>, после нажатия на которую среда осуществит компиляцию, компоновку (связь с библиотекой стандартных процедур и функций) и загрузку программы, а затем остановит прогон перед исполнением первого оператора. Строка программы, содержащая этот оператор, будет выделена на экране указателем (цветом). Теперь каждое новое нажатие <F7> будет вызывать исполнение всех операций, запрограммированных в текущей строке, и смещение указателя к следующей строке программы. В подозрительном месте программы можно просмотреть текущее значение переменной или выражения. Для этого нужно установить курсор в то место строки, где находится имя интересующей вас переменной, и нажать <Ctrl>+<F4>. На экране появится диалоговое окно, состоящее из трех полей (в верхнем поле будет стоять имя переменной, два других поля будут пустыми). Нажмите <Enter>, чтобы в среднем поле получить текущее значение переменной. Если перед нажатием <Ctrl>+<F4> курсор стоял на пустом участке строки или указывал на имя другой переменной, верхнее поле диалогового окна также будет пустым или содержать имя этой другой переменной. В этом случае следует ввести с помощью клавиатуры имя нужной переменной и нажать <Enter>. Кстати, таким образом можно вводить не только имена прослеживаемых переменных, но и выражения — среда вычислит и покажет значение введенного выражения.
Справочная служба Турбо Паскаля
Неотъемлемой составной частью среды Турбо Паскаля является встроенная справочная служба. Если вы достаточно хорошо владеете английским языком, у вас не будет проблем при работе с Турбо Паскалем, в затруднительной ситуации достаточно нажать <F1> и на экране появится необходимая справка. Эта справка зависит от текущего состояния среды, поэтому справочную службу называют контекстно-чувствительной. Например, если нажать <F1> в момент, когда среда обнаружила ошибку в программе, в справке будут сообщены дополнительные сведения о причинах ошибки и даны рекомендации по ее устранению.
Существуют четыре способа обращения к справочной службе непосредственно из окна редактора:
<F1> — получение контекстно-зависимой справки;
<Shift>+<Fl> — выбор справки из списка доступных справочных сооб-
щений;
<Ctrl>+<Fl> — получение справки о нужной стандартной процедуре, функции, о стандартной константе или переменной;
П— получение предыдущей справки.
По нажатии комбинации клавишна экране появится окно, со
держащее упорядоченный по алфавиту список стандартных процедур, ций, типов, констант и переменных, для которых можно получить справочную информацию.
Эту же справку можно получить и по-другому. Напечатайте на экране имя процедуры (функции, типа и т. д.) или подведите курсор к имеющемуся в тексте стандартному имени и нажмите <Ctrl>+<Fl>. Среда проанализирует ближайшее окружение курсора, выделит имя и даст нужную справку.
Во многих случаях справка содержит небольшой пример, иллюстрирующий соответствующие возможности Турбо Паскаля. Не торопитесь запоминать его или записывать на листе бумаги: его можно "вырезать" из справки и перенести в окно редактора. Для этого после вызова справки нажмите выберите в появившемся дополнительном меню пункт Сору examples (Копировать примеры) и нажмите <Enter> — текст примера ско- пируется во внутренний буфер редактора. Для извлечения примера из буфера нажмите <Esc>, чтобы выйти из справочной службы, затем подведите курсор к свободной строке в окне редактора, нажмите
(копирование содержимого буфера в текст программы) и<H>,
чтобы убрать выделение скопированного текста цветом.
Пример 2,1Пример 2,1ЖЙЬ^Г,• <^ • • £ЖЙЬ^Г,• <^ • • £
Пример 2,1
Пример 2,1
ЖЙЬ^Г,• <^ • • £
ЖЙЬ^Г,• <^ • • £
Глава 2
Знакомство с языком
Турбо Паскаля
В этой главе описывается ядро Турбо Паскаля — минимальный набор средств, достаточный для написания сравнительно простых программ. В частности, рассматриваются все операторы языка, наиболее популярные типы данных и операции над ними. Вы познакомитесь с приемами разработки процедур и функций, позволяющими создавать структурированные программы. В заключительной части главы на примерах показано применение методики нисходящего программирования, обеспечивающей сравнительно простой и надежный способ детальной проработки алгоритма программы.
Ваша первая программа
Для знакомства с языком Турбо Паскаля попробуем составить несложную программу, осуществляющую вывод какого-либо сообщения на экран ПК. Пусть это будет фраза "Я программирую на Турбо Паскале". Вот возможный вариант такой программы (пример 2.1).
Program M.y_First_Program;
const
Text = ’Я программирую на Турбо Паскале1;
begin
WriteLn(Text);
end.
Прежде всего проанализируем форму представления текста. В программе шесть строк. Строки программы обычно выделяют некоторые смысловые фрагменты текста и могут не связываться с конкретными действиями в программе: расположение текста программы по строкам — дело вкуса про-
2 Зак 941
граммиста, а не требование синтаксиса языка. Ту же программу можно было бы написать, например, так:
Program My_First_Program; const Text =
’Я программирую на Турбо Паскале';begin WriteLn(Text); end.
В отличие от некоторых других языков программирования пробел в языке Турбо Паскаль используется как разделитель отдельных конструкций языка, поэтому программа
PROGRAMMy_First_Program;constText=
'Я программирую на Турбо Паскале1 ,-BEGINWriteLn (Text) ;end.
будет неверной.
В Турбо Паскале игнорируется различие в высоте букв (заглавные или строчные), если только это не связано с текстовыми константами. Начало программы могло бы, например, выглядеть так:
program my_first_program;
Теперь о смысле отдельных строк. Первая строка
Program My_First_Program;
начинается словом Program и содержит объявление имени программы. Слово Program зарезервировано В Турбо Паскале, т. е. не может использоваться ни в каких иных целях, кроме как для объявления имени программы. В Турбо Паскале имеется множество зарезервированных слов (см. главу 3). Любое из них нельзя использовать в качестве идентификатора (имени) какого-либо объекта программы — переменной, константы и т. д. Замечу, что редактор среды Турбо Паскаля обычно выделяет зарезервированные слова цветом. В связи с этим в тексте книги эти слова выделены полужирным шрифтом Courier. Поскольку имя программы никак в дальнейшем не используется, требование его объявления кажется излишним. В Турбо Паскале можно опускать объявление имени оператором program без каких-либо последствий для программы.
В рассматриваемом примере имя My_First_Program есть не что иное, как английская фраза "Моя первая программа", но только написанная без пробелов — пробел является разделителем и не может использоваться произвольно (пробелы в идентификаторах допускается заменять символом подчеркивания).
Первая строка заканчивается особым разделителем — точкой с запятой. Этот разделитель в языке Турбо Паскаль отмечает конец оператора или описания. Применение такого специального разделителя позволяет располагать несколько операторов на одной строке.
Вторая строка
const
содержит единственное зарезервированное слово const, означающее, что далее будут описаны одна или несколько констант ( CONSTants — константы). Константами в языке считаются такие объекты программы, которые не могут изменять своего значения. В отличие от многих других языков программирования, константа в Турбо Паскале может иметь собственное имя, что соответствует принятой в научных и инженерных расчетах практике именования часто используемых констант. Например, со школы мы помним о существовании константы 71 = 3,14159265. При обработке программы имя константы pi будет заменяться компилятором на ее значение.
Описать константу в Турбо Паскале — значит указать ее имя и значение. Такое указание содержится в третьей строке
Text = 'Я программирую на Турбо Паскале';
в которой константе с именем Text присваивается в качестве значения строка символов "Я программирую на Турбо Паскале".
В Турбо Паскале существуют константы разного типа — целые или вещественные числа, символы, строки символов, массивы и т. д. Признаком того, что элемент Text является константой типа "строка символов", служат два апострофа, обрамляющих строку, причем сами апострофы этой строке не принадлежат, а лишь указывают компилятору на то, что все заключенные в них символы следует рассматривать как единое целое — текстовую константу. Если понадобится включить сам апостроф в текстовую константу, достаточно его написать дважды подряд. Например, описание
Text = 'Турбо1'Паскаль';
создаст константу со значением
Турбо'Паскаль
Все три первые строки не связаны с какими-либо конкретными действиями при работе программы. Они сообщают компилятору некоторые сведения о самой программе и использующихся в ней объектах. Эта часть программы называется разделом описаний. Зарезервированное слово begin в четвертой строке сигнализирует компилятору о начале другой части программы — раздела операторов. В нашем примере этот раздел содержит оператор
WriteLn(Text);
который, собственно, и выводит сообщение на экран компьютера.
Завершает всю программу зарезервированное слово end с точкой. Точка оповещает компилятор о конце текста программы. За сочетанием end. можно размещать какой угодно текст — он не будет обрабатываться компилятором.
Перед тем как попробовать откомпилировать и исполнить нашу программу, обсудим ее единственный исполняемый оператор
WriteLn(Text);
Любопытно, что в Паскале вообще и Турбо Паскале в частности, нет специальных операторов ввода/вывода. Для обмена информацией с окружающим миром в программах, написанных на языке Турбо Паскаль, предусмотрены особые стандартные процедуры. Таким образом, по своей сути оператор
WriteLn(Text);
является оператором обращения к встроенной процедуре вывода данных (свое название она получила от WRITE LiNe — записать строку).
Понятие процедуры (см. главу 8) — одно из центральных понятий Турбо Паскаля. Процедура — это некоторая последовательность операторов, к которой можно обратиться по имени. Всякий раз, когда мы называем в операторе имя процедуры, инициируется последовательность запрограммированных в ней действий.
Процедура WriteLn относится к стандартным или встроенным процедурам Турбо Паскаля. Стандартная процедура не нуждается в предварительном описании, она доступна любой программе, в которой содержится обращение к ней. Разница между оператором вывода и обращением к процедуре вывода состоит в том, что имя процедуры вывода, как и любой другой процедуры Турбо Паскаля, не является зарезервированным словом, а следовательно, пользователь может написать свою собственную процедуру с именем writeLn. Впрочем, эта возможность для большинства пользователей остается лишь языковой тонкостью и очень редко применяется на практике.
Процедура writeLn — одна из немногих процедур Турбо Паскаля, при обращении к которым допускается использование произвольного числа параметров. Параметры передаются процедуре в виде списка, располагающегося в круглых скобках сразу за именем процедуры. В нашем примере процедуре передается единственный параметр — константа Text. Как мы увидим дальше (см. главу 5), самым первым параметром при обращении к процедуре writeLn можно указать адрес приемника информации — устройство или дисковый файл, в который направляется вывод. Таким способом программист может легко переадресовать вывод данных. Если, как это сделано в нашем примере, адрес вывода не указан, вывод направляется на экран дисплея.
Анализируя программу в целом, мы обнаружим, что четыре использовавшихся в ней слова (program, const, begin и end) являются зарезервированными. Слово WriteLn, как уже отмечалось, не относится к зарезервированным, но вряд ли может возникнуть необходимость переопределить его, т. к. в этом случае программа лишится мощного и удобного средства вывода данных. Два слова — My_First_Program и Text — служат идентификаторами (именами) некоторых объектов программы. Программист может выбрать в качестве идентификаторов любые последовательности символов, которые удовлетворяют следующим ограничениям:
идентификатор может состоять из букв латинского алфавита, цифр, знака подчеркивания; никакие другие символы в идентификаторе недопустимы;
идентификатор не может начинаться с цифры;
О идентификатор не может совпадать ни с одним из зарезервированных слов;
длина идентификатора может быть произвольной, но значащими считаются первые 63 символа.
Как и всюду в программе, в идентификаторах игнорируется разница в высоте букв, поэтому, например, идентификаторы Text, text и TEXT с точки зрения компилятора идентичны.
Теперь попробуйте выполнить программу. Для этого после набора ее текста нажмите <Ctrl>+<F9>. Если вы не ошиблись при вводе текста, то, спустя несколько секунд, заметите быструю смену изображений на экране: сразу после загрузки программы Турбо Паскаль очищает экран, предоставляя его в распоряжение работающей программы пользователя. Такой экран называется окном программы. После завершения прогона (работа программы часто называется ее прогоном) на экране вновь появится окно редактора с текстом программы. Если вы не успели разглядеть изображение окна программы, нажмите <Alt>+<F5>. После нажатия на любую клавишу среда вернет экран в режим воспроизведения окна редактора.
Перед тем как двигаться дальше, полезно подробнее ознакомиться с некоторыми возможностями среды Турбо Паскаля. Нажмите клавишу <F10>, чтобы перейти к режиму выбора из главного меню, подведите курсор к опции Debug (Отладка) и нажмите клавишу <Enter> — на экране раскроется меню второго уровня, связанное с этой опцией. Новое меню как бы "всплыло" из верхней строки, поэтому такое меню часто называют всплывающим. Отыщите в новом меню опцию Output (Вывод программы), подведите к ней указатель и нажмите клавишу <Enter> еще раз. На экране вновь появится окно программы, но оно уже не будет исчезать после нажатия на любую клавишу — экран будет связан с этим окном постоянно. Теперь добьемся того, чтобы на экране демонстрировались два окна одновременно: вновь нажмите клавишу <F10>, выберите опцию Window, нажмите клавишу <Enter>, подведите курсор к опции Tile (Черепица) и нажмите клавишу <Enter> еще раз. Если все сделано правильно, экран приобретет вид, показанный на рис. 2.1.
Двойная рамка, очерчивающая окно программы, свидетельствует о том, что именно это окно активно в данный момент. Сделаем активным окно редактора: нажмем клавишу <Alt> и, не отпуская ее, — клавишу с цифрой 1 (окно редактора имеет номер 1, окно программы — номер 2, см. верхние правые углы рамок на рис. 2.1). Теперь все готово к дальнейшим экспериментам с программой.
Попробуем изменить выводимый на экран текст. Например, уберем в конце третьей строки точку с запятой и отредактируем ее таким образом:
Text = * я учусь программировать на турбо паскале' •
Рис. 2.1. Вид экрана с окнами редактора и программы
Если вы запустите программу вновь, нажав комбинацию клавиш <Ctrl>+ +<F9>, компилятор сообщит:
Error 85:expected.
(Ошибка 85: Отсутствует
а редактор установит курсор на первый символ слова begin, показывая то место, где при разборе текста обнаружена ошибка (разделитель ; может отделять от конца оператора сколько угодно пробелов; компилятор пропускает эти пробелы в поисках разделителя до тех пор, пока не обнаружит зарезервированное слово, — вот почему курсор стоит не в конце строки с объявлением константы, а перед словом begin). Исправьте программу — поставьте в конце третьей строки разделитель ;и вновьзапуститесчет.Наэтотразвсепройдетнормально,но
вывод программы изменится, и в окне программы появится текст
я учусь программировать на турбо паскале
Этот текст строго соответствует заданному в текстовой константе набору символов, поэтому в нем отсутствуют прописные буквы.
Типы данных
Структура рассмотренной программы имеет следующий вид:
Program MyFirstProgram;
{Раздел описаний}
begin
{Раздел операторов} end.
Слова Program, begin и end выделяют две части программы — раздел описаний и раздел операторов. Такая структура обязательна для любой программы, что является следствием жесткого требования языка: любой нестандартный идентификатор, используемый в исполняемых операторах, должен быть предварительно описан в разделе описаний. (Стандартные идентификаторы связаны с предварительно объявленными объектами и входят в стандартную библиотеку Турбо Паскаля. Таким, например, является идентификатор WriteLn. Стандартные идентификаторы, если они используются в программе, описывать не нужно).
Требование предварительного описания идентификаторов кажется чрезмерно строгим и делающим язык менее свободным. На самом деле в нем проявляется тенденция развития языков программирования в сторону повышения надежности создаваемых программ. Кто программировал на Фортране или Бейсике (в этих языках не требуется предварительное описание идентификаторов), знает, как порой бывает трудно обнаружить в большой программе ошибочно введенный или пропущенный символ в идентификаторе. Если, например, всюду в программе используется переменная с именем EPSILON, а в одном месте ошибочно написано EPSLON, то программа может благополучно откомпилироваться и даже давать почти правдоподобный результат для некоторых наборов данных, но в какой-то момент начнет вести себя странно. Обязательное предварительное описание идентификаторов в Турбо Паскале защищает программы от такого рода ошибок и повышает их надежность.
Описать идентификатор — это значит указать тип связанного с ним объекта программы (константы или переменной). Понятие типа — одно из фундаментальных понятий Турбо Паскаля. В главе 4 подробно рассмотрены различные типы; чтобы пояснить описываемые ниже особенности языка и при этом не слишком забегать вперед, укажем, что тип определяет, во-первых, способ внутреннего для компьютера представления объекта и, во-вторых, действия, которые разрешается над ним выполнять.
В рассматриваемых далее в этой главе программах понадобятся следующие типы данных:
INTEGER — целочисленные данные, во внутреннем представлении занимают 2 байта; диапазон возможных значений — от -32 768 до +32 767; данные представляются точно;
П REAL — вещественные данные, занимают 6 байт; диапазон возможных значений модуля — от 2.9E-39 до 1.7Е+38; точность представления данных — 11-12значащих цифр;
О CHAR — символ, занимает 1 байт;
STRING — строка символов, занимает MAX + 1 байт, где MAX- максимальное число символов в строке;
П BOOLEAN — логический тип, занимает 1 байт и имеет два значения: FALSE (ложь) и TRUE (истина).
Тип константы определяется способом записи ее значения. Например: const cl = с23.14;
сЗ= 'A';
с4 = '3.14';
с5False;
V
При анализе этого фрагмента программы компилятор отнесет первую константу К ТИПУ INTEGER, ВТОруЮ — К ТИПу REAL, третью — К CHAR, четвертую — к STRING и последнюю — к BOOLEAN. Признаком, позволяющим отнести константу к REAL или к INTEGER, является наличие или отсутствие десятичной точки в ее значении. Разумеется, константы С2 и С4 относятся к разным типам: С2 — к REAL (в константе есть десятичная точка), а С4 — к STRING (константа обрамлена апострофами). Константу сз компилятор будет считать относящейся к типу CHAR: одиночный символ в апострофах относится к CHAR, в то время как несколько символов — к STRING.
В отличие от константы, переменная именует объект программы, который может изменять свое значение в ходе счета. При описании переменных за идентификатором ставятся двоеточие и имя типа. Несколько однотипных переменных можно объединять в список, разделяя их запятыми. В начале раздела описания переменных должно стоять зарезервированное слово VAR (VARiables— переменные). Например:
var
sigma : Real;
a,b, c, d : Char;
textl :
text2: String;
flag :
Как уже говорилось, тип данных определяет длину внутреннего представления соответствующих переменных. В частности, длина внутреннего представления переменных типа STRING (строка символов) зависит от максимального числа символов, которые могут составлять строку, В приведенном выше примере переменная textl описана с указанием ее максимальной длины (15 символов), а в описании переменной text2 максимальная длина
не указана и компилятор установит для нее предельно допустимую в Турбо Паскале длину — 255 символов.
Рассмотрим еще одну несложную программу (пример 2.2). Ее назначение — ввести с клавиатуры два целых числа, найти результат деления первого числа на второе и вывести полученный результат на экран.
Program
вводит два целых
и выводит частное от деления 1-го на 2-е)
var
nl,n2 : Integer;
и — вводимые
хReal;
begin
(х —
Write ( 'nl =
о вводе
ReadLn(nl);
nl)
Write('n2 = ');
о вводе
ReadLn(n2);
{Вводим n2 }
x := nl/n2;
{Находим результат)
WriteLn('nl/n2 = *,x;, {Выводим его/ end.
Прежде всего бросается в глаза появление в программе поясняющих комментариев. Комментарий в Турбо Паскале — это произвольная последовательность любых символов, обрамленная фигурными скобками. Комментарий разрешается вставлять в любое место программы, где по смыслу может стоять пробел. В качестве ограничителей комментария допускается использование фигурных скобок { и }, а также пары символов: (* — слева от комментария и — справа от него:
{ Это — комментарий }
(* Это — тоже комментарий
Комментарии с однотипными ограничителями нельзя вкладывать друг в друга, т. е. недопустимы последовательности вида
или с* ... С* ... *)*)
Редактор Турбо Паскаля выделяет комментарии наклонным шрифтом (курсивом). так же выделяются комментарии и в тексте книги.
Однако можно вкладывать комментарии с ограничителями разных типов (не более одной глубины вложения):
{(**)} или с* ...
Последнее обстоятельство проясняет кажущуюся странной избыточность ограничителей: всюду в программе будут использоваться ограничители одного типа, то для того, чтобы временно исключить из программы какой-либо фрагмент текста, достаточно заключить его в ограничители другого типа.
Наличие комментариев избавляет от необходимости пояснять назначение отдельных строк программы. Несколько слов о вводе данных. Пары операторов
Write . )
ReadLn(..) ;
работают следующим образом. Вначале оператор write выводит строку на экран и оставляет курсор в конце только что выведенной строки текста. Заметим, что оператор
WriteLn(Text);
в примере 2.1 после вывода текста осуществлял перевод строки и устанавливал курсор в начало следующей строки экрана. Именно в этом простом действии (переводе строки) заключается единственное отличие в работе процедуры WriteLn ОТ Процедуры Write.
Затем по оператору ReadLn вызывается встроенная процедура ввода данных, и программа останавливается в ожидании ввода. В этот момент необходимо набрать на клавиатуре нужное число и нажать клавишу <Enter>. Сразу после этого программа продолжит работу: проанализирует введенное число и перейдет к вводу следующего числа или вычислению результата. Таким образом, сигналом окончания подготовки очередного числа является нажатие на клавишу <Enter>, до этого момента можно стирать любой ошибочно введенный символ клавишей <Backspace>.
Для вычисления введенных чисел используется один из основных операторов Турбо Паскаля — оператор присваивания. В его левой части указывается имя переменной, правая часть представляет собой выражение того же типа, что и переменная. Пара символов :=, связывающая левую и правую части оператора присваивания, означает "присвоить значение". Запомним, что в операторах присваивания Турбо Паскаля всегда применяются символы :=, в то время как при описании констант — одиночный символ =. С точки зрения синтаксиса языка, два символа := рассматриваются как один специальный символ и обязательно пишутся слитно.
Оператор присваивания существует практически во всех языках программирования. В некоторых языках, например в Фортране или Бейсике, символом присваивания является знак равенства, однако новичка, привыкшего к
строгости математических формул, может озадачить типичная форма записи Фортран-оператора присваивания, например, такая:
X = X + 1
Вариант записи этого же оператора на Турбо Паскале:
X := X + 1;
в этом смысле кажется более логичным. Разумеется, вряд ли кому-нибудь придет в голову видеть уравнения там, где их нет и не может быть. Конечно же, и в и в другом случае реализуется одно и то же алгоритмическое действие: к содержимому х прибавляется 1 и полученный результат вновь присваивается переменной х. Обратите внимание на оператор вывода результатов
=
В нем в качестве одного из параметров явно указывается константа типа "строка символов"= Конечно же, константы (в отличие от пере
менных) вовсе не обязательно указывать в разделе описаний, т. к. их тип легко определяется компилятором по форме записи константы. С учетом этого можно было бы записать программу из примера предельно лаконично:
begin ДОг1ЪеЬп('Я программирую на Турбоend.
Преобразования типов и действия над ними
I
Как уже говорилось, тип переменной позволяет неустанавливать
длину ее внутреннего представления, но и контролировать те действия, которые выполняются над ней в программе. Контроль за использованием переменных еще на этапе компиляции программы — важное преимущество Турбо Паскаля перед другими языками программирования, в которых допускается автоматическое преобразование типов. В Турбо Паскале почти невозможны неявные (автоматические) преобразования типов. Исключение сделано только в отношении констант и переменных типа INTEGER (целые), которые разрешается употреблять в выражениях типа REAL (вещественные). Если, например, переменные х и Y описаны следующим образом:
var
х: Integer;
у: Real;
то оператор у := х + 2;
будет синтаксически правильным. Хотя справа от знака присваивания стоит целочисленное выражение, а слева — вещественная переменная, компилятор сделает необходимые преобразования автоматически. В то же время оператор
х := 2.0;
будет неверным, т. к. автоматическое преобразование типа REAL (константа 2.0 содержит десятичную точку и, следовательно, принадлежит к типу REAL) в тип INTEGER в Турбо Паскале запрещено.
Разумеется, запрет на автоматическое преобразование типов еще не означает, что в Турбо Паскале нет средств преобразования данных. Они, конечно же, есть, но их нужно указывать явно (подробнее об этом см. в главе 4). Для преобразования данных в языке существуют встроенные функции, которые получают в качестве параметра значение одного типа, а возвращают результат в виде значения другого типа. В частности, для преобразования REAL в INTEGER имеются даже две встроенные функции такого рода: ROUND округляет REAL до ближайшего целого, a TRUNC усекает REAL путем отбрасывания дробной части.
Например, ошибочным будет оператор
х := у/х;
но правильным
х := round(y/x);
(объявления этих переменных см. выше).
Понятие функции в Турбо Паскале близко к понятию процедуры. Как и процедура, функция вызывается своим именем и может содержать произвольное КОЛИЧЕСТВО операторов Турбо Паскаля и даже внутренних процедур и функций. Существенным отличием функции от процедуры является то обстоятельство, что функция имеет собственное значение и, следовательно, может использоваться наравне с переменными в выражениях соответствующего типа.
Для преобразования данных типа CHAR (символ) в целое число предназначена функция ORD, обратное преобразование INTEGER в CHAR осуществляет функция CHR.
С помощью следующей несложной программы (пример 2.3) вы сможете узнать внутренний код произвольного символа.
ffie™6?2-3■'^ЖН!Я№в11НвН1
Program. Code_of_Char;
(Программа читает символ с клавиатуры и ВЫВОДИТ на экран этот символ и соответствующий ему внутренний код}
var
ch: Char;(Эта переменная хранит значение символа}
begin
Write('Введите любой символ: *);
ReadLn(ch);{Чита ем один символ}
WriteLn(ch, ' =его к
целому и выводим на экран} end.
Обратите внимание, при вызове
WriteLn(ch,' =
третьим параметром обращения указан вызов функции ORD(CH), что с точки зрения языка является выражением; как мы увидим дальше (см. главу 8), во многих случаях при вызове процедур и функций в качестве параметров вызова можно указывать не только переменные или константы, но и выражения с их участием.
По мере надобности мы будем знакомиться с другими функциями преобразования типов данных, а сейчас — о тех операциях, которые разрешены над различными типами.
Конечно же, в Турбо Паскале есть все четыре арифметические операции над переменными REAL и INTEGER:
+ — сложение;
вычитание;
* — умножение;
О / — деление вещественное;
div — деление целочисленное.
Наличие двух операций деления есть еще одно проявление основополагающего принципа Турбо Паскаля: программист должен явно подтверждать компилятору, что он готов к возможным последствиям преобразования типов. Если, например, в языке Фортран используется выражение 1/2, то результат этого выражения будет зависеть от того, переменной какого типа он будет присвоен: если N есть переменная целого типа, a x — вещественного, то в программе на Фортране присваивания
N = 1/2
X = 1/2
дадут значения 0 для N и 0,5 для х. В Турбо Паскале такой двусмысленности нет: выражение 1/2 всегда имеет значение 0,5 и поэтому оператор
var
N : Integer;
begin
N,: = 1/2;
просто недопустим. В то же время допустимый в Турбо Паскале оператор
var
X : Real;
begin
X := 1 div 2;
самим фактом использования операции целочисленного деления div свидетельствует о том, что программист сознательно отбрасывает дробную часть результата. (Надеюсь, что читатель извинит явную искусственность этих примеров, которая вызвана лишь стремлением проиллюстрировать обсуждаемые особенности языка).
Для данных типа INTEGER в Турбо Паскале есть еще одна операция — mod — получение остатка от целочисленного деления. Например:
5 mod 2= 1
31 mod 16= 15
18 mod 3= 0
В Турбо Паскале отсутствует операция возведения в степень, что, очевидно, будет вызывать определенные неудобства при реализации вычислительных алгоритмов. Некоторым утешением может служить наличие встроенной функции SQR, возвращающей квадрат от значения параметра, причем тип результата определяется типом параметра.
И еще об одном существенном недостатке Турбо Паскаля: в нем отсутствуют комплексный тип и соответствующие операции над ним. Вообще, в отношении реализации разнообразных вычислительных процедур Турбо Паскаль значительно уступает некоторым другим языкам программирования, тому же Фортрану, например. В частности, в нем намного беднее набор встроенных математических функций (см. главу 4).
При работе с целыми числами могут оказаться полезными две процедуры (здесь и далее в квадратных скобках указываются необязательные параметры):
DEC (X [,N] ) — уменьшает содержимое переменной х на значение выражения N (если N не задано, то на 1); тип переменной х и выражения N — INTEGER (точнее, любой целый, см. главу 4);
О INC(X [,N] ) — увеличивает значение х на N (если N не задано, то на 1).
Над символами и строками символов определена единственная операция — сцепление двух строк. Операция обозначается символом +. Например, программа
var
st: String;
begin
st := 1 Турбо' +' ' +'Паскаль';
WriteLn(st);
end.
напечатает строку
Турбо Паскаль
.
Все остальные действия над строками и символами реализуются с помощью встроенных процедур и функций (см. главу 4).
И, наконец, об операциях отношения и логических операциях.
Над данными типа REAL, INTEGER, CHAR, STRING определены следующие операции отношения (сравнения):
= — равно;
о — не равно;
О < — меньше;
> — больше;
<= — меньше или равно;
П >= — больше или равно.
В операциях сравнения должны участвовать однотипные операнды. Исключение сделано опять-таки в отношении REAL и INTEGER, которые могут сравниваться друг с другом. Результат применения операции отношения к любым операндам имеет тип BOOLEAN.
Сравнение двух строк осуществляется следующим образом. Символы строк сравниваются попарно друг с другом так, что первый символ первой строки сравнивается с первым символом второй строки, второй символ первой строки — со вторым символом второй и т. д. Символы сравниваются путем сравнения их кодов во внутреннем представлении (см. главу 4). Если одна строка короче другой, недостающие символы заменяются нулем. Отношение первой несовпадающей друг с другом пары символов и принимается за отношение двух строк.
При сравнении данных типа BOOLEAN учитывается внутреннее соглашение Турбо Паскаля, в соответствии с которым FALSE есть нулевой байт, a TRUE — байт с единицей в младшем разряде. Заметим, что функция ORD преобразует к целому не только символы, но и логические величины, поэтому ord(false) = 0 ord (true) = 1
В Турбо Паскале определены следующие логические операции:
О not — логическое НЕ;□ and — логическое И;
П or — логическое ИЛИ;О xor — исключающее ИЛИ.
Логические операции применимы к операндам целого и логического типов. Если операнды — целые числа, то результат логической операции есть тоже целое число (подробнее об этом сказано в главе 4). Логические операции над логическими данными дают результат логического типа.
При вычислении выражений любого типа приоритет вычислений определяется расставленными скобками, а при их отсутствии — по табл. 2.1 (в порядке убывания приоритета).
Таблица2.1. Приоритет операций
Приоритет
Операция
Приоритет
Операция
1
not, @
3
+.-, or, xor
2
/, div, mod, and, shl, shr
4
>, >=, <, <=, in
Примечание
Операции @ (получение адреса), shl (сдвиг влево), shr (сдвиг вправо) и in (принадлежность к множеству) описаны в главе 4.
Следует учесть, что в отличие от многих других языков программирования в Турбо Паскале логические операции имеют более высокий приоритет, чем операции отношения. В связи с этим, в сложных логических выражениях обычно необходимо расставлять скобки. Если, например, ь и с имеют тип INTEGER, то выражение
а = Ь and с < d
вызовет сообщение о синтаксической ошибке, т. к. сначала выполнится операция b and с. Правильным будет выражение:
(а = b) and (с < d)
Операторы языка
С одним из наиболее часто используемых операторов языка Турбо Паскаль — оператором присваивания мы уже познакомились. Ниже рассматриваются остальные операторы языка.
Составной оператор и пустой оператор
Составной оператор — это последовательность произвольных операторов программы, заключенная в операторные скобки — зарезервированные слова beginend. Составные операторы — важный инструмент Турбо Паска-
ля, дающий возможность писать программы по современной технологии структурного программирования (без операторов перехода GOTO).
Язык Турбо Паскаль не накладывает никаких ограничений на характер операторов, входящих в составной оператор. Среди них могут быть и другие составные операторы — Турбо Паскаль допускает произвольную глубину их вложенности:
begin
begin
begin
end;
end;
end;
Фактически весь раздел операторов, обрамленный словами beginend,
представляет собой один составной оператор. Поскольку зарезервированное слово end является закрывающей операторной скобкой, оно одновременно указывает и конец предьщущего оператора, поэтому ставить перед ним символнеобязательно,и далеевовсехпримерахмынебудемэтогоделать.
Наличие точки с запятой перед end в предыдущих примерах означало, что между последним оператором и операторной скобкой end располагается пустой оператор. Пустой оператор не содержит никаких действий, просто в программу добавляется лишняя точка с запятой. В основном пустой оператор используется для передачи управления в конец составного оператора.
Условный оператор
Условный оператор позволяет проверить некоторое условие и в зависимости от результатов проверки выполнить то или иное действие. Таким образом, условный оператор — это средство ветвления вычислительного процесса.
Структура условного оператора имеет следующий вид:
<условие> THENELSE <оператор2>
где
IF, THEN, ELSE — зарезервированные слова (если, то,
<условие> — произвольное выражение логического типа;
О<оператор2> — любые операторы языка Турбо Паскаль.
Условный оператор работает по следующему алгоритму. Вначале вычисляется условное выражение <условие>. Если результат есть TRUE (истина), то выполняется <оператор1>, а <оператор2> пропускается; если результат есть FALSE (ложь), наоборот, <оператор1> пропускается, а выполняется <оператор2>.Например:
var
х, у, max: Integer;
begin
if x > max then уmax
else
У := x;
При выполнении этого фрагмента переменная Y получит значение переменной х, если только это значение не превышает МАХ, в противном случае Y станет равно МАХ.
Часть ELSE <оператор2> условного оператора может быть опущена. Тогда при значении TRUE условного выражения выполняется <оператор1>, в противном случае этот оператор пропускается:
var
х, у, max: Integer;
begin
' if x > max then maxx;
у := x;
В этом примере переменная Y всегда будет иметь значение переменной х, а в МАХ запоминается максимальное значение х.
Поскольку любой из операторов <оператор1> и <оператор2> может быть любого типа, в том числе и условным, а в то же время не каждый из "вложенных" условных операторов может иметь часть ELSE <оператор2>, то возникает неоднозначность трактовки условий. Эта неоднозначность в Турбо Паскале решается следующим образом: любая встретившаяся часть ELSE соответствует ближайшей к ней "сверху части THEN условного оператора. Например:
var
a,b,c,d : Integer;
begin
a := 1; b := 2; с := 3; d 4;
if a > b than if с < d then if с < 0 then с := О else а := b; {a равно 1} • if a > b then if c then if с then с := О else else else a b;{<2 равно 2}
Рассмотрим профамму (пример 2.4), которая вводит произвольное десятичное целое число в диапазоне 0-15, преобразует его к шестнадцатеричному и выводит на экран полученный результат.
Program Hex;
{Программа вводит с клавиатуры целое число в диапазоне от 0 до 15, преобразует его к шестнадца теричной системе счисления и выводит результат на экран}
var
n : Integer; .(Вводимое число)
ch : Char;(Результат)
begin
Write(’п =
ReadLn(п);/Вводим число)
{Проверяем число на принадлежность к диапазону 0...15}
if (n >= 0) and (n <= 15) then
begin(Да, принадлежит диапазону)
if n < 10 then
ch := chr(ord('0') + n)
else
ch := chr(ord( 'А') + n - 10) ;
WriteLn('n = end else{He принадлежит диапазону}
WriteLn('Ошибка') end.
В шестнадцатеричной системе счисления используется 16 цифр в каждом разряде: цифры О—9 обозначают первые 10 возможных значений разряда, буквы А, ..., F — остальные шесть.
В программе учитывается непрерывность и упорядоченность множеств цифр О—9, букв А, ..., F и их кодов (см. главу 4).
Операторы повторений
В языке Турбо Паскаль имеются три различных оператора, с помощью которых можно запрограммировать повторяющиеся фрагменты программ.
Счетный оператор цикла FOR имеет такую структуру:
FOR <пар_цик> := <нач_знач> ТО <кон_знач> DO <оператор>
Здесь:
FOR, то, DO — зарезервированные слова (для, до, выполнить);
<пар_цик> — параметр цикла — переменная типа INTEGER (точнее, любого порядкового типа, см. главу 4);
<нач_знач> — начальное значение — выражение того же типа;
<кон_знач> — конечное значение — выражение того же типа;
<оператор> — произвольный оператор Турбо Паскаля.
При выполнении оператора FOR вначале вычисляется выражение <нач_знач> и осуществляется присваивание <пар_цик> := <нач_знач>. После этого циклически повторяется последовательность следующих действий:
Проверка условия <пар_цик> <= <кон_знач>; если условие не выполнено, оператор FOR завершает свою работу.
Выполнение оператора <оператор>.
Наращивание переменной <пар_цик> на единицу.
В качестве иллюстрации применения оператора FOR рассмотрим программу, осуществляющую ввод с клавиатуры произвольного целого числа N и вычисление суммы всех целых чисел от 1 до N (пример 2.5).
Program Summ_of_Integer;
{ Программа вводит целое положительное число N и подсчитывает сумму всех целых чисел от 1 до N} var
n, s : Integer;
begin
=
');
Readln(n);
s := 0;
for i1
to n do
{Вводим N)
(Начальное значение суммы) (Цикл подсчета суммы)
s := s + i;
writein('Сумма = ',s)
{Выводим результат}
end.
Отметим два обстоятельства. Во-первых, условие, управляющее работой оператора FOR, проверяется перед выполнением оператора <оператор>: если условие не выполняется в самом начале работы оператора FOR, исполняемый оператор не будет выполнен ни разу. Другое обстоятельство — шаг наращивания параметра цикла строго постоянен и равен + 1. Существует другая форма оператора:
FOR <пар цик>: = <нач_знач> DOWNTO <кон_знач> DO <оператор>
Замена зарезервированного слова то на DOWNTO означает, что шаг наращивания параметра цикла равен -1, а управляющее условие приобретает вид <пар_цик> =
Пример 2.5 можно модифицировать так, чтобы сделать его пригодным для подсчета любых сумм — положительных и отрицательных:
s 0;
if n >= 0 then
for i 1 to n do
s := s + i
else
for i := -1 down to n do
s := s + i;
Два других оператора повторений лишь проверяют условие выполнения или повторения цикла, но не связаны с изменением счетчика цикла.
Оператор цикла WHILE с предпроверкой условия:
WHILE <условие> DO <оператор>
Здесь:
О WHILE, DO — зарезервированные слова (пока [выполняетсяусловие], делать);
<условие> — выражение логического типа;
<оператор> — произвольный оператор Турбо Паскаля.
Если выражение <условие> имеет значение TRUE, то выполняется <оператор>, после чего вычисление выражения <условие> и его проверка повторяются. Если <условие> имеет значение FALSE, оператор WHILE прекращает свою работу.
Рассмотрим пример 2.6, иллюстрирующий использование оператора WHILE. Найдем так называемое "машинное эпсилон" — такое минимальное, не равное нулю вещественное число, которое после прибавления его к 1,0 еще дает результат, отличный от 1,0.
: Пример 2.6
аМЯяК^.>^Х1~.лИН1111|я!й!ЯЯ1111я11111111111мВЯ|Я11мЖЯк^ЭД.&яп«Як!тмлЭМ^
Program EpsilonDetect;
(Программа вычисляет и выводит на экран значение "машинного эпсилон"}
var
epsilon: Real;
begin
epsilon := 1;
while epsilon/2 + 1 > 1 do
epsilon := epsilon/2
WriteLn('Машинное эпсилон = 1,epsilon) end.
У читателя, привыкшего к непрерывной вещественной арифметике, может вызвать недоумение утверждение о том, что в дискретной машинной арифметике всегда существуют такие числа 0 < X < eps, что 1,0 + X = 1,0. Дело в том, что внутреннее представление типа REAL может дать "лишь" приблизительно 1014 возможных комбинаций значащих разрядов в отведенных для него 6 байтах. Конечно же, это очень большое число, но оно несопоставимо с бесконечным множеством вещественных чисел. Аппроксимация бесконечного непрерывного множества вещественных чисел конечным (пусть даже и
очень большим) множеством их внутреннего машинного представления и приводит к появлению "машинного эпсилон".
Оператор цикла REPEAT. UNTIL с постпроверкой условия:
REPEAT <тело_цикла> UNTIL <условие>
Здесь:
О REPEAT, UNTIL — зарезервированные слова (повторять до тех пор, пока не будет выполнено условие);
<тело_цикла> — произвольная последовательность операторов Турбо Паскаля;
<условие> — выражение логического типа.
Операторы <тело_цикла> выполняются хотя бы один раз, после чего вычисляется выражение <условие>: если его значение есть FALSE, операторы <тело_цикла> повторяются, в противном случае оператор REPEAT. UNTIL завершает свою работу.
Для иллюстрации применения оператора REPEAT.UNTIL модифицируем
программу из примера 2.3. Модификация (пример 2.7) состоит в том, что программа будет все время повторять цикл ввода символа и печати его кода до тех пор, пока очередным символом не будет символ CR (вводится клавишей <Enter>).
^ ' , ' - *. /7- • Г. ' .1 •'• • Л -- ' ■-. ''
Program Codes_of_Chars;
{Программа вводит символ и выводит на экран его код. Для завершения работы программы нужно дважды нажать <Enter>}
var
ch : Char;{Вводимый символ}
const
CR = 13; {Код символа CR)
begin
repeat
ReadLn(ch);
WriteLn(ch, ' = ', ord (ch))
until ord(ch) = CR
end.
Обратите внимание, что пара REPEAT. . . UNTIL подобна операторным скобкам begin.. . end, поэтому перед UNTIL ставить точку с запятой не обязательно.
Для гибкого управления циклическими операторами FOR, WHILE и REPEAT в состав Турбо Паскаля включены две процедуры:
BREAK — реализует немедленный выход из цикла; действие процедуры заключается в передаче управления оператору, стоящему сразу за концом циклического оператора;
CONTINUE — обеспечивает досрочное завершение очередного прохода цикла; эквивалент передачи управления в самый конец циклического оператора.
Введение в язык этих процедур практически исключает необходимость использования операторов безусловного перехода GOTO (см. разд. 2.4.5).
2.4.4. Оператор выбора
Оператор выбора позволяет выбрать одно из нескольких возможных продолжений программы. Параметром, по которому осуществляется выбор, служит ключ выбора — выражение любого порядкового типа (любого из рассмотренных, кроме типов REAL и STRING, см. главу 4).
Структура оператора выбора такова:
CASE <ключ_выбора> OF <список_выбора> [ELSE <операторы>] END
Здесь:
□ CASE, OF, ELSE, end — зарезервированные слова (случай, из, иначе, конец);
О <ключ_выбора> — ключ выбора;
П <список_выбора> — одна или более конструкций вида:
<константа_выбора> : <оператор>;
<константа_выбора> — константа того же типа, что и выражение <ключ_выбора>",\
□ <операторы> — произвольные операторы Турбо Паскаля.
Оператор выбора работает следующим образом. Вначале вычисляется значение выражения <ключ_выбора>, а затем в последовательности операторов <список_выбора> отыскивается такой, которому предшествует константа, равная вычисленному значению. Найденный оператор выполняется, после чего оператор выбора завершает свою работу. Если в списке выбора не будет найдена константа, соответствующая вычисленному значению ключа выбора, управление передается операторам, стоящим за словом ELSE. Часть ELSE <оператор> можно опускать. Тогда при отсутствии в списке выбора нужной константы ничего не произойдет, и оператор выбора просто завершит свою работу.
Составим программу (пример 2.8), имитирующую работу микрокалькулятора. Программа вводит две строки, первая содержит два произвольных чис-
39
ла, разделенных пробелом, вторая — символ арифметического действия, например:
2 2
*
ИЛИ
18.35 0.12
I
Над введенными числами осуществляется соответствующее действие, и результат выводится на экран. Признаком окончания работы программы служит ввод любого символа, отличного от +, -, *, /.
sagswsagsw^a«yj^4i№ipviMtoej#*
sagsw
sagsw
Пример 2.8
Program Calc;
f Программа вводит два числа в первой строке и один из знаков + f -, / — во второй и выводит на экран результат соответствующего арифметического действия/
var
operation : Char;(Знак операции}
х, у, z : Real;{Операнды и результат}
stop: Boolean;, {Признак ошибочной операции и останова}
begin
stopfalse;
repeat
WriteLn;{Пустая строка-разделитель)
Write ('х, у= ');
ReadLn (х, у) ;
Write('операция: ');
ReadLn(operation);
case operation of
'к ’ : z =x+y;
z x — y;
i*i. z := x * y;
’ / 1 *• z :=x / y;
else
stop := true;
end;
if not stop then
WriteLn('результат= ', z)
until stop end. ,
Любому из операторов списка выбора может предшествовать не одна, а несколько констант выбора, разделенных запятыми. Например, следующая программа при вводе одного из символов: у или Y выведет на экран слово "Да", а при вводе n или N — слово "Нет": var
ch : Char;
begin
ReadLn(ch);
case ch of
'n', 'N': WriteLn ('Нет') ;
'у', 'У : WriteLn ('Да')
end end.
Метки и операторы перехода
Можно теоретически показать, что рассмотренных операторов вполне достаточно для написания программ любой сложности. В этом отношении наличие в языке операторов перехода кажется излишним. Более того, современная технология структурного программирования основана на принципе "программировать без GOTO". Считается, что злоупотребление операторами перехода затрудняет понимание профаммы, делает ее запутанной и сложной в отладке.
Тем не менее, в некоторых случаях использование операторов перехода может упростить профамму.
Оператор перехода имеет вид:
GOTO <метка>
Здесь:
П GOTO — зарезервированное слово (перейти [на метку])', П <метка> — метка.
Метка в Турбо Паскале — это произвольный идентификатор, позволяющий именовать некоторый оператор программы и таким образом ссылаться на него. В целях совместимости со стандартным языком Паскаль в языке Турбо Паскале в качестве меток допускаются также целые числа без знака.
Метка располагается непосредственно перед помечаемым оператором и отделяется от него двоеточием. Оператор можно помечать несколькими метками, которые в этом случае отделяются друг от друга двоеточием. Перед тем как появиться в программе, метка должна быть описана. Описание меток состоит из зарезервированного слова LABEL (метка), за которым следует список меток:
label
loop, 1Ы, 1Ь2;
begin
goto 1Ы;
loop:
1Ы:1Ь2:
goto 1Ь2;
Действие оператора GOTO состоит в передаче управления соответствующему помеченному оператору.
При использовании меток необходимо руководствоваться следующими правилами:
О метка, на которую ссылается оператор GOTO, должна быть описана в разделе описаний и она обязательно должна встретиться где-нибудь в теле программы;
О метки, описанные в процедуре (функции), локализуются в ней, поэтому передача управления извне процедуры (функции) на метку внутри нее невозможна.
Массивы
Рассмотренные выше простые типы данных позволяют работать с одиночными объектами — числами, символами, строками и т. п. В Турбо Паскале могут использоваться также объекты, содержащие множество однотипных элементов. Это массивы — формальное объединение нескольких однотипных объектов (чисел, символов, строк и т. п.), рассматриваемое как единое целое. К необходимости применения массивов мы приходим всякий раз, когда требуется связать и использовать целый ряд родственных величин. Например, результаты многократных замеров температуры воздуха в течение
года удобно рассматривать как совокупность вещественных чисел, объединенных в один сложный объект — массив измерений.
При описании массива необходимо указать общее число входящих в массив элементов и их тип. Например:
var
а : array [1..10] of Real;
b : array■ [0..50] of Char;
с : array [-3..4] of Boolean;
Как видим, при описании массива используются зарезервированные слова ARRAY и OF (массив, из). За словом ARRAY в квадратных скобках указывается тип-диапазон, с помощью которого компилятор определяет общее число элементов массива. Тип-диапазон (подробнее см. в главе 4) задается левой и правой границами изменения индекса массива, так что массив А состоит из 10 элементов, массив в — из 51, а массив с — из 8 элементов. За словом OF указывается тип элементов, образующих массив.
Доступ к каждому элементу массива в программе осуществляется с помощью индекса — целого числа (точнее, выражения порядкового типа, см. главу 4), служащего своеобразным именем элемента в массиве (если левая граница типа-диапазона равна 1, индекс элемента совпадает с его порядковым номером). При упоминании в программе любого элемента массива сразу за именем массива должен следовать индекс элемента в квадратных скобках, например:
var
array [1..10] of Integer;
array [0..40] of Char;
array [-2..2] of Boolean;
Integer;
begin
b[17] := 'F';
c[-2] := a[l] > [2];
for k := 1 to 10 do
a[k] := 0;
end.
В правильно составленной программе индекс не должен выходить за пределы, определенные типом-диапазоном. Например, допустимыми являются элементы a [1], b[38], с[0], а ошибочными будут — а[0] или с[38] (определение массивов см. выше). Турбо Паскаль может контролировать
m : array [1..N] of Integer;iInteger;max, min : Integer;s Real;m : array [1..N] of Integer;iInteger;max, min : Integer;s Real;(Массив чисел)(Массив чисел)(Индекс массива)(Максимальное и минимальное число) {Сумма чисел)(Индекс массива)(Максимальное и минимальное число) {Сумма чисел)
m : array [1..N] of Integer;
iInteger;
max, min : Integer;
s Real;
m : array [1..N] of Integer;
iInteger;
max, min : Integer;
s Real;
(Массив чисел)
(Массив чисел)
(Индекс массива)
(Максимальное и минимальное число) {Сумма чисел)
(Индекс массива)
(Максимальное и минимальное число) {Сумма чисел)
использование индексов в программе на этапе компиляции и на этапе счета программы.
Для иллюстрации приемов работы с массивами составим программу (пример 2.9), которая создает массив случайных целых чисел, подсчитывает их среднее арифметическое, а также определяет и выводит на экран минимальное и максимальное из этих чисел.
begin
{Наполняем массив случайными числами:)
for i 1 to N do
' m[i] :=
(Задаем начальные значения переменных:}
s 0;
max : = т[1] ;
min := ш[1];
(Цикл вычисления суммы всех случайных чисел и поиска минимального и максимального:)
for i 1 to N do
begin
s: s+m[i];
if m[i] < min then
minm[i]
else if m[i] > max then
maxm[i]
end;
/Вычисляем среднее значение и печатаем результат:}
WriteLn('Мин = Макс = ', max, ' Среднее = 1,s/N) end.
Для создания массива используется встроенная функция RANDOM (MAX) , которая возвращает случайное целое число, равномерно распределенное в диапазоне от о до max-1 (max — параметр обращения).
Процедуры и функции
Процедуры и функции представляют собой важный инструмент Турбо Паскаля, позволяющий писать хорошо структурированные программы. В таких программах обычно легко прослеживается основной алгоритм, их нетрудно понять любому читателю, они проще в отладке и менее чувствительны к ошибкам программирования. Все эти свойства являются следствием важной особенности процедур (функций), каждая из которых представляет собой во многом самостоятельный фрагмент программы, связанный с основной программой лишь с помощью нескольких параметров. Самостоятельность процедур (функций) позволяет локализовать в них все детали программной реализации того или иного алгоритмического действия и поэтому изменение этих деталей, например, в процессе отладки обычно не приводит к изменениям основной программы.
Многие примеры в этой книге невелики по размерам (не более 30— 40 строк), поэтому написать такие программы можно и без процедур. Иное дело — создание крупных программ в сотни, тысячи и десятки тысяч строк. Писать такие программы как нечто единое целое, без расчленения на относительно самостоятельные фрагменты, т. е. без структурирования, просто невозможно. Практически во всех языках программирования имеются средства структурирования. Языки, в которых предусмотрены такие механизмы, называются процедурно-ориентированными. К их числу принадлежит и Турбо Паскаль.
Процедурой в Турбо Паскале называется особым образом оформленный фрагмент программы, имеющий собственное имя. Упоминание этого имени в тексте программы приводит к активизации процедуры и называется ее вызовом. Сразу после активизации процедуры начинают выполняться входящие в нее операторы, после выполнения последнего из них управление возвращается обратно в основную программу, и выполняются операторы, стоящие непосредственно за оператором вызова процедуры (рис. 2.2).
Для обмена информацией между основной программой и процедурой используется один или несколько параметров вызова. Как мы увидим дальше (см. главу 8), процедуры могут иметь и другой механизм обмена данными с вызывающей программой, так что параметры вызова могут и не использо-
ваться. Если они есть, то они перечисляются в круглых скобках за именем процедуры и вместе с ним образуют оператор вызова процедуры.
Рис. 2.2. Взаимодействие вызывающей программы и процедуры
Функция отличается от процедуры тем, что результат ее работы возвращается в виде значения этой функции, и, следовательно, вызов функции может использоваться наряду с другими операндами в выражениях.
С примерами процедур и функций мы уже сталкивались — это стандартные процедуры чтения и записи READ, READLN, WRITE, WRITELN, функции ORD, CHR, математические функции и др. Стандартными они называются потому, что созданы одновременно с системой Турбо Паскаль и являются ее неотъемлемой частью. В Турбо Паскале имеется много стандартных процедур и функций. Наличие богатой библиотеки таких программных заготовок существенно облегчает разработку прикладных программ. Однако в большинстве случаев некоторые специфичные для данной прикладной программы действия не находят прямых аналогов в библиотеках Турбо Паскаля, и тогда программисту приходится разрабатывать свои, нестандартные процедуры и функции.
Нестандартные процедуры и функции необходимо описать, чтобы компилятор мог установить связь между оператором вызова и теми действиями, которые предусмотрены в процедуре (функции). Описание процедуры (функции) помещается в разделе описаний и внешне выглядит как программа, но вместо заголовка программы фигурирует заголовок процедуры (функции).
Не вдаваясь в дальнейшие подробности, попробуем составить собственную процедуру, чтобы пояснить сказанное. Пусть в этой процедуре преобразуется некоторая символьная строка таким образом, чтобы все строчные буквы заменялись соответствующими прописными. В Турбо Паскале есть стандартная функция UPCASE (см. главу 4), которая выполняет аналогичные действия над одиночным символом. Наша процедура (назовем ее UPSTRING) будет преобразовывать сразу все символы строки, причем сделаем ее пригодной не только для латинских букв, но и для букв русского алфавита.
Разработку программы проведем в два этапа. Сначала сконструируем основную (вызывающую) часть программы. Ее действия очень просты: она должна ввести входную строку (назовем ее sinp) с клавиатуры, преобразовать ее с помощью процедуры UpString в выходную строку sout и напечатать результат. Эти действия нетрудно запрограммировать, например:
Program CharsConvert;
Procedure UpString(si: String; var s2: String);,
begin {UpString}
s2 := si (Пока еще нет преобразования!)
end;(UpString)
var
Sinp, Sout : String; (Исходная и преобразованная строки)
begin(Начало основной (вызывающей) i ipo граммы}
Write(’Введите строку: *);
ReadLn(S inp);{Вводим исходную строку)
UpString(Sinp,Sout);(Преобразуемее к прописным буквам)
WriteLn(' Результат: ', Sout)(Печатаем результат)-
end.{Конец вызывающей программы)
В этой программе используется замещение процедуры UPSTRING так называемой "заглушкой", т. е. процедурой, в которой на самом деле не осуществляется нужных нам действий, а выходная строка просто копирует входную. (Однако эта программа синтаксически абсолютно правильна и при желании ее можно запустить на счет.) Заглушка понадобилась нам по двум причинам. Во-первых, приведенная программа очень проста, в ней отсутствует детальная реализация Процедуры и это позволяет наглядно проиллюстрировать механизм ее описания. Во-вторых, на ее примере мы знакомимся с универсальным методом конструирования сложных программ, получившим название нисходящее программирование. В соответствии с этим методом создание программы начинается "сверху", т. е. с разработки самого главного, генерального алгоритма. На верхнем уровне обычно еще не ясны детали реализации той или иной части программы, поэтому эти части следует заменить временными заглушками. Желательно, чтобы временный вариант программы был синтаксически правильным, тогда можно его откомпилировать и убедиться в отсутствии в нем синтаксических ошибок. Такой прогон даст определенную уверенность перед разработкой и реализацией алгоритмов нижнего уровня, т. е. перед заменой заглушек реально работающими процедурами. Если реализуемый в заглушке алгоритм достаточно сложен,
его вновь структурируют, выделяя главный алгоритм и применяя новые заглушки, и т. д. Процесс продолжается "вниз" до тех пор, пока не будет создан полностью работоспособный вариант программы.
В дальнейшем мы еще не раз будем применять метод нисходящего программирования, а сейчас вернемся к описанию нашей процедуры. Как видим, это описание начинается зарезервированным словом Procedure, за которым следуют имя процедуры и список формальных параметров. Список параметров заключается в круглые скобки и содержит перечень параметров с указанием их типа. Заметим, что перед параметром s2, с помощью которого в вызывающую программу возвращается результат преобразования, стоит зарезервированное слово VAR. Именно таким способом компилятору указываются те параметры, в которых процедура возвращает вызвавшей ее программе результат своей работы (подробнее см. в главе 6)..3арезервиро- ванное слово Procedure, имя процедуры и список ее параметров образуют заголовок процедуры. За заголовком следует тело процедуры, содержащее новый раздел описаний (этот раздел пока еще пуст) и раздел исполняемых операторов (оператор s2 := si).
Приступим к разработке алгоритма процедуры. Для этого обратимся к таблице кодировки символов, используемой в ПК (см. приложение 2). В соответствии с этой таблицей коды символов латинских строчных букв от а до z образуют непрерывный массив монотонно нарастающих чисел от 97 до 122, а коды соответствующих им прописных букв — непрерывный массив чисел от 65 до 90. Преобразование строчных латинских букв в прописные, следовательно, состоит в уменьшении кода буквы на 32. Сложнее обстоит дело с символами русского алфавита (кириллицей). В зависимости от принятого способа кодировки русские строчные буквы могут образовывать один сплошной массив (кодировки ГОСТ и М1С), два массива (альтернативная кодировка), несплошной массив (кодировка типа ЕСТЕЛ), неупорядоченный массив (кодировка КОИ-8). Если исключить два последних варианта кодировки, использовавшихся на устаревших ПК, то задача преобразования буквы состоит в том, чтобы к внутреннему коду русской буквы А (для букв от а до п) или к коду буквы Р (для букв от р до я) прибавить разницу в кодах текущего символа и кодах букв а и п. Например, если преобразуется буква б, то к коду А нужно прибавить разницу между кодами а и б, т. е. единицу, в результате получим код буквы Б. Точно так же при преобразовании буквы ф к коду буквы П будет прибавлено число 5 (как разница кодов ф и п), поэтому в результате получится код буквы Ф. С учетом этого можно составить следующий алгоритм реализации процедуры: для каждого символа исходной строки si определить, к какому подмассиву д..-Z, а...р или П...Я принадлежит код этого символа, и затем изменить его, добавив к кодам букв А (латинская), А (русская) или П соответствующую разницу. Если символ не принадлежит ни к какому из подмассивов, нужно поместить его код в выходную строку без изменений.
3 Зак 94,
Вот возможный вариант процедуры:
ProcedureString; var s2:
var
: Integer;цикла
: Char;,переменная
begin
выходная строка пуста)
посимвольного анализа исходной
for i := 1 to Length(sl) do
begin
входной строки очередной
с
символ на принадлежность к одному
из трех
if (с >=and (с <=then
с :=
else if (c >=and (c .<=then
с :=-ord
else if (c >=and (с <=then
с :=
s2 := s2+c
end
end;
В процедуре вначале с помощью оператора
s2 : =
подготавливается "пустая" выходная строка, т. е. строка нулевой длины. Затем используется цикл от 1 до длины входной строки(эта длина получа
ется с помощью стандартной функции Length), в ходе которого проверяется принадлежность очередного символа указанным подмассивам и осуществляется необходимая коррекция его внутреннего кода. Для доступа к отдельным символам строки используется замечательное свойство типа данных STRING, позволяющее рассматривать строку как набор (массив) символов. Первый символ этого набора имеет индекс 1, второй — 2 и т. д. Индекс указывается сразу за именем строки в квадратных скобках. Таким образом,— это
символ строкиПреобразованный символ добавляется в конец выход
ной строки.
Добавив комментарии и поместив тело процедуры вместо заглушки в первоначальный вариант программы, получим окончательно ее рабочий вариант (пример 2.10).
Program CharsConvert;
вводит произвольную текстовую строку, преобразует все в нее буквы в прописные и печатает результат
: String; var s2 :
{Эта процедура преобразует буквы входной строки в прописные буквы латинского или русского алфавита и помещает результат преобразования в выходную строкупредположение о том, что
последовательности латинских букв от "а" до и русских букв от "а" до и от до "я", а также последовательное
прописных буквнепрерывные
var
Integer;цикла
Char;переменная
begin {UpString}
выходная строка
(Цикл посимвольного анализа исходной
for i := 1 todo
begin
(Берем из входной строки очередной
с :=
символ на принадлежность к одному
из трех
if (с >=and (с <=then
с :=
else if (c >=and (c <=then
с :=
else if (c'p') and (с <=then
с
s2 := s2+c
end
end; {UpString)
var
Sinp, Sout : String; (Исходнаяи преобразованная строки)
begin(Начало основной (вызывающей) программы)
Write('Введите строку: ') ;
ReadLn(Sinp);{Вводимисходную строку)
UpString(Sinp,Sout);(Преобразуем ее к прописным буквам)
WriteLn(' Результат: ',Sout) (Печатаемрезультат)
end.{Конец вызывающей программы)
Рассмотрим иной способ реализации ТОЙ же профаммы: оформим алгоритм преобразования в виде функции. Кроме того, .с помощью стандартной функции UPCASE преобразуем каждый очередной символ (это преобразование осуществляется только для букв латинского алфавита) и тем самым исключим проверку принадлежности символа к строчным латинским буквам:
Function UpString(si: String): String;
var
i : Integer;1
с : Char;
: String;(Результат преобразования)
begin (UpString)
• * * #
for i := 1 to Length(si) do
begin
(Получаем и преобразуем очередной символ)
с : = UpCase (si [i]) ;
if (с >= 'a') and (с <= 'п') then
с := chr(ord('A')+ord (c)-ord('a'))
else
if (с >= 'р') and (с <= ’ Я ’ ) then
с := chr(ord('P')+ord(c)-ord('p'));
s2 := s2+c
end;
UpString := s2(Присваиваем значение функции UpString)
end; (UpString)
var
Sinp: String;
beginосновной
строку:
ReadLn(Sinp);
Результат: ' ,UpString
end.основной
Программа получилась несколько проще за счет того, что функцию можно использовать в качестве параметра обращения к другой процедуре (в нашем случае кОбратите внимание, что в теле любой функции нужно
осуществить присваивание ей вычисленного значения (см. оператор В левой части оператора присваивания в этом случае указывается имя функции.
Примеры программ
Мы познакомились с основными возможностями языка Турбо Паскаль. Как видите, ядро языка очень компактно и отличается простотой, — именно в этом состоит главная заслуга его автора Н. Вирта: язык, придуманный им, прост и естественен, он легко осваивается, на нем не трудно писать самые разнообразные программы. Конечно, рассмотрены далеко не все свойства Турбо Паскаля, ведь его главнаячерта — это богатство типов
данных. Однако уже рассмотренного вполне достаточно для написания многих полезных программ.
Приводимые далее программы относительно сложны, поэтому они реализуются поэтапно, по методу нисходящего программирования. Мне кажется, что тем читателям, кто не имеет большого опыта в программировании или кто захочет подробнее ознакомиться с нисходящим программированием, изучение этой главы принесет определенную пользу. Если вам будет скучно разбираться в "кухне" программирования, но вас заинтересуют описываемые здесь профаммы и вы захотите их повторить, то в приложении 5 вы найдете полный текст соответствующей профаммы; однако в каждой из них используются некоторые дополнительные возможности языка Турбо Паскаль, которые не рассматривались ранее и которые обсуждаются в пропущенных вами фрагментах книги.
При оформлении профамм я стремился использовать хороший стиль написания профамм, т. е. такую их форму, которая дает наиболее полное представление о структуре профаммы в целом и ее отдельных частей. Не существует какого-либо стандарта, определяющего хороший стиль профаммы. Обычно это интуитивное понятие включает способ расположения операторов и описаний по строкам (не рекомендуется размещать более одного оператора на каждой строке), а также выделение отступами тела составных и условных операторов. Последнее особенно важно в профаммах Турбо Пас-
каля: сплошь и рядом в них встречаются операторные скобки begin ... end, причем часто вложенные друг в друга; использование отступа служит дополнительным средством проверки правильности их расстановки — не случайно в редакторе среды предусмотрена соответствующая опция. Принятый мною стиль оформления программ не претендует на эталон, просто мне кажется, что таким образом оформленные программы читаются лучше. Если вы всерьез намерены программировать на Турбо Паскале, имеет смысл составить собственное представление о хорошем стиле и далее неукоснительно придерживаться его — очень скоро некоторые дополнительные издержки на подготовку программ с лихвой окупятся их "читабельностью", а это поможет вам вспомнить все детали реализации программы, которая была написана несколько месяцев тому назад.
Вычисление дня недели
Случалось ли вам мучительно вспоминать, какой именно день недели приходился на то или иное число год или два назад, или вычислять, на какой день недели в этом году приходится ваш день рождения? Если да, то вас, думаю, заинтересует простая программа, позволяющая по заданной дате мгновенно вычислить соответствующий день недели. В ее основе лежит такая формула:
день недели = остаток от деления X на 7,
где
X = abs(trunc(2.6*m-0.2)+d+y/4+y+c/4-2*c);
m — номер месяца (см. ниже);
d — число (день месяца);
с — номер столетия (см. ниже);
у — номер года в столетии.
При использовании этой формулы следует учесть два обстоятельства. Во- первых, формула верна для григорианского календаря нового стиля (от 1582 до 4903 года). Во-вторых, год и месяц следует предварительно преобразовать так, как если бы начало года приходилось на 1 марта. Иными словами, март в этой формуле имеет порядковый номер 1, апрель — 2, ..., январь — 11 и февраль — 12, причем январь и февраль следует отнести к предыдущему году. Например, для 1 февраля 1991 года номер месяца должен быть равен 12, а год 1990, в то время как для 31 декабря 1991 года номер месяца — 10, а год — 1991. Результат вычисления дается в виде целого числа в диапазоне от О до 6, причем 0 соответствует воскресенью.
Приступим к разработке программы. Прежде всего, предположим, что программа уже создана и вы осуществляете ее прогон. Какая форма взаи-
модействия с программой кажется вам наиболее подходящей? Вряд ли вас удовлетворит однократное ее исполнение (ввод некоторой даты и вывод на экран соответствующего дня недели). Скорее всего вы захотите повторить работу программы для нескольких дат, например, поинтересоваться, в какой день недели вы родились, затем, на какой день недели приходится в этом году ваш день рождения, дни рождения близких, друзей; может быть, определить, в какой день родились известные вам исторические деятели, й т. д. Таким образом, в программе следует предусмотреть многократное выполнение действий <ввод даты> — вычисление дня недели>, причем количество циклов вычисления заранее неизвестно. Сразу же возникает новый вопрос: как сообщить программе, что вы завершаете работу с ней? Для этого можно условиться, что ввод некоторой заранее обусловленной или недопустимой даты должен интерпретироваться программой, как указание на прекращение работы. С учетом сказанного, напишем такой начальный вариант программы: ,
IsCorrectDate: Boolean; {Признак правильной даты)
d,m, у: Integer; {Вводимая дата — день, месяц и год)
begin
repeat
{Ввести в переменные d, m и у очередную дату и проверить ее. Если дата правильная, установить IsCorrectDate=True, иначе IsCorrectDate=False) if IsCorrectDate then
{Вычислить и выдать на окран день недели} ;
until not IsCorrectDate
end.
Если вы попытаетесь запустить эту программу на счет, то ее поведение будет зависеть от начального значения переменной IsCorrectDate. Это значение случайно, т. к. компилятор Турбо Паскаля не проводит начальной инициализации переменных. Скорее всего, тот байт оперативной памяти, в котором она разместится, окажется нулевым, что в Турбо Паскале расценивается как логическое значение FALSE, поэтому с большой вероятностью ничего не произойдет, и программа сразу же завершит свою работу (условие not IsCorrectDate будет выполнено). Если начальное значение IsCorrectDate окажется ненулевым, ТО ЦИКЛ REPEAT. . .until будет ВЫПОЛНЯТЬСЯ ДО тех ПОР, пока ВЫ не выключите компьютер или не нажмете клавиши <Ctrl>+<Break>.
Будем считать, что необходимые действия осуществляются в двух процедурах с именами InputDate (ввод даты) и writeDay (вычисление и печать дня недели). В процедуру inputDate не нужно ничего передавать из программы,
т. к. в ней самой осуществляются ввод и контроль даты. Поэтому заголовок процедуры может иметь такой вид:
Procedure InputDate (var d,m, у: Integer; var correctly: Boolean);
Процедура WriteDay, напротив, только получает из программы нужные ей данные и ничего не возвращает в программу, поэтому в ее заголовке параметры описываются без слова var:
Procedure WriteDay (d,m,y :
С учетом этого программу можно уточнить следующим образом:
var
IsCorrectDate: Boolean; (Признак правильной даты}
d,m, у: Integer;(Вводимаядата — день, месяц и год}
Г 1
t• /
Procedure InputDate(var d,m, у : Integer;
var correctly : Boolean) ;
{Вводит в переменные d, m и у очередную дату и проверяет ее. Если дата правильная, устанавливает correctly=true, иначе correctly=false)
begin (InputDate)
correctly := false
end;(InputDate)
{^;
Procedure WriteDay (d,m,y : Integer);
(Вычисляет день недели и выводит его на экран)
begin (WriteDay)
end; (WriteDay)
f 1
I I
begin
repeat
InputDate (d,nt,y, IsCorrectDate) ;
if IsCorrectDate then
WriteDay (d,m, y)
until not IsCorrectDate end.
Теперь можно разработать процедуру INPUTDATE. Ввод даты не вызывает трудностей — стандартные процедуры WRITE и READLN отлично приспособлены для этой цели. Для проверки правильности даты нужно проверить принадлежность месяца диапазону 1-12 и года — диапазону 1582-4903. Кроме того, число не должно выходить из диапазона 1-31. Если вы не очень настаиваете
на более точной проверке числа в зависимости от месяца и года (для февраля), то программная реализация процедуры будет следующей:
Procedure InputDate (var d,m, у : Integer;
var correctly : Boolean);
{Вводит в переменные d, m и у очередную дату и проверяет ее. Если дата правильная, устанавливает correctly=true, иначе
correctly-false }
•
begin {InputDate}
Write('Введите дату в формате ДД ММ ГГГГ: ');
ReadLn (d,m, y) ;
correctly := (d>=l) and (d<=31) and (m>=l)
and (m<=12) and (y>=1582) and (y<=4903)
end; {InputDate)
При выполнении этой процедуры ввод, например, трех нулей приведет к присвоению переменной CORRECTLY значения FALSE, что вызовет завершение работы программы.
Теперь разберемся с процедурой WRITEDAY. Получив в параметрах обращения день, месяц и год, она должна выполнить следующие действия:
Преобразовать месяц и год так, как описано выше (год должен начинаться с 1 марта).
Вычислить день недели.
Выдать на экран результат.
Первое и второе действия очень просты и легко программируются. Что касается выдачи на экран, то можно потребовать от программы, <итобы эта выдача была не просто числом от 0 до 6, а одной из строк "воскресенье", "понедельник", ..., "суббота". Для этого необходимы дополнительные усилия: нужно сначала создать массив строковых констант с именем, например, days_of_week (дни_недели), а затем выбрать из этого массива и выдать на экран нужную строку. Создать массив текстовых констант можно с помощью объявления типизированной константы (см. главу 7);
const
Days_of_week: array■ [0. .6] of String [11] =
('воскресенье','понедельник','вторник','среда',1 четверг','пятница','суббота');
В этом объявлении идентификатор Days_of_week описывается в разделе констант, однако справа от него указан тип данных (массив строк), как если бы описывалась переменная, а уже только после типа стоит знак равенства
и заключенный в круглые скобки список элементов массива. В результате получим следующую процедуру:
Procedure WriteDay(d,m,у :
const
Days_of_week: array [0..6] of String [11] =
('воскресенье', 'понедельник','вторник','среда1,'четверг','пятница','суббота');
var
с, w : Integer;
begin
if m < 3 then
begin{Месяц январь или февраль}
m m + 10;
у :=у-1
end
else
m m — 2;(Остальные месяцы}
с := у div 100;(Вычисляем столетие)
у := у mod 100;{Находим год в столетии)
w := abs(trunc(2.6*m-0.2)+d+y div 4+y+c div 4-2*c) mod 7;
WriteLn(Days_of_week[w])
end;
Окончательный вариант программы приведен в приложении 5.
Биоритмы
Давно известно, что творческая и физическая активность человека не остается постоянной, циклически меняется, причем периодичность ее изменения приблизительно согласуется с периодом вращения Луны вокруг Земли. Существует теория, согласно которой физическая, эмоциональная и интеллектуальная активность человека подчиняется соответствующим биоритмам. Каждый биоритм представляет собой синусоиду со строго постоянным периодом, причем для каждого биоритма существует свой период. В отдельные дни все три биоритма человека могут достигнуть своего максимума, и тогда человек испытывает подъем творческих И физических сил, в такие дни у него все спорится, он легко решает проблемы, которые в другое время ему решить гораздо сложнее. Точно так же существуют и "черные" дни, соответствующие спаду всех трех биоритмов.
Используя уже опробованную методику нисходящего программирования, создадим программу, в которой запрашивается дата рождения человека и дата, для которой требуется оценить его состояние. Программа должна рассчитать и выдать на экран ближайшие к этой дате дни пика и спада биоритмов.
Алгоритм программы можно укрупненно записать следующим образом:
Ввести дату рождения и текущую дату, проконтролировать их правильность и непротиворечивость.
Вычислить количество дней между двумя датами, чтобы определить фазу синусоид для текущей даты.
Вычислить количество дней от текущей даты до даты ближайшего пика биоритмов и даты ближайшего спада.
Определить и напечатать обе даты.
Будем считать, что каждое из перечисленных действий реализуется в отдельной процедуре, тогда начальный вариант программы будет таким:
Procedure InputDates (var dO,mO,уО,d,m, у: Integer);
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения) }
begin (InputDates}
end;(InputDates)
f I
t/
Procedure Get_count_of_days (dO,mO,yO, d,m,y: Integer;
var days : Integer) ;
{Определяет полное количество дней, прошедших от одной даты
до другой)
begin(Get_count_of_days}
end;{Ge t_coun t_of_days /
I >
Procedure FindMaxMin (var drain, dmax: Integer; days: Integer);
(Ищет критические дни)
begin (FindMaxMin)
end; (FindMaxMin)
{;
Procedure WriteDates (drain,dmax, days : Integer) ;
(Определяет критические даты по количеству дней, прошедших от момента рождения, и выдает эти даты на экран)
begin (WriteDates}
end;(WriteDates)
var
dO,d,(Днирождения и текущий}
гаО,га,(Месяцырождения и текущий}
уО,у,(Годырождения и текущий}
drain,(Наименее благоприятный день)
dmax,(Наиболее благоприятный день)
days: Integer;(Количество дней от рождения)
begin (Главная программа}
InputDates(dO,mO,уО,d,m,y);
Get_numbers_of_days(dO,mO,уО,d,га,у, days) ;
FindMaxMin(drain,dmax,days) ;
jWriteDates (drain, dmax, days)
end.
Начинаем детализацию программы. Прежде всего подумаем, как по двум датам вычислить разделяющее их количество дней? Если вспомнить, что следует учитывать неодинаковое количество дней по месяцам года, а также 29 февраля для високосных лет, то ответ на этот вопрос окажется не таким уж простым. Предлагаемый алгоритм подсчета количества дней заключается в вычислении количества дней от даты рождения до конца месяца, а затем и года рождения, количества дней от начала текущего года до текущего месяца и текущей даты, а также — в подсчете количества полных лет, разделяющих обе даты. Количество лет затем легко пересчитывается в количество дней с учетом длины года (365 дней для обычных и 366 дней для високосных лет). Это очень прямолинейный алгоритм, но, откровенно говоря, мне не пришло в голову ничего другого. Возможно, существует более изящный способ подсчета и вы его знаете, тогда программная реализация будет другой.
Упростить алгоритм можно за счет создания и использования массива из 12 целых чисел, содержащего количества дней по месяцам невисокосного года, т. е. 31, 28, 31, 30 и т. д. Этот массив (назовем его SIZE_OF_MONTH — длина_месяца) можно использовать и для обратной задачи, т. е. для определения даты критических дней, а также для проверки правильности вводимых дат. Таким образом, массив SIZE_OF_MONTH будет использоваться сразу в трех процедурах. Сделаем его глобальным, для чего его описание поместим перед описанием процедур:
const
Size_of_Month: array [1..12] of Byte =
(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
(;
Procedure InputDates (var dO,mO, yO, d,m, y: Integer);
Поскольку описание массива размещается до описания процедур, он становится доступным внутри каждой из процедур и является для них глобальным. В отличие от этого, все константы и переменные, объявляемые внутри некоторой процедуры, будут локальными и могут использоваться только в этой процедуре.
С учетом сказанного напишем следующий начальный вариант программной реализации процедуры INPUTDATES:
Procedure InputDates (var dO,mO, yO,d,m,.y: Integer);
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения) ) var
correctly: Boolean; {Признак правильного ввода}
begin {InputDates)
repeat
{Вводим и контролируем дату рождения dO,mO,yO. )
{Вводим и контролируем текущую дату d,m,y.)
{Проверяем непротиворечивость дат: )
correctly у > уО;
if not correctly and (уyO) then
begin
correctly := m > mO;
if not correctly and (m = mO) then correctly := d>=dO end
until correctly
end; {Inpu tDa tes }
В этой процедуре дважды выполняется одно и то же алгоритмическое действие (ввод и контроль даты). Это действие можно вынести в отдельную внутреннюю процедуру с именем INPDATE, тогда получим следующий окончательный вариант:
Procedure InputDates (vardO,mO, yO,d,m, у :
{Вводит дату рождения и текущую дату. Контролирует правильность дат и их непротиворечивость (текущая дата должна быть позже даты рождения) )
var
correctly: Boolean; (Признак правильного ввода)
(;
Procedure InpDate(text: String; var d,m,у: Integer);
{Выводит приглашение TEXT, вводит дату в формате ДЦ ММ ГГГГ и проверяет ее правильность} const
YMIN = 1800;{ Минимальныйправильный год}
УМАХ = 2000; {Максимальный правильный год} begin {InpDate} repeat
Write(text);
ReadLn(d,mу.) ;
correctly:=(y>=YMIN)and(Y<= УМАХ)and(m>= 1)
and (m <= 12)and (d > 0)
if correctly then
if (m = 2) and (d = 29) and (y mod 4 = 0) then
{Ничего не делать: это 29 февраля високосного года!}
else
correctly := d <= Size_of_Month[m]; if not correctly then
WriteLn('Ошибка в дате 1')
"until correctly end;(InpDate}
/;.
begin {InputDates}
repeat
InpDate(' Введите дату рождения в формате ДЦ ММ ГГГГ:', dO,mO,yO);
InpDate(' Введите текущую дату: ', d,m, y) ;
{Проверяем непротиворечивость дат:}
correctly := у > yO;
if not correctly and (y = yO) then begin
correctly := m > mO;
if not correctly and (m = mO) then correctlyd >= dO
end
until correctly
end;{InputDates}
В самом общем виде алгоритм подсчета количества дней, разделяющих две даты, описан выше. При его реализации следует учесть три возможных варианта:
месячный младенец (год и месяц обеих дат одинаков) — количество дней находится простым вычитанием двух чисел;
годовалый младенец (год обеих дат совпадает) — количество дней = (остаток дней в месяце рождения) + (количество дней в текущем месяце) + (количество дней в месяцах, разделяющих обе даты);
О общий вариант (отличаются года) — количество дней = (количество дней от даты рождения до конца года) + (количество дней в разделяющих даты годах) + (количество дней от начала текущего года до текущей даты).
С учетом этого составим начальный вариант программной реализации процедуры GET_NUMBERS_OF_DAYS:
Procedure Get_nunibers_of_days (dOrmO,yO,d,m,у: Integer; var days: Integer);
{Определение полного количества дней, прошедших от одной даты до другой }
(,
Procedure Variants;
{Подсчет количества дней в месяцах, разделяющих обе даты)
begin (Variants/
end;(Variant?)
{;
Procedure Variants;
{Подсчет количества дней в месяцах и годах, разделяющих обе даты)
begin (Variant?) end;(Variant?)
{;
begin(Ge t_numbers_of_days)
if (y = yO) and (m = mO) then (Даты отличаются только днями:) .
days := d - dO else (Даты отличаются не только днями:) begin
days := d + Size_of_Month[mO] — dO;
(Учитываем количество дней в текущем месяце и количество дней до конца месяца рождения)
if (уО mod 4 = 0) and (mO = 2) then
inc(days); (Учитываем високосный год}
if у = уО then
Variant2 (Разница в месяцах ОДНОГО и того же года)
else
Variants (Даты отличаются годами) end
end;{Get_numbers_of_days)
В этом фрагменте используется способ связи вспомогательных процедур VARIANT2 и VARIANTS с основной процедурой через глобальные переменные, которыми являются параметры обращения к основной процедуре. Вспомогательные процедуры удобнее всего реализовать на основе циклов WHILE:
Procedure Variant2;
(Подсчет количества дней в месяцах, разделяющих обе даты)
varI
mm : Integer;
begin {Variants}
mm : = mO ;
.
whileram< m do
begin
days := days + Size_of_Month[mm] ;
if (mm = 2) and (yO mod 4 = 0) then
inc(days);
inc (mm)
end
end; (Variant2}
{;
Procedure Variants ;
{Подсчет количества дней в месяцах и годах, pa оделяющих обе даты)
var
mm, yy : Integer;
begin {Variant3}
mmmO + 1;
while ram12 do {Учитываем остаток года рождения:}
begin
days := days+Size_of_Month[mm];
if (mm = 2) and (yO mod 4 = 0) then
inc(days);
inc (mm)
end;
yy := yO + 1;
while yy < у do {Прибавляв разницу лет:)
begin
daysdays + 365;
if yy mod 4 = 0 then
inc(days);
inc (yy)
end;
mm := 1;
while mm < m do {Прибавляемначало текущего года;}
begin
days := days + Size_of_Month[mm];
if (y mod 4 = 0) and (mm = 2) then
inc(days);
inc (mm)
end
end;{Variant3)
В процедуре FINDMAXMIN осуществляется поиск критических дней, т. е. ближайших к текущей дате дней, для которых все три биоритма достигают своего максимума и минимума. Предполагается, что биоритмы изменяются по законам синуса от количества прожитых дней с периодами TF, ТЕ и TI соответственно для физической, эмоциональной и интеллектуальной активности человека. В программе приняты следующие периоды (в днях):
TF = 23.6884
ТЕ= 28.4261
TI = 33.1638
Самый простой алгоритм поиска заключается в том, чтобы вычислить значения сумм всех трех синусоид для текущего дня и для каждого из последующих дней на некотором заранее обусловленном интервале, например, в пределах месяца. Сопоставив результаты расчетов для каждого дня, не трудно определить критические дни:
Procedure FindMaxMin(var drain,dmax: Integer;
days: Integer);
(Поиск критических дней)
const
TF = 2*3.1416/23.6884; (Период физической активности)
ТЕ = 2*3.1416/28.4261; (Периодэмоциональной активности}
TI = 2*3.1416/33.1638; (Периодинтеллектуальной активности)
INTERVAL = 30;(Интервал прогноза)
var
min,(Накапливает минимум биоритмов)
max,(Накапливаетмаксимум биоритмов)
х : Real;(Текущее значение биоритмов)
i : Integer;
begin (FindMaxMin} max := sin(days*TF)+sin(days*TE)+sin(days*TI);
min := max; (Начальное значение минимума и максимума равно значению биоритмов для текущего дня) drain : = days ;
dmax : = days;
for i := 0 to INTERVAL do
begin
x := sin((days+i)*TF) + sin((days+i)*TE) + sin((days+i)*TI);
if x > max then
begin
max := x;
dmaxdays + i
end
else if x < min then begin
min := x;
,dmin := days +i
end
end;
end;(FindMaxMin}
При разработке алгоритма процедуры WRITEDATES, с помощью которой на экран выводится результат работы программы, учтем, что основные сложности будут связаны с определением новой даты по начальной дате и количеству прошедших дней. Этот расчет будет повторяться дважды — для даты пика и даты спада биоритмов, поэтому его следует вынести в отдельную процедуру WRITEDATES. Кроме того, вряд ли вы откажетесь от возможности
вывода на экран дополнительной информации о том, сколько полных дней, часов, минут и секунд разделяют дату рождения человека и текущую дату. Однако реализация этого вывода не столь проста, как это может показаться на первый взгляд. Дело в том, что диапазон возможных значений данных типа INTEGER составляет от —32 768 до +32 767. Средняя продолжительность жизни человека — около 70 лет, т. е. 25 550 дней. Это значение еще можно представить в переменной типа INTEGER, однако часы, минуты и тем более секунды средней продолжительности жизни далеко превышают этот диапазон. Чтобы получить вывод достоверных данных, необходимо расширить диапазон значений целых чисел. Для этого в Турбо Паскале предусмотрен специальный тип данных LONGINT ("длинный" целый), имеющий диапазон значений от —2 147 483 648 до +2 147 483 647 (см. главу 4). Поэтому в процедуре WRITEDATES следует предусмотреть вспомогательную переменную этого типа, присвоить ей значение переменной DAYS и уже затем использовать "длинную" переменную для вычисления (и вывода) часов, минут, секунд. В результате начальный вариант процедуры WRITEDATES может быть таким:
Procedure WriteDates(drain,dmax,days :
(Определение и вывод дат критических дней.
Вывод дополнительном информации о количестве
прожитых дней, часов, минут и секунд/
(;
Procedure WriteDate (text : String; dd :
{Определение даты для дня DD от момента рождения. В глобальных переменных d, m и у имеется текущая дата, в переменной DAYS - количество дней, прошедших от момента рождения до текущей даты. Выводится сообщение TEXT и найденная дата в формате ДЦ-МЕС-ГГГГ)
begin {WriteDate/
end;{Wri teDa te)
{)
var
LongDays: Longlnt; ("Длинная"целая переменная для часов, минут и секунд)
begin (WriteDates)
LongDays := days;
WriteLn('Прошло: *,LongDays,1 дней, 1,longDays*24,
1 часов, ', LongDays*24*60,' минут, ', LongDays*24*60*60, ’ секунд’);
WriteDate('Наименее благоприятный день: ',drain);
WriteDate('Наиболее благоприятный день: ',dmax)
end; (WriteDates)
Реализация процедуры WRITEDATE не вызывает особых сложностей:
Procedure WriteDate(text: String; dd: Integer);
const
Names_of_Monthes : array [1..12] of String=
('янв','фев','мар','апр','мая','июн','июл','авг','сен','окт','ноя','дек');
var
dO,mO,yO,ddd :
begin (WriteDate}
dO := d;
mO := m;
yOy;
ddd := days;
while dddodd do
begin
inc(dO);{Наращиваем число)
if (yO mod 4 <> 0) and (dO > Size_of_Month[mO]) or
(yO mod 4=0) and (dO=30) then
begin{Корректируеммесяц)
dO := 1;
inc(mO);
if mO = 13 then{Корректируем год}
begin
mO := 1;
inc (yO)
end
end;
inc(ddd)
end;
WriteLn(text, dO,'-',Names_of_Monthes [mO],' —',y0)
end; {WriteDate}
Собрав воедино отдельные части, получим полный текст программы (см. приложение 5), предназначенной для определения биоритмов.
Игра "ним"
"Ним" — одна из самых старых и увлекательных математических игр. Для игры в "ним" необходим партнер (в ним играют вдвоем), стол и набор фи-
шек. В качестве фишек обычно используются камешки или монетки. В наиболее известном варианте "нима 12 фишек раскладываются в три ряда так, как показано на рис. 2.3.
Рис. 2.3. Фишки, расположенные для игры в "ним" по схеме 3-4-5
Правила "нима" просты. Игроки по очереди забирают одну или несколько фишек из любого ряда. Не разрешается за один ход брать фишки из нескольких рядов. Выигрывает тот, кто возьмет последнюю фишку (фишки).
Если вы сыграете несколько партий, то скоро заметите, что существует некоторая оптимальная последовательность ходов, которая гарантирует победу, если только вы начинаете игру и первым ходом берете две фишки из первого ряда. Любой другой ход даст шанс вашему сопернику, который в этом случае наверняка победит, если, в свою очередь, воспользуется оптимальной стратегией.
Полный анализ игры с обобщением на любое количество рядов с любым количеством фишек в каждом ряду впервые опубликовал в 1901 г. профессор математики из Гарвардского университета Чарльз Л. Бутон, который и назвал игру "ним" от устаревшей формы английских глаголов "стянуть", "украсть". Открытая им оптимальная стратегия основана на двоичной системе счисления и довольно проста. Каждую комбинацию фишек Бутон назвал либо опасной, либо безопасной: если позиция, создавшаяся после очередного хода игрока, гарантирует ему победу, она называется безопасной, если такой гарантии нет — опасной. Бутон строго доказал, что любую опасную позицию всегда можно превратить в безопасную нужным ходом. Наоборот, если перед очередным ходом игрока уже сложилась безопасная позиция, то любой его ход превращает позицию в опасную. Таким образом, оптимальная стратегия состоит в том, чтобы каждым ходом опасную позицию превращать в безопасную и заставлять противника "портить" ее. Использование оптимальной стратегии гарантирует победу игроку только тогда, когда он открывает партию и начальная позиция фишек опасна или он делает второй ход, а начальная позиция безопасна.
Чтобы определить, опасна позиция или безопасна, нужно количество фишек в каждом ряду записать в двоичной системе счисления. Если сумма чисел в каждом столбце (разряде) равна нулю или четна, позиция безопасна. Если же сумма нечетна хотя бы в одном разряде, то позиция опасна.
Например, для начальной позиции по схеме 3-4-5 получим результаты, представленные в табл. 2.2.
Таблица 2.2. Двоичная и десятичная запись количества фишек
Десятичная запись количества фишек
Двоичная запись количества фишек
3
011
4
100
5
101
Сумма по разрядам
212
При двоичной записи сумма цифр в среднем столбце равна 1 — нечетному числу, что свидетельствует об опасности этой позиции. Поэтому первый игрок может сделать ее безопасной для себя, если он возьмет две фишки из первого ряда. В результате в первом ряду остается только одна фишка (двоичное число также 1), и сумма чисел в среднем столбце изменится на ноль.
В привычной нам десятичной системе счисления емкость каждого разряда равна 10, а для записи значений разряда используются цифры от 0 до 9. В двоичной системе счисления емкость каждого разряда равна 2, а из всех цифр употребляются только 0 и 1. В этой системе число записывается в виде суммы степеней двойки и при переходе от одного разряда к соседнему левому вес разряда увеличивается в 2 раза. Если нужно записать число 2 в двоичной системе, следует действовать точно так же, как при записи числа 10 в десятичной системе: записать ноль в первом (младшем) разряде и единицу — слева от него, т. е. 10 в двоичной системе означает 2 в десятичной системе. Точно так же 100 в двоичной системе означает 4 в десятичной, 1000 — 8 и т. д.
Для перевода любого целого положительного числа из десятичной системы в двоичную можно выполнить последовательное деление числа на 2. Например, для перевода десятичного числа 11 в двоичную систему служит цепочка последовательных операций деления, приведенная в табл. 2.3.
Таблица2.3. Пример перевода числа из десятичной системы в двоичную
Делимое
Результат
Остаток
11
5
1
5
2
1
2
1
0
Если, начиная с последнего результата, остатки от деления записать в обратном порядке, получим 1011 — это и есть двоичное представление десятичного числа 11. В этом легко убедиться, записав двоичное число 1011 как сумму степеней числа 2:
1 х 2^ + 1 х 2^ + 1 х 21 + 1 = 11.
Попробуем разработать программу, которая будет выполнять роль партнера в игре, причем это будет весьма опасный противник, т. к. он будет "знать" оптимальную стратегию и умело ею пользоваться.
Представим себе на минутку, что вы уже создали программу и начинаете работу с ней. Как организовать удобное взаимодействие с программой? Конечно, возможно простейшее решение: вы предварительно раскладываете на столе монетки, по запросу программы вводите в нее ваш ход, затем читаете на экране ход программы, делаете нужные изменения в раскладке монет и т. д. Вряд ли вас удовлетворит такая программа. Гораздо эффектнее имитировать на экране игровое поле с фишками и своеобразное табло игры, в котором сообщается об очередных ходах противников. Однако использованные ранее средства вывода данных (процедуры WRITE и writeln) недостаточны для этих целей, ведь с их помощью нельзя указать конкретное место на экране, куда нужно поместить выводимую информацию. Вывод этими процедурами всегда начинается с той позиции на экране, которую в данный момент занимает курсор. Следовательно, для вывода текста в нужное место экрана требуется перед обращением к этим процедурам изменить положение курсора. Для этих целей служит процедура GOTOXY, которая хотя и является стандартной, но располагается в отдельной библиотеке (модуле) с именем CRT. Подробнее о модулях мы поговорим в главе 9, а сейчас просто учтем, что процедуры и функции из дополнительных библиотек становятся доступны в программе, если в самом ее начале объявить об их использовании. Так, указание о работе с библиотекой CRT делается таким образом:
Uses CRT;
После такого указания программе становятся доступны дополнительные процедуры и функции, с помощью которых можно организовать гибкое управление текстовым экраном, в том числе процедура GOTOXY, перемещающая курсор в произвольное место на экране.
Теперь попробуем составить алгоритм главной программы. В простейшем виде он таков:
Uses CRT; {Подключение библиотеки дополнительных процедур и функций для управления экраном} var
exit: Boolean; {Признак окончания работы}
begin
(Подготовить экран к работе}
repeat
(Ввести, проконтролировать и отобразить ход игрока)
(Найти и отобразить ход программы}
until exit
- end.
В этом алгоритме выделяются три главных действия и организуется цикл, который будет выполняться до тех пор, пока где-то в программе переменной exit (выход) не будет присвоено значение TRUE.
Вначале экран подготавливается к работе: формируется игровое поле с фишками и выводится информация о правилах игры. Как уже говорилось, "ним" позволяет играть с произвольным количеством фишек. Разумно ввести в программу возможность, которая бы позволила пользователю самому указывать количество рядов и количество фишек в рядах, т. е. настраивать программу на нужную раскладку фишек. Можно несколько модифицировать главную программу, чтобы предусмотреть эту возможность:
Uses CRT; {Подключение библиотеки дополнительных
процедур и функций для управления экраном)
var
exit : Boolean; (Признак окончания работы)
change : Boolean; (Признак изменения условий игры)
(;
Procedure Prepare;
(Готовит экран к игре)
begin { Prepare}
end; {Prepare} 1
,'
Procedure GetPlayerMove;
(Получает, контролирует и отображает ход игрока}
begin {GetPlayerMove}
end;(GetPlayerMove}
Procedure SetOwnerMove;
(Находит и отображает очередной ход программы)
begin { SetOwnerMove}
end; {SetOwnerMove}
(;
begin {Главная программа)
{ Подготовить начальную расстановку фишек)
repeat{Цикл изменения условий игры}
Prepare;{Подготовить экран}
repeatf (Игровой цикл}
GetPlayerMove;(Получить ход пользователя}
if not (exit or change) then
SetOwnerMove(Определить собственный ход)
until exit or change until exit end.
В этом варианте главная программа содержит два вложенных друг в друга цикла Repeat...Until: внутренний цикл управляет игрой, внешний отвечает за изменение условий игры. Оба цикла управляются двумя логическими переменными, которые являются глобальными для трех основных процедур PREPARE, GETPLAYERMOVE, SETOWNERMOVE И, следовательно, могут ИЗМенЯТЬСЯ внутри этих процедур.
Теперь настал момент подумать о том, каким способом в программе будет храниться и использоваться информация о текущем состоянии игры. Судя по всему, нам понадобятся хотя бы две переменные: в одной, назовем ее NROW, будет содержаться количество рядов фишек, в другой (NCOL) — количество фишек в каждом ряду. Переменная NROW содержит одно целое положительное число, поэтому ее тип должен быть INTEGER. В переменной NCOL должно быть не менее NROW целых чисел, т. е. ее тип — это массив целых чисел. Поскольку в профамме предусмотрена возможность изменения условий игры самим ифоком, переменная NROW может меняться от партии к партии. В соответствии с этим должна была бы меняться и длина массива NCOL. Однако в Турбо Паскале недопустимы массивы, длина которых меняется динамически, т. е. в процессе работы программы. Эта длина должна определяться статически (на этапе компиляции) и не может меняться в работающей профамме. Значит, понадобится массив достаточно большой длины, чтобы его хватило на все случаи. На экране одновременно можно отобразить максимум 25 сфок по 80 символов в каждой строке. Однако использовать все строчки экрана как возможные ряды фишек вряд ли целесообразно: во-первых, сама ифа при большом количестве рядов становится неинтересной, т. к. ифок не сможет проанализировать в уме все варианты ходов; во-вторых, на экране не останется места для вывода другой информации. Будем считать, что максимальное количество рядов фишек не должно превышать 14. Укажем это константой MAXROW — теперь, если вы захотите назначить другое максимальное количество рядов, понадобится изменить значение этой константы и перекомпилировать профамму. Именно таким способом профаммам придается дополнительная гибкость: вы сосредоточиваете в нескольких константах параметры, которые выбраны вами произ-
вольно и которые вы или кто-то другой, возможно, захочет изменить. Все размерности массивов или другие особенности программной реализации следует определять через эти константы, тогда«процедура переделки программы предельно упростится.
С учетом сказанного назначим следующие глобальные константы и переменные:
const
MAXROW = 14;{Максимальное количество рядов}
MAXCOL = 20;{Максимальное количество фишек в ряду}
type
ColType = array [1..MAXROW] of Integer;
var
exit:Boolean;{Признак окончания работы}
change: Boolean;{Признак изменения условий игры}
nrow:Integer;(Количество рядов}
ncol:ColType;(Максимальное количество фишекпо рядам}
col:ColType;{Текущее количество фишек по рядам}
Константа MAXCOL не участвует в формировании массивов, она будет использоваться для контроля горизонтальных размеров игрового поля. Поэтому она, а также пять переменных сделаны глобальными. Если считать, что начальная раскладка фишек соответствует схеме 3-4-5, то можно написать такой окончательный вариант главной программы:
Uses CRT; {Подключение библиотеки дополнительных
процедур и функций для управления экраном}
const
MAXROW = 14;(Ма ксимальноеколичество рядов)
MAXCOL = 20;{Максимальное количество фишек в ряду}
type
ColType = array [1..MAXROW] of Integer;
var
exit : Boolean; {Признак окончания работы)
change: Boolean;(Признак изменения условий игры)
nrowInteger;{Количество рядов)
ncol: ColType;{Ма ксимальное количество фишек по рядам)
col: ColType;{Текущее количество фишек по рядам)
{;
Procedure Prepare;
(Готовит экран к итре)
begin {Prepare} end;{Prepare}
(r,
Procedure GetPlayerMove; {Получает, контролирует и отображает ход игрока)
begin {GetPlayerMove} end;{Get Pl ayerMove}
(,
Procedure SetOwnerMove; {Находит и отображает очередной ход программы)
begin {SetOwnerMove} end; (SetOwnerMove) {}
begin {Главная программа}
nrow
:= 3;
{Готовим игру. . . }
ncol [1]
:= 3;
{ на поле из трех }
ncol[2]
:= 4;
{ рядов фишек }
ncol[3]
:= 5;
{ по схеме 3-4-5.)
repeat
{Цикл изменения условий игры)
Prepare;
{Подготовить экран)
repeat
{Игровой цикл)
GetPlayerMove;
{Получить ход пользователя)
if not (exit or change) then
SetOwnerMove{Определить собственный ход)
until exit or change until exit end.
Приступим к конструированию процедуры PREPARE. В ходе ее работы формируется значение переменной COL, соответствующее начальной раскладке фишек, и выводится информация о правилах игры. Чтобы было понятнее дальнейшее описание программной реализации, на рис. 2.4 показан вид экрана в начальном состоянии игры.
Процедура начинает свою работу с очистки экрана от имеющейся на нем информации. Это достигается обращением к стандартной процедуре без параметров CLRSCR. Затем выводятся три строчки с названием игры и кратким описанием ее правил. Кроме того, слева и справа на экране формируются заголовки для двух колонок цифр, в которых затем будут отображаться номер ряда (слева) и текущее количество фишек в ряду (справа). Эта инфор-
мация поможет игроку сообщить программе свой ход. Для размещения информации на нужных участках экрана служит процедураY) , с по
мощью которой курсор перемещается нужным образом. Параметры х и этой процедуры задают новые координатыНачало координат соот
ветствует точке (1, 1) и размещается в левом верхнем углу экрана, поэтому горизонтальная координата увеличивается слева направо, а вертикальная — сверху вниз.
Номер ряда
1
2
3
И Г Р ft Н И Вычисло фишек изряда.
Выигрываеткто возьмет последнюю
■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
Кол-во фишек
3
1
5
Введите Ваш ход в формате РЯД или введите 0для выхода
Ваш ход:
(например, из-1
взять из 2
8 для настройки
ряда 3 игры
фишки)
Рис. 2.4. Вид экрана в начале игры "ним"
Procedure
данных и экрана к
const
= Г Р А
=можете взять любое число фишек из любого
Header2 =тот, кто возьмет последнюю
=
Header4 =
var
i : Integer;
begin Pr ера
ClrScr;
строки
2,1);
Write(HeaderO);
GotoXY((80-Length(Headerl)) div 2,2);
Write(Headerl);
GotoXY((80-Length(Header2)) div 2,3) ;
Writein(Header2);.
Write(Header3);
GotoXY(80-Length(Header4) , 4) ;
Write(Header4);
{Готовим начальную раскладку:)
for i 1 to nrow do
col[i] := ncol[i]
end; {Prepare}
Для вывода верхних строк строго посередине экрана горизонтальная координата курсора для процедуры GotoXY задается как половина от разницы между полной длиной экрана (80 позиций) и длиной выводимой строки (определяется с помощью функции LENGTH).
В процедуре GetPlayerMove осуществляются ввод, контроль и отображение на экране очередного хода игрока. Предварительно нужно показать игроку текущее состояние игрового поля. Поскольку поле будет обновляться как минимум дважды (после хода игрока и после хода программы), действия, связанные с изображением поля на экране, следует вынести в отдельную процедуру. Назовем ее ShowField и займемся ее реализацией.
Судя по всему, нам понадобится организовать цикл; в ходе цикла для каждого ряда игрового поля будет выведена строка, в левой части которой указывается номер ряда, в правой — текущее количество фишек в нем, а посередине выводятся символы, имитирующие фишки. В принципе, можно выбрать любой символ ПК для обозначения фишки, например, X или О. Я предпочел воспользоваться символом псевдографики с кодом 220: этот символ представляет собой небольшой квадратик и легко ассоциируется с фишкой.
Procedure ShowField;
•
{ Отображает на экране текущее состояние игрового поля }
const
FISH = #220;
{Символ-указатель фишки}
ХО = 4;
(Левая колонка номеров рядов)
XI =
{Правая колонка количества фишек}
X = 20;
край
var
i/j •' Integer;
begin (ShowField) for i 1 to nrow do
begin
GotoXY(X0,i+4);
Write (i);( Номер ряда )
GotoXY(Xl,i+4);
Write(col[i]:2);(Количество фишек в ряду)
for j := 1 to ncol [i] do(Выводряда фишек:)
begin
GotoXY(X+2*j , i+4);
if j [il then
Write(FISH)
else
Write('.')
end
end
end; (ShowField)
Символы FISH (квадратики) выводятся через одну позицию, чтобы не сливались на экране. В те позиции, в которых ранее стояли уже снятые с поля фишки, выводится точка.
Теперь вернемся к процедуре GETPLAYERMOVE. При вводе любого очередного хода игрок должен задать два целых числа XI и Х2. Первое из них указывает номер ряда, а второе — количество фишек, которые игрок хочет забрать из этого ряда. Программа должна проконтролировать правильность задания этих чисел: XI должно указывать непустой ряд, Х2 не может превышать количество фишек в этом ряду. Кроме того, мы должны условиться о двух особых случаях:
пользователь больше не хочет играть и дает команду завершить работу программы;
пользователь хочет изменить условия игры.
Пусть ввод числа X1=O означает команду выхода из программы, a Xl= -1 — команду изменения условий игры. Тогда можно написать такой начальный вариант процедуры:
Procedure GetPlayerMove ;
(Получает, контролирует и отображает ход игрока)
var
correctly : Boolean; (Признак правильности сделанного хода)
xl,x2: Integer;{Вводимый ход}
begin {GetPlayerMove}
(Показываем начальное состояние игрового поля}
ShowField;
(Сообщаемигроку правила ввода хода}
repeat
(Приглашаем игрока ввести ход}
ReadLn(xl,x2);.{Вводим очередной, ход }
exit:= xl=0;(Контроль команды выхода)
change xl=-l;(Контроль команды изменения}
if not (exit or change) then
(Проверить правильность хода и установить нужное значение переменной CORRECTLY. Если ход правильный, сделать нужные изменения в раскладке фишек и показать поле.}
else
correctly := true(Случай EXIT или CHANGE}
until correctly;
if change then
{ Изменить условия игры }
end;(GetPlayerMove}
В этом варианте в процедуре GetPlayerMove нет описания процедуры SHOWFIELD. Сделано это не случайно: процедура ShowField может понадобиться также и при реализации процедуры setOwnerMove, поэтому она должна быть глобальной по отношению и к GetPlayerMove, и к SetOwnerMove, т. е. ее описание должно в тексте программы предшествовать описаниям двух использующих ее процедур.
Действия
{ Сообщить игроку правила ввода хода },
{ Пригласить игрока ввести ход }
И
/ Проверить правильность хода и установить нужное значение переменной Correctly. Если ход правильный, сделать нужные изменения в раскладке фишек и показать поле.}
не очень сложны в реализации, поэтому их можно осуществить непосредственно в теле процедуры GETPLAYERMOVE. Иное дело — изменение условий
игры. Это действие полезно реализовать в отдельной процедуре GETCHANGE. С учетом этого второй вариант процедуры GETPLAYERMOVE примет такой вид:
Procedure GetPlayerMove;
{Получает, контролирует и отображает ход игрока)
..
const
ТЕХТ1 =Ваш ход в формате РЯД КОЛИЧ ';
ТЕХТ01= '(например, 23-взять из 2 ряда 3 фишки)';
ТЕХТ2 = ’или введите 0 0 для выхода из игры;
ТЕХТ02= ’ -1 0 для настройки игры';
ТЕХТЗ = 'Ваш ход:
Y = 20;{Номер строки для вывода сообщений)
var
correctly : Boolean;{Признак правильности сделанного хода)
xl,x2 : Integer;{Вводимый ход)
Procedure GetChange;
{Устанавливает новую настройку игры (количество рядов и количество фишек в каждом ряду)
begin {GetChange}
'
end; {GetChange}
begin {GetPlayerMove}
ShowField; (Показываем начальное состояние поля}
{Сообщить игроку правила ввода хода:}
GotoXY((80-Length(ТЕХТ1+ТЕХТ01)) div 2, Y);
Write(TEXT1+TEXT01);
GotoXY((80-Length(TEXT2+TEXT02)) div 2,Y+1);
Write(TEXT2+TEXT02);
repeat
(Пригласить игрока ввести ход:}
GotoXY(l,Y+2);
Write(TEXT3); (Выводимприглашение и стираем предыдущий ход}
GotoXY(WhereX-16,Y+2);(Курсорвлево на 16 позиций)
ReadLn(xl,x2);{Вводим очередной ход)
exit:= xl=0;{Контроль команды выхода}
change := xl=-l;{Контроль команды изменения}
if not (exit or change) then
begin
correctly := (xl > 0) and (xl <= nrow) and
(x2 <= col[xl]) and (x2 > 0) ;
if correctly then
begin{Ход правильный:}
col[xl]:= col[xl]-x2;{Изменяемраскладкуфишек}
ShowField{Показываем поле)
end
else
Write (#7){Ход неправильный: дать звуковой сигнал}
end
else
correctly := true until correctly;if change thenGetChangeend;{GetPlayerMove}correctly := true until correctly;if change thenGetChangeend;{GetPlayerMove}{Случай EXIT или CHANGE){Случай EXIT или CHANGE)
correctly := true until correctly;
if change then
GetChange
end;{GetPlayerMove}
correctly := true until correctly;
if change then
GetChange
end;{GetPlayerMove}
{Случай EXIT или CHANGE)
{Случай EXIT или CHANGE)
Обратите внимание, что константа•
ТЕХТЗ = 'Ваш ход:
имеет длинный "хвост" из пробелов (их 17), поэтому после вывода этого приглашения курсор возвращается влево на 16 позиций оператором
GotoXY(WhereX-16, Y+2); {Курсорвлево на 16 позиций)
(функция WHEREX возвращает текущую горизонтальную координату курсора, а функция WHEREY — его вертикальную координату). Сделано это для того, чтобы в случае, если игрок ввел неверный ход и программа повторяет вывод приглашения, пробелы в константе ТЕХТЗ затерли бы строку предыдущего ввода.
Чтобы завершить создание процедуры GETPLAYERMOVE, нужно спроектировать процедуру GETCHANGE, в которой осуществляется изменение условий игры. Я привожу текст этой процедуры без пояснений и приглашаю вас самостоятельно разобраться в том, как она работает:
Procedure GetChange;
{Устанавливает новую настройку игры (количество рядов и
количество фишек в каждом ряду)
const
tl='H АСТРОЙКА ИГРЫ*;
4 Зак 94]
t2 ='(ввод количества рядов и количества '+1.
1фишек в каждом ряду)';
var correctly : Boolean; i : Integer;
begin /GetChange)
ClrScr;
GotoXY((SO-Length(tl)) div 2,1) ;
Write(tl);
GotoXY((80-Length(t2)) div 2,2);
Write(t2); repeat
GotoXY(1,3);
Write ('Введите количество рядов (максимум
MAXROW,'):');
GotoXY(WhereX-6,WhereY) ;
ReadLn(nrow);
correctly := (nrow <= MAXROW) and (nrow > 1);
if not correctly then Write (#7)
until correctly;
for i := 1 to nrow do
repeat
GotoXY(l,i+3);
Write(' рядколичество фишек (максимум
MAXCOL,*):');
GotoXY(WhereX-6,WhereY) ;
ReadLn(ncol[i]);
correctly := (ncol[i] <= MAXCOL) and (ncol [i] > 0);
if not correctly then Write(#7) until correctly
end;{GetChange)
Переходим к конструированию процедуры SETOWNERMOVE, в которой программа должна проконтролировать текущую ситуацию на игровом поле и выбрать собственный ход. Работа процедуры начинается с подсчета количе-
ства непустых рядов. В зависимости от этого подсчета реализуются следующие действия:
если все ряды пусты, значит предыдущим ходом игрок забрал последнюю фишку и он победил; нужно поздравить его с победой, усложнить игру и предложить сыграть еще раз;
если есть только один непустой ряд, то очередной ход программы очевиден — забрать все фишки, что означает победу машины; следует сообщить об этом и предложить сыграть еще раз;
если осталось два или более непустых ряда, выбрать собственный ход на основе оптимальной стратегии.
Начальный вариант процедуры:
Procedure SetOwnerMove;
и отображает очередной ход
{ —}
Function: Integer;
состояния игры. Возвращает если нет ни одной
фишки (победа1 - есть один (победа машины)
и — количество непустых рядов в остальных
begin
end;
f;
Procedure PlayerVictory;
игрока с победой и усложнить игру)
begin
end;
()
Procedure OwnVictory;
{Победа машины)
begin
end;
{—--)
Procedure ChooseMove;
очередного
begin
end;
begin {SetOwnerMove} case CheckField of 0 :1 : OwnVictory; elseChooseMove; end; (case} end;(SetOwnerMove)begin {SetOwnerMove} case CheckField of 0 :1 : OwnVictory; elseChooseMove; end; (case} end;(SetOwnerMove)
begin {SetOwnerMove} case CheckField of 0 :
1 : OwnVictory; else
ChooseMove; end; (case} end;(SetOwnerMove)
begin {SetOwnerMove} case CheckField of 0 :
1 : OwnVictory; else
ChooseMove; end; (case} end;(SetOwnerMove)
(Проверяем!количествонепустых рядов)
{Все ряды пусты — победа игрока)
(Один непустой ряд — победа машины)
(Выбираем очередной ход)
ФуНКЦИЯ CHECKFIELD И Процедуры PLAYERVICTORY И OWNVICTORY ДОСТаТОЧНО просты и их текст помещается без каких-либо пояснений в окончательный вариант программы (см. приложение 5). Отмечу лишь, что в случае победы игрока нет смысла повторять партию заново с той же самой раскладкой фишек. Поэтому игра усложняется: в исходную раскладку добавляется еше по одной фишке в каждый ряд.
В процедуре CHOOSEMOVE анализируется позиция и выбирается очередной ход программы. Описание оптимальной стратегии уже приводилось выше. Действия программы заключаются в поиске первого слева (старшего) двоичного разряда, для которого сумма чисел нечетная. Если такой разряд не обнаружен, то текущая позиция безопасна для игрока, а значит любой ход программы сделает ее опасной. В этом случае для программы не существует оптимального выбора, и она лишь убирает одну фишку из любого непустого ряда. Такая тактика означает пассивное ожидание ошибки игрока.
Если обнаружен разряд i с нечетной суммой, программа приступает к реализации оптимальной стратегии и тогда игрок обречен на поражение. Для выбора ряда, из которого следует взять фишки, программа просматривает последовательно все ряды и отыскивает тот ряд j, количество фишек в котором (в двоичном представлении) дает единицу в разряде i. Значение этого разряда для количества фишек в ряду j заменяется нулем. Затем программа продолжает подсчет суммы для оставшихся младших разрядов. Если в каком-либо из них вновь обнаружена нечетность, значение этого разряда для количества фишек в РЯДУ 7 инвертируется, т. е. О заменяется на 1, а 1 на 0. Например, если двоичные представления числа фишек и четности сумм таковы: количество фишек в ряду у: 01001, четность сумм: 01011 (единицей указаны разряды с нечетными суммами), то в результате этой операции получим: количество фишек в ряду/ 00010, четность сумм: 00000.
Таким образом, в исходном состоянии в ряду у было 1001 = 9 фишек, безопасная позиция требует, чтобы в ряду осталось 0010 = 2 фишки, следовательно, из него нужно забрать 9 — 2 = 7 фишек.
Окончательный вариант программы представлен в приложении 5. Попробуйте разобраться в ее деталях самостоятельно.
В программной реализации алгоритма широко используется то обстоятельство, что ваш компьютер, как и все остальные вычислительные машины, работает с числами, представленными в двоичной системе счисления. Поэтому для получения двоичного представления числа в процедуре BITFORM оно проверяется на четность с помощью стандартной функции ODD, затем сдвигается вправо на один двоичный разряд (операция shr), вновь осуществляется проверка на четность и т. д. до тех пор, пока не будут проверены все разряды. Максимальное количество двоичных разрядов, достаточное для двоичного представления количества фишек в ряду MAXCOL=63, задается константой BIT=6.
Для получения суммы двоичных разрядов в процедуре CHOOSEMOVE осуществляется суммирование разрядов по модулю 2 с помощью операции XOR. Такое суммирование дает 0, если количество единиц четное или равно нулю, и 1 — если нечетное. В этой же процедуре для инверсии двоичного разряда применяется оператор if nbit[i] = 1 then
ncbit[j, i] :=
в котором используется соглашение о внутреннем представлении логических величин в Турбо Паскале: 0 соответствует FALSE, а 1 — TRUE.
Глава 3
Элементы языка
Алфавит
Алфавит языка Турбо Паскаль включает буквы, цифры, цифры, специальные символы, пробелы и зарезервированные слова.
Буквы — это буквы латинского алфавита от а до z и от А до Z, а также знак подчеркивания _ (код ASCII 95). В Турбо Паскале нет различия между прописными и строчными буквамиесли только они не входят в сим
вольные и строковые выражения.
Цифры — арабские цифры от 0 до 9.
Каждаяцифра имеет значение от 0 до 15. Первые 10 значений обозначаются арабскими цифрамиостальные шесть — латин
скими буквами A—F или
Специальные символы Турбо Паскаля:
+ -*/-,'.:;<>[] О { }@ $ #
К специальным символам относятся также следующие пары символов:
<>(.
В программе эти пары символов нельзя разделять пробелами, если они используются как знаки операций отношения или ограничители комментария. Пары символов и могут заменять символы [ и ] соответственно.
Особое место в алфавите языка занимают пробелы, к которым относятся любые символы ASCII в диапазоне кодов от 0 до 32. Эти символы рассматриваются как ограничители идентификаторов, констант, чисел, зарезервированных слов. Несколько следующих друг за другом пробелов считаются одним пробелом (последнее не относится к строковым константам).
В Турбо Паскале имеются следующие зарезервированные
and
end
nil
shr
asm
file
not
string
array
for
object
then
begin
function
of
to
case
goto
or
type
const
if
packed
unit
constructor
implementation
procedure
until
destructor
in
program
uses
div
inline
record
var
do
interface
repeat
while
downto
label
set
with
else
mod
shl
xor
Зарезервированные слова не могут использоваться в качестве идентификаторов.
Стандартные директивы первоначально связаны с некоторыми стандартными объявлениями в программе. К ним относятся:
absolute farnear
assembler forward private
external interrupt virtual
Как и зарезервированные слова, стандартные директивы в окне редактора Турбо Паскаля выделяются цветом, тем не менее вы можете переопределить любую стандартную директиву, т. е. объявить одноименный идентификатор. Стандартные директивы PRIVATE и VIRTUAL действуют только в пределах объявления объектов.
Идентификаторы
Идентификаторы в Турбо Паскале — это имена констант, переменных, меток, типов, объектов, процедур, функций, модулей, программ и полей в записях. Идентификаторы могут иметь произвольную длину, но значащими (уникальными в области определения) являются только первые 63 символа.
Идентификатор всегда начинается буквой, за которой могут следовать буквы и цифры. Напомню, что буквой считается также символ подчеркивания, поэтому идентификатор может начинаться этим символом и даже состоять только из одного или нескольких символов подчеркивания. Пробелы и специальные символы алфавита не могут входить в идентификатор.
Примеры правильных идентификаторов:
а .
ALPHA
MyProgramlsBestProgram
date_27_sep_39 external
_beta
Примеры непраtвильных идентификаторов:
IProgram{Начинается цифрой}
block#1{Содержит специальныйсимвол)
My Prog{Содержит пробел}
mod(Зарезервированное слово)
Константы
В качестве констант в Турбо Паскале могут использоваться целые, вещественные и шестнадцатеричные числа, логические константы, символы, строки символов, конструкторы множеств и признак неопределенного указателя NIL.
Целые числа записываются со знаком или без него по обычным правилам и могут иметь значение от —2 147 483 648 до +2 147 483 647. Следует учесть, что, если целочисленная константа выходит за указанные границы, компилятор дает сообщение об ошибке. Такие константы должны записываться с десятичной точкой, т. е. определяться как вещественные числа.
Вещественные числа записываются со знаком или без него с использованием десятичной точки и/или экспоненциальной части. Экспоненциальная часть начинается символом е или Е, за которым могут следовать знаки + или - и десятичный порядок. Символ е (Е) означает десятичный порядок и имеет смысл "умножить на 10 в степени". Например:
з. 14Е5 — 3,14 умножить на 10 в степени 5;
-17е-2 — минус 17 умножить на 10 в степени минус 2.
Если в записи вещественного числа присутствует десятичная точка, перед точкой и за ней должно быть хотя бы по одной цифре. За символом экспоненциальной части е (Е) также должна следовать хотя бы одна цифра десятичного порядка.
Шестнадцатеричное число состоит из шестнадцатеричных цифр, которым предшествует знак доллара $ (код 36 в ASCII). Диапазон шестнадцатеричных чисел — от $оооооооо до $FFFFFFFF.
Логическая константа — это либо слово FALSE (ложь), либо слово TRUE (истина).
Символьная константа — это любой символ ПК, заключенный в апострофы:
П 1 Z 1 — символ
П1 ф 1 — символ Ф.
Если необходимо записать собственно символ апострофа, он удваивается: — символ ' (апостроф).
Символ можно записать, указав после # (код 35) его внутренний код, например:
П #97 — символ а;
#90 — символ Z;
#39 — символ ';
П #13 - символ CR.
Строковая константа — любая последовательность символов (кроме символа CR — возврат каретки), заключенная в апострофы. Если в строке нужно указать сам символ апострофа, он удваивается, например: .
'Это — строка символов'
'That''s string'
Строка символов может быть пустой, т. е. не иметь никаких символов в обрамляющих ее апострофах. Строку можно составлять из кодов нужных символов с предшествующими каждому коду символами #, например, строка #83#121#109#98#11#108 Эквивалентна Строке 'Symbol'.
Наконец, в строке можно чередовать части, записанные в обрамляющих апострофах, с частями, записанными кодами. Таким способом можно вставлять в строки любые управляющие символы, в том числе и символ CR (код 13), например:
#7'Ошибка !'#13'Нажмителюбую клавишу ...'#7
Конструктор множества — это список элементов множества, обрамленный квадратными скобками, например:
• [blue, red]
[] [true]
В отличие от стандартного Паскаля, в Турбо Паскале разрешается в объявлении констант использовать произвольные выражения, операндами которых могут быть ранее объявленные нетипизированные константы, имена типов и объектов, а также следующие функции от них:
abs
10
ptr
swap
chr
odd
round
trunc
hi
ord
sizeof
length
pred
succ
Например:
const
MaxReal =(real) ;
NumChars =- ord('a') + 1;
LnlO =
LnlOR = 1 / LnlO;
Выражения
Основными элементами, из которых конструируется исполняемая часть программы, являются константы, переменные и обращения к функциям. Каждый из этих элементов характеризуется своим значением и принадлежит к какому-либо типу данных. С помощью знаков операций и скобок из них можно составлять выражения, которые фактически представляют собой правила получения новых значений.
Частным случаем выражения может быть просто одиночный элемент, т. е. константа, переменная или обращение к функции. Значение такого выражения имеет, естественно, тот же тип, что и сам элемент. В более общем случае выражение состоит из нескольких элементов (операндов) и знаков операций, а тип его значения определяется типом операндов и видом примененных к ним операций.
Примеры выражений:
у
21
(а + Ь) * с
sin(t)
а > 2
not Flag and (a = b)
NIL
[1, 3..7] * setl
Операции
В Турбо Паскале определены следующие операции:
унарные — not, @;
мультипликативные —/, div, mod, and, shl, shr;
О аддитивные — +,or, xor;
отношения — =, о, <, >, <=, >=, in.
Приоритет операций убывает в указанном порядке, т. е. наивысшим приоритетом обладают унарные операции, низшим — операции отношения. Порядок выполнения нескольких операций равногоустанавли
вается компилятором из условия оптимизации кода программы и не обязательно слева направо. При исчислении логических выражений операции равного приоритета всегда вычисляются слева направо, причем будут вычисляться все или только достаточные операции в зависимости от установленной в среде Турбо Паскаля опции options | compiler | complete boolean при установленном значении этой опции вычисляются все операции отношения, при неустановленном — только те, которые достаточны для получения результата.
Это обстоятельство необходимо учитывать при использовании операций отношения с функциями, в которых изменяются глобальные переменные или параметры, передаваемые по имени, например:
x:Integer;
begin
inc(x);
:= x
end {AddI};
var
:
begin. ,
if (a > b) or> 100) then b := a;
/
При выполнении этого фрагмента значение переменной А будет зависеть от настройки опции: если опция активизирована, значение А всегда наращивается на 1, если не активизирована — только в случае А <= в.
Правила использования операций с операндами различного типа приводятся в табл. 3.1.
Таблица Правила использования операндов различных типов
Операция
Действие
Тип операндов
Тип результата
not
Отрицание
Логический
Логический
not
To же
Любой целый
Тип операнда
@
Адрес
Любой
Указатель
*
Умножение
Любой целый
Наименьший целый
*
To же
Любой вещественный
EXENDED
Таблица 3.1 (окончание)
Операция
Действие
Тип операндов
Тип результата
♦
Пересечение множеств
Множественный
Множественный
/
Деление
Любой вещественный
EXTENDED
div
Целочисленное деление
Любой целый
Наименьший целый
mod
Остаток от деления
То же
То же
and
Логическое И
Логический
Логический
and
To же
Любой целый
Наименьший целый
shl
Левый сдвиг
То же
То же
shr
Правый сдвиг
•
"
+
Сложение
И
И
+
То же
Любой вещественный
EXTENDED
+
Объединение множеств
Множественный
Множественный
+
Сцепление строк
Строковый
Строковый
-
Вычитание
Любой целый
Наименьший целый
—
То же
Любой вещественный
EXTENDED
or
Логическое ИЛИ
Логический
Логический
or
To же
Любой целый
Наименьший целый
=
Равно
Любой простой или строковый
Логический
<>
Не равно
То же
То же
<
Меньше
Логический
Логический
<=
Меньше или равно
То же
То же
>
Больше
•1
И
>=
Больше или равно
М
При действиях с вещественным типом одним из операндов может быть значение любого целого типа. Результат операций имеет указанный в таблице тип EXTENDED только для установленного в среде Турбо Паскаля режима генерации кода, рассчитанного на арифметический сопроцессор или на его эмуляцию (см. приложение 1). Если этот режим не установлен, результат будет иметь значение типа REAL.
Унарная операция @ применяется к операнду любого типа и возвращает результат типа POINTER (см. главу 6)^ в котором содержится адрес операнда. Пусть, например, задано описание
type
TwoChar = array [1..2] of char;
var
Int: integer;
TwoCharPtr :
Тогда оператор
TwoCharPtr := @Int;..
приведет к тому, ЧТО в TwoCharPtr будет храниться адрес целочисленной переменной INT, которая может теперь интерпретироваться как совокупность двух символов. Поэтому возможен, например, такой оператор:
if TwoCharPtrA[l] =
Если операция @ применяется к процедуре, функции или методу в объекте, ее результатом будет адрес точки входа в эту процедуру (функцию, метод). Этот адрес можно использовать только в подпрограмме, написанной на ассемблере, или в фрагментах, определенных оператором INLINE.
В Турбо Паскале определены следующие логические операции:
О not — логическое НЕ;
and — логическое И;
or — логическое ИЛИ;
xor — исключительное ИЛИ.
Логические операции применимы к операндам целого И логического типов. Если операнды — целые числа, то результат логической операции есть тоже целое число, биты которого (двоичные разряды) формируются из битов операндов по правилам, указанным в табл. 3.2.
Таблица 3.2. Логические операции над данными типа INTEGER (поразрядно)
Операнд1
Операнд 2
not
and
or
xor
1
—
0
—
—
—
0
—
1
—
--
—
0
0
—
0
о
0
0
1
—
0
1
1
1
0
—
0
1
1
1
1
^^^^^^^™
1
1
0
К логическим же в Турбо Паскале обычно относятся и две сдвиговые операции над целыми числами:
i j — сдвиг содержимого i на j разрядов влево; освободившиеся младшие разряды заполняются нулями;
i shr j — сдвиг содержимого i на j разрядов вправо; освободившиеся старшие разряды заполняются нулями.
В этих операциях i и j — выражения любого целого типа.
С помощью программы, показанной в примере 3.1, можно вывести на экран результат применения логических операций к двум целым числам.
(Программа вводит два целых числа и печатает результат применения к ним логических операций. Для выхода из программы ввести Ctrl-Z и нажать Enter}
var
n,m : integer;
begin
while not EOF do
begin
Write('n,m =
ReadLn(n,m);
WriteLn(' not=not n, 1 ’ r not m);
WriteLn(* and= 11 n and m) ;
WriteLn(1 or = ’. n or m) ;
WriteLn(* xor= 1i n xor m) ;
WriteLn (' shl= ', n shl m) ;
WriteLn(' shr= 1, n shr m)
end
end.
В программе организуется ввод двух произвольных целых чисел и печать результата применения К НИМ всех логических операций. Для выхода из программы следует нажать комбинацию клавиш <Ctrl>+<Z>, а затем клавишу <Enter>.
Логические операции над логическими данными дают результат логического типа по правилам, указанным в табл. 3.3.
Таблица 3.3. Логические операциинад даннымитипа BOOLEAN
Операнд 1
Операнд 2
not
and
or
xor
True
—
False
—
—
—
False
—
True
—
—
—
False
False
False
False
False
False
True
—
False
True
True
True
False
False
True
True
True
True
.
True
. True
False
Операция отношения IN применяется к двум операндам. Первым (левым) операндом должно быть выражение любого порядкового типа, вторым — множество, состоящее из элементов того же типа, или идентификатор множественного типа. Операция дает TRUE, если левый операнд принадлежит множеству, например: ,
var
с: char;
type
digit = set of 'O'..'9';
begin if с in digit then
•
Структура программы
Структура любой программной единицы (программы, процедуры или функции) должна быть такой:
<Объявление программной единицы>
{Раздел описаний)
BEGIN
{Раздел исполняемых операторов)
ЕКОСсимвол конца программной единицы>
Здесь:
□ собъявление программной единицы> — заголовок программы, процедуры или функции; заголовок программы можно" опускать без каких-либо последствий для программы; для процедур и функций наличие заголовка обязательно;
О <символ конца программной единицы> — это символ . (точка) для программы и ; (точка с запятой) — для процедуры и функции.
Любой из двух разделов программной единицы — раздел описаний или исполняемых операторов, или оба одновременно могут быть пустыми, т. е. не содержать никаких описаний или исполняемых операторов.
В разделе описаний должны содержаться описания всех идентификаторов, используемых в разделе исполняемых операторов. Исключением являются идентификаторы, определенные в интерфейсных частях программных модулей (библиотек), а также глобальные для процедуры или функции идентификаторы (см. главу 8). Если программная единица использует идентификатор из интерфейсной части какого-либо модуля (см. главу 9), в начале программы в предложении USES необходимо указать имя этого модуля. Последнее не относится к идентификаторам, определенным в стандартном модуле SYSTEM, т. е. имя этого модуля в предложении USES указывать не нужно. Более того, модуль SYSTEM считается предварительно объявленным, поэтому объявление
Uses System;
компилятор расценит как попытку двойного объявления модуля SYSTEM и даст соответствующее сообщение об ошибке.
В разделе описаний объявляются идентификаторы типов, объектов, констант, переменных, а также метки, процедуры и функции. Описанию типов и объектов должно предшествовать зарезервированное слово TYPE, описанию констант — CONST, переменных — VAR и меток — LABEL, например: type
DigType = set of 'O'..'9';
StrType = string [40];
const
N = 100;
EPS = le-9;
var
x,y : real;
st : StrType;
label
1Ы, lb2;
В отличие от стандартного Паскаля разделы TYPE, CONST, VAR, LABEL могут следовать друг за другом в любом порядке и встречаться в разделе описаний сколько угодно раз.
Описание процедуры или функции заключается в указании заголовка этой процедуры (функции) и ее тела (подробнее см. в главе 8).
Структура программных библиотек (модулей) описана в главе 9.
Рис. 4.1. Структура типов данныхРис. 4.1. Структура типов данных
Рис. 4.1. Структура типов данных
Рис. 4.1. Структура типов данных
Глава4
Типы данных
Любые данные, т. е. константы, переменные, значения функций или выражения, в Турбо Паскале характеризуются своими типами. Тип определяет множество допустимых значений, которые может иметь тот или иной объект, а также множество допустимых операций, которые применимы к нему. Кроме того, тип определяет также и формат внутреннего представления данных в памяти ПК.
Турбо Паскаль характеризуется разветвленной структурой типов данных (рис. 4.1).
В Турбо Паскале предусмотрен механизм создания новых типов данных, благодаря чему общее количество типов, используемых в программе, может быть сколь угодно большим.
В этой главе приводится подробное описание всех типов, за исключением файлов и указателей, которые рассматриваются в следующих двух главах, а также процедурных типов, которые рассматриваются в главе 8, и объектов (см. главу 10).
Простые типы
К простым типам относятся порядковые и вещественные типы.
Порядковые типы отличаются тем, что каждый из них имеет конечное число возможных значений. Эти значения можно определенным образом упорядочить (отсюда — название типов) и, следовательно, с каждым из них можно сопоставить некоторое целое число — порядковый номер значения.
Вещественные типы, строго говоря, тоже имеют конечное число значений, которое определяется форматом внутреннего представления вещественного числа. Однако количество возможных значений вещественных типов настолько велико, что сопоставить с каждым из них целое число (его номер) не представляется возможным.
Порядковые типы
К порядковым типам относятся (см. рис. 4.1) целые, логический, символьный, перечисленный и тип-диапазон. К любому из них применима функция ORD(X), которая возвращает порядковый номер значения выражения х. Для целых типов функция ORD(X) возвращает само значение х, т. е. ORD(X) = х для х, принадлежащего любому целому типу. Применение ORD(X) к логическому, символьному и перечисленному типам дает положительное целое число в диапазоне от 0 до 1 (логический тип), от 0 до 255 (символьный), от 0 до 65 535 (перечисленный). Тип-диапазон сохраняет все свойства базового порядкового типа, поэтому результат применения к нему функции ORD (X) зависит от свойств этого типа.
К порядковым типам можно также применять функции:
□ PRED(X) — возвращает предыдущее значение порядкового типа (значение, которое соответствует порядковому номеру ORD (X) -1), т. е.
ORD(PRED(X)) =- 1
□ SUCC (х) — возвращает следующее значение порядкового типа, которое соответствует порядковому номеру ORD (X) +1, т. е.
ORD(SUCC(X)) =+ 1
Например, если в программе определена переменная
var.
с :
begin
о .. ——
end.
то функция PRED (с) вернет значениеа функция succ (С) — значение '
Если представить себе любой порядковый тип как упорядоченное множест- значений, возрастающих слева направо и занимающих на числовой оси некоторыйотрезок,тофункцияPRED(X)неопределенадлялевого,а
succ (X) — для правого конца этого отрезка.
Целые типы
Диапазон возможных значений целых типов зависит от их внутреннего представления, которое может занимать один, два или четыре байта. В табл. 4.1 приводятся названия целых типов, длина их внутреннего представления в байтах и диапазон возможных значений.
Таблица Целые типы
Название
Длина, байт
Диапазон значений
Byte
1
От 0 до 255,
Shortlnt
1
От-128 до+127
Word
2
От 0 до 65 535
Integer
2
От -32 768 до +32 767
Longlnt
4
От -2483 648 до +2483 647
При использовании процедур и функций с целочисленными параметрами следует руководствоваться "вложенностью" типов, т. е. везде, где может использоваться WORD, допускается использование BYTE (но не наоборот), в LONGINT "входит" INTEGER, который, в свою очередь, включает в себя SHORTINT.
Перечень процедур и функций, применимых к целочисленным типам, приведен в табл. 4.2. Буквами b, s, w, i, обозначены выражения соответственно ТИПа BYTE, SHORTINT, WORD, INTEGER И LONGINT, x — Выражение ЛЮбоГО ИЗ ЭТИХ типов; буквы vb, vs, vi, vx обозначают переменные соответствующих типов. В квадратных скобках указывается необязательный параметр.
Таблица 4.2. Стандартные процедуры и функции, применимые к целым типам
Обращение
Тип результата Действие
abs(х)
хВозвращает модуль х
chr(b)
CharВозвращает символ по его коду
dec(vx[, 1])
Уменьшает значение vx на i, а при отсутствии i — на 1
inc(vx[,i])
Увеличивает значение vx на i, а при отсутствии i—на1
Hi (i)
ByteВозвращает старший байт аргумента
Hi (w)
To жеTo же
Lo(i)
Возвращает младший байт аргумента
Lo (W)
*To же
odd(l)
BooleanВозвращает True, если аргумент — нечетное число
Random(w)
Как у парамет- Возвращает псевдослучайное число, равномерно paраспределенное в диапазоне от 0 до (w - 1)
sqr(x)
хВозвращает квадрат аргумента
swap(i)
IntegerМеняет местами байты в слове
swap(w)
WordTo же
При действиях с целыми числами тип результата будет соответствовать типу операндов, а если операнды относятся к различным целым типам, — типу того операнда, который имеет максимальную мощность (максимальный диапазон значений). Возможное переполнение результата никак не контролируется, что может привести к недоразумениям, например:
var
а : Integer;
х, у : Real;
begin
а := 32767;{Максимально возможное значение типа INTEGER)
х := а + 2;{Переполнение при вычислении
этого выражения!)
у := Longlnt(а)+2;{Переполнения нет после приведения
• переменной к более мощному типу)
WriteLn(х:10:0, у:10:0)
end.
.
В результате прогона программы получим
-3276732769
Логический тип
Значениями логического типа может быть одна из предварительно объявленных констант FALSE (ложь) или TRUE (истина). Для них справедливы следующие правила:
О ord(False) = О;
ord(True) = 1;
П False < True;
О succ(False)= True;
pred(True) = False.
Поскольку логический тип относится к порядковым, его можно использовать в операторе счетного типа, например:
var
: Boolean;
begin
for 1False to True do
Символьный тип
Значением символьного типа является множество всех символов ПК. Каждому символу приписывается целое число в диапазоне от 0 до 255. Это число служит кодом внутреннего представления символа, его возвращает функция ORD.
Для кодировки используется код ASCII (American Standard Code for Information Interchange — американский стандартный код для обмена информацией). Это 7-битный код, т. е. с его помощью можно закодировать лишь 128 символов в диапазоне от 0 до 127. В то же время в 8-битном байте, отведенном для хранения символа в Турбо Паскале, можно закодировать в два раза больше символов в диапазоне от 0 до 255. Первая половина символов ПК с кодами 0-127 соответствует стандарту ASCII (табл. 4.3). Вторая половина символов с кодами 128-255 не ограничена жесткими рамками стандарта и может меняться на ПК разных типов (в приложении 2 приведены некоторые распространенные варианты кодировки этих символов).
Таблица 4.3. Кодировка символов в соответствии со стандартом ASCII
Код
Символ
Код
Символ
Код
Символ
Код
Символ
0
NUL
32
BL
64
@
96
1
SOH
33
I
65
A
97
а
2
STX
34
к
66
в
98
Ь
3
ЕТХ
35
#
67
- с
99
с
4
EOT
36
$
68
D
100
d
5
ENQ
37
%
69
E
101
е
6
ACK
38
&
70
F
102
f
7
BEL
39
1
71
G
103
g
8
BS
40
(
72
H
104
h
9
НТ
41
)
73
I
105
i
10
LF
42
*
74
J
106
D
11
VT
43
+
75
К
107
k
12
FF
44
I
76
L
108
1'
13
CR
45
-
77
M
109
m
14
SO
46
.
78
N
110
n
15
SI
47
/
79
0
111
о
16
DEL
48
0
80
P
112
P
17
DC1
49
1
81
Q
113
q
18
DC2
50
2
82
R
114
r
19
DCS
51
3
83
S
115
S
20
DC4
52
4
84
Т
116
t
21
NAK
53
5
85
и
117
U
22
SYN
54
6
86
V
118
V
23
ETB
55
7
87
И
119
w
24 -
CAN
56
8
88
X
120
X
25
EM
57
9
89
Y
121
У
26
SUB
58
90
Z
122
Z
27
ESC
59
91
[
123
{
28
FS
60
<
92
Л
124
29
GS
61
=
93
]
125
}
30
RS
62
>
94
Л
126
~
31
US
63
7
95
127
(2)
Символы с кодами О—31 относятся к служебным. Если эти коды встречаются в символьном тексте программы, то они считаются пробелами. При использовании их в операциях ввода/вывода они могут иметь следующее самостоятельное значение (табл. 4.4).
Таблица 4.4. Специальные символы и их значение
Символ
Код
Значение
BEL
7
Звонок; вывод на экран этого символа сопровождается звуковым сигналом
HT
9
Горизонтальная табуляция; при выводе на экран смещает курсор в ПОЗИЦИЮ, кратную 8, плюс 1 (9, 17, 25 и т. д.)
LF
10
Перевод строки; при выводе его на экран все последующие символы будут выводиться, начиная с той же позиции, но на следующей строке
VT
11
Вертикальная табуляция; при выводе на экран заменяется специальным знаком
FF
12
Прогон страницы; при выводе на принтер формирует страницу, при выводе на экран заменяется специальным знаком
CR
13
Возврат каретки; вводится нажатием на клавишу <Enter> (при вводе с помощью READ или READLN означает команду "Ввод" и в буфер ввода не помещается; при выводе означает команду "Продолжить вывод с начала текущей строки")
'SUB
2 б
Конец файла; вводится с клавиатуры нажатием комбинации клавиш <Ctrl>+<Z>; при выводе заменяется специальным знаком
ESC
27
Конец работы; вводится с клавиатуры нажатием на клавишу <Esc>; при выводе заменяется специальным знаком
К типу CHAR применимы операции отношения, а также встроенные функции:
CHR(B) — функция типа CHAR; преобразует выражение в типа BYTE в символ, который является значением функции;
UPCASE(CH) — функция типа CHAR; возвращает прописную букву, если сн является строчной латинской буквой, в противном случае возвращает сам символ сн, например:
-
cl,c2: Char;
begin
cl :=
c2 := UpCase(' ф');
WriteLn(cl,' ',c2)
end.
Так как функция UPCASE не обрабатывает кириллицу, в результате прогона этой программы на экран будет выдано
s Ф
Перечисленный тип
Перечисленный тип задается перечислением тех значений, которые он может получать. Каждое значение именуется некоторым идентификатором и располагается в списке, обрамленном круглыми скобками, например:
type
colors = (red, white, blue);
Применение перечисленных типов делает программы нагляднее. Если, например, в программе используются данные, связанные с месяцами года, то такой фрагмент программы:
type
ТипМесяц=(янв,фев,мар,аир,май,июн,июл,авг,сен,окт,ноя,дек);
var
месяц : ТипМесяц;
begin
if месяц = авг then WriteLn('Хорошо бы поехать к морю1');
end.
был бы, согласитесь, очень наглядным. Увы! В Турбо Паскале нельзя использовать кириллицу в идентификаторах, поэтому мы вынуждены писать так:
type
TypeMonth=(jan,feb,mar,may,jun,jul,aug,sep,oct,nov,dec);
var
month: TypeMonth;
begin
if month = aug then WriteLn('Хорошо бы поехать к морю1 ');
end.
Соответствие между значениями перечисленного типа и порядковыми номерами этих значений устанавливается порядком перечисления: первое значение в списке получает порядковый номер 0, второе — 1 и т. д. Максимальная мощность перечисленного типа составляет 65 536 значений, поэтому фактически перечисленный тип задает некоторое подмножество целого типа WORD и может рассматриваться как компактное объявление сразу группы целочисленных констант со значениями О, 1 и т. д.
Благодаря перечисленным типам повышается надежность программ за счет возможности контроля тех значений, которые получают соответствующие переменные. Пусть, например, заданы такие перечисленные типы:
type
colors = (black, red, white);.
ordenal= (one, two, three);
days= (monday, tuesday, Wednesday);
С точки зрения мощности и внутреннего представления все три типа эквивалентны:•
ord(black)=0, .. ., ord(white)=2, ord(one)=0, . .., ord(three)=2, ord(monday)=0, . .., ord(Wednesday)=2.
Однако, если определены переменные
var
col : colors;
num :
day : days
то допустимы операторы col := black;
num := succ(two);j
day := pred(tuesday);
но недопустимы
col := one;
day := black;
Как уже упоминалось, между значениями перечисленного типа и множеством целых чисел существует однозначное соответствие, задаваемое функци-
ей ORD (X). В Турбо Паскале допускается и обратное преобразование: любое выражение типа WORD можно преобразовать в значение перечисленного типа, если только значение целочисленного выражения не превышает мощности перечисленного типа. Такое преобразование достигается применением автоматически объявляемой функции с именем перечисленного типа (см. разд. 4.4). Например, для рассмотренного выше объявления типов эквивалентны следующие присваивания: col := one;
col := colors(0);
Разумеется, присваивание -
col := 0;
будет недопустимым.
Переменные любого перечисленного типа можно объявлять без предварительного описания этого типа, например: var
col: (black, white, green);
Тип-диапазон
Тип-диапазон есть подмножество своего базового типа, в качестве которого может выступать любой порядковый тип, кроме типа-диапазона.
Тип-диапазон задается границами своих значений внутри базового типа:
<мин.знач,>..<макс.знач.>
Здесь:
<мин.знач. > — минимальное значение типа-диапазона;
<макс.знач. > — максимальное его значение.
Например:
type
digit = 'О'..'9';
dig2 =48 .. 57;
Тип-диапазон необязательно описывать в разделе ТУРЕ, а можно указывать непосредственно при объявлении переменной, например:
var
date : 1..31;
month: 1..12;
lehr : 'А' ..'Z';
При определении типа-диапазона нужно руководствоваться следующими правилами:
О два символарассматриваются как один символ, поэтому между ними
недопустимы пробелы;
левая граница диапазона не должна превышать его правую границу.
Тип-диапазон наследует все свойства своего базового типа, но с ограничениями, связанными с его меньшей мощностью. В частности, если определена переменная
type
days =;
WeekEnd = sa . . su;
. var
w : WeekEnd;
begin
w := sa;
end;
TO ORD (w) вернет значение 5, в то время как PRED(W) приведет к ошибке.
В стандартную библиотеку Турбо Паскаля включены две функции, поддерживающие работу с типами-диапазонами:
HIGH(X) — возвращает максимальное значение типа-диапазона, к которому принадлежит переменная х;
О LOW(X) — возвращает минимальное значение типа-диапазона.
Следующая короткая программа
var k: Integer;
begin
WriteLn(Low(k),'...', High(k))'
end.
выведет на экран строку
-32768...32767
4.1.2. Вещественные типы
В отличие от порядковых типов, значения которых всегда сопоставляются с рядом целых чисел и, следовательно, представляются в ПК абсолютно точ-
но, значения вещественных типов определяют произвольное число лишь с некоторой конечной точностью, зависящей от внутреннего формата вещественного числа (табл. 4.5).
' Таблица 4.5. Формат вещественных типов
Длина, байт
Название
Количество значащих цифр
Диапазон
десятичного порядка
6
Real
11-12
от -39 до +38
8
Double
15-16
от -324 до +308
10
extended
19-20
от -4951 до +4932
8
comp
19-20
от-263+ 1 до +263-1
Как видно из табл. 4.5, вещественное число в Турбо Паскале занимает от 4 до 10 смежных байтов и имеет следующую структуру в памяти ПК:
S
е
m
Здесь s — знаковый разряд числа; е — экспоненциальная часть; содержит двоичный порядок; m — мантисса числа.
Мантисса m имеет длину от 23 (для SINGLE) до 63 (для EXTENDED) двоичных разрядов, что и обеспечивает точность для SINGLE и для EXTENDED десятичных цифр. Десятичная точка (запятая) подразумевается перед левым (старшим) разрядом мантиссы, но при действиях с числом ее положение сдвигается влево или вправо в соответствии с двоичным порядком числа, хранящимся в экспоненциальной части, поэтому действия над вещественными числами арифметикой с плавающей точкой (запятой).
Как видим, Турбо Паскаль характеризуется богатой гаммой вещественных типов, однако доступ к типам SINGLE, DOUBLE и EXTENDED возможен только при особых режимах компиляции. Дело в том, что эти типы рассчитаны на аппаратную поддержку арифметики с плавающей точкой и для их эффективного использования в состав ПК должен входить арифметический сопроцессор. Компилятор Паскаля позволяет создавать работающие на любых ПК (с сопроцессором или без него) и использующие любые вещественные типы. Необходимая для этого настройка компилятора описана в приложении 1. В процессе запуска Турбо Паскаль проверяет состав аппаратных средств и выявляет наличие отсутствие сопроцессора.
В некоторых случаях бывает необходимо отключить автоконтроль. Для этого перед запуском Турбо Паскаля следует дать такую команду MS-DOS:
set
Команда
set 87=Y
напротив, включает автоконтроль — эта команда активна по умолчанию.
Отметим, что арифметический сопроцессор всегда обрабатывает числа в формате EXTENDED, a три других вещественных типа в этом случае получаются простым усечением результатов до нужных размеров и применяются в основном для экономии памяти.
Например, если "машинное эпсилон" (см. пример 2.6 в главе 2) вычисляется с помощью такой программы:
($N+,E+}
type .
= Real:
var
epsilon : RealType;
begin
epsilon := 1;
while> 1 do
epsilonepsilon/2;
WriteLn(epsilon) end.
то независимоотобъявлениятипаREALTYPE(онможетбытьSINGLE,REAL,
или EXTENDED) на печать будет выдан результат
0019, что соответствует типу EXTENDED. Происходит это по той причине, что все операнды вещественного выражения + epsilon/2 в операторе WHILE перед вычислением автоматически преобразуются к типу EXTENDED. Чтобы получить правильный результат (например, для типа REALTYPE = REAL ОН будет
мо изменить следующим образом:
{$N+,E+}
type
RealType = Real;
var
: RealType;
begin
epsilon := 1;
repeat
epsilon := epsilon/2;
epsl := 1 + epsilon
until epsl = 1;
WriteLn(2*epsilon)■
end.
Следует учесть, что тип REAL оптимизирован для работы без сопроцессора. Если ваш ПК оснащен сопроцессором, использование типа REAL приведет к дополнительным затратам времени на преобразование REAL к EXTENDED. Поэтому никогда не используйте REAL на ПК с сопроцессором, т. к. дополнительные затраты времени на преобразование типов могут свести на нет все преимущества сопроцессора. При разработке программ, критичных ко времени счета, следует заменять его типами SINGLE или DOUBLE: по сравнению с типом REAL скорость вычислений на машинах с сопроцессором в этом случае увеличивается в 2—3 раза. Если в ПК нет арифметического сопроцессора, скорость обработки данных всех вещественных типов приблизительно одинакова.
Особое положение в Турбо Паскале занимает тип COMP, который трактуется как вещественное число без экспоненциальной и дробной частей. Фактически СОМР — это "большое" целое число со знаком, сохраняющее 19-20 значащих десятичных цифр (во внутреннем представлении СОМР занимает 8 смежных байтов). В то же время в выражениях СОМР полностью совместим с любыми другими вещественными типами: над ним определены все вещественные операции, он может использоваться как аргумент математических функций и т. д. Наиболее подходящей областью применения типа СОМР являются бухгалтерские расчеты: денежные суммы выражаются в копейках или центах и действия над ними сводятся к операциям с достаточно длинными целыми числами.
Для работы с вещественными данными могут использоваться встроенные математические функции, представленные в табл. 4.6. Здесь REAL означает любой вещественный, INTEGER — любой целый тип.
Таблица 4.6. Стандартные математические функции Турбо Паскаля
Обращение
Тип параметра
Тип результата
Примечание
abs(х)
Real,
Тип
Модуль аргумента
Integer
аргумента
ArcTan(х)
Real
Real
Арктангенс (значение в радианах)
Таблица4.6 (окончание)
Обращение
Тип параметра
Тип результата
Примечание
cos(х)
Тоже
Тоже
Косинус, угол в радианах
exp(х)
Н
•1
Экспонента
frac(х)
*
Дробная часть числа
int(x)
И
II
Целая часть числа
In (х)
И
II
Логарифм натуральный
pi
*
II
я = 3,141592653...
Random
-
II
Псевдослучайное число, равномерно
распределенное в диапазоне от 0 до 1
Random(х)
Integer
Integer
Псевдослучайное целое число, равномерно распределенное в диапазоне от О ДО (х- 1)
Randomize
—
—
Инициация генератора псевдослучайных чисел
sin(х)
Real
Real
Синус, угол в радианах
sqr(х)
То же
То же
Квадрат аргумента
sqrt(х)
II
II
Корень квадратный
4.2. Структурированные типы
Любой из структурированных типов (а в Турбо Паскале их четыре: массивы, записи, множества и файлы) характеризуется множественностью образующих этот тип элементов, т. е. переменная или константа структурированного типа всегда имеет несколько компонентов. Каждый компонент, в свою очередь, может принадлежать структурированному типу, что позволяет говорить о возможной вложенности типов. В Турбо Паскале допускается произвольная глубина вложенности типов, однако суммарная длина любого из них во внутреннем представлении не должна превышать 65 520 байт.
В целях совместимости со стандартным Паскалем в Турбо Паскале разрешается перед описанием структурированного типа ставить зарезервированное слово PACKED, предписывающее компилятору, по возможности, экономить память, отводимую под объекты структурированного типа; но компилятор фактически игнорирует это указание: "упаковка" данных в Турбо Паскале осуществляется автоматически, везде, где это возможно.
Массивы
Массивы в Турбо Паскале во многом схожи с аналогичными типами данных в других языках программирования. Отличительная особенность массивов заключается в том, что все их компоненты суть данные одного типа (возможно, структурированного). Эти компоненты можно легко упорядочить и обеспечить доступ к любому из них простым указанием его порядкового номера, например:
type
digit = array [0..9] of Char;
matrix = array [Byte] of Single;
var
m : matrix;
d : digit;
i : Integer;
begin
m[17] :=
end.
Описание типа массива задается следующим образом:
<имя типа> = ARRAY [ <cn.ИНД.типов> ] OF <тип>
Здесь:
О <имя типа> — правильный идентификатор;
О ARRAY, OF — зарезервированные слова (массив, из);
<сп.инд.типов> — список из одного или нескольких индексных типов, разделенных запятыми; квадратные скобки, обрамляющие список, — требование синтаксиса;
<тип> — любой тип Турбо Паскаля.
В качестве индексных типов в Турбо Паскале можно использовать любые порядковые типы, кроме LONGINT и типов-диапазонов С базовым типом LONGINT.
Определить переменную как массив можно и непосредственно при описании этой переменной, без предварительного описания типа массива, например:
var
a,b : array [1..10] of Real;
Обычно в качестве индексного типа употребляется тип-диапазон, в котором задаются границы изменения индексов. Так как фрагмент <тип>, идущий за словом OF, — любой тип Турбо Паскаля, то он может быть, в частности, и другим массивом, например:
type
mat = arrayof arrayof array [Char] of
Такую запись можно заменить более компактной:
type
mat = arrayof Byte;
Глубина вложенности структурированных типов вообще, а следовательно, и массивов — произвольная, поэтому количество элементов в списке индексных типов (размерность массива) не ограничено, однако суммарная длина внутреннего представления любого массива, как уже говорилось, не может быть больше 65 520 байт. В памяти ПК элементы массива следуют друг за другом так, что при переходе от младших адресов к старшим наиболее быстро меняется самый правый индекс массива. Если, например, имеется массив
var
а : arrayof Byte;
begin
а[1,1]:-1;
а[2,1]:=2;
а[1,2]:=3;
a
end.
то в памяти последовательно друг за другом будут расположены байты со значениями 1, 3, 2, 4. Это обстоятельство может оказаться важным при использовании стандартной процедуры копирования памяти MOVE.
В Турбо Паскале можно одним оператором присваивания передать все элементы одного массива другому массиву того же типа, например:
var
: arrayof Single;
begin
a :=
end.
5941
После этого присваивания все пять элементов массива А получат те же значения, что и в массиве в. Однако над массивами не определены операции отношения. Нельзя, например, записать
if a = b then
Сравнить два массива можно поэлементно, например:
var
а,Ь : array [1..5] of Single;
eq : Boolean;
i : Byte;
begin
eqTrue;
for i := 1 to 5 do
if a[i] <> b[i] then
eq := False;
if eq then
end.
Записи
Запись — это структура данных, состоящая из фиксированного числа компонентов, называемых полями записи. В отличие от массива, компоненты (поля) записи могут быть различного типа. Чтобы можно было ссылаться на тот или иной компонент записи, поля именуются.
Структура объявления типа записи такова:
<имя типа> = RECORD <сп.полей> END
Здесь:
О <имя типа> — правильный идентификатор;
□ RECORD, END — зарезервированные слова (запись, конец);
О <сп.полей> — список полей; представляет собой последовательность разделов записи, между которыми ставится точка с запятой.
Каждый раздел записи состоит из одного или нескольких идентификаторов полей, отделяемых друг от друга запятыми. За идентификатором (идентификаторами) ставится двоеточие и описание типа поля (полей), например:
type
BirthDay = record
day,month : Byte;
year : Word
end;
var
a, b : Birthday;
В этом примере тип BIRTHDAY (день рождения) есть запись с полями DAY, MONTH и YEAR (день, месяц и год); переменные А И В содержат записи типа BIRTHDAY. .
Как и в массиве, значения переменных типа записи можно присваивать другим переменным того же типа, например а := b;
К каждому из компонентов записи можно получить доступ, если использовать составное имя, т. е. указать имя переменной, затем точку и имя поля:
a. day := 27;
b.year : = 1939;
Для вложенных полей приходится продолжать уточнения:
type
BirthDay = record
day,month: Byte;
year : Word
end;
var
с : record
name : String;
bd: BirthDay
end;
begin
if c.bd.year = 1939 then
end.
Чтобы упростить доступ к полям записи, используется оператор присоединения WITH:
WITH <переменная> DO <оператор>
Здесь:
О WITH, DO — ключевые слова (с, делать);
О <переменная> — имя переменной типа "запись", за которым, возможно, следует список вложенных полей;
□ <оператор> — любой оператор Турбо Паскаля.
Например:
with c.bd do month9;
Это эквивалентно
with с do with bd do month := 9;
ИЛИ
with c,bd do month := 9;
ИЛИ
c.bd.month := 9;
Турбо Паскаль разрешает использовать записи с так называемыми вариантными полями, например:
type
Forma = record
Name: String;
case Byte of
0: (BirthPlace: String [40]);
: (Country : String [20];
EntryPort : String [20];
EntryDate : 1..31;
ExitDate : 1..31)
end;
В этом примере тип FORMA определяет запись с одним фиксированным полем NAME и вариантной частью, которая задается предложением CASEOF.
Вариантная часть состоит из нескольких вариантов (в нашем примере — из двух вариантов: 0 и 1). Каждый вариант определяется константой выбора, за которой следует двоеточие и список полей, заключенный в круглые скобки. В любой записи может быть только одна вариантная часть, и, если она есть, она должна располагаться за всеми фиксированными полями.
Замечательной особенностью вариантной части является то обстоятельство, что все заданные в ней варианты "накладываются" друг на друга, т. е. каждому из них выделяется одна и та же область памяти. Это открывает дополнительные возможности преобразования типов, например:
var
mem4 : record
case Byte of
О : (by : array [0..3] ofByte);
1 : (wo : array [0..1] of Word) ;
: (lo : longint);
end;
В этом примере запись MEM4 имеет три варианта, каждый из которых занимает в памяти один и тот же участок из 4 байтов. В зависимости от того, к какому полю записи мы обращаемся в программе, этот участок может рассматриваться как массив из 4 байтов (поле BY), массив из двух целых типа WORD (поле wo) или, наконец, как одно целое число типа LONGINT (поле LO). Например, этой записи можно сначала присвоить значение как длинному целому, а затем проанализировать результат по байтам или словам:
var
х : Word;
xb: Byte;
xl: Longint;
begin
with m do ■
begin
:= trunc(2*pi*x);
if wo [1] = 0 then
if by[l] = 0 then xb := x[0]
else
x := wo[0]
else
xl := lo
end;
end.
Предложение CASE OF, открывающее вариантную часть, внешне похоже на соответствующий оператор выбора, но на самом деле лишь играет роль своеобразного служебного слова, обозначающего начало вариантной части. Именно поэтому в конце вариантной части не следует ставить END как пару к CASE OF. (Поскольку вариантная часть — всегда последняя в записи, за ней все же стоит END, но лишь как пара к RECORD). Ключ выбора в предложении CASE OF фактически игнорируется компилятором. Единственное требование, предъявляемое к нему Турбо Паскалем, состоит в том, чтобы
ключ определял некоторый стандартный или предварительно объявленный порядковый тип. Причем сам этот тип никак не влияет ни на количество следующих ниже вариантных полей, ни даже на характер констант выбора. В стандартном Паскале в качестве ключа выбора необходимо указывать некоторую переменную порядкового типа, причем в исполняемой части программы можно присваивать значение этой переменной и таким образом влиять на выбор полей. В Турбо Паскале также можно в поле ключа выбора указывать переменную порядкового типа и даже присваивать ей в программе значение, что однако не влияет на выбор поля: значения констант выбора в Турбо Паскале могут быть произвольными, в том числе повторяюши- мися, например:
type
reel = record
а : Byte;
b : Word;
end;
rec2 = record
с : longint;
case x : Byte of
: (d : Word);
: (e : record
case Boolean of
: (f : reel);
: (g : Single);
'3' : (c : Word)
end)
end;
var
r : rec2;
begin
r.x := 255;
if r.e.g = 0 then
WriteLn('O.K.')
else
WriteLn(r.e.g)
end.
В этом примере предложение
case Boolean of
в записи, определяемой в поле Е, объявляет ключом выбора логический тип, который, как известно, имеет лишь два значения — TRUE и FALSE. Константы же выбора следующих далее вариантов не толвко содержат совершенно не свойственные этому типу значения, но и две из них повторяются, а общее количество вариантов — три, а не два, как следовало бы ожидать.
Имена полей должны быть уникальными в пределах той записи, где они объявлены, однако, если записи содержат поля-записи, т. е. вложены одна в другую, имена могут повторяться на разных уровнях вложенности (см. поле с в последнем примере).
4.2.3. Множества
Множества — это наборы однотипных логически связанных друг с другом объектов. Характер связей между объектами лишь подразумевается программистом и никак не контролируется Турбо Паскалем. Количество элементов, входящих в множество, может меняться в пределах от 0 до 256 (множество, не содержащее элементов, называется пустым). Именно непостоянством количества своих элементов множества отличаются от массивов и записей.
Два множества считаются эквивалентными тогда и только тогда, когда все их элементы одинаковы, причем порядок следования элементов в множестве безразличен. Если все элементы одного множества входят также и в другое, говорят о включении первого множества во второе. Пустое множество включается в любое другое.
Пример определения и задания множеств:
type
digitChar set of ' O' ..'9’;
digit =setofО . .9;
var
sl,s2,s3 : digitChar;
4,s5,s6 : digit;
begin
si : =t '2', '3'];
=/ '2', 'I'];
=
= [0..3, 6];
[4, 5] ;
= [3..9] ;
end.
В этом примере множестваи S2 эквивалентны, а множествовключено
в S2, но не эквивалентно ему.
Описание типа множества имеет вид:
типа> = SET OP <баз.тип>
Здесь:
□типа> — правильный идентификатор;
О SET, OF — зарезервированные слова (множество,
Птип> — базовый тип элементов множества, в качестве которого может
использоваться любой порядковый тип, кроме WORD, INTEGER,
Для задания множества используется так называемый конструктор множества: список спецификаций элементов множества, отделяемых друг от друга запятыми; список обрамляется квадратными скобками (см. предыдущий пример). Спецификациями элементов могут быть константы или выражения базового типа, а также — тип-диапазон того же базового типа.
Над множествами определены следующие операции:
П * — пересечение множеств; результат содержит элементы, общие для обоих множеств; например, S4*s6 содержит— пустое множе
ство (см. выше);
П + — объединение множеств; результат содержит элементы первого множества, дополненные недостающими элементами из второго множества:
S4+S5 СОДерЖИТ [0,1,2,3,4,5,61;
S5+S6 содержит [3,4,5,6,7,8,91;
разность множеств; результат содержит элементы из первого множества, которые не принадлежат второму:
S6-S5 содержит
S4-S5 содержит
= — проверка эквивалентности; возвращает TRUE, если оба множества эквивалентны; .
проверка неэквивалентности; возвращает TRUE, если оба множества неэквивалентны;
П <= — проверка вхождения; возвращает TRUE, если первое множество включено во второе;
П >= — проверка вхождения; возвращает TRUE, если второе множество включено в первое;
П IN — проверка принадлежности; в этой бинарной операции первый элемент — выражение, а второй — множество одного и того же типа;
возвращает TRUE, если выражение имеет значение, принадлежащее множеству:
3 in s 6 возвращает TRUE
2*2 in si возвращает FALSE
Дополнительно к этим операциям можно использовать две процедуры.
О INCLUDE — включает новый элемент во множество. Обращение к процедуре:
INCLUDE (S, I)
Здесь:
s — множество, состоящее из элементов базового типа TSetBase;
I — элемент типа TSetBase, который необходимо включить во множество.
О EXCLUDE — исключает элемент из множества. Обращение: EXCLUDE (s, I) Параметры обращения — такие же, как у процедуры INCLUDE.
В отличие от операций + и реализующих аналогичные действия над двумя множествами, процедуры оптимизированы для работы с одиночными элементами множества и поэтому отличаются высокой скоростью выполнения.
В примере 4.1, иллюстрирующем приемы работы с множествами, реализуется алгоритм выделения из первой сотни натуральных чисел всех простых чисел. В его основе лежит прием, известный под названием "решето Эратосфена". В соответствии с этим алгоритмом вначале формируется множество BEGINSET, состоящее из всех целых чисел в диапазоне от 2 до N. В множество PRIMERSET (оно будет содержать искомые простые числа) помещается 1. Затем циклически повторяются два действия:
Взять из BEGINSET первое входящее в него число NEXT и поместить его в PRIMERSET.
Удалить из BEGINSET число NEXT и все другие числа, кратные ему, т. е. 2*NEXT, 3*NEXT И Т. Д.
Цикл повторяется до тех пор, пока множество BEGINSET не станет пустым.
ЭтупрограммунельзяиспользоватьдляпроизвольногоN,т.к.в любом
множестве не может быть больше 256 элементов.
Program Primer_numbers_detect;
{Выделение всех простых чисел из первых N целых)
const
N = 255; {Количество элементов исходного множества}
type
SetOENumber = set of 1..N; var
nl,next,i : Word;{Вспомогательные переменные)
BeginSet,{Исходное множество)
PrimerSet : SetOENumber; {Множество простых чисел)
begin
BeginSet := [2..NJ;(Создаемисходное множество}
PrimerSet:=[!];{Первоепростое число}
next2;(Следующее простое число)
while BeginSet о [] do(Начало основного цикла}
begin
nl :=очередному простому (next) }
(Цикл удаления из. исходного множества непростых чисел:} while nl <= N do
\
//begin
/
/
Exclude(BeginSet,nl);
nl := nl+next{Следующее кра тное}
end;(Конец цикла удаления}
Include(PrimerSet,next);
(Получаем следующее простое, которое есть первое невычеркнутое из исходного множества}
repeat
inc(next)
until (next in BeginSet) or (next > N) end; (Конец основного цикла)
{Выводим результат:}
for i := 1 to N do
if i in PrimerSet then Write (i:8);
WriteLn
end.
Перед тем как закончить рассмотрение множеств, полезно провести небольшой эксперимент. Измените описание типа SETOENUMBER следующим образом:
type
SetOENumber = set of 1. .1;
и еще раз запустите программу из предыдущего примера. На экран будет выведено
1357
Множества BeginSet и PrimerSet состоят теперь из одного элемента, а программа сумела поместить в них не менее семи!
Секрет этого прост: внутреннее устройство множества таково, что каждому его элементу ставится в соответствие один двоичный разряд (один бит); если элемент включен во множество, соответствующий разряд имеет значение
в противном случае — 0. Минимальной единицей памяти является один байт, содержащий 8 бит. Компилятор выделил множествам по одному байту, в результате мощность каждого из них стала равна 8 элементов. Максимальная мощность множества — 256 элементов. Для таких множеств компилятор выделяет по 16 смежных байтов.
И еще один эксперимент: измените диапазон базового типа на t—256. Хотя мощность этого типа составляет 256 элементов, при попытке компиляции программы компилятор сообщит:
Error 23: Set base type out of range.
(Ошибка 23: Базовый тип множества выходит за допустимые границы.)
Компилятор разрешает использовать в качестве базового типа целочисленный тип-диапазон с минимальной границей 0 и максимальной 255 или любой перечисленный тип не более чем с 256 элементами (максимальная мощность перечисленного типа — 65 536 элементов).\
Строки
Тип STRING (строка) в Турбо. Паскале широко используется для обработки текстов. Он во многом похож на одномерный массив символов ARRAY
[O..N] OF CHAR, однако, в отличие от последнего, количество символов в строке-переменной может меняться от 0 до N, ’где N — максимальное количество символов в строке. Значение N определяется объявлением типа STRING [N] и может быть любой константой порядкового типа, но не больше 255. Турбо Паскаль разрешает не указывать N, в этом случае длина строки принимается максимально возможной, а именно N — 255.
Строка в Турбо Паскале трактуется как цепочка символов. К любому символу в строке можно обратиться точно так же, как к элементу одномерного массива ARRAY to. .N] OF CHAR, например:
var st : String;
begin
if=then
end.
Самый первый байт в строке имеет индекс о и содержитдлину
строки. Первый значащий символ строки занимает второй байт и имеет индекс 1. Над длиной строки можно осуществлять необходимые действия и таким способом изменять длину. Например, удалить из строки все ведомые пробелы можно следующим образом:
var
st : String;
i : Byte;
begin
i— текущая длина строки)
while (i о 0) and (st[i] = 'do
begin
dec(i);
st[0]chr(i)
end;
end.
Значениет. е. текущую длину строки, можно получить и с помощью функциинапример:
whileand') do
st[0] :=
К строкам можно применять операцию + (сцепление), например:
st :=+
st := st т 'c'; (st содержит’ "abc"}
Если длина сцепленной строки превысит максимально допустимую длину N, то "лишние" символыСледующая программа, например,
напечатает символ
var
st: String
begin
st:='123’;
WriteLn(st)
end.
Все остальные действия над строками и символами реализуются с помощью описываемых ниже стандартных процедур и функций.
О CONCAT(SI [, S2, ...,SN] — функция типа STRING; возвращает строку, представляющую собой сцепление строк-параметров SI, S2, ..., SN.
COPY(ST, INDEX, COUNT) — фуНКЦИЯ типа STRING; копирует ИЗ СТрОКИ ST COUNT символов, начиная с символа с номером INDEX.
DELETE (ST, INDEX, COUNT) — процедура; удаляет COUNT символов из строки ST, начиная с символа с номером INDEX.
INSERT (SUBST, ST, INDEX) — процедура; вставляет подстроку SUBST в строку ST, начиная с символа с номером INDEX.
LENGTH (ST) — функция типа INTEGER; возвращает длину строки ST.
POS (SUBST, ST) — функция типа INTEGER; отыскивает в строке ST первое вхождение подстроки SUBST и возвращает номер позиции, с которой она начинается; если подстрока не найдена, возвращается ноль.
STR(X [:WIDTH [:DECIMALS]], ST) — процедура; преобразует число х любого вещественного или целого типов в строку символов st так, как это делает процедура WRITELN перед выводом; параметры WIDTH и DECIMALS, если они присутствуют, задают формат преобразования: WIDTH определяет общую ширину поля, выделенного под соответствующее символьное представление вещественного или целого числа х, a DECIMALS — количество символов в дробной части (этот параметр имеет смысл только в том случае, когда х — вещественное число).
П VAL(ST,x,CODE)—процедура;преобразуетстрокусимволовSTво
внутреннее представление целой или вещественной переменной х, которое определяется типом этой переменной; параметр CODE содержит ноль, если преобразование прошло успешно, и тогда в х помещается результат преобразования, в противном случае он содержит номер позиции в строке ST, где обнаружен ошибочный символ, и в этом случае содержимое х не меняется; в строке ST могут быть ведущие пробелы, однако ведомые пробелы недопустимы; например, обращение val ('123',k, i) пройдет
успешно: k получит значение 123, в i будет помещен 0, в то время как обращение vaK'123',k,l) будет ошибочным: значение k не изме
нится, a i будет содержать 4.
□ UPCASE(CH) — функция типа CHAR; возвращает соответствующую заглавную букву для символьного выражения сн, которое должно представлять собой строчную латинскую букву; если значением сн является любой другой символ (в том числе строчная буква русского алфавита), функция возвращает его без преобразования.
Примеры:
var
х : Real;
у Integer;
st,stl: String;
begin
{строка st содержит 12345) {stl содержит 345}{строка stl содержит 3-45} {строка st содержит 15} {строка st содержит 3.14} (У содержит 2, х остался без изменения){строка st содержит 12345) {stl содержит 345}{строка stl содержит 3-45} {строка st содержит 15} {строка st содержит 3.14} (У содержит 2, х остался без изменения)st := concat('12','345');
{строка st содержит 12345) {stl содержит 345}
{строка stl содержит 3-45} {строка st содержит 15} {строка st содержит 3.14} (У содержит 2, х остался без изменения)
{строка st содержит 12345) {stl содержит 345}
{строка stl содержит 3-45} {строка st содержит 15} {строка st содержит 3.14} (У содержит 2, х остался без изменения)
stl := copy(st,3,Length(st)-2);
insert('-',stl,2);
delete(st,pos('2',st),3);
str(pi:6:2,st);
val('3,1415',x,y);
end.
Операции отношения =, о, >, <, >=, <= выполняются над двумя строками посимвольно, слева направо с учетом внутренней кодировки символов (см. табл. 4.1 и приложение 2). Если одна строка меньше другой по длине, недостающие символы короткой строки заменяются значением CHR(0).
Следующие операции отношения дадут значение TRUE:
аiiti < ii
а 'A* > '1'
□ ’Turbo' < 'Turbo Pascal'
□ 'Паскаль' > 'Turbo Pascal'
Совместимость
и преобразование типов
Как уже неоднократно отмечалось, Турбо Паскаль — это типизированный язык. Он построен на основе строгого соблюдения концепции типов, в соответствии с которой все применяемые в языке операции определены только над операндами совместимых типов. При обсуждении операций над вещественными данными мы уже затрагивали проблему совместимости вещественных и целых типов. Аналогичные проблемы возникают при операциях над строками разной длины, строками и символами и т. д. Ниже приводится более полное определение совместимости типов.
Два типа считаются совместимыми, если:
О оба они есть один и тот же тип;
□ оба вещественные;
П оба целые;
О один тип есть тип-диапазон второго типа;
П оба являются типами-диапазонами одного и того же базового типа;
П оба являются множествами, составленными из элементов одного и того же базового типа;
П оба являются упакованными строками (определены с предшествующим словом PACKED) одинаковой максимальной длины;
П один тип есть тип-строка, а другой — тип-строка, упакованная строка или символ;
П один тип есть любой указатель, а другой — нетипизированный указатель;
О один тип есть указатель на объект, а другой — указатель на родственный ему объект;
П оба есть процедурные типы с одинаковыми типом результата (для типа- функции), количеством параметров и типом взаимно соответствующих параметров.
Совместимость типов приобретает особое значение в операторах присваивания. Пусть Т1 — тип переменной, а Т2 — тип выражения, т. е. выполняется присваивание Т1 Т2. Это присваивание возможно в следующих случаях:
О Т1 и Т2 есть один и тот же тип и этот тип не относится к файлам или массивам файлов, или записям, содержащим поля-файлы, или массивам таких записей;
П Т1 и Т2 являются совместимыми порядковыми типами и значение Т2 лежит в диапазоне возможных значений и;
О Т1 и Т2 являются вещественными типами и значение Т2 лежит в диапазоне возможных значений Ti;
□ Т1 — вещественный тип и Т2 — целый тип;
П Т1 — строка и т2 — символ;
П Т1 — строка и Т2 — упакованная строка;
П Т1 и Т2 — совместимые упакованные строки;
О Т1 и Т2 — совместимые множества и все члены т2 принадлежат множеству возможных значений ti;
П Т1 и Т2 — совместимые указатели; "
о" Т1 и Т2 — совместимые процедурные типы;
П Т1 — объект и Т2 — его потомок.
В программе данные одного типа могут преобразовываться в данные другого. Такое преобразование может быть явным или неявным.
При явном преобразовании типов используются вызовы специальных функций преобразования, аргументы которых принадлежат одному типу, а значение — другому. Таковыми являются уже рассмотренные функции ORD, TRUNC, ROUND, CHR. В главе 6 описывается функция PTR, преобразующая четырехбайтный целочисленный аргумент к типу-указателю.
В Турбо Паскале существует и более общий механизм преобразования типов, согласно которому преобразование достигается применением идентификатора (имени) стандартного типа или типа, определенного пользователем, как идентификатора функции преобразования к выражению преобразуемого типа (так называемое автоопределенное преобразование типов). Например, допустимы следующие вызовы функций:
type
MyType = (а, b, с, d) ;
MyType (2)
Integer CD')
pointer (longint(a) + $FF)
Char (127 mod c)
Byte (k)
При автоопределенном преобразовании типа выражения может произойти изменение длины его внутреннего представления (длина может увеличиться или уменьшиться).
В Турбо Паскале определен еще один явный способ преобразования данных: в ту область памяти, которую занимает переменная некоторого типа, можно поместить значение выражения другого типа, если только длина внутреннего представления вновь размещаемого значения в точности равна длине внутреннего представления переменной. С этой целью вновь используется автоопределенная функция преобразования типов, но уже в левой части оператора присваивания:
type
byt = array [1..2] of Byte;
int = array [1.-2] of Integer;
rec = record
x, у : Integer
end;
var
:.
vint : int; • •
vrec : rec;
begin
byt(vint[1])[2] := 0;
int(vrec)[1]:= 256
end.
Неявное преобразование типов возможно только в двух случаях:
в выражениях, составленных из вещественных и целочисленных переменных, последние автоматически преобразуются к вещественному типу, и все выражение в целом приобретает вещественный тип;
одна и та же область памяти попеременно трактуется как содержащая данные то одного, то другого типа (совмещение в памяти данных разного типа).
Совмещение данных в памяти может произойти при использовании записей с вариантными полями (см. подразд. 4.2.2), типизированных указателей, содержащих одинаковый адрес (см. главу 6), а также при явном размещении данных разного типа по одному и тому же абсолютному адресу. Для размещения переменной по нужному абсолютному адресу она описывается с последующей стандартной директивой ABSOLUTE, за которой помещается либо абсолютный адрес, либо идентификатор ранее определенной переменной. Абсолютный адрес указывается парой чисел типа-WORD, разделенных двоеточием; первое число трактуется как сегмент, второе — как смещение адреса (см. главу 6). Пример:
Ь : Byte absolute $0000:$0055;
w : Longlnt absolute 128:0;
Если за словом ABSOLUTE указан идентификатор ранее определенной переменной, то происходит совмещение в памяти данных разного типа, причем первые байты внутреннего представления этих данных будут располагаться по одному и тому же абсолютному адресу, например: var
х : Real;
у : array [1..3] of Integer absolute x;
В этом примере переменные х и Y будут размещены, начиная с одного и того же абсолютного адреса. Таким образом, одну и ту же область памяти длиной 6 байт, а следовательно, и размещенные в этой области данные теперь можно рассматривать как данные либо типа REAL, либо как массив из
трех данных типа INTEGER. Например, следующая программа выдаст на экран содержимое первых двух байтов внутреннего представления вещественного числа л 3,1415 в виде целого числа:
var
х : Real;
у : array [1..3] of Integer absolute x;
begin
x := pi;
WriteLn(у[1])
end.
На экран будет выведен результат 8578.
Неявные преобразования типов могут служить источником трудно обнаруживаемых ошибок в программе, поэтому везде где это возможно, следует избегать их.
Глава5
Файлы
Под файлом понимается либо именованная область внешней памяти ПК (жесткого диска, гибкой дискеты, электронного "виртуального" диска), либо логическое устройство — потенциальный источник или приемник информации.
Любой файл имеет три характерные особенности. Во-первых, у него есть имя, что дает возможность программе работать одновременно с несколькими файлами. Во-вторых, он содержит компоненты одного типа. Типом компонентов может быть любой тип Турбо Паскаля, кроме файлов. Иными словами, нельзя создать "файл файлов". В-третьих, длина вновь создаваемого файла никак не оговаривается при его объявлении и ограничивается только емкостью устройств внешней памяти.
Файловый тип или переменную файлового типа можно задать одним из трех способов:
<имя> = FILE OF <тип>;
О <имя> TEXT;
<имя> = FILE;
Здесь:
О <имя> — имя файлового типа (правильный идентификатор);
FILE, OF — зарезервированные слова (файл, из)-,
О TEXT — имя стандартного типа текстовых файлов;
<тип> — любой тип Турбо Паскаля, кроме файлов.
Например:
type
product = record
name : String;
code : Word;
cost : comp
end;
text80 = file of String [80];
var fl : file of char; f2 : text; f3 : file; f4 : f5 :of product;
В зависимости от способа объявления можно выделить три вида файлов: □ типизированные файлы (задаются предложением FILE of...);
П текстовые файлы (определяются типом TEXT);
П нетипизированные файлы (определяются типом FILE).
В наших примерах Fl, F4 и F5 — типизированные файлы, F2 — текстовый файл, F3 — нетипизированный файл. Вид файла, вообще говоря, определяет способ хранения информации в файле. Однако в Турбо Паскале нет средств контроля вида ранее созданных файлов. При объявлении уже существующих файлов программист должен сам следить за соответствием вида объявления характеру файла.
Доступ к файлам
Любой программе доступны два предварительно объявленных файла со стандартными файловыми переменными: INPUT — для чтения данных с клавиатуры и OUTPUT — для вывода на экран. Стандартный Паскаль требует обязательного упоминания этих файлов в заголовке программы, например, так: PROGRAM NameOf Program (input, output);
В Турбо Паскале это не обязательно, вот почему заголовок программы можно опускать.
Любые другие файлы, а также логические устройства становятся доступны программе только после выполнения особой процедуры открытия файла (логического устройства). Эта процедура заключается в связывании ранее объявленной файловой переменной с именем существующего или вновь создаваемого файла, а также в указании направления обмена информацией: чтение из файла или запись в него.
Файловая переменная связывается с именем файла в результате обращения к стандартной процедуре ASSIGN:
ASSIGN (<ф.п.>, <имя файла или л.у.>);
Здесь:
О <ф.п. > — файловая переменная (правильный идентификатор, объявленный в программе как переменная файлового типа);
О <имя файла или л.у. > — текстовое выражение, содержащее имя файла или логического устройства.
Если имя файла задается в виде пустой строки, например, ASSIGN (f, ' 1), то в зависимости от направления обмена данными файловая переменная связывается со стандартным устройством INPUT или OUTPUT. •
Имена файлов
Имя файла — это любое выражение строкового типа, которое строится по правилам определения имен в MS-DOS (операционной системе ПК):
имя содержит до восьми разрешенных символов; разрешенные символы — это прописные и строчные латинские буквы, цифры, а также символы ! @ #$ %а&о ' — _;
имя начинается с любого разрешенного символа;
О за именем может следовать расширение — последовательность до трех разрешенных символов; расширение, если оно есть, отделяется от имени точкой.
Перед именем может указываться так называемый путь к файлу: имя диска и/или имя текущего каталога и имена каталогов вышестоящих уровней.
Имя диска — это один из символов A-Z, после которого ставится двоеточие. Имена А: и В: относятся к дисковым накопителям на гибких дискетах, имена С:, D: и т. д. — к жестким дискам. Эти имена могут относиться также к одному или нескольким виртуальным дискам, созданным в оперативной памяти ПК специальной командой VDISK в ходе выполнения файла автоустановки CONFIG.SYS дисковой операционной системы.
Если имя диска не указано, подразумевается устройство по умолчанию — то, которое было установлено в операционной системе перед началом работы программы.
За именем диска может указываться имя каталога, содержащего файл. Если имени каталога предшествует обратная косая черта, то путь к файлу начинается из корневого каталога, если черты нет — из текущего каталога, установленного в системе по умолчанию. За именем каталога может следовать одно или несколько имен каталогов нижнего уровня. Каждому из них должна предшествовать обратная косая черта. Весь путь к файлу отделяется от имени файла обратной косой чертой. Максимальная длина имени вместе с путем — 79 символов, например:
var
finp :
fout : file of String;
const
name = 'c: \dir\subdir\oiit. txt';
begin
assign(finp,'123.dat');
assign(fout,name);
end.
Логические устройства
Стандартные аппаратные средства ПК, такие как клавиатура, экран дисплея, печатающее устройство (принтер) и коммуникационные каналы ввода/вывода, определяются в Турбо Паскале специальными именами, которые называются логическими устройствами. Все они в Турбо Паскале рассматриваются как потенциальные источники или приемники текстовой информации.
□ CON — логическое имя, которое определяет консоль — клавиатуру или экран дисплея. Турбо Паскаль устанавливает различие между этими физическими устройствами по направлению передачи данных: чтение данных возможно только с клавиатуры, а запись — только на экран. Таким образом, с помощью логического устройства CON нельзя, например, прочитать данные с экрана ПК, хотя такая аппаратная возможность существует.
Ввод с клавиатуры буферизуется: символы по мере нажатия на клавиши помещаются в специальный строковый буфер, который передается программе только после нажатия на клавишу <Enter>. Буферизация ввода обеспечивает возможность редактирования вводимой строки стандартными средствами MS-DOS. При вводе символов осуществляется их эхо-повтор на экране ПК. В Турбо Паскале можно прочитать любой символ клавиатуры, в том числе и символ CR, вырабатываемый клавишей <Enter>, сразу после нажатия на соответствующую клавишу без эхо-повтора.
П PRN — логическое имя принтера. Если к ПК подключено несколько принтеров, доступ к ним осуществляется по логическим именам LPTl, LPT2 и LPT3. Имена PRN и LPTl первоначально — синонимы. Средствами MS-DOS можно присвоить имя PRN любому другому логическому устройству, способному принимать информацию.
Стандартный библиотечный модуль PRINTER, входящий в библиотеку TURBO.TPL, объявляет имя файловой переменной LST и связывает его с логическим устройством LPTl. Это дает возможность использовать простое обращение к принтеру. Например, программа
Uses Printer;
begin
WriteLn(LST, 'Привет, мир! ')
end.
выведет на принтер фразу "Привет, мир!", а все необходимые операции по открытию логического устройства выполнит библиотечный блок PRINTER (подробности работы с модулями см. в главе 9).
□ AUX — логическое имя коммуникационного канала, который обычно используется для связи ПК с другими машинами. Коммуникационный канал может осуществлять и прием, и передачу данных, однако в программе в каждый момент времени ему можно назначить только одну из этих функций. Как правило, в составе ПК имеются два коммуникационных канала, которым даются имена логических устройств СОМ1 и COM2. Первоначально имена AUX и СОМ1 — синонимы.
О NUL — логическое имя "пустого" устройства. Это устройство чаще всего используется в отладочной режиме и трактуется как устройство-приемник информации неограниченной емкости. При обращении к NUL как источнику информации выдается признак конца файла EOF.
Связывание логического устройства с файловой переменной осуществляется процедурой ASSIGN, например:
var
fi, fo :
begin
assign(fi,'AUX');
assign(fo,'LPT2');
end.
Турбо Паскаль никогда не связывает имена логических устройств с дисковыми файлами, в этом смысле такие имена можно считать зарезервированными. Иными словами, нельзя, например, обратиться к дисковому файлу с именем PRN, т. к. Турбо Паскаль всегда интерпретирует такой запрос как обращение к принтеру.
Инициация файла
Инициировать файл означает указать для этого файла направление передачи данных. В Турбо Паскале можно открыть файл для чтения, для записи информации, а также для чтения и записи одновременно.
Для чтения файл инициируется с помощью стандартной процедуры RESET:
RESET (<ф.п.>);
Здесь <ф. п. > — файловая переменная, связанная ранее. процедурой ASSIGN с уже существующим файлом или логическим устройством-приемником информации.
При выполнении этой процедуры дисковый файл или логическое устройство подготавливаются к чтению информации. В результате специальная пе-
ременная-указатель, связанная с этим файлом, будет указывать на начало файла, т. е. на компонент с порядковым номером 0.
Если делается попытка инициировать чтение из несуществующего файла или из логического устройства PRN, возникает ошибка периода исполнения, которая может быть сообщена программе ненулевым значением встроенной функции IORESULT типа WORD. Например, следующий фрагмент программы позволяет установить, существует ли требуемый файл на диске:
var
f: file of char;
begin
assign(f,'myfile.dat');
{SI-}(Отключаем контроль ошибок ввода-вывода}
reset(f);
{$!+} {Включаем контроль ошибок ввода-вывода}
if lOResult О 0 then
{Файл не существует)
else
(Файл существует)
end.
В этом фрагменте с помощью директивы компилятора {$!-} отключается автоматический контроль ошибок ввода/вывода. Если этого не сделать, то отсутствие файла приведет к аварийному завершению программы.
В Турбо Паскале разрешается обращаться к типизированным файлам, открытым процедурой RESET (т. е. для чтения информации), с помощью процедуры WRITE (т. е. для записи информации). Такая возможность позволяет легко обновлять ранее созданные типизированные файлы и при необходимости расширять их. Для текстовых файлов, открытых процедурой RESET, нельзя использовать процедуру WRITE или WRITELN.
Стандартная процедура
REWRITE (<ф.п.>)
инициирует запись информации в файл или в логическое устройство, связанное ранее с файловой переменной <ф. п. >. Процедурой REWRITE нельзя инициировать запись информации в ранее существовавший дисковый файл: при выполнении этой процедуры старый файл уничтожается и никаких сообщений об этом в программу не передается. Новый файл подготавливается к приему информации и его указатель принимает значение 0.
Стандартная процедура
APPEND (<ф.п.>)
инициирует запись в ранее существовавший текстовый файл для его расширения, при этом указатель файла устанавливается в его конец. Процедура APPEND применима только к текстовым файлам, т. е. их файловая переменная должна иметь тип TEXT (см. выше). Процедурой APPEND нельзя инициировать запись в типизированный или нетипизированный файл. Если текстовый файл ранее уже был открыт с помощью RESET или REWRITE, использование процедуры APPEND приведет к закрытию этого файла и открытию его вновь, но уже для добавления записей.
Процедуры и функции
для работы с файлами
Ниже описываются процедуры и функции, которые можно использовать с файлами любого вида. Специфика работы с типизированными, текстовыми и нетипизированными файлами рассматривается в следующих разделах.
Процедура close. Закрывает файл, однако связь файловой переменной с именем файла, установленная ранее процедурой ASSIGN, сохраняется. Формат обращения:
CLOSE (<ф.п.>)
При создании нового или расширении старого файла процедура обеспечивает сохранение в файле всех новых записей и регистрацию файла в каталоге. Функции процедуры CLOSE выполняются автоматически по отношению ко всем открытым файлам при нормальном завершении программы. Поскольку связь файла с файловой переменной сохраняется, то для повторного открытия файла процедура ASSIGN не нужна.
Процедура RENAME. Переименовывает файл. Формат обращения:
RENAME (<ф.п.>, <новое имя>)
Здесь <новое имя> — строковое выражение, содержащее новое имя файла.
Перед выполнением процедуры необходимо закрыть файл, если он ранее был открыт процедурами RESET, REWRITE или APPEND.
Процедура ERASE. Уничтожает файл. Формат обращения:
ERASE (<ф.п.>)
Перед выполнением процедуры необходимо закрыть файл, если он ранее был открыт процедурами RESET, REWRITE или APPEND.
Следующий фрагмент программы показывает, как можно использовать процедуры RENAME и CLOSE при работе с файлами. Предположим, что требуется отредактировать файл, имя которого содержит переменная NAME. Перед редактированием необходимо убедиться, что нужный файл имеется на диске,
и переименовать его — заменить расширение этого файла на ВАК (страховочная копия). Если файл с таким расширением уже существует, его надо стереть.
var
fi : text; (Исходный файл}. ;
fo : text; {Отредактированный файл)
name: String;
name_bak: String;
k,i: Word;
const
bak = ’.bak';
begin
(Получаем в name_bak имя файла с расширением ВАК:} k := роз('.',name);
if k = 0 then
k := length(name) + 1;
name_bak :=+ bak;
(Проверяем существование исходного файла:} assign(fi,name);
($1-)
reset(fi);
if lOResult <> 0 then
halt; (Завершаем программу: файла не существует} close(fi);
(Проверяем существованиеВАК-файла:)
assign(fо,name_bak) ;
reset(fo);
($!+}
if lOResult = 0 then
begin(Файл ВАК существует:}
close(fo);close(fo);erase(fo)erase(fo){Закрываем его}
close(fo);
close(fo);
erase(fo)
erase(fo)
{и уничтожа ем)
end;
(Проверки закончены, подготовка к работе:/ rename (fi,name_bak);
reset(fi);
assign(fо,name);
rewrite(fо) ;
end.
Обратите внимание, что проверка на существование файла с расширением ВАК в данном примере необходима, т. к. обращение
rename(fi, name_bak);
вызовет ошибку в случае, если такой файл существует.
Процедура FLUSH. Очищает внутренний буфер файла и, таким образом, гарантирует сохранность всех последних изменений файла на диске. Формат обращения:
FLUSH (<ф.п.>)
Любое обращение к файлу в Турбо Паскале осуществляется через некоторый буфер, что необходимо для согласования внутреннего представления файлового компонента (записи) с принятым в MS-DOS форматом хранения данных на диске. В ходе выполнения процедуры FLUSH все новые записи будут действительно записаны на диск. Процедура игнорируется, если файл был инициирован для чтения процедурой RESET.
Функция EOF (<ф.п.>) : BOOLEAN. Логическая функция, тестирующая конец файла. Возвращает TRUE, если файловый указатель стоит в конце файла. При записи это означает, что очередной компонент будет добавлен в конец файла, при чтении — что файл исчерпан.J
Процедура CHOIR. Изменение текущего каталога. Формат обращения:
CHDIR (<путь>)
Здесь <путь> — строковое выражение, содержащее путь к устанавливаемому по умолчанию каталогу.
Процедура GETDIR. Позволяет определить имя текущего каталога (каталога по умолчанию). Формат обращения:
GETDIR (<устройство>, <каталог>)
Здесь:
<устройство> — выражение типа WORD, содержащее номер устройства: о — устройство по умолчанию, 1 — дискA:, 2 — диск В: и т. д.;
<каталог> — переменная типа STRING, в которой возвращается путь к текущему каталогу на указанном диске.
Процедура MKDIR. Создает новый каталог на указанном диске. Формат обращения:
MKDIR (<каталог>)
Здесь <каталог> — выражение типа STRING, задающее путь к каталогу. Последним именем в пути, т. е. именем вновь создаваемого каталога не может быть имя уже существующего каталога.
Процедура RMDIR. Удаляет каталог. Формат обращения:
RMDIR (<каталог>)
Удаляемый каталог должен быть пустым, т. е. не содержать файлов или имен каталогов нижнего уровня.
ФункцияWORD. Возвращает условный признак последней опера
ции ввода/вывода.
Если операция завершилась успешно, функция возвращает ноль. Коды ошибочных операций ввода/вывода представлены в приложении 3. Следует помнить, что IORESULT становится доступной только при отключенном автоконтроле ошибок ввода/вывода. Директива компилятораотключает,
авключает автоконтроль. Если автоконтроль отключен, а операция
ввода/вывода привела к возникновению ошибки, устанавливается флаг ошибки и все последующие обращения к вводу/выводу блокируются, пока не будет вызвана функция IORESULT.
Ряд полезных файловых процедур и функций становится доступным при использовании библиотечного модулявходящего в стандартную
библиотеку TURBO.TPL. Эти процедуры и функции указаны ниже. Доступ к ним возможен только после объявления USES DOS в начале программы (подробнее о работе с модулями см. главу 9).
Функция«диск» : LONGINT. Возвращает объем в байтах свобод
ного пространства на указанном диске. При обращении к функции выражение <диск> типа BYTE определяет номер диска: о — устройство по умолчанию, 1 — диск2 — диск В: и т. д. Функция возвращает значение -1,
если указан номер несуществующего диска.
Функция DISKSIZE «диск» : LONGINT. Возвращает полный объем диска в байтах или -1, если указан номер несуществующего диска.
ПроцедураВозвращает атрибуты первого из файлов, зарегистри
рованных в указанном каталоге. Формат обращения:
FINDFIRST
Здесь:
— строковое выражение, содержащее маску файла;
О— выражение типа BYTE, содержащее уточнение к маске
(атрибуты);
О — переменная типа SEARCHREC, в которой будет возвращено имя файла.
При формировании маски файла используются следующие символы — заменители MS-DOS:
* — означает, что на месте этого символа может стоять сколько угодно (в том числе ноль) разрешенных символов имени или расширения файла;
? — означает, что на месте этого символа может стоять один из разрешенных символов.
Например:
□ *. * — выбирает все файлы из каталога;
□ с*. * — выбирает все файлы с именами, начинающимися на "с" (cl.pas, СС12345, c.dat и т. д.);
П a?.dat — выбирает имена файлов типа a0.dat, az.dat и т. д.
Маске может предшествовать путь. Например, строка
c:\dir\subdir\*.pas
означает выбирать все файлы с расширением PAS из каталога SUBDIR, находящегося на диске С:; каталог SUBDIR зарегистрирован в каталоге верхнего уровня DIR, который, в свою очередь, входит в корневой каталог.
Байт <атрибуты> содержит двоичные разряды (биты), уточняющие, к каким именно файлам разрешен доступ при обращении к процедуре FINDFIRST. Вот как объявляются файловые атрибуты в модуле DOS.TPU:
const
Readonly = $01; (только чтение)
Hidden= $02; {скрытый файл}
SysFile = $04; {системный файл}
VolumelD = $08; {идентификатор тома)
Directory = $10; {имя подкаталога)
Archive= $20; {архивный файл)
AnyFile =
Комбинацией битов в этом байте можно указывать самые разные варианты, например $06 — выбирать все скрытые и/или системные файлы.
Результат работы процедуры FINDFIRST возвращается в переменной типа SEARCHREC. Этот тип в модуле DOS.TPU определяется следующим образом:
type
SearchRec = record
Fill : array [1..21] of Byte;
Attr : Byte;
Time : Longlnt;
Size : Longlnt;
Name : String [12]
end;
Здесь:
Attr — атрибуты файла (см. выше);
Time — время создания или последнего обновления файла; возвращается в упакованном формате; распаковать параметр можно процедурой UNPACKTIME (см. ниже);
Size — длина файла в байтах;
Name — имя и расширение файла.
Для распаковки параметра TIME используется процедура
UNPACKTIME(Time: Longlnt; var T:DateTime).
В модуле DOS.TPU объявлен следующий тип DateTime: type
DateTime = record
year : Word; (год в формате 19XX)
month Word; (месяц 1..12}
day Word; (день 1..31}
hour Word; (час 0..23}
min : Word; {минуты 0. . 59}
sec Word {секунды 0. . 59}
end;
Результат обращения к процедуре FINDFIRST можно проконтролировать с помощью функции DOSERROR типа WORD, которая возвращает следующие значения:
О — нет ошибок;
2 — не найден каталог;
О 18 — каталог пуст (нет указанных файлов).-
Процедура FINDNEXT. Возвращает имя следующего файла в каталоге. Формат обращения:
FINDNEXT (<сл.файл>)
Здесь <сл.файл> — запись типа SEARCHREC (см. выше), в которой возвращается информация о файле.
Следующая простая программа иллюстрирует способ использования процедур FINDFIRST И FINDNEXT.
Программа выводит на экран список всех PAS-файлов текущего каталога:
Uses DOS;'
var
SearchRec;
begin
FindFirst('*.pas',AnyFile,S);
while DosError = 0 do
begin
with S do
WriteLn(Name:12,Size:12); FindNext(S) end > end.
Процедура GETFTIME. Возвращает время создания или последнего обновления файла. Формат обращения:
GETFTIME (<ф.п.>, <время>)
Здесь <время> — переменная типа LONGINT, в которой в упакованном формате возвращается значение времени.
Процедура SETFTIME. Устанавливает новую дату создания или обновления файла. Формат обращения: SETFTIME (<ф.п.>, <время>)
Здесь <время> — время и дата в упакованном формате.
Упаковать запись типа DATETIME в переменную типа LONGINT можно процедурой
PACKTIME (var T:DateTime; var Time:Longlnt).
(Описание типа DATETIME см. выше).
Процедура GETFATTR. Позволяет получить атрибуты файла. Формат обращения:
GETFATTR (<ф.п.>, <атрибуты>)
Здесь <атрибуты> — переменная типа WORD, в младшем байте которой возвращаются устанавливаемые атрибуты файла.
Процедура SETFATTR. Позволяет установить атрибуты файла. Формат обращения: SETFATTR<атрибуты>)
Функция FSEARCH : PATHSTR. Ищет файл в списке каталогов. Формат вызова: FSEARCH (<имя>, <сп.каталогов>)
Здесь:
<имя> — имя отыскиваемого файла (строковое выражение или переменная типа PATHSTR; имени может предшествовать путь);
<сп.каталогов> — список каталогов, в которых отыскивается файл (строковое выражение или переменная типа STRING); имена каталогов разделяются точкой с запятой.
Результат поиска возвращается функцией FSEARCH в виде строки типа PATHSTR. В строке содержится путь и имя файла, если поиск был успешным, в противном случае возвращается пустая строка.
Тип PATHSTR в модуле DOS.TPU объявлен следующим образом:
type
PathStr = String[79];'
Следует учесть, что поиск файла всегда начинается в текущем каталоге и только после этого продолжается в тех, что перечислены в списке <сп.каталоговх Если файл обнаружен, дальнейший поиск прекращается, даже если часть каталогов осталась непросмотренной. В частности, если файл зарегистрирован в текущем каталоге, он "заслонит" собой одноименные файлы в других каталогах.
Пусть, например, на диске в каталоге SUBDIR имеется файл MYFILE.PAS. Тогда в случае, если текущий каталог — корневой, обращение
FSEARCH ('MYFILE,PAS','\SUB; \SUBDIR')
вернет строку \SUBDIR\MYFILE.РАЗ, а обращение
FSEARCH ('MYFILE. PAS', '\SUB')
вернет пустую строку. Однако, если текущим установлен каталог SUBDIR, то в обоих случаях вернется строка MYFILE.PAS (если файл находится в текущем каталоге, в выходной строке путь к нему не указывается).
Процедура FSPLIT. "Расщепляет" имя файла, т. е. возвращает в качестве отдельных параметров путь к файлу, его имя и расширение. Формат обращения: FSPLIT (<файл>, <путь>, <имя>, <расширение>)
Здесь:
<файл> — строковое выражение, содержащее спецификацию файла (имя с расширением и, возможно, с предшествующим путем);
<путь> — переменная типа DIRSTR=STRING [67], в которой возвращается путь к файлу;
О <имя> — переменная типа NAMESTR=STRING [ 8] , в которой возвращается имя файла;
О <расширение> — переменная типа EXTSTR=STRING [4 ], в которой возвращается расширение с предшествующей ему точкой.
Процедура не проверяет наличие на диске указанного файла. В качестве входного параметра может использоваться переменная типа PATHSTR.
Функция FEXPAND: PATHSTR. Дополняет файловое имя до полной спецификации, т. е. с указанием устройства и пути. Формат вызова:
FEXPAND (<файл>)
Здесь <файл> — строковое выражение или переменная типа PATHSTR.
Функция не проверяет наличие указанного файла на диске, а просто дополняет имя файла недостающими параметрами — текущим устройством и путем к текущему каталогу. Результат возвращается в строке типа PATHSTR.
Текстовые файлы
Текстовые файлы связываются с файловыми переменными, принадлежащими стандартному типу TEXT. Текстовые файлы предназначены для хранения текстовой информации. Именно в такого типа файлах хранятся, например, исходные тексты программ. Компоненты (записи) текстового файла могут иметь переменную длину, что существенно влияет на характер работы с ними.
Текстовый файл трактуется в Турбо Паскале как совокупность строк переменной длины. Доступ к каждой строке возможен лишь последовательно, начиная с первой. При создании текстового файла в конце каждой записи (строки) ставится специальный признак EO LN (End Of LiNe — конец строки), а в конце всего файла — признак EOF (End Of File — конец файла). Эти признаки можно протестировать одноименными логическими функциями (см. ниже). При формировании текстовых файлов используются следующие системные соглашения:
EOLN — последовательность кодов ASCII #13 (CR) и #10 (LF);
EOF — код #26 стандарта ASCII.
Для доступа к записям применяются процедуры READ, READLN, WRITE, WRITELN. Они отличаются возможностью обращения к ним с переменным количеством фактических параметров, в качестве которых могут использоваться символы, строки и числа. Первым параметром в любой из перечисленных процедур может стоять файловая переменная. В этом случае осуществляется обращение к дисковому файлу или логическому устройству, связанному с переменной процедурой ASSIGN. Если файловая переменная не указана, происходит обращение к стандартным устройствам INPUT и OUTPUT.
6 Зак 941
Процедура READ. Обеспечивает ввод символов, строк и чисел. Формат обращения:
READ (<ф. п. >, <сп.ввода>) или READ (<сп.ввода>)
Здесь <сп.ввода> — список ввода: последовательность из одной или более переменных типа CHAR, STRING, а также любого целого или вещественного типа.
При вводе переменных типа CHAR выполняется чтение одного символа из файла и присваивание считанного значения переменной. Если перед выполнением чтения указатель файла достиг конца очередной строки, то результатом чтения будет символ CR (ASCII код #13), а если достигнут конец файла, то — символ EOF (код #26). При вводе с клавиатуры символ CR вводится при нажатии на клавишу <Enter>, а символ EOF — при одновременном нажатии клавиш <Ctrl>+<Z> .
При вводе переменных типа STRING количество считанных процедурой и помещенных в строку символов равно максимальной длине строки, если только раньше не встретились символы CR или EOF. В этом случае сами символы CR и EOF в строку не помещаются. Если количество символов во входном потоке данных больше максимальной длины строки, "лишние" символы до конца строки отбрасываются, а новое обращение к READ возвращает пустую строку. Таким образом, процедура READ не в состоянии прочесть последовательность строк: первая строка будет прочитана нормально, а все последующие окажутся пустыми. Для ввода последовательности строк нужно использовать процедуру READLN (см. ниже).
При вводе числовых переменных процедура READ вначале выделяет подстроку во входном потоке по следующему правилу: все ведущие пробелы, символы табуляции и маркеры конца строк EOLN пропускаются; после выделения первого значащего символа, наоборот, любой из перечисленных символов или символ EOF служат признаком конца подстроки. Выделенная таким образом подстрока затем рассматривается как символьное представление числовой константы соответствующего типа и преобразуется во внутреннее представление, а полученное значение присваивается переменной. Если в подстроке был нарушен требуемый формат представления численной константы, возникает ошибка ввода/вывода. Если при пропуске ведущих пробелов встретился символ EOF, переменная получает значение 0. Отметим, что в Турбо Паскале не предусмотрен ввод шестнадцатеричных констант.
При использовании процедуры READ применительно к стандартному устройству INPUT, т. е. при вводе с клавиатуры, символьные строки запоминаются в буфере, который передается процедуре только после нажатия на клавишу <Enter>. Это позволяет редактировать данные при их вводе. Для редактирования используются следующие клавиши:
<Backspace>, <Ctrl>+<H>, <<-> — стирают символ слева от курсора;
<-*> — восстанавливает символ за символом предыдущую строку ввода;
<Ctrl>+<Z>, <Enter> — завершает ввод по процедуре READ; оставшиеся "лишние" символьные параметры принимают значение CHR(26), строки возвращаются пустыми, а численные переменные остаются без изменения.
Максимальная длина буфера ввода при работе с клавиатурой составляет 127 символов. Ввод с клавиатуры по процедуре READ сопровождается эхо- повтором вводимых символов на экране ПК.
Процедура READ прекрасно приспособлена к вводу чисел. При обращении к ней за вводом очередного целого или вещественного числа процедура "перескакивает" маркеры конца строк, т. е. фактически весь файл рассматривается ею как одна длинная строка, содержащая текстовое представление чисел. В сочетании с проверкой конца файла функцией EOF процедура READ позволяет организовать простой ввод массивов данных, например, так:
const
N = 1000;(Максимальная длина ввода}
var
f : text;
ш : array [1..N] of real;
i : Integer;
begin
assign(f, 'prog.dat');
reset (f);
i1;
while not EOF(f) and (iN) do
begin
read(f,m[i]) ; inc (i)
.
end;
close(f);
end.
Процедура READLN. Обеспечивает ввод символов, строк и чисел. Эта процедура идентична процедуре READ за исключением того, что после считывания последней переменной оставшаяся часть строки до маркера EOLN пропускается, поэтому следующее обращение к READLN или READ начинается с первого символа новой строки. Кроме того, эту процедуру можно вызвать без параметра <сп.ввода> (см. процедуру READ), что приведет к пропуску всех символов текущей строки вплоть до EOLN.
Если процедура используется для чтения с клавиатуры, нажатие на клавишу <Enter> отобразится на экране как последовательность CR+LF и курсор будет
помещен в начало следующей строки, в то время как в процедуре READ эхо- повтором клавиши <Enter> является символ CR и курсор помещается в начало текущей строки.
Процедура WRITE. Обеспечивает вывод информации в текстовый файл или передачу ее на логическое устройство. Формат обращения:
WRITE (<ф.п.>, <сп.вывода>) или WRITE (<сп.вывода>)
Здесь <сп,вывода> — список вывода: последовательность из одного или более выражений типа CHAR, STRING, BOOLEAN, а также любого целого или вещественного типа.
Файловая переменная <ф.п.>, если она указана, должна быть предварительно описана как переменная типа TEXT и связана с именем файла или логическим устройством процедурой ASSIGN. Если файловая переменная отсутствует, подразумевается вывод в стандартное устройство OUTPUT, которое обычно связано с экраном ПК.
Любой элемент списка вывода может иметь форму
OutExpr [ : MinWidth [ : DecPlaces ] ]
Здесь:
OUTEXPR — выводимое выражение;
О MINWIDTH, DECPLACES * выражения типа WORD (квадратные скобки означают возможность отсутствия заключенных в них параметров).
Подпараметр MINWIDTH, если он присутствует, указывает минимальную ширину поля, в которое будет записываться символьное представление значения OUTEXPR. Если символьное представление имеет меньшую длину, чем MINWIDTH, оно будет дополнено слева пробелами, если — большую длину, то подпараметр MINWIDTH игнорируется и выводится необходимое число символов.
Подпараметр DECPLACES задает количество десятичных знаков в дробной части вещественного числа. Он может использоваться только совместно с MINWIDTH и только по отношению к выводимому выражению одного из ве- шественных типов.
Если ширина поля вывода не указана, соответствующий параметр выводится вслед за предыдущим без какого-либо их разделения.
Символы и строки передаются выводному файлу без изменений, но снабжаются ведущими пробелами, если задана ширина поля вывода и эта ширина больше требуемой для вывода.
При выводе логических выражений в зависимости от их значения выводятся строки TRUE или FALSE. (Ввод логических констант процедурами READ или READLN не предусмотрен).
Вещественные числа выводятся в экспоненциальном формате, если не указан подпараметр DECPLACES, в противном случае выбирается формат представления числа с фиксированной точкой. Экспоненциальный формат представляет вещественное число в видегде:
_ — пробел;
s — пробел для положительного и знакдля отрицательного чисел;
— десятичная цифра;
Е — символ десятичного основания;
— знакилив зависимости от знака десятичного порядка числа.
Если подпараметропущен, принимается его значение по умолчанию (23). Если MINWIDTH10, считается, что он равен 10.
Если подпараметрравен нулю, ни дробная часть числа, ни деся
тичная точка не выводятся. При отрицательном значении DECPLACES этот параметр игнорируется и число выводится в экспоненциальном формате с учетом MINWIDTH. Если значение DECPLACES больше 18, принимается значение 18. Следует учесть, что при указании подпараметра DECPLACES вещественное число всегда будет выводиться в формате с фиксированной точкой и требуемым количеством знаков в дробной части, даже если значение подпараметра MINWIDTH окажется недостаточным для размещения целой части, в этом случае значение MINWIDTH автоматически увеличивается.
При выводе на экран в случае, когда длина выводимой последовательности символов превышает ширину экрана или созданного на нем окна, "лишние" символы переносятся на следующую экранную строку. При заполнении экрана или окна его содержимое сдвигается вверх на одну строку.
ПроцедураЭта процедура полностью идентична процедуре WRITE за
исключением того, что выводимая строка символов завершается кодами CR и LF. При вызове WRITELN можно опускать параметрВ этом
случае в файл передается маркер EOLN, что при выводе на экран приведет к переводу курсора в начало следующей строки.
Логическая функция EOLN. Возвращает TRUE, если во входном текстовом файле достигнутконцаобращения:
EOLN
Если параметропущен, функция проверяет стандартное устройство
INPUT.
Существует некоторое отличие в работе функций EOLN и EOF с дисковыми файлами и логическими устройствами. Дело в том, что для логического устройства невозможно предвидеть,будет результат чтения очередного
символа. Поэтому при работе с логическим устройством функция EOLN возвращает TRUE, если последним считанным с устройства символом был EOLN
или EOF, в то время как при чтении с диска TRUE возвращается в случае, ecr ли следующим считываемым символом будет EOLN или EOF. Аналогичное различие наблюдается и в функции EOF: для логического устройства TRUE возвращается в случае, если последним символом был EOF, а при чтении с диска — если следующим считываемым символом будет EOF. Иными словами, функции тестируют соответствующие признаки для логического устройства после очередного чтения, а для файла — перед чтением.
Логическая функция SEEKEOLN. Пропускает все пробелы и знаки табуляции до маркера конца строки EOLN или до первого значащего символа и возвращает TRUE, если маркер обнаружен. Формат обращения:
SEEKEOLN (<ф.П.>)
Если параметр <ф.п.> опущен, функция проверяет стандартный файл INPUT.
Логическая функция SEEKEOF. Пропускает все пробелы, знаки табуляции и маркеры конца строки EOLN до маркера конца файла или до первого значащего символа и возвращает TRUE, если маркер обнаружен. Формат обращения:
SEEKEOF (<ф.п.>)
Если параметр <ф.п.> опущен, функция проверяет стандартный файл INPUT.
В следующем примере, иллюстрирующем работу с текстовым файлом, подсчитывается общее количество символов в файле, и результат делится на 40 000 — таким способом можно оценить объем рукописи в так называемых учетно-издательских листах:
var
f: text;
s : String;
const
Sum: Longlnt = 0; {Здесь будет количество1 символов /
begin
Write ('Имя файла: ') ; {Запрашиваем...)
Readin(з) ;{и вводим имя файла)
assign(f, s) ;
Reset(f);{Открываем файл}
while not EOF(f) do(Подсчитываем...}
begin(количество...}
ReadLn(f,s);{символов...}
inc(Sum, Length (s))(в файле)
end;
Close (f);(Закрываем файл!
WriteLn ( * Объем =
end.
Типизированные файлы
Длина любого компонента типизированного файла строго постоянна, что дает возможность организовать прямой доступ к каждому из них (т. е. доступ к компоненту по его порядковому номеру).
Перед первым обращением к процедурам ввода/вывода указатель файла стоит в его начале и указывает на первый компонент с номером 0. После каждого чтения или записи указатель сдвигается к следующему компоненту файла. Переменные в списках ввода/вывода должны иметь тот же тип, что и компоненты файла. Если этих переменных в списке несколько, указатель будет смещаться после каждой операции обмена данными между переменными и дисковым файлом.
Процедура READ. Обеспечивает чтение очередных компонентов типизированного файла. Формат обращения:
READ (<ф.п.>,<сп.ввода>)
Здесь <сп.ввода> — список ввода, содержащий одну или более переменных такого же типа, что и компоненты файла.
Файловая переменная <ф.п.> должна быть объявлена предложением FILE OF. и связана с именем файла процедурой ASSIGN. Файл необходимо открыть процедурой RESET. Если файл исчерпан, обращение к READ вызовет ошибку ввода/вывода.
Процедура WRITE. Используется для записи данных в типизированный файл. Формат обращения:
WRITE (<ф.п.>,<сп.вывода>)
Здесь <сп.вывода> — список вывода, содержащий одно или более выражений того же типа, что и компоненты файла.
Процедура SEEK. Смещает указатель файла к требуемому компоненту. Формат обращения:
SEEK (<ф. п. >, <N компонента>)
Здесь <N компонента> — выражение типа LONGINT, указывающее номер компонента файла.
Первый компонент файла имеет номер 0. Процедуру нельзя применять к текстовым файлам.
Функция FILESIZE. Возвращает значение типа LONGINT, которое содержит количество компонентов файла. Формат обращения:
FILESIZE (<ф.п.>)
Функцию нельзя использовать для текстовых файлов. Чтобы переместить указатель в конец типизированного файла, можно написать: seek (FileVar, FileSize(FileVar));
где FILEVAR — файловая переменная.
Функция FILEPOS. Возвращает значение типа LONGINT, содержащее порядковый номер компонента файла, который будет обрабатываться следующей операцией ввода/вывода. Формат обращения:
FILEPOS (<ф.П.>)
Функцию нельзя использовать для текстовых файлов. Первый компонент файла имеет порядковый номер 0.
Нетипизированныефайлы
Нетипизированные файлы объявляются как файловые переменные типа FILE и отличаются тем, что для них не указан тип компонентов. Отсутствие типа делает эти файлы, с одной стороны, совместимыми с любыми другими файлами, а с другой — позволяет организовать высокоскоростной обмен данными между диском и памятью.
При инициации нетипизированного файла процедурами RESET или REWRITE можно указать длину записи нетипизированного файла в байтах. Например, так:
var
f: file;
begin
assign(f,'myfile.dat') ; reset(f,512);
end.
Длина записи нетипизированного файла указывается вторым параметром при обращении к процедурам RESET или REWRITE, в качестве которого может использоваться выражение типа WORD. Если длина записи не указана, она принимается равной 128 байтам.
Турбо Паскаль не накладывает каких-либо ограничений на длину записи нетипизированного файла, за исключением требования положительности и ограничения максимальной длины 65 535 байтами (емкость целого типа WORD). Однако для обеспечения максимальной скорости обмена данными следует задавать длину, которая была бы кратна длине физического сектора
I
Глава 5.
дискового носителя информации (512 бант). Более того, фактически пространство на диске выделяется любому файлу порциями — кластерами, которые в зависимости от типа диска могут занимать 2 и более смежных секторов. Как, правило, кластер может быть прочитан или записан за один оборот диска, поэтому наивысшую скорость обмена данными можно получить, если указать длину записи, равную длине кластера.
При работе с нетипизированными файлами могут применяться все процедуры и функции, доступные типизированным файлам, за исключением READ WRITE, которые заменяются соответственно высокоскоростными процедурамииВызов этих процедур осуществляется следую
щим образом:
BLOCKREAD Кф.п.>,<буф>,<[,<NN>])
BLOCKWRITE(<ф.п.>,<буф>,<[,<NN>])
Здесь:
<буф> — буфер: имя переменной, которая будет участвовать в обмене данными с дисками;
О <D> — количество записей, которые должны быть прочитаны или записаны за одно обращение к диску;
П— необязательный параметр, содержащий при выходе из процедуры
количество фактически обработанных записей.
За одно обращение к процедурам может быть передано до N*RECS байт, где RECS — длина записи нетипизированного файла. Передача идет, начиная с первого байта переменной <буф>. Программист должен позаботиться о том, чтобы длина внутреннего представления переменной <буф> была достаточной для размещения всех N*RECS байт при чтении информации с диска. Если при чтении указана переменная <буф> недостаточной длины или если в процессе записи на диск не окажется нужного свободного пространства, возникнет ошибка ввода/вывода, которую можно заблокировать, указав необязательный параметр(переменная типа WORD).
После завершения процедуры указатель смещается назаписей. Процедурами SEEK, FILEPOS иможно обеспечить доступ к любой запи
си нетипизированного файла.
Глава 6
Указатели и динамическая память
Динамическая память
Все переменные, объявленные в программе, размещаются в одной непрерывной области оперативной памяти, которая называется сегментом данных. Длина сегмента данных определяется архитектурой микропроцессоров 8086 и составляет 65 536 байт, что может вызвать известные затруднения при обработке больших массивов данных. С другой стороны, объем памяти ПК (обычно не менее 640 Кбайт) достаточен для успешного решения задач с большой размерностью данных. Выходом из положения может служить использование так называемой динамической памяти.
Динамическая память — это оперативная память ПК, предоставляемая программе при ее работе, за вычетом сегмента данных (64 Кбайт), стека (обычно 16 Кбайт) и собственно тела программы. Размер динамической памяти можно варьировать в широких пределах (см. приложение 1). По умолчанию этот размер определяется всей доступной памятью ПК и, как правило, составляет не менее 200-300 Кбайт.
Динамическая память — это фактически единственная возможность обработки массивов данных большой размерности. Без динамической памяти трудно или невозможно решить многие практические задачи. Такая необходимость возникает, например, при разработке систем автоматизированного проектирования (САПР): размерность математических моделей, используемых в САПР, может значительно отличаться в разных проектах; статическое (т. е. на этапе разработки САПР) распределение памяти в этом случае, как правило, невозможно. Наконец, динамическая память широко применяется для временного запоминания данных при работе с графическими и звуковыми средствами ПК.
Динамическое размещение данных означает использование динамической памяти непосредственно при работе программы. В отличие от этого, статическое размещение осуществляется компилятором Турбо Паскаля в процессе компиляции программы. При динамическом размещении заранее не известны ни тип, ни количество размещаемых данных, к ним нельзя обращаться по именам, как к статическим переменным.
Адреса и указатели
Оперативная память ПК представляет собой совокупность элементарных ячеек для хранения информации — байтов, каждый из которых имеет собственный номер. Эти номера называются адресами, они позволяют обращаться к любому байту памяти.
Турбо Паскаль предоставляет в распоряжение программиста гибкое средство управления динамической памятью — так называемые указатели. Указатель — это переменная, которая в качестве своего значения содержит адрес байта памяти.
В ПК адреса задаются совокупностью двух шестнадцатиразрядных слов, которые называются сегментом и смещением. Сегмент — это участок памяти, имеющий длину 65 536 байт (64 Кбайт) и начинающийся с физического адреса, кратного 16 (т. е. О, 16, 32, 48 и т. д.). Смещение указывает, сколько байтов от начала сегмента необходимо пропустить, чтобы обратиться к нужному адресу.
Адресное пространство ПК составляет 1 Мбайт (речь идет о так называемой стандартной памяти ПК; на современных компьютерах с процессорами 80386 и выше адресное пространство составляет 4 Гбайт, однако в Турбо Паскале нет средств, поддерживающих работу с дополнительной памятью; при использовании среды Borland Pascal with Objects 7.0 такая возможность имеется). Для адресации в пределах 1 Мбайт нужно 20 двоичных разрядов, которые получаются из двух шестнадцатиразрядных слов (сегмента и смещения) следующим образом (рис. 6.1): содержимое сегмента смещается влево на 4 разряда, освободившиеся правые разряды заполняются нулями, результат складывается с содержимым смещения.
Сегмент |1 S|14|1 3|12|11110| 9\ 8\ 7\ б| 5| 4\ з| 2\ i| о!
Сме*шениеIisTmI?з|1—2|11— 8| —4\ з| 2\ 1| о]
Адрес 119|18|17|16|15|14|13|12|11|1O|9| 8| 7\ б| S| A\ 3| 2| 1| 0|
Рис. 6.1. Схема формирования адреса в ПК
Фрагмент памяти в 16 байт называется параграфом, поэтому можно сказать, что сегмент адресует память с точностью до параграфа, а смещение — с точностью до байта. Каждому сегменту соответствует непрерывная и отдельно адресуемая область памяти. Сегменты могут следовать в памяти один за другим без промежутков или с некоторым интервалом, или, наконец, перекрывать друг друга.
Таким образом, по своей внутренней структуре любой указатель представляет собой совокупность двух слов (данных типа WORD), трактуемых как сег-
мент и смещение. С помощью указателей можно размещать в динамической памяти любой из известных в Турбо Паскале типов данных. Лишь некоторые из них (BYTE, CHAR, SHORTINT, BOOLEAN) занимают во внутреннем представлении один байт, остальные — несколько смежных. Поэтому на самом деле указатель адресует лишь первый байт данных.
Объявление указателей
Как правило, в Турбо Паскале указатель связывается с некоторым типом данных. Такие указатели будем называть типизированными. Для объявления типизированного указателя используется значок который помещается перед соответствующим типом, например:
var
:
р2 :
type
PerconPointer =
= record
Name : String;
Job : String;
Next : PerconPointer
end;
Обратите внимание: притипа PerconPointer мы сослались на
тип PerconRecord, который предварительно в программе объявлен не был. Как уже отмечалось, в Турбо Паскале последовательно проводится в жизнь принцип, в соответствии с которым перед использованием какого-либо идентификатора он должен быть описан. Исключение сделано только для указателей, которые могут ссылаться на еще не объявленный тип данных. Это сделано не случайно. Динамическая память дает возможность реализовать широко применяемую в некоторых программах организацию данных в виде списков. Каждый элемент списка имеет в своем составе указатель на соседний элемент (рис. 6.2), что обеспечивает возможность просмотра и коррекции списка. Если бы в Турбо Паскале не было этого исключения, реализация списков была бы значительно затруднена.
В Турбо Паскале можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных. Для этого служит стандартный тип POINTER, например:
var
р: pointer;
Рис. 6.2. Списочная структура данных
Указатели такого рода будем называть нетипизированными. Поскольку нети- пизированные указатели не связаны с конкретным типом, с их помощью удобно динамически размещать данные, структура и тип которых меняются в ходе работы программы.
Как уже говорилось, значениями указателей являются адреса переменных в памяти, поэтому следовало бы ожидать, что значение одного указателя можно передавать другому. На самом деле это не совсем так. В Турбо Паскале можно передавать значения только между указателями, связанными с одним и тем же типом данных. Если, например, объявлены переменные
var
pl,p2: АInteger;
рЗ: AReal;
рр: pointer;
то присваивание
Pl :=
вполне допустимо, в то время как присваивание
pl := рЗ;
запрещено, поскольку Pl и РЗ указывают на разные типы данных. Это ограничение, однако, не распространяется на нетипизированные указатели, поэтому мы могли бы записать
рррЗ;
pl := рр;
и тем самым достичь нужного результата.
Читатель вправе задать вопрос, стоило ли вводить ограничения и тут же давать средства для их обхода. Все дело в том, что любое ограничение, с одной стороны, вводится для повышения надежности программ, а с другой — уменьшает мощность языка, делает его менее пригодным для каких-то применений. В Турбо Паскале немногочисленные исключения в отношении типов данных придают языку необходимую гибкость, но их использование требует от программиста дополнительных усилий и таким образом свидетельствует о вполне осознанном действии.
Выделение и освобождение динамической памяти
Вся динамическая память в Турбо Паскале рассматривается как сплошной массив байтов, который называется кучей. Физически куча располагается в старших адресах сразу за областью памяти, которую занимает тело программы.
Начало кучи хранится в стандартной переменной HeapOrg (рис. 6.3), конец — в переменной HeapEnd. Текущую границу незанятой динамической памяти содержит переменная Heapptr.
Память под любую динамически размещаемую переменную выделяется процедурой NEW. Параметром обращения к этой процедуре является типизированный указатель. В результате обращения указатель приобретает значение, соответствующее динамическому адресу, начиная с которого можно разместить данные, например:
var
j : AInteger;
r :
begin
New(i) ;
end.
После выполнения этого фрагмента указатель I приобретет значение, которое перед этим имел указатель кучи HEAPPTR, а сам HEAPPTR увеличит свое значение на 2, т. к. длина внутреннего представления типа INTEGER, с которым связан указатель I, составляет 2 байта (на самом деле это не совсем так: память под любую переменную выделяется порциями, кратными 8 байтам — см. разд. 6.7). Оператор new (r) ;
вызовет еще раз смещение указателя HEAPPTR, но теперь уже на 6 байт, потому что такова длина внутреннего представления типа REAL. Аналогичным образом выделяется память и для переменной любого другого типа.
После того как указатель приобрел некоторое значение, т. е. стал указывать на конкретный физический байт памяти, по этому адресу можно разместить любое значение соответствующего типа. Для этого сразу за указателем без каких-либо пробелов ставится значок например:
iA := 2;(B область памяти i помещено значение 2}
rA :=область памяти Г помещено значение 6.28}
Старшие адресаСтаршие адресаHeapEndHeapEndHeapPtrHeapPtrHeapOrgHeapOrgМладшие адресаМладшие адресаРис. 6.3. Расположение кучи в памяти ПКРис. 6.3. Расположение кучи в памяти ПК
Старшие адреса
Старшие адреса
HeapEnd
HeapEnd
HeapPtr
HeapPtr
HeapOrg
HeapOrg
Младшие адреса
Младшие адреса
Рис. 6.3. Расположение кучи в памяти ПК
Рис. 6.3. Расположение кучи в памяти ПК
Таким образом, значение, на которое указывает указатель, т. е'. собственно данные, размещенные в куче, обозначаются значком который ставится сразу за указателем. Если за указателем нет значка то имеется в виду адрес, по которому размещены данные. Имеет смысл еще раз задуматься над только что сказанным: значением любого указателя является адрес, а чтобы указать, что речь идет не об адресе, а о тех данных, которые размещены по этому адресу, за указателем ставится Если вы четко уясните себе это, у вас не будет проблем при работе с динамической памятью.
Динамически размещенные данные можно использовать в любом месте программы, где это допустимо для констант и переменных соответствующего типа, например:
rA := sqr(rA) + iA - 17;
Разумеется, совершенно недопустим оператор
r :=+ iA - 17;
т. к. указателю R нельзя присвоить значение вещественного выражения. Точно так же недопустим оператор
rA :=
поскольку значением указателя R является адрес, и его (в отличие от того значения, которое размещено по этому адресу) нельзя возводить в квадрат. Ошибочным будет и такое присваивание:
rA :=
т. к. вещественным данным, на которые указывает R, нельзя присвоить значение указателя (адрес).
Динамическую память можно не только забирать из кучи, но и возвращать обратно. Для этого используется процедура DISPOSE. Например, операторы
dispose(г);
dispose(i);
вернут в кучу 8 байт, которые ранее были выделены указателям IиR (см. выше).
Отметим, что процедура DISPOSE(PTR) не изменяет значения указателя PTR, а лишь возвращает в кучу память, ранее связанную с этим указателем. Однако повторное применение процедуры к свободному указателю приведет к возникновению ошибки периода исполнения. Освободившийся указатель программист может пометить зарезервированным словом NIL. Помечен ли какой-либо указатель или нет, можно проверить следующим образом:
const
р: AReal = NIL;
begin
if p = NIL then new(p);
dispose(p);
p NIL;
end.
Никакие другие операции сравнения над указателями не разрешены.
Приведенный выше фрагмент иллюстрирует предпочтительный способ объявления указателя в виде типизированной константы (см. главу 7) с одновременным присвоением ему значения NIL. Следует учесть, что начальное значение указателя (при его объявлении в разделе переменных) может быть произвольным. Использование указателей, которым не присвоено значение процедурой NEW или другим способом, не контролируется системой и может привести к непредсказуемым результатам.
Чередование обращений к процедурам NEW и DISPOSE обычно приводит к "ячеистой" структуре памяти. Дело в том, что все операции с кучей выполняются под управлением особой подпрограммы, которая называется администратором кучи. Она автоматически пристыковывается к вашей программе компоновщиком Турбо Паскаля и ведет учет всех свободных фрагментов в куче. При очередном обращении к процедуре NEW эта подпрограмма отыскивает наименьший свободный фрагмент, в котором еще может разместиться
требуемая переменная. Адрес начала найденного фрагмента возвращается в указателе, а сам фрагмент или его часть нужной длины помечается как занятая часть кучи. (Подробнее о работе администратора кучи см. в разд. 6.7).
Другая возможность состоит в освобождении целого фрагмента кучи. С этой целью перед началом выделения динамической памяти текущее значение указателя HEAPPTR запоминается в переменной-указателе с помощью процедуры MARK. Теперь можно в любой момент освободить фрагмент кучи, начиная от того адреса, который запомнила процедура MARK, и до конца динамической памяти. Для этого используется процедура RELEASE. Например:
var
p,pl,p2,
рЗ,р4,р5 :
begin
new(pl);
new (p2);
mark(p);
new(p3);
new(p4);
new(p5)
release (p) end.
В этом примере процедурой MARK(P) в указатель р было помещено текущее значение HEAPPTR, однако память под переменную не резервировалась. Обращение RELEASE (P) освободило динамическую память от помеченного места до конца кучи. На рис. 6.4 проиллюстрирован механизм работы процедур NEW—DISPOSE И NEW—MARK—RELEASE ДЛЯ рассмотренного Примера И ДЛЯ случая, когда вместо оператора RELEASE (P) используется, например, DISPOSE(P3). Следует учесть, что вызов RELEASE уничтожает список свободных фрагментов в куче, созданных до этого процедурой DISPOSE, поэтому совместное использование обоих механизмов освобождения памяти в рамках одной программы не рекомендуется.
Как уже отмечалось, параметром процедуры NEW может быть только типизированный указатель. Для работы с нетипизированными указателями служат процедуры:
GETMEM (P, SIZE) — резервирование памяти;
О FREEMEM(P, SIZE) — освобождение памяти.
Здесь:
р — нетипизированный указатель;
SIZE — размер в байтах требуемой или освобождаемой части кучи.
абв
Рис. 6.4. Состояние динамической памяти:
перед освобождением;после Disposeв — после Release (p)
За одно обращение к куче процедурой можно зарезервировать до 65 521 байт динамической памяти.
Использование процедур как и вообще вся работа с динамической памятью, требует особой осторожности и тщательного соблюдения простого правила: освобождать нужно ровно столько памяти, сколько ее было зарезервировано, и именно с того адреса, с которого она была зарезервирована.
Понятно, что наличие нетипизированных указателей в Турбо Паскале (в стандартном Паскале их нет) открывает широкие возможности неявного преобразования типов. К сожалению, трудно обнаруживаемые ошибки в программе, связанные с некорректно используемыми обращениями к процедурам NEW и DISPOSE, также могут привести к нежелательному преобразованию типов. В самом деле, пусть имеется программа:
var
:
r :
begin
new(1);
HeapOrg; HeapPtr
HeapOrg
+ 2}
j := i;
HeapOrg)
jA := 2; dispose
{HeapPt r := HeapOrg)
new (r);
HeapOrg; HeapPtr
HeapOrg
+ 6}
:= pi;
WriteLn(jA)
{ ??
end.
Что будет выведено на экран дисплея? Чтобы ответить на этот вопрос, проследим за значениями указателя HEAPPTR. Перед исполнением программы этот указатель имел значение адреса начала кучи HEAPORG, которое и было передано указателю I, а затем и J. После выполнения DISPOSE (I) указатель кучи вновь приобрел значение HEAPORG, этот адрес передан указателю R в процедуре NEW(R) . После того как по адресу R разместилось вещественное число я = 3,14159, первые 2 байта кучи оказались заняты под часть внутреннего представления этого числа. В то же время J все еще сохраняет адрес HEAPORG, поэтому оператор WRITELN(JA) будет рассматривать 2 байта числа Л как внутреннее представление целого числа (ведь J — это указатель на тип INTEGER) и выведет 8578.
Использование указателей
Подведем некоторые итоги. Итак, динамическая память составляет 200— 300 Кбайт или больше, ее начало хранится в переменной HEAPORG, а конец соответствует адресу переменной HEAPEND. Текущий адрес свободного участка динамической памяти хранится в указателе HEAPPTR.
Посмотрим, как можно использовать динамическую память для размещения крупных массивов данных. Пусть, например, требуется обеспечить доступ к элементам прямоугольной матрицы 100x200 типа EXTENDED. Для размещения такого массива требуется память размером 200 000 байт (100x200x10).
Казалось бы, эту проблему можно решить следующим образом:
var
, j : Integer;
PtrArr : array [1..100, 1..200] of AReal;
begin
for i := 1 to 100 do
for j := 1 to 200 do
new(PtrArr [ i, j ]);
end.
Теперь к любому элементу вновь созданного динамического массива можно обратиться по адресу, например:
PtrArr[1,1] A := 0;
if PtrArr[i, j*2]A > 1 then
Вспомним, однако, что длина внутреннего представления указателя составляет 4 байта, поэтому для размещения массива PTRARR потребуется WOx х200х4 80 000 байт, что превышает размер сегмента данных (65 536 байт),
доступный, как уже отмечалось, программе для статического размещения данных.
Выходом из положения могла бы послужить адресная арифметика, т. е. арифметика над указателями, потому что в этом случае можно было бы отказаться от создания массива указателей PTRARR и вычислять адрес любого элемента прямоугольной матрицы непосредственно перед обращением к нему. Однако в Турбо Паскале над указателями не определены никакие операции, кроме операций присваивания и отношения.
Тем не менее, решить указанную задачу все-таки можно. Как мы уже знаем, любой указатель состоит из двух слов типа WORD, в которых хранятся сегмент и смещение. В Турбо Паскале определены две встроенные функции типа WORD, позволяющие получить содержимое этих слов:
SEG(X) — возвращает сегментную часть адреса;
OFS (X) — возвращает смещение.
Аргументом х при обращении к этим функциям может служить любая переменная, в том числе и та, на которую указывает указатель. Например, если имеем
var
р : AReal;
begin
new(p);
pA := 3.14;
end.
то функция SEG(P) вернет сегментную часть адреса, по которому располагается 4-байтный указатель р, в то время как SEG (PA) — сегмент 6-байтного участка кучи, в котором хранится число 3,14.
С другой стороны, с помощью встроенной функции
PTR(SEG,OFS: WORD): POINTER
можно создать значение указателя, совместимое с указателями любого типа. Таким образом возможна такая последовательность действий. Вначале процедурой GETMEM из кучи забираются несколько фрагментов подходящей длины (напомню, что за одно обращение к процедуре можно зарезервировать не более 65 521 байт динамической памяти). Для рассматриваемого примера удобно резервировать фрагменты такой длины, чтобы в них могли, например, разместиться строки прямоугольной матрицы, т. е. 200x10 2000 байт. Начало каждого фрагмента, т. е. фактически начало размещения в памяти каждой строки, запоминается в массиве PTRSTR, состоящем из 100 указате-
лей. Теперь для доступа к любому элементу строки нужно вычислить смещение этого элемента от начала строки и сформировать соответствующий указатель:
var
1/j: Integer;
PtrStrarray [1..100] of pointer;
pr: AReal;
const
SizeOfReal = 6;
begin
for i := 1 to 100 do
GetMem(PtrStr[i],SizeOfReal*200);*
/Обращение к элементу матрицы [i,j]}
pr := ptr(seg(PtrStr[1]A),
ofs (PtrStr [i]'A) + (j-l) *SizeOfReal) ;
if prA > 1 then
end.
Поскольку оператор вычисления адреса PR : = PTR будет, судя по всему, использоваться в программе неоднократно, полезно ввести вспомогательную функцию GETR, возвращающую значение элемента матрицы, и процедуру PUTR, устанавливающую новое значение элемента (правила объявления процедур и функций изложены в главе 8). Каждая из них, в свою очередь, обращается к функции ADDRR для вычисления адреса. В примере 6.1 приводится программа, создающая в памяти матрицу из NxM случайных чисел и вычисляющая их среднее значение.
; Пример 6.1
const
SizeOfReal = 6; {Длина переменной типа REAL}
N = 100;{Количество столбцов}
М = 200;(Количество строк}
var
i,j: Integer;
PtrStr: array [1..N] of pointer;
s: Real;
type
RealPoint =
{-
FunctionRealPoint;
сегменту i и смещению j выдает адрес вещественной begin AddrR := ptr[i]
ofs(PtrStr[i]A)+(j-1)*SizeOfReal)
end {)
Function GetR(i,j:Real;
вещественной переменной по сегменту i и смещению j ее begin GetR := end {;
Procepure PutR(i,j : Integer; x: в переменную, адрес которой имеет сегмент i и смещение вещественное значение begin := x end {PutR); {}
begin for ito N do
begin GetMem(PtrStr[i] , M*SizeOfReal) ; for j := 1 to M doRandom)
end; s := 0; for i := 1 to N do for j := 1 to M do s :=s + / (N * M) : 12:10)
end
В рассмотренном примере предполагается, что каждая строка размещается в куче, начиная с границы параграфа, и смещение для каждого указателя PTRSTR равно нулю. В действительности при последовательных обращениях к процедуре GETMEM начало очередного фрагмента следует сразу за концом предыдущего и может не попасть на границу сегмента. В результате, при размещении фрагментов максимальной длины (65 521 байт) может возникнуть переполнение при вычислении смещения последнего байта.
Процедуры и функции для работы с динамической памятью
Ниже приводится описание как уже рассмотренных процедур и функций, так и некоторых других, которые могут оказаться полезными при обращении к динамической памяти.
Функция ADDR. Возвращает результат типа POINTER, в котором содержится адрес аргумента. Обращение:
ADDR (X)
Здесь х — любой объект программы (имя любой переменной, процедуры, функции). Возвращаемый адрес совместим с указателем любого типа. Отметим, что аналогичный результат возвращает операция @.
Функция CSEG. Возвращает значение, хранящееся в регистре cs микропроцессора (в начале работы программы в регистре cs содержится сегмент начала кода программы). Обращение:
CSEG
Результат возвращается в слове типа WORD.
Процедура DISPOSE. Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за типизированным указателем. Обращение:
DISPOSE(TP)
•
Здесь ТР — типизированный указатель. При повторном использовании процедуры применительно к уже освобожденному фрагменту возникает ошибка периода исполнения. При освобождении динамических объектов можно указывать вторым параметром обращения к DISPOSE имя деструктора (подробнее см. в главе 10).
Функция DSEG. Возвращает значение, хранящееся в регистре DS микропроцессора (в начале работы программы в регистре DS содержится сегмент начала данных программы). Обращение:
DSEG
Результат возвращается в слове типа WORD.
Процедура FREEMEM. Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за нетипизированным указателем. Обращение:
FREEMEM (Р, SIZE)
Здесь:
р — нетипизированный указатель;
SIZE — длина в байтах освобождаемого фрагмента.
При повторном использовании процедуры применительно к уже освобожденному фрагменту возникает ошибка периода исполнения.
Процедура GE THEM. Резервирует за нетипизированным указателем фрагмент динамической памяти требуемого размера. Обращение:
GETMEM (P, SIZE)
За одно обращение к процедуре можно зарезервировать не более 65 521 байт динамической памяти. Если нет свободной памяти требуемого размера, возникает ошибка периода исполнения. Если память не фрагментирована, последовательные обращения к процедуре будут резервировать последовательные участки памяти, так что начало следующего будет располагаться сразу за концом предыдущего.
Процедура MARK. Запоминает текущее значение указателя кучи HEAPPTR. Обращение:
MARK (PTR)
Здесь PTR — указатель любого типа, в котором будет возвращено текущее значение HEAPPTR. Используется совместно с процедурой RELEASE для освобождения части кучи.
Функция MAXAVAIL. Возвращает размер в байтах наибольшего непрерывного участка кучи. Обращение:
MAXAVAIL
Результат имеет тип LONGINT. За один вызов процедуры NEW или GETMEM нельзя зарезервировать памяти больше, чем значение, возвращаемое этой функцией.
Функция MEMAVAIL. Возвращает размер в байтах общего свободного пространства кучи. Обращение:
MEMAVAIL
Результат имеет тип LONGINT.
Процедура NEW. Резервирует фрагмент кучи для размещения переменной. Обращение:
NEW (TP)
Здесь тр — типизированный указатель.
За одно обращение к процедуре можно зарезервировать не более 65 521 байт динамической памяти. Если нет свободной памяти требуемого размера, возникает ошибка периода исполнения. Если память не фрагментирована, последовательные обращения к процедуре будут резервировать последовательные участки памяти, так что начало следующего будет располагаться сразу за концом предыдущего.
Процедура NEW может вызываться как функция. В этом случае параметром обращения к ней является тип переменной, размещаемой в куче, а функция NEW возвращает значение типа "указатель". Например:
type
Pint =AInteger;
.
р: Pint;
begi.n
р := New(Pint);
end.
•
При размещении в динамической памяти объекта разрешается в качестве второго параметра обращения к NEW указывать имя конструктора (см. главу 10).
Функция OFS. Возвращает значение типа WORD, содержащее смещение адреса указанного объекта. Вызов:
OFS (X)
Здесь х — выражение любого типа или имя процедуры.
Функция PTR. Возвращает значение типа POINTER по заданному сегменту SEG и смещению OFS. Вызов:
PTR (SEG, OFS)
Здесь:
О SEG — выражение типа WORD, содержащее сегмент;
OFS — выражение типа WORD, содержащее смещение.
Значение, возвращаемое функцией, совместимо с указателем любого типа.
Процедура RELEASE. Освобождает участок кучи. Обращение:
RELEASE (PTR)
Здесь PTR — указатель любого типа, в котором предварительно было сохранено процедурой MARK значение указателя кучи. Освобождается участок кучи от адреса, хранящегося в PTR, до конца кучи. Одновременно уничтожается
СПИСОК всех свободных фрагментов, которые, возможно, были созданы процедурами DISPOSE ИЛИ FREEMEM.
Функция SEG. Возвращает значение типа WORD, содержащее сегмент адреса указанного объекта. Вызов:
SEG (X)*
Здесь х — выражение любого типа или имя процедуры.
Функция SIZEOF. Возвращает длину в байтах внутреннего представления указанного объекта. Вызов:
SIZEOF (X) •
Здесь х — имя переменной, функции или типа.
Например, везде в программе из примера 6.1 вместо константы SIZEOFREAL можно было бы использовать обращение SIZEOF (REAL) .
Администратор кучи
Как уже отмечалось, администратор кучи — это служебная подпрограмма, которая обеспечивает взаимодействие пользовательской программы с кучей. Администратор кучи обрабатывает запросы процедур NEW, GETMEM, DISPOSE, FREEMEM и др. и изменяет значения указателей HEAPPTR и FREELIST. Указатель HEAPPTR содержит адрес нижней границы свободной части кучи, а указатель FREELIST — адрес описателя первого свободного блока. В модуле SYSTEM указатель FREELIST описан как POINTER, однако фактически он указывает на следующую структуру данных:
type
PFreeRec = ATFreeRec;
TFreeRecrecord
Next : pointer;
Size : pointer
end;
Эта списочная структура предназначена для описания всех свободных блоков памяти, которые расположены ниже границы HEAPPTR. Происхождение блоков связано со случайной последовательностью использования процедур NEW—DISPOSE или GETMEM—FREEMEM ("ячеистая" структура кучи). Поле NEXT в записи TFREEREC содержит адрес описателя следующего по списку свободного блока кучи или адрес, совпадающий с HEAPEND, если этот участок последний в списке. Поле SIZE содержит ненормализованную длину свободного блока или 0, если ниже адреса, содержащегося в HEAPPTR, нет свободных блоков. Ненормализованная длина определяется так: в старшем слове этого поля содержится количество свободных па-
раграфов, а в младшем — количество свободных байтов в диапазоне 0-15. Следующая функция преобразует значение поля SIZE в фактическую длину свободного блока:
Function BlockSize(Size: pointer): Longint;
{ Функция преобразует ненормализованную длину свободного блока в байты}
type
PtrRec = record
Lo, Hi : word
end;
var
LengthBlock: Longint;
begin
BlockSize := Longlnt(PtrRec(Size).Hi)*16 + PtrRec(Size).Lo end;
Сразу после загрузки программы указатели HEAE>PTR и FREELIST содержат один и тот же адрес, который совпадает с началом кучи (этот адрес содержится в указателе HEAPORG). При этом в первых 8 байтах кучи хранится запись, соответствующая типу TFREEREC (поле NEXT содержит адрес, совпадающий со значением HEAPEND, а поле SIZE — ноль, что служит дополнительным признаком отсутствия "ячеек" в динамической памяти). При работе с кучей указатели HEAPPTR и FREELIST будут иметь одинаковые значения до тех пор, пока в куче не образуется хотя бы один свободный блок ниже границы, содержащейся в указателе HEAPPTR. Как только это произойдет, указатель FREELIST станет ссылаться на начало этого блока, а в первых 8 байтах освобожденного участка памяти будет размещена запись TFREEREC. Используя FREELIST как начало списка, программа пользователя всегда сможет просмотреть весь список свободных блоков и при необходимости модифицировать его.
Описанный механизм вскрывает один не очень существенный недостаток, связанный с работой администратора кучи, а именно: в любой освободившийся блок администратор должен поместить описатель этого блока, а это означает, что длина блока не может быть меньше 8 байт. Администратор кучи всегда вьщеляет память блоками, размер которых кратен размеру записи TFREEREC, т. e. кратен 8 байтам. Даже если программа запросит 1 байт, администратор выделит ей фактически 8 байт. Те же 8 байт будут выделены при запросе 2, 3, ..., 8 байт; при запросе 9 байт будет выделен блок в 16 байт и т. д. Это обстоятельство следует учитывать, если вы хотите минимизировать возможные потери динамической памяти. Если запрашиваемый размер не кратен 8 байтам, в куче образуется "дырка" размером от 1 до 7 байт, при-
чем она не может использоваться ни при каком другом запросе динамической памяти вплоть до того момента, когда связанная с ней переменная не будет удалена из кучи.
Если при очередном обращении к функции NEW или GETMEM администратор не может найти в куче нужный свободный блок, он обращается к функции, адрес которой содержит переменная HEAPERROR. Эта функция соответствует следующему процедурному типу:
type
HeapErrorFun = function (Size : word): Integer;
Здесь SIZE — размер той переменной, для которой нет свободной динамической памяти. Стандартная функция, адрес которой при запуске программы содержит переменная HEAPERROR, возвращает 0, что приводит к останову программы по ошибке периода счета с кодом 203 (см. приложение 3). Вы можете переопределить эту функцию и таким образом блокировать останов программы. Для этого необходимо написать собственную функцию и поместить ее адрес в указатель HEAPERROR. Например:
Function HeapFunc(Size: Word): Integer; far;
begin
1
end;
begin {Основная программа}
HeapError := @HeapFunc;
end.
Отметим, что функция типа HEAPERRORFUN вызывается только в том случае, когда обращение с требованием выделения динамической памяти было неуспешным. Она может возвращать одно из трех значений:
0 — прекратить работу программы;
1 — присвоить соответствующему указателю значение NIL и продолжить работу программы;
2 — повторить выделение памяти; разумеется, в этом случае внутри функции типа HEAPERRORFUN необходимо освободить память нужного размера.
I
Глава 7
Типизированные константы
В Турбо Паскале допускается использование типизированных констант. Они задаются в разделе объявления констант следующим образом: <идентификатор> : <тип> = <значение>
Здесь:
<идентификатор> — идентификатор константы;
<тип> — тйп константы;
О <значение> — значение константы.
Типизированным константам можно присваивать другие значения в ходе выполнения программы, поэтому фактически они представляют собой переменные с начальными значениями. Типизированная константа приобрел тает указанное в ее объявлении значение, т. е. инициализируется, лишь один раз: к моменту начала работы программы. При повторном входе в блок (процедуру или функцию), в котором она объявлена, инициализация типизированной константы не производится и она сохраняет то значение, которое имела к моменту выхода из блока.
Типизированные константы могут быть любого типа, кроме файлов. Нельзя также объявить типизированную константу-запись, если хотя бы одно из ее полей является полем файлового типа.
Поскольку типизированная константа фактически не отличается от переменной, она не может выступать в качестве значения при объявлении других констант или границ типа-диапазона.
Константы простых типов и типа String
Объявление таких констант обычно не вызывает трудностей, т. к. их значениями являются нетипизированные константы или их идентификаторы.
Примеры объявлений:
type
colors =
(white,
ed, black)
{Пра вильные
const
объявления
CurrCol :
colors
= red;
name
String
=
year
Word
= 1989;
x
Real
= 0.1;
min:
Integer
= 0;
• max
Integer
= 10;
days
1. .31
= 1;
answer
Char
=
{Неправильные объявления:}
mass :
array [min..max] of Real;
(Нельзяиспользовать
a, b, с
типизированные : Byte = 0;
константы в качестве границ диапазона)
{Нельзя использовать список идентификаторов)
var
NameF
: String [22] =
’prog.pas’;
{Нельзя объявлять
типизированную константу в разделе переменных)
Константы-массивы
В качестве начального значения типизированной константы-массива используется список констант, отделенных друг от друга запятыми; список заключается в круглые скобки, например:
type
colors = (white, red, black);
const
ColStr : array [colors] of String [5] =
('white', 'red', 'Ыак');
vector : array [1..5] of Byte = (О, О, О, 0, 0) ;
При объявлении массива символов можно учесть то обстоятельство, что все символьные массивы и строки в Турбо Паскале хранятся в упакованном формате, поэтому в качестве значения массива-константы типа CHAR допускается задание символьной строки соответствующей длины.
Два следующих объявления идентичны:
const
digit : array [0..9] of Char =
('O', ’ 1', ' 2 ',' 3 ',' 4 ',' 5’, ' 6', ' 7','8',' 9 ') ;
digchr: array [0. .9] of Char = '0123456789';
При объявлении многомерных констант-массивов множество констант, соответствующих каждому измерению, заключается в дополнительные круглые скобки и отделяется от соседнего множества запятыми. В результате образуются вложенные структуры множеств, причем глубина вложения должна соответствовать количеству измерений (размерности) массива. Множества констант с наибольшей глубиной вложенности связываются с изменением самого правого индекса массива.
Следующая программа выведет на экран три строки с монотонно увеличивающимися целыми числами:
var
j, k, 1Byte;
const
matr : array [1..3, 1..5] of Byte =
( (0, 1, 2, 3, 4),
(5, 6, 7, 8, 9), (10,11,12,13,14));
cube : array [0.. 1 , 0..1, 0. .2] of Integer =
(((0,1,2), (3,4,5)),
((6,7,8),(9,10,11)));
mas4 : array [0..1, 0..1, 0..1, 0..1] of Word =
((((0,1),(2,3)),
((4,5),(6,7))),
(((8,9),(10,11)),
((12,13),(14,15))));
begin
for i := 1 to 3 do
forj := 1 to5 doWrite(matr[i, j ] : 3);
writeln;
for i := 0 to 1 do
for j := 0 to 1 do
for k := 0 to 2 do Write(cube[i,j,k]:3);
writeln;
for i := 0 to 1 do
forj := 0 to 1 do
for k := 0 to 1 do
for1 :=0 to1 doWrite (mas4 [i, j, к, 1]: 3) ;.
WriteLn
end.
Количество переменных в списке констант должно строго соответствовать объявленной длине массива по каждому измерению.
• * \X
Константы-записи
Определение константы-записи имеет следующий вид:
<идентификатор> : <тип> = (<сп.знач.полей>)
Здесь:
П <идентификатор> — идентификатор константы;
□ <тип> — тип записи;
П <сп.знач.полей> — список значений полей.
Список значений полей представляет собой список из последовательностей вида: имя поля, двоеточие и константа. Элементы списка отделяются друг от друга двоеточиями, например:
type
point = record
х, у : Real
end;
vect
= array
[0..1] of
point;
month
date
= (Jan, Feb, Mar,
Лу, Aug, Sep, = record
d : 1..31;
m :
Apr, May, Jun, Oct, Nov, Dec);
у : 1900..1999 end;
const
origon point=(x :0; у : ■1 ) г
line : vector=((х:-3.1; y: 1.5),(x: 5.9;y: 3.0));
SomeDay: date=(d :16; m : Mar; у: 1989);
Поля должны указываться в той последовательности, в какой они перечислены в объявлении типа. Если в записи используется хотя бы одно поле
Глава 7. Типизированные константы
файлового типа, такую запись нельзя объявить типизированной константой. Для записей с вариантными полями указывается только один из возможных вариантов констант. Например:
type
forma = record
case Boolean of
true
: (BirthPlace
: String
[40]) ;
const
false
end;
: (Country
EntryPort EntryDate count
: String
: String
:. array
: Word)
[20];
[20];
[1..3] of Word;
Perconl
: forma =
(Country:
EntryPort :
EntryDate : count:
'Норвегия';
'Мурманск';
(16, 3,
12);
Percon2
: forma =
(Birthplace :
'Москва
');
Константы-множества
Значение типизированной константы-множества задается в виде правильного конструктора множества, например:
type,
days = set of
digc = set of '. '
error = set of
const
: days29,
digc ='''8']
ErrorFlag : error=
Константы-указатели
Единственным значением типизированной константы-указателя может быть толькоNIL, например:
const
pr := NIL;
7941
Глава 8
Процедуры и функции
Как отмечалось в главе 2, процедуры и функции представляют собой относительно самостоятельные фрагменты программы, оформленные особым образом и снабженные именем. Упоминание этого имени в тексте программы называется вызовом процедуры (функции). Отличие функции от процедуры заключается в том, что результатом исполнения операторов, образующих тело функции, всегда является некоторое единственное значение или указатель, поэтому обращение к функции можно использовать в соответствующих выражениях наряду с переменными и константами. Условимся далее называть процедуру или функцию общим именем "подпрограмма", если только для излагаемого материала указанное отличие не имеет значения.
Подпрограммы представляют собой инструмент, с помощью которого любая программа может быть разбита на ряд в известной степени независимых друг от друга частей. Такое разбиение необходимо по двум причинам.
Во-первых, это средство экономии памяти: каждая подпрограмма существует в программе в единственном экземпляре, в то время как обращаться к ней можно многократно из разных точек программы. При вызове подпрограммы активизируется последовательность образующих ее операторов, а с помощью передаваемых подпрограмме параметров нужным образом модифицируется реализуемый в ней алгоритм.
Вторая причина заключается в применении методики нисходящего проектирования программ (см. главу 2). В этом случае алгоритм представляется в виде последовательности относительно крупных подпрограмм, реализующих более или менее самостоятельные смысловые части алгоритма. Подпрограммы в свою очередь могут разбиваться на менее крупные подпрофаммы нижнего уровня и т. д. (рис. 8.1). Последовательное структурирование программы продолжается до тех пор, пока реализуемые подпрофаммами алгоритмы не станут настолько простыми, чтобы их можно было легко запро- фаммировать.
В этой главе подробно рассматриваются все аспекты использования под- профамм в Турбо Паскале.
Локализация имен
Напомню, что вызов подпрограммы осуществляется простым упоминанием имени процедуры в операторе вызова процедуры или имени функции в выражении. При использовании расширенного синтаксиса Турбо Паскаля (см. ниже) функции можно вызывать точно так же, как и процедуры. Как известно, любое имя в программе должно быть обязательно описано перед тем как оно появится среди исполняемых операторов. Не делается исключения и в отношении подпрограмм: каждую свою процедуру и функцию программисту необходимо описать в разделе описаний.
Описать подпрограмму — это значит указать ее заголовок и тело. В заголовке объявляются имя подпрограммы и формальные параметры, если они есть. Для функции, кроме того, указывается тип возвращаемого ею результата. За заголовком следует тело подпрограммы, которое, подобно программе, состоит из раздела описаний и раздела исполняемых операторов. В разделе описаний подпрограммы могут встретиться описания подпрограмм низшего уровня, в тех — описания других подпрограмм и т. д.
Программа
Подгфограмма ft
Подпрограмма А1
Подпрограмма А2
Подг। рограмма В
Подпрограмма В1
Подг рограмма В2
Подпрограмма В21
Подпрограмма В22
Рис. 8.1. Пример структуры программы
Вот какую иерархию описаний получим, например, для программы, структура которой изображена на рис. 8.1 (для простоты считается, что все подпрограммы представляют собой процедуры без параметров):
Program
Procedure A;
Procedure А1;
begin
end {Al};
Procedure
begin
end
begin
end
Procedure
Procedure
begin
end
Procedure
Procedure
ИТ. Д.
любого уровня имеет обычно множество имен констант, переменных, типов и вложенных в нее подпрограмм низшего уровня. Считается, что все имена, описанные внутри подпрограммы, локализуются в ней, т. е. они как бы "невидимы" снаружи подпрограммы. Таким образом, со стороны операторов, использующих обращение к подпрограмме, она трактуется как "черный ящик", в котором реализуется тот или иной алгоритм. Все этой реализации скрыты от глаз пользователя подпрограммы и потому не* доступны ему. Например, в рассмотренном выше примере из основной программы можно обратиться к процедурам А и в, но нельзя вызвать ни одну из вложенных в них процедурA2,и т. п.
Сказанное относится не только к именам подпрограмм, но вообще к любым именам, объявленным в них, — типам, константам, переменным и меткам. Все имена в пределах подпрограммы, в которой они объявлены, должны быть уникальными и не могут совпадать с именем самой подпрограммы.
При входе в подпрограмму низшего уровня становятся доступными не только объявленные в ней имена, но и сохраняется доступ ко всем именам верхнего уровня. Образно говоря, любая подпрограмма как бы окружена полупрозрачными стенками: снаружи подпрограммы мы не видим ее внутренности, но,
попав в подпрограмму, можем наблюдать все, что делается снаружи. Так, например, из подпрограммы В21 мы можем вызвать подпрограмму А, использовать имена, объявленные в основной программе, в подпрограммах в и и даже обратиться к ним. Любая подпрограмма может, наконец, вызвать саму себя — такой способ вызова называется рекурсией.
Пусть имеем такое описание:
Program
var:
Procedure A;
var V2 :
end
Procedure
var V3 :
Procedure
var V4 :
Procedure
var V5;
Из процедуры доступны все пять переменных из процедуры доступны переменные из центральной программы — только
При взаимодействии подпрограмм одного уровня иерархии вступает в силу основное правило Турбо Паскаля: любая подпрограмма перед ее вызовом должна быть описана. Поэтому из в можно вызвать подпрограмму А, но из А вызвать в невозможно (точнее, такая возможность появляется только с использованием опережающего описания, см. разд. 8.6.) Продолжая образное сравнение, подпрограмму можно уподобить ящику с непрозрачными стенками и дном и полупрозрачной крышей: из подпрограммы можно смотреть только "вверх" и нельзя "вниз", т. е. подпрограмме доступны только те объекты верхнего уровня, которые определены до описания данной подпрограммы. Эти объекты называются глобальными по отношению к подпрограмме.
В отличие от стандартного Паскаля, в Турбо Паскале допускается произвольная последовательность описания констант, переменных, типов, меток и подпрограмм. Например, раздел описания переменных может появляться в пределах раздела описаний одной и той же подпрограммы много раз и перемежаться с объявлениями других объектов и подпрограмм. Для Турбо Паскаля совершенно безразличен порядок следования и количество разделов VAR, CONST, TYPE, LABEL, но при определении области действия этих
описаний следует помнить, что имена, описанные ниже по тексту программы, недоступны из ранее описанных подпрограмм, например:
var: . .
Procedure
var V2 :
end
var V3 :
Из процедуры s можно обратиться к переменными V2, но нельзя
использоватьт. к. описание следует в программе за описанием про
цедуры s.
Имена, локализованные в подпрограмме, могут совпадать с ранее объявленными глобальными именами. В этом случае считается, что локальное имя "закрывает" глобальное и делает его недоступным, например:
var
i Integer;
Procedure
var
i : Integer;
begin
writein(i)
end
begin
i :1;
P
end.
Что напечатает эта программа? Все, что угодно: значение внутренней переменной при входе в процедуру р не определено, хотя одноименная глобальная переменная имеет значение 1. Локальная переменная "закроет" глобальную и на экран будет выведено произвольное значение, содержащееся в неинициированной внутренней переменной. Если убрать описание
var
i : Integer;
из процедуры Р, то на экран будет выведено значение глобальной переменной т. е. 1.
Таким образом, одноименные глобальные и локальные переменные — это разные переменные. Любое обращение к таким переменным в теле подпрограммы трактуется как обращение к локальным переменным, т. е. глобальные переменные в этом случае попросту недоступны.
Описание подпрограммы
Описание подпрограммы состоит из заголовка и тела подпрограммы.
Заголовок
Заголовок процедуры имеет вид:
PROCEDURE <имя> [(<сп.ф.п.>)];
I
Заголовок функции:
FUNCTION <имя> [(<сп.ф.п.>)] :
Здесь:
<имя> — имя подпрограммы (правильный идентификатор);
<сп.ф.и. > — список формальных параметров;
f
П <тип> — тип возвращаемого функцией результата.
Сразу за заголовком подпрограммы может следовать одна из стандартных ДИреКТИВ ASSEMBLER, EXTERNAL, FAR, FORWARD, INLINE, INTERRUPT, NEAR. ЭТИ директивы уточняют действия компилятора и распространяются на всю подпрограмму и только на нее, т. е. если за подпрограммой следует другая подпрограмма, то стандартная директива, указанная за заголовком первой, не распространяется на вторую.
П ASSEMBLER — эта директива отменяет стандартную последовательность машинных инструкций, вырабатываемых при входе в процедуру и перед выходом из нее. Тело подпрограммы в этом случае должно реализоваться с помощью команд встроенного ассемблера (см. разд. 11.8).
EXTERNAL — С ПОМОЩЬЮ ЭТОЙ директивы объявляется внешняя ПОДПрОграмма (см. разд. И. 1).
П FAR — компилятор должен создавать код подпрограммы, рассчитанный на дальнюю модель вызова. Директива NEAR заставит компилятор создать код, рассчитанный на ближнюю модель памяти. По умолчанию все подпрограммы, объявленные в интерфейсной части модулей, генерируются с расчетом на дальнюю модель вызова, а все остальные подпрограммы — на ближнюю модель.
Примечание.
В соответствии с архитектурой микропроцессора ПК, в программах могут быть реализованы две модели памяти: ближняя и дальняя. Модель памяти определяет возможность вызова процедуры из различных частей программы: если используется ближняя модель, вызов возможен только в пределах 64 Кбайт (в пределах одного сегмента кода, который выделяется основной программе и каждому используемому в ней модулю); при дальней модели вызов возможен из любого сегмента. Ближняя модель экономит один байт и несколько микросекунд на каждом вызове подпрограммы, поэтому стандартный режим компиляции предполагает эту модель памяти. Однако при передаче процедурных параметров (см. разд. 8.4), а также в оверлейных модулях (см. разд. 11-6) соответствующие подпрограммы должны компилироваться с расчетом на универсальную — дальнюю — модель памяти, одинаково пригодную при любом расположении процедуры и вызывающей ее программы в памяти.
Явное объявление модели памяти стандартными директивами имеет более высокий приоритет по сравнению с опциями настройки среды Турбо Паскаля.
О FORWARD — используется при опережающем описании (см. разд. 8.6) для сообщения компилятору, что описание подпрограммы следует где-то дальше по тексту программы (но в пределах текущего программного модуля).
INLINE — указывает на то, что тело подпрограммы реализуется с помощью встроенных машинных инструкций (см. разд. 11.2).
О INTERRUPT — применяется при создании процедур обработки прерываний (см. разд. 11.4).
Параметры
Список формальных параметров необязателен и может отсутствовать. Если же он есть, то в нем должны быть перечислены имена формальных параметров и их типы, например:
Procedure SB(a: Real; b: Integer; с: Char);
Как видно из этого примера, параметры в списке отделяются друг от друга точками с запятой. Несколько следующих подряд однотипных параметров можно объединять в подсписки, например, вместо
Function F(a: Real; b: Real): Real;
можно написать проще:
Function F(a,b: Real): Real;
Операторы тела подпрограммы рассматривают список формальных параметров как своеобразное расширение раздела описаний: все переменные из этого списка могут использоваться в любых выражениях внутри подпро-
граммы. Таким способом осуществляется настройка алгоритма подпрограммы на конкретную задачу.
Рассмотрим следующий пример. В языке Турбо Паскаль нет операции возведения в степень, однако с помощью встроенных функций LN(X) и EXP (X) не трудно реализовать новую функцию с именем, например, POWER, осуществляющую возведение любого вещественного числа в любую вещественную степень. В программе (пример 8.1) вводится пара чисел х и Y и выводится на экран дисплея результат возведения х сначала в степень +Y, а затем — в степень -у. Для выхода из программы нужно нажать клавиши <Ctrl>+<Z> и <Enter>.
var
x,y : Real;
{;
Function Power (a,b : Real) : Real;
begin {Power}
if a > 0 then
Power := exp(b * ln(a))
else if a < 0 then
Power := exp(b * ln(abs (a) ) )
else if b = 0 then
Power1
else
Power0
end {Power};
()
begin {main}
repeat
readln(x,y) ;
writein(Power(x,y):12:10, Power(x,-y):15:10) until EOF
end {main).
Для вызова функции POWER мы просто указали ее в качестве параметра при обращении к встроенной процедуре WRITELN. Параметры х и Y в момент обращения к функции — это фактические параметры. Они подставляются вместо формальных параметров А и в в заголовке функции и затем над ними
осуществляются нужные действия. Полученный результат присваивается идентификатору функции — именно он и будет возвращен как значение функции при выходе из нее. В программе функция POWER вызывается дважды — сначала с параметрами х и Y, а затем — х и -Y, поэтому будут получены два разных результата. f
Механизм замены формальных параметров на фактические позволяет нужным образом настроить алгоритм, реализованный в подпрограмме. Турбо Паскаль следит за тем, чтобы количество и тип формальных параметров строго соответствовали количеству и типам фактических параметров в момент обращения к подпрограмме. Смысл используемых фактических параметров зависит от того, в каком порядке они перечислены при вызове подпрограммы. В примере 8.1 первый по порядку фактический параметр будет возводиться в Степень, задаваемую вторым параметром, а не наоборот. Пользователь должен сам следить за правильным порядком перечисления фактических параметров при обращении к подпрограмме.
Любой из формальных параметров подпрограммы может быть либо параметром-значением, либо параметром-переменной, либо, наконец, параметром-константой.
В предыдущем примере параметрыАИВ определены как параметры-значения. Если параметры определяются как параметры-переменные, перед ними необходимо ставить зарезервированное слово VAR, а если это параметры-константы, — CONST, например:
Procedure MyProcedure (var a: Real; b: Real; const c: String); .
Здесь:
A — параметр-переменная;
.
в — параметр-значение;
с — параметр-константа.
Определение формального параметра тем или иным способом существенно, в основном, только для вызывающей программы: если формальный параметр объявлен как параметр-переменная, то при вызове подпрограммы ему должен соответствовать фактический параметр в виде переменной нужного типа; если формальный параметр объявлен как параметр-значение или параметр-константа, то при вызове ему может соответствовать произвольное выражение. Контроль за неукоснительным соблюдением этого правила осуществляется компилятором Турбо Паскаля. Если бы для предыдущего примера был использован такой заголовок функции:
Function Power (var a, b : Real) : Real;
то при втором обращении к функции компилятор указал бы на несоответствие типа фактических и формальных параметров (параметр Y есть выраже-
ние, в то время как соответствующий ему формальный параметр описан как параметр-переменная).
Для того чтобы понять, в каких случаях выбрать тот или иной тип параметров, рассмотрим, как осуществляется замена формальных параметров на фактические в момент обращения к подпрограмме.
Если параметр определен как параметр-значение, то перед вызовом подпрограммы это значение вычисляется, полученный результат копируется во временную память и передается подпрограмме. Важно учесть, что даже если в качестве фактического параметра указано простейшее выражение в виде переменной или константы, все равно подпрограмме будет передана лишь копия переменной (константы). Любые возможные изменения в подпрограмме параметра-значения никак не воспринимаются вызывающей программой, т. к. в этом случае изменяется копия фактического параметра.
Если параметр определен как параметр-переменная, то при вызове подпрограммы передается сама переменная, а не ее копия (фактически в этом случае подпрограмме передается адрес переменной). Изменение параметра- переменной приводит к изменению самого фактического параметра в вызывающей программе.
В случае параметра-константы в подпрограмму также передается адрес области памяти, в которой располагается переменная или вычисленное значение. Однако компилятор блокирует любые присваивания параметру- константе нового значения в теле подпрограммы.
Представленный пример 8.2 поясняет изложенное. В программе задаются два целых числа 5 и 7, эти числа передаются процедуре INC2, в которой они удваиваются. Один из параметров передается как параметр-переменная, другой — как параметр-значение. Значения параметров до и после вызова процедуры, а также результат их удвоения выводятся на экран.
const
а : Integer5;
b : Integer
(;
Procedureс: Integer; b:
begin с ;= с + с; b := b + b;
WriteLn( 'удвоенные :', с: 5, Ь:5) end {inc2/; ()
begin (main}
WriteLn('исходные :', a: 5, b:5);
Inc2(a,b);
WriteLn('результат :', a:5, b:5) end {main}.
В результате прогона программы будет выведено:
исходные:57
удвоенные:1014
результат:107
Как видно из примера, удвоение второго формального параметра в процедуре INC2 не вызвало изменения фактической переменной в, т. к. этот параметр описан в заголовке процедуры как параметр-значение. Этот пример может служить еще и иллюстрацией механизма "перекрывания" глобальной переменной одноименной локальной: хотя переменная в объявлена как глобальная (она описана в вызывающей программе перед описанием процедуры), в теле процедуры ее "закрыла" локальная переменная в, объявленная как параметр-значение.
Итак, параметры-переменные могут служить средством связи алгоритма, реализованного в подпрограмме, с внешним миром: с помощью этих параметров подпрограмма может передавать результаты своей работы вызывающей программе. Разумеется, в распоряжении программиста всегда есть и другой способ передачи результатов — через глобальные переменные. Однако злоупотребление глобальными связями делает программу, как правило, запутанной, трудной в понимании и сложной в отладке. В соответствии с требованиями хорошего стиля программирования рекомендуется там, где это возможно, осуществлять передачу результатов через фактические параметры-переменные.
С другой стороны, описание всех формальных параметров как параметров- переменных нежелательно по двум причинам. Во-первых, это исключает возможность вызова подпрограммы с фактическими параметрами в виде выражений, что делает программу менее компактной. Во-вторых (и это главное), в подпрограмме возможно случайное использование формального параметра, например, для временного хранения промежуточного результата, т. е. всегда существует опасность непреднамеренно испортить фактическую переменную. Вот почему параметрами-переменными следует объявлять только те, через которые подпрограмма в действительности передает результаты вызывающей программе. Чем меньше параметров объявлено параметрами-переменными и чем меньше в подпрограмме имеется глобальных переменных, тем меньше опасность получения непредусмотренных программистом побочных эффектов, связанных с вызовом подпрограммы, тем проще программа в понимании и отладке. По той же причине не рекомен-
l
дуется употреблять параметры-переменные в заголовке функции: если результатом работы функции может быть не единственное значение, то логичнее использовать процедуру или нужным образом декомпозировать алгоритм на несколько подпрограмм.
Существует еще одно обстоятельство, которое следует учитывать при выборе вида формальных параметров. Как уже говорилось, при объявлении параметра-значения осуществляется копирование фактического параметра во временную память. Если этим параметром будет массив большой размерности, то существенные затраты времени и памяти на копирование при многократных обращениях к подпрограмме можно минимизировать, объявив этот параметр параметром-константой. Параметр-константа не копируется во временную область памяти, что сокращает затраты времени на вызов подпрограммы, однако любые его изменения в теле подпрограммы невозможны — за этим строго следит компилятор.
Параметры-массивы и параметры-строки
Может сложиться впечатление, что объявление переменных в списке формальных параметров подпрограммы ничем не отличается от объявления их в разделе описания переменных. Действительно, в обоих случаях много общего, но есть одно существенное различие: типом любого параметра в списке формальных параметров может быть только стандартный или ранее объявленный тип. Поэтому нельзя, например, объявить следующую процедуру:
Procedure S (a: array [1..10] of Real);
т. к. в списке формальных параметров фактически объявляется тип- диапазон, указывающий границы индексов массива.
Если мы ХОТИМ передать какой-то элемент массива, то проблем, как правило, не возникает, но если в подпрограмму передается весь массив, то следует первоначально описать его тип. Например:
type
atype = array [1..10] of Real;
Procedure S (a: atype);
Поскольку строка является фактически своеобразным массивом, ее передача в подпрограмму осуществляется аналогичным образом:
type
intype = String [15] ;
outype = String [30];
Function St (s : intype) :
Требование описать любой тип-массив или тип-строку перед объявлением подпрограммы на первый взгляд кажется несущественным. Действительно, в рамках простейших вычислительных задач обычно заранее известна структура всех используемых в программе данных, поэтому статическое описание массивов не вызывает проблем. Однако разработка программных средств универсального назначения связана со значительными трудностями.
По существу, речь идет о том, что в Турбо Паскале в подпрограммах запрещены массивы с "плавающими" границами изменения индексов. Например, если разработана программа, обрабатывающая матрицу 10x10 элементов, то для обработки матрицы 9x11 элементов необходимо переопределить тип, т. е. перекомпилировать всю программу (речь идет не о динамическом размещении массивов в куче, а о статическом описании массивов и передаче их как параметров в подпрограммы). Этот недостаток, как и отсутствие в языке средств обработки исключительных ситуаций (прерываний), унаследован из стандартного Паскаля и представляет собой объект постоянной и вполне заслуженной его критики. Разработчики Турбо Паскаля не рискнули кардинально изменить свойства базового языка, но, тем не менее, включили в него некоторые средства, позволяющие в известной степени смягчить отмеченные недостатки.
Прежде всего, в среде Турбо Паскаля можно установить режим компиляции, при котором отключается контроль за совпадением длины фактического и формального параметра-строки (см. приложение 1). Это позволяет легко решить вопрос о передаче подпрограмме строки произвольной длины. При передаче строки меньшего размера формальный параметр будет иметь ту же длину, что и параметр обращения; передача строки большего размера приведет к ее усечению до максимального размера формального параметра. Следует сказать, что контроль включается только при передаче строки, объявленной как формальный параметр-переменная. Если соответствующий параметр объявлен параметром-значением, эта опция игнорируется, и длина не контролируется.
Значительно сложнее обстоит дело с передачей массивов произвольной длины. Наиболее универсальным приемом в этом случае будет, судя по всему, работа с указателями и использование индексной арифметики. Несколько проще можно решить эту проблему при помощи нетипизированных параметров (см. разд. 8.5). В версии Турбо Паскаля 7.0 язык поддерживает так называемые открытые массивы, легко решающие проблему передачи подпрограмме одномерных массивов переменной длины.
Открытый массив представляет собой формальный параметр подпрограммы, описывающий базовый тип элементов массива, но не определяющий его размерности и границы:
Procedure MyProc(OpenArray: array of Integer);
Внутри подпрограммы такой параметр трактуется как одномерный массив с нулевой нижней границей. Верхняя граница открытого массива возвращается функцией HIGH, упоминавшейся в разд. 4.1.1. Используя о как минимальный индекс и значение, возвращаемое функцией HIGH, как максимальный индекс, подпрофамма может обрабатывать одномерные массивы произвольной длины:
. (Иллюстрация использования открытых массивов:
программа выводит на экран содержимое двух одномерных
массивов разной длины с помощью одной процедуры Array Print)
Procedure ArrayPrint(aArray: array of Integer);
var
k: Integer;
begin
for k := 0 to High(aArray) do
Write(aArray[k]:8);
WriteLn
end;
const/
array [-1..2] of Integer = (0,1,2,3);
array [5..7] of Integer = (4,5,6) ;
begin
ArrayPrint(A);
ArrayPrint(B)
end.
'
Как видно из этого примера, фактические границы массивов А И B, передаваемых в качестве параметров вызова процедуре ArrayPrint, не имеют значения. Однако размерность открытых массивов (количество индексов) всегда равна 1 — за этим следит компилятор. Если бы, например, мы добавили в профамму двумерный массив с
var
array [1..3,1..5] of Integer;
то об*ращение
ArrayPrint(C)
вызвало бы сообщение об ошибке
Error26: Type mismatch.
(Ошибка 26: Несоответствие типов.)
Процедурные типы.
Параметры-функции
и параметры-процедуры
Процедурные типы — это нововведение фирмы Borland (в стандартном Паскале таких типов нет). Основное назначение этих типов — дать программисту гибкие средства передачи функций и процедур в качестве фактических параметров обращения к другим процедурам и функциям.
Для объявления процедурного типа служит заголовок процедуры (функции), в котором опускается ее имя, например:
type
Prod = Procedure (a, b, с: Real; var d: Real);
Proc2 = Procedure (var a, b);
РгосЗ = Procedure;
Fund = Function: String;
Func 2 = Function (var s: String) : Real;
Как видно из приведенных примеров, существуют два процедурных типа: тип-процедура и тип-функция.
Пример 8.3 иллюстрирует механизм передачи процедур в качестве фактических параметров вызова. Программа выводит на экран таблицу двух функций:
sinl (х) = (sin (x) + 1) * ехр(-х)
И
cosl (x) - (cos(x) + 1) * exp(-x).
Вычисление и печать значений этих функций реализуются в процедуре PRINTFUNC, которой в качестве параметров передаются номер позиции N на экране, куда будет выводиться очередной результат (с помощью этого параметра реализуется вывод в две колонки), и имя нужной функции.
Uses CRT;
type
Func = Function (x: Real): Real;
Procedure PrintFunc (XPos: Byte; F:Func);
{Осуществляет печать функции F (XPos — горизонтальная' позиция начала вывода) }
const
пр = 20;{Количество вычислений функций}-
var
х : Real;
i : Integer;
begin {PrintFunc}
for i := 1 to np do
begin
x := i * (2 * pi / np);
GotoXY (XPos, WhereY) ;
WriteLn (x:5:3, F(x):18:5)
end
end; {Prin tFunc)
()
Function Sinl(x: Real): Real; far;
begin
sinl :=+ 1) * exp(-x)
end;
(;
Function Cosl(x: Real): Real; far;
begin
cosl :=+ 1) * exp(-x)
end;
(;
begin {основная программа} ClrScr;{Очищаем экран)
PrintFunc (1, sinl)
GotoXY (1,1);{Переводимкурсор
в левый верхний угол}
PrintFunc (40, cosl)
end.
Обратите внимание, что для установления правильных связей функций SIN1 и cosl с процедурой PRINTFUNC они должны компилироваться с расчетом на дальнюю модель памяти. Вот почему в программу вставлены стандартные директивы FAR сразу за заголовками функций. В таком режиме должны
компилироваться любые процедуры (функции), которые будут передаваться в качестве фактических параметров вызова.
Стандартные процедуры (функции) Турбо Паскаля не могут передаваться рассмотренным способом.
В программе могут быть объявлены переменные процедурных типов, например, так:
var
pl : Prod;
fl, f2 :
ар: array [1..N] of Procl;
Переменным процедурных типов допускается присваивать в качестве значений имена соответствующих подпрограмм. После такого присваивания имя переменной становится синонимом имени подпрограммы, например:
type
Proc = Procedure (n: word; var a: Byte);
var
ProcVar: Proc;
x, у : Byte;
Procedure Procl(x: word; var y: Byte); far;
begin
if x > 255 then
у x mod 255
else
у := Byte(x)
end;beginend;begin(Главная программа}(Главная программа}
end;
begin
end;
begin
(Главная программа}
(Главная программа}
ProcVar := Procl; for x := 150 to 180 do begin
ProcVar (x + 100, у) ;
Write (у:8) end end.
Разумеется, такого рода присваивания допустимы и для параметров- функций, например: type
FuncType = Function (i : Integer) : Integer;
var
VarFunc : FuncType;
i : Integer;
Function MyFunc (count : Integer) : Integer; far;
begin
end;{My Func}
begin {Основная программа}
i := MyFunc(1) ;{Обычное использование результата функции}
VarFunc := MyFunc;{Присваивание переменной процедурного
типа имени функции My Func}
end.
Отметим, что присваивание
VarFunc := MyFunc(1) ;
будет недопустимым, т. к. слева и справа от знака присваивания присутствуют несовместимые типы: слева — процедурный ТИП, а справа — integer; имя функции со списком фактических параметров MyFunc (1) трактуется Турбо Паскалем как обращение к значению функции, в то время как имя функции без списка параметров рассматривается как имя функции.
В отличие от стандартного Паскаля, в Турбо Паскале в передаваемой процедуре (функции) допустимы любые типы параметров: параметры-значения, параметры-переменные, параметры-константы (в стандартном Паскале только параметры-значения).
Нетипизированные параметры-переменные
Еще одно очень полезное нововведение фирмы Borland — возможность использования нетипизированных параметров. Параметр считается нетипизи- рованным, если тип формального параметра-переменной в заголовке подпрограммы не указан, при этом соответствующий ему фактический параметр может быть переменной любого типа. Заметим, что нетипизиро- ванными могут быть только параметры-переменные.
Нетипизированные параметры обычно применяются в случае, когда тип данных несущественен. Такие ситуации чаще всего возникают при разного
рода копированиях одной области памяти в другую, например, с помощью Процедур BLOCKREAD, BLOCKWRITE, MOVE ИТ. П.
Нетипизированные параметры в сочетании с механизмом совмещения данных в памяти (см. разд. 4.4) можно использовать для передачи подпрограмме одномерных массивов переменной длины (этот способ подходит для Турбо Паскаля версии 6.0 и более ранней, в которых нет открытых массивов).
В примере 8.4 функция NORMA вычисляет норму вектора, длина которого меняется случайным образом. Стандартная константа MAXINT содержит максимальное значение целого типа INTEGER и равна 32 767.
Следует учесть, что при обращении к функции NORMA массив х помещается в стек и передается по ссылке, поэтому описание локальной переменной А в виде одномерного массива максимально возможной длины в 65 532 байт (встроенная константа MAXINT определяет максимально возможное значение типа INTEGER и равна 32 767), совпадающего с х, на самом деле не приведет к выделению дополнительного объема памяти под размещение этой переменной. Иными словами, переменная А — фиктивная переменная, размер которой никак не влияет на объем используемой памяти. С таким же успехом можно было бы объявить ее в виде массива из одного элемента, правда, в этом случае необходимо позаботиться об отключении контроля выхода индекса за границы диапазона.
const
NN = 100;(Ma ксимальная длина вектора}
var
а : array [1.-NN] of Real;
j, N : Integer;
/)
Function Nonna (var x; N: Integer) : Real; var
a : array [1..2*MaxIntdiv SizeOf (Real)] of Real absolute x;
i : Integer;
s : Real;
begin {Norma}
s := 0;
for i := 1 to N do
s := s + sqr(a[i]) ;
Norma := sqrt (s)
end {Norma};
{;
begin (main}
for i := 1 to 10 do
begin
N := Random(NN) + 1; {Текущая длина вектора}
for j 1 to N do
a [ j ] := Random;
WriteLn ('N = ', N:2, ' норма =', Norma(a, N):10:7) end
end /main).
Как видно из рассмотренного примера, передача одномерных массивов переменной длины не вызывает никаких трудностей. Сложнее обстоит дело с многомерными массивами, однако и в этом случае указанный прием (совмещение в памяти нетипизированного параметра с фиктивной переменной) все-таки проще, чем описанная в главе 6 индексная арифметика. Еще раз напомню, что в случае многомерных массивов их элементы располагаются в памяти так, что при переходе от младших адресов к старшим наиболее быстро меняется самый правый индекс массива.
Рекурсия и опережающее описание
Рекурсия — это такой способ организации вычислительного процесса, при котором подпрограмма в ходе выполнения составляющих ее операторов обращается сама к себе.
Рассмотрим классический пример — вычисление факториала (пример 8.5). Программа вводит с клавиатуры целое число N и выводит на экран значение TV!, которое вычисляется с помощью рекурсивной функции FAC. Для выхода из программы необходимо либо ввести достаточно большое целое число, чтобы вызвать переполнение при умножении чисел с плавающей запятой, либо нажать <Ctrl>+<Z> и <Enter>.
При выполнении правильно организованной рекурсивной подпрограммы осуществляется многократный переход от некоторого текущего уровня организации алгоритма к нижнему уровню последовательно до тех пор, пока, наконец, не будет получено тривиальное решение поставленной задачи. В примере 8.5 решение при N = 0 тривиально и оно используется для остановки рекурсии.
Program Factorial;
{$S+/ {Включаем контроль переполнения стека)
var
n: Integer;
Function Fac(n: Integer): Real;
{Рекурсивная функция, вычисляющая n!)
begin {Fac}
if n < 0 then
WriteLn ( ' Ошибка в задании N ' )
else
if n = 0 then
Fac := 1
else Facn * Fac(n-1)
end {Fac} ;
/;
begin (main)
repeat
ReadLn(n);
WriteLn ('n! =
until EOF
end(main).
Рекурсивная форма организации алгоритма обычно выглядит изящнее итерационной и дает более компактный текст программы, но при выполнении, как правило, медленнее и может вызвать переполнение стека (при каждом входе в подпрограмму ее локальные переменные размещаются в особым образом организованной области памяти, называемой программным стеком). Переполнение стека особенно ощутимо сказывается при работе с сопроцессором: если программа использует арифметический сопроцессор, результат любой вещественной функции возвращается через аппаратный стек сопроцессора, рассчитанный всего на 8 уровней. Если, например, попытаться заменить тип REAL функции FAC (см. пример 8.5) на EXTENDED, программа перестанет работать уже при N= 8. Чтобы избежать переполнения стека сопроцессора, следует размещать промежуточные результаты во вспомогательной переменной. Вот правильный вариант примера 8.5 для работы с типом EXTENDED:
Program Factorial;
($S+,N+,E+) {Включаем контроль стека и работу сопроцессора)
var
n: Integer;
Function Fac(n: Integer): extended;
var
F: extended;переменная
для разгрузки стекафункция, вычисляющая
begin {Fac}
if n < 0
в задании
else
if n = 0 then
Fac := 1
else
begin
F
Fac := F * n
end
end {Fac);
(/
begin
repeat
ReadLn(n);
=
until EOF
end
Рекурсивный вызов может быть косвенным. В этом случае подпрограмма обращается к себе опосредованно, путем вызова другой подпрограммы, в которой содержится обращение к первой, например:
Procedure A (i : Byte) ;
begin
В
end;
Procedure В:
begin
A(j);
end;
Если строго следовать правилу, согласно которому каждый идентификатор перед употреблением должен быть описан, то такую программную конструкцию использовать нельзя. Для того чтобы такого рода вызовы стали возможны, вводится опережающее описание:
Procedure В ( j :forward;
Procedure A (i : Byte) ;
begin
В (i);
...
end;
Procedure B;
(
begin
A(j);
end;
Как видим, опережающее описание заключается в том, что объявляется лишь заголовок процедуры в, а ее тело заменяется стандартной директивой FORWARD. Теперь в процедуре А допустимо обращение к процедуре в — ведь она уже описана, точнее, известны ее формальные параметры, и компилятор может правильным образом организовать ее вызов. Обратите внимание: тело процедуры в начинается заголовком, в котором уже не указываются описанные ранее формальные параметры.
Расширенный синтаксис вызова функций
В Турбо Паскале есть возможность вызывать функцию и не использовать то значение, которое она возвращает. Иными словами, вызов функции может внешне выглядеть как вызов процедуры, например:
($X+) {Включа ем расширенный синтаксисГ}
Function MyFunc (var x : Integer) : Integer;
if x<0 then x:=0if x<0 then x:=0begin
if x<0 then x:=0
if x<0 then x:=0
\
else MyFunc :=
end; {MyFunc)
var
i :
begin (main)/
:= 1;
i :=
MyFunc(i){Pa сширенный синтаксис вызова)
end. {main}
Расширенный синтаксис делает использование функций таким же свободным, как, например, в языке Си, и придает Турбо Паскалю дополнительную гибкость. С помощью расширенного синтаксиса нельзя вызывать стандартные функции. Компиляция с учетом расширенного синтаксиса включается активным состоянием опции Extended syntax диалогового окна Options | Compiler (см. приложение 1) или глобальной директивой компилятора {$X+}.
Глава 9
Модули
Стандартный Паскаль не предусматривает механизмов раздельной компиляции частей программы с последующей их сборкой перед выполнением. Более того, последовательное проведение в жизнь принципа обязательного описания любого объекта перед его использованием делает фактически невозможной разработку разнообразных библиотек прикладных программ. Точнее, такие библиотеки в рамках стандартного Паскаля могут существо* вать только в виде исходных текстов и программист должен сам включать в программу подчас весьма обширные тексты различных поддерживающих процедур, таких как процедуры матричной алгебры, численного интегрирования, математической статистики и т. п.
Вполне понятно поэтому стремление разработчиков коммерческих компиляторов Паскаля включать в язык средства, повышающие его модульность. Чаще всего таким средством является разрешение использовать внешние процедуры и функции, тело которых заменяется стандартной директивой EXTERNAL. Разработчики Турбо Паскаля пошли в этом направлении еще дальше, включив в язык механизм так называемых модулей.
Модуль — это автономно компилируемая программная единица, включающая в себя различные компоненты раздела описаний (типы, константы, переменные, процедуры и функции) и, возможно, некоторые исполняемые операторы инициирующей части. По своей организации и характеру использования в программе модули Турбо Паскаля близки к модулям-пакетам (PACKAGE) языка программирования Ада. В них так же, как в пакетах Ады, явным образом выделяется некоторая "видимая" интерфейсная часть, в которой сконцентрированы описания глобальных типов, констант и переменных, а также приводятся заголовки глобальных процедур и функций. Появление объектов в интерфейсной части делает их доступными для других модулей и основной программы. Тела процедур и функций располагаются в исполняемой части модуля, которая может быть скрыта от пользователя.
Насколько сильно изменяются свойства языка Паскаль при введении механизма модулей, свидетельствует следующее замечание его автора Н. Вирта, сделанное им по поводу более позднего языка Модула-2: "Модули — самая
важная черта, отличающая язык Модула-2 от его предшественника Паскаля".
Модули представляют собой прекрасный инструмент для разработки библиотек прикладных программ и мощное средство модульного программирования. Важная особенность модулей заключается в том, что компилятор Турбо Паскаля размещает их программный код в отдельном сегменте памяти. Максимальная длина сегмента не может превышать 64 Кбайт, однако количество одновременно используемых модулей ограничивается лишь доступной памятью, что дает возможность создавать весьма крупные программы.
Структура модулей
Модуль имеет следующую структуру:
UNIT <имя>;
'
INTERFACE
Интерфейсная часть>
IMPLEMENTATION
Исполняемая часть >
BEGIN
Инициализирующая часть>
END.
Здесь:
UNIT — зарезервированное слово (единица); начинает заголовок модуля;
<имя> — имя модуля (правильный идентификатор);
П INTERFACE — зарезервированное слово (интерфейс); начинает интерфейсную часть модуля;
П IMPLEMENTATION — зарезервированное слово (выполнение); начинает исполняемую часть;
П BEGIN — зарезервированное слово; начинает инициализирующую часть модуля; конструкция BEGIN Инициализирующая часть> необязательна;
П END — зарезервированное слово — признак конца модуля.
Таким образом, модуль состоит из заголовка и трех составных частей, любая из которых может быть пустой. .
Заголовок модуля и связь модулей
ДРУГ с другом
Заголовок модуля состоит из зарезервированного слова UNIT и следующего за ним имени модуля. Для правильной работы среды Турбо Паскаля и возможности подключения средств, облегчающих разработку крупных про-
грамм (см. разд. 9.6), это имя должно совпадать с именем дискового файла, в который помещается исходный текст модуля. Если, например, имеем заголовок
Unit Global;
то исходный текст соответствующего модуля должен размещаться в дисковом файле GLOBAL.PAS. Имя модуля служит для его связи с другими модулями и основной программой. Эта связь устанавливается специальным предложением
USES
Здесь:
USES — зарезервированное слово (использует);
<сп.модулей> — список модулей, с которыми устанавливается связь; элементами списка являются имена модулей, отделяемые друг от друга запятыми, например:
Uses CRT, Graph, Global;
Если встречается объявление USESто оно должно открывать раздел опи
саний основной программы. Модули могут использовать другие модули. Предложение USES в модулях может следовать либо сразу за зарезервированным СЛОВОМ INTERFACE, Либо Сразу За СЛОВОМ IMPLEMENTATION, Либо, и там, и там (т. е. допускаются два предложения USES).
Интерфейсная часть
Интерфейсная часть открывается зарезервированным словом INTERFACE. Здесь содержатся объявления всех глобальных объектов модуля (типов, констант, переменных и подпрограмм), которые должны стать доступными основной программе и/или другим модулям. При объявлении глобальных подпрограмм в интерфейсной части указывается только их заголовок, например:
Unit .
Interface
type
complex = record re,: real
end;
Procedure(x, у complex; var z :
Procedure(x, у : complex; var z :
Если теперь в основной программе написать предложение
Uses Cmplx;
то в программе станут доступными тип COMPLEX и две процедуры — ADDC и MULC ИЗ МОДУЛЯ CMPLX.
Отметим, что объявление подпрограмм в интерфейсной части автоматически сопровождается их компиляцией с использованием дальней модели памяти (см. главу 8). Таким образом обеспечивается доступ к подпрограммам из основной программы и других модулей.
Следует учесть, что все константы и переменные, объявленные в интерфейсной части модуля, равно как и глобальные константы и переменные основной программы, помещаются компилятором Турбо Паскаля в общий сегмент данных (максимальная длина сегмента 65 536 байт). Порядок появления различных разделов объявлений и их количество могут быть произвольными. Если в интерфейсной части объявляются внешние подпрограммы или подпрограммы в машинных кодах (см. главу 11), их тела (т. е. зарезервированное слово EXTERNAL, в первом случае, и машинные коды вместе со словом INLINE — во втором) должны следовать сразу за их заголовками в исполняемой части модуля (не в интерфейсной!). В интерфейсной части модулей нельзя использовать опережающее описание.
Исполняемая часть/
Исполняемая часть начинается зарезервированным словом IMPLEMENTATION и содержит описания подпрограмм, объявленных в интерфейсной части. В ней могут объявляться локальные для модуля объекты — вспомогательные типы, константы, переменные и блоки, а также метки, если они используются в инициирующей части.
Описанию подпрограммы, объявленной в интерфейсной части модуля, в исполняемой части должен предшествовать заголовок, в котором можно опускать список формальных переменных (и тип результата для функции), т. к. они уже описаны в интерфейсной части. Но если заголовок подпрограммы приводится в полном виде, т. е. со списком формальных параметров и объявлением результата, он должен совпадать с заголовком, объявленным в интерфейсной части, например:
Unit Cmplx;
Interface
type
re, im : real
end;
Procedure AddC (х, у : complex; var z : complex);
Implementation
Procedure AddC;
begin
z.re :=X.re+y. re; :=+
end;
end.I
Локальные переменные и константы, а также все профаммные коды, порожденные при компиляции модуля, помещаются в общий сегмент памяти.
Инициализирующая часть
Инициализирующая часть завершает модуль. Она может отсутствовать вместе с начинающим ее словом BEGIN или быть пустой, — тогда за BEGIN сразу следует признак конца модуля (слово END и следующая за ним точка).
В инициализирующей части размещаются исполняемые операторы, содержащие некоторый фрагмент программы. Эти операторы исполняются до передачи управления основной профамме и обычно используются для подготовки ее работы. Например, в них могут инициализироваться переменные, открываться нужные файлы, устанавливаться связи с другими ПК по коммуникационным каналам и т. п.:
Unit FileText;
Interface
Procedure Print(s :
Implementation
var
f: text;
const
name = 'output.txt';
Procedure Print;
begin
WriteLn(f, s)
end;
{ Начало инициализирующей части: } .
begin
assign(f, name);
rewrite(f);
{ Конец инициализирующей части }
end.
Не рекомендуется делать инициирующую часть пустой, лучше ее опустить: пустая часть содержит пустой оператор, которому будет передано управление при запуске программы. Это часто вызывает проблемы при разработке оверлейных программ (см. главу 11).
Компиляция модулей
В среде Турбо Паскаля имеются средства, управляющие способом компиляции модулей и облегчающие разработку крупных программных проектов. В частности, определены три режима компиляции: COMPILE, MAKE и BUILD (см. приложение 1). Режимы отличаются только способом связи компилируемого модуля или основной программы с другими модулями, объявленными в предложении uses.
При компиляции модуля или основной программы в режиме COMPILE все упоминающиеся в предложении USES модули должны быть предварительно откомпилированы и результаты компиляции помещены в одноименные файлы с расширением TPU. Например, если в программе (модуле) имеется предложение
Uses Global;
то на диске в каталоге, объявленном опцией UNIT DIRECTORIES (см. приложение 1), уже должен находиться файл GLOBAL.TPU. Файл с расширением TPU (от англ. Turbo Pascal Unit) создается автоматически в результате компиляции модуля (если основная профамма может компилироваться без создания исполняемого ЕХЕ-файла, то компиляция модуля всегда приводит к созданию TPU-файла).
В режиме МАКЕ компилятор проверяет наличие TPU-файлов для каждого объявленного модуля. Если какой-либо из файлов не обнаружен, система пытается отыскать одноименный файл с расширением PAS, т. е. файл с исходным текстом модуля, и, если искомый файл найден, приступает к его компиляции. Кроме того, в этом режиме система следит за возможными изменениями исходного текста любого используемого модуля. Если в PAS- файл (исходный текст модуля) внесены какие-либо изменения, то независимо от того, есть ли уже в каталоге соответствующий TPU-файл или нет, система осуществляет его компиляцию перед компиляцией основной программы. Более того, если изменения внесены в интерфейсную часть модуля, то будут перекомпилированы также и все другие модули, обращающиеся к нему. Режим МАКЕ, таким образом, существенно облегчает процесс разра-
крупных программ с множеством модулей: профаммист избавляется от необходимости следить за соответствием существующих TPU-файлов их исходному тексту, т. к. система делает это
В режиме BUILD существующие TPU-файлы игнорируются, и система пытается отыскать (и компилировать) соответствующийдля каждого
объявленного в предложении USES модуля. После компиляции в режиме BUILD профаммист может быть уверен в том, что учтены все сделанные им изменения в любом из модулей.
Подключение модулей к основной профамме и их возможная компиляция осуществляются в порядке их объявления в предложении USES. При переходе к очередному модулю система предварительно отыскивает все модули, на которые он ссылается. Ссылки модулей друг на друга могут образовывать древовидную структуру любой сложности, однако запрещается явное или косвенное обращение модуля к самому себе.недопустимы сле
дующие объявления:
Unit A;Unit В;
InterfaceInterfaceV
В;Uses A;
Implementation
end.end.
. 1 ■
Это офаничение можно обойти, если "спрятать" предложение USES в исполняемые части зависимых модулей:
Unit A;Unit В;
InterfaceInterface
ImplementationImplementation
Uses В;Uses A;
end. end.
Дело в том, что Турбо Паскаль разрешает ссылки на частично откомпилированные модули, что приблизительно соответствует опережающему описанию Если интерфейсные части любых двух модулей независимы (это непременное Турбо Паскаль сможет идентифицировать все глобальные идентификаторы в каждом из модулей, после чего откомпилирует тела модулей обычным способом.
Доступ к объявленным в модуле объектам
Пусть, например, мы создаем модуль, реализующий арифметику комплексных чисел (такая арифметика ни в стандартном Паскале, ни в Турбо Паскале не предусмотрена). К сожалению, в Турбо Паскале нельзя использовать функции, значения которых имели бы тип (запись, например), поэтому арифметика комплексных чисел реализуется четырьмя процедурами:
UNIT Cmplx;
INTERFACE
type complex = record re, im : real end;
Procedure AddC(x,уcomplex;varz
Procedure SubC(x,уcomplex;varz
Procedureуcomplex;varz
Procedure DivC(x,уcomplex;varz
const
: complex = (re0.1; im:
(;
IMPLEMENTATION
(}
Procedure AddC;
begin
+ im := im+im
end
Procedure SubC;
begin
re :=re —
im :=im—im
end
Procedure
941
begin
:=*—*
im :=re *im +im *re
end {Mu IC);
Procedure
var
zz : real;
begin
zz :=+.
/ zz;/ zz/ zz;/ zzz.re := (x.re *+x.im* y.im)
/ zz;
/ zz
/ zz;
/ zz
:= (x.re * y.im — x.im * y.re) end {DivC};
end.
Текст этого модуля следует поместить откомпилировать, создав TPU-файл,Текст этого модуля следует поместить откомпилировать, создав TPU-файл,в файл CMPLX.PAS. Вы можете его после чего вашей программе станутв файл CMPLX.PAS. Вы можете его после чего вашей программе станут
Текст этого модуля следует поместить откомпилировать, создав TPU-файл,
Текст этого модуля следует поместить откомпилировать, создав TPU-файл,
в файл CMPLX.PAS. Вы можете его после чего вашей программе станут
в файл CMPLX.PAS. Вы можете его после чего вашей программе станут
доступны процедуры из новой библиотеки. Например, в следующей программе (пример 9.1) осуществляются четыре арифметические операции над парой комплексных чисел.
Uses Cmplx;
var
b, с : complex;
begin
re =.im:=1
b. re : = 1; b.im := 2;
b,
', c.re:5:l,
b, c)
: ',
b,
:
c)
WriteLn('Деление : ', c.re:5:1, c.im:5:1, 'i');
end.
После объявления Uses Cmplx программе стали доступны все объекты, объявленные в интерфейсной части модуля cmpix. При необходимости можно переопределить любой их этих объектов, как это произошло, например, с объявленной в модуле типизированной константой с. Переопределение объекта означает, что вновь объявленный объект "закрывает" ранее определенный в модуле одноименный объект. Чтобы получить доступ к "закрытому" объекту, нужно воспользоваться составным именем: перед именем объекта поставить имя модуля и точку. Например, оператор
WriteLn(cmplx.c.re:5:l, cmplx.c.im:5:1,'i*);
выведет на экран содержимое "закрытой" типизированной константы из предыдущего примера.
9.8. Стандартные модули
В Турбо Паскале имеется восемь стандартных модулей, в которых содержится большое число разнообразных типов, констант, процедур и функций. Этими МОДУЛЯМИ ЯВЛЯЮТСЯ SYSTEM, DOS, CRT, PRINTER, GRAPH, OVERLAY, TURB03 И GRAPHS. Модули GRAPH, TURBOS и GRAPHS выделены в отдельные TPU-файлы, а остальные входят в состав библиотечного файла TURBO.TPL. Лишь один модуль SYSTEM подключается к любой программе автоматически, все остальные становятся доступны только после указания их имен в списке, следующем за словом USES.
Ниже приводится краткая характеристика стандартных модулей. Полное описание входящих в них программных средств приведено в приложении 4, а описанию объектно-ориентированной библиотеки Turbo Vision посвящена вся часть II книги.
Модуль SYSTEM. В него входят все процедуры и функции стандартного Паскаля, а также встроенные процедуры и функции, которые не вошли в другие стандартные модули (например, INC, DEC, GETDIR и т. п.). Как уже отмечалось, модуль SYSTEM подключается к любой программе независимо от того, объявлен ли он в предложении USES или нет, поэтому его глобальные константы, переменные и подпрограммы считаются встроенными в Турбо Паскаль.
Модуль PRINTER. Делает доступным вывод текстов на матричный принтер. В нем определяется файловая переменная LST типа TEXT, которая связывается с логическим устройством PRN. После подключения модуля может быть выполнена, например, такая программа:
Uses Printer;
begin
writeln (LST, ' Турбо Паскаль ')
end.
Модуль CRT. В нем сосредоточены процедуры и функции, обеспечивающие управление текстовым режимом работы экрана. С помощью входящих в модуль подпрограмм можно перемещать курсор в произвольную позицию экрана, менять цвет выводимых символов и окружающего их фона, создавать окна. Кроме того, в модуль включены также процедуры "слепого" чтения клавиатуры и управления звуком.
Модуль GRAPH. Содержит обширный набор типов, констант, процедур и функций для управления графическим режимом работы экрана. С помощью подпрограмм, входящих в модуль GRAPH, можно создавать разнообразные графические изображения и выводить на экран текстовые надписи стандартными или разработанными программистом шрифтами. Подпрограммы модуля GRAPH после соответствующей настройки могут поддерживать различные типы аппаратных графических средств. Настройка на имеющиеся в распоряжении программиста технические средства графики осуществляется специальными программами — драйверами, которые не -входят в файл GRAPH.TPU, но поставляются вместе с ним.
Модуль DOS. В модуле собраны процедуры и функции, открывающие доступ программам к средствам дисковой операционной системы MS-DOS.
Модуль OVERLAY. Он необходим при разработке громоздких программ с перекрытиями. Как уже говорилось, Турбо Паскаль обеспечивает создание программ, длина которых ограничивается лишь основной оперативной памятью ПК. Операционная система MS-DOS оставляет исполняемой программе около 580 Кбайт основной памяти (без учета резидентных программ и самой системы Турбо Паскаль). Память такого размера достаточна для большинства применений, тем не менее использование программ с перекрытиями (см. главу 11) снимает это ограничение.
Два библиотечных модуля TURBO3 и GRAPH3 введены для совместимости с ранней версией 3.0 системы Турбо Паскаль.
Глава 10
Объекты
В основе того или иного языка программирования лежит некоторая руководящая идея, оказывающая существенное влияние на стиль соответствующих программ.
Исторически первой была идея процедурного структурирования программ, в соответствии с которой программист должен был решить, какие именно процедуры он будет использовать в своей программе, а затем выбрать наилучшие алгоритмы для реализации этих процедур. Появление этой идеи было следствием недостаточной изученности алгоритмической стороны вычислительных процессов, столь характерной для ранних программных разработок (40-50 годы прошлого столетия). Типичным примером процедурно-ориентированного языка является Фортран — первый и все еще один из наиболее популярных языков программирования. Последовательное использование идеи процедурного структурирования программ привело к созданию обширных библиотек программирования, содержащих множество сравнительно небольших процедур, из которых, как из кирпичиков, можно строить "здание" программы.
По мере прогресса в области вычислительной математики акцент в программировании стал смещаться с процедур в сторону организации данных. Оказалось, что эффективная разработка сложных программ нуждается в действенных способах контроля правильности использования данных. Контроль должен осуществляться как на стадии компиляции, так и при прогоне программ, в противном случае, как показала практика, резко возрастают трудности создания крупных программных проектов. Отчетливое осознание этой проблемы привело к созданию Алгола-60, а позже — Паскаля, Модулы-2, Си и множества других языков программирования, имеющих более или менее развитые структуры типов данных. Логическим следствием развития этого направления стал модульный подход к разработке программ, характеризующийся стремлением "спрятать" данные и процедуры внутри модуля.
Начиная с языка Симула-67, в программировании наметился новый подход, который получил название объектно-ориентированного программирования
(ООП). Его руководящая идея заключается в стремлении связать данные с обрабатывающими эти данные процедурами в единое целое — объект. Характерной чертой объектов является инкапсуляция (объединение) данных и алгоритмов их обработки, в результате чего и данные, и процедуры во многом теряют самостоятельное значение. Фактически объектноориентированное программирование можно рассматривать как модульное программирование нового уровня, когда вместо во многом случайного, механического объединения процедур и данных акцент делается на их смысловую связь.
Какими мощными средствами располагает объектно-ориентированное программирование наглядно демонстрирует библиотека Turbo Vision, входящая в комплект поставки Турбо Паскаля и описываемая во второй части этой книги. В этой главе мы рассмотрим основные идеи ООП и способы их реализации.
Следует заметить, что преимущества ООП в полной мере проявляются лишь при разработке достаточно сложных программ. Более того, инкапсуляция придает объектам совершенно особое свойство "самостоятельности", максимальной независимости от остальных частей программы. Правильно сконструированный объект располагает всеми необходимыми данными и процедурами их обработки, чтобы успешно реализовать требуемые от него действия. Попытки применить ООП для программирования несложных алгоритмов, связанных, например, с расчетными вычислениями по готовым формулам, чаще всего выглядят искусственными нагромождениями ненужных языковых конструкций. Такие программы обычно не нуждаются в структуризации, расчленении алгоритма на ряд относительно независимых частей, их проще и естественнее разрабатывать традиционными способами Паскаля. При разработке сложных диалоговых программ программист вынужден структурировать программу, т. к. только в этом случае он может рассчитывать на успех: "критической массой" неструктурированных программ является объем в 1000-1200 строк исходного текста, отладка неструктурированных программ большего объема обычно сталкивается с чрезмерными трудностями. Структурирование программы ведет, фактически, к разработке собственной библиотеки программирования — вот в этот момент к вам на помощь и приходят новые средства ООП.
Основные принципы ООП
Объектно-ориентированное программирование основано на "трех китах" — трех важнейших принципах, придающих объектам новые свойства. Этими принципами являются инкапсуляция, наследование и полиморфизм.
Инкапсуляция
Инкапсуляция есть объединение в единое целое данных и алгоритмов обработки этих данных. В рамках ООП данные называются полями объекта, а алгоритмы — объектными методами.
Инкапсуляция позволяет в максимальной степени изолировать объект от внешнего окружения. Она существенно повышает надежность разрабатываемых программ, т. к. локализованные в объекте алгоритмы обмениваются с программой сравнительно небольшими объемами данных, причем количество и тип этих данных обычно тщательно контролируются. В результате замена или модификация алгоритмов и данных, инкапсулированных в объект, как правило, не влечет за собой плохо прослеживаемых последствий для программы в целом (в целях повышения защищенности программ в ООП почти не используются глобальные переменные).
Другим немаловажным следствием инкапсуляции является легкость обмена объектами, переноса их из одной программы в другую. Можно сказать, что ООП "провоцирует" разработку библиотек объектов, таких как Turbo Vision.
Наследование
Наследование есть свойство объектов порождать своих потомков. Объект- потомок автоматически наследует от родителя все поля и методы, может дополнять объекты новыми полями и заменять (перекрывать) методы родителя или дополнять их.
Принцип наследования решает проблему модификации свойств объекта и придает ООП в целом исключительную гибкость. При работе с объектами программист обычно подбирает объект, наиболее близкий по своим свойствам для решения конкретной задачи, и создает одного или нескольких потомков от него, которые "умеют" делать то, что не реализовано в родителе.
Последовательное проведение в жизнь принципа "наследуй и изменяй" хорошо согласуется с поэтапным подходом к разработке крупных программных проектов и во многом стимулирует такой подход.
Полиморфизм
Полиморфизм — это свойство родственных объектов (т. е. объектов, имеющих одного общего родителя) решать схожие по смыслу проблемы разными способами. В рамках ООП поведенческие свойства объекта определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках объекта, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть его в потомке, т. е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и
объекте-потомке будут действовать два одноименных метода, имеющие разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.
В Турбо Паскале полиморфизм достигается не только описанным выше механизмом наследования и перекрытия методов родителя, но и их виртуализацией (см. ниже), позволяющей родительским методам обращаться к методам потомков.
Постановка учебной задачи
Знакомство с техникой ООП в этом разделе иллюстрируется примерами, объединенными рамками следующей учебной задачи.
Требуется разработать программу, которая создает на экране ряд графических изображений (точки, окружность, линия, квадрат) и может перемещать эти изображения по экрану. Вид создаваемого программой экрана показан на рис. 10.1.
Рис. 10.1. Экран, создаваемый учебной программой
Для перемещения изображений в программе будут использоваться клавиши управления курсором, клавиши <Home>, <End>, <PgUp>, <PgDn> (для перемещения по диагональным направлениям) и клавиша <Tab> для выбора перемещаемого объекта. Для выхода из программы — клавиша <Esc>.
Техническая реализация программы потребует подключения двух стандартных библиотек — CRT и GRAPH, которые еще не рассматривались в этой книге. Чтобы не отвлекать ваше внимание от основных проблем ООП, при описании реализации учебной задачи особенности работы с этими библиотеками лишь очень кратко комментируются в текстах программы. Если вы не привыкли "принимать на веру" предлагаемые программные решения и хотите разобраться с деталями вызова незнакомых вам процедур и функций, рекомендую просмотреть материал глав 13 и 14, где описаны эти библиотеки (они не используют средств ООП и, следовательно, могут изучаться до чтения настоящей главы).
/
Создание объектов
В Турбо Паскале для создания объектов предназначены три зарезервированных слова object, constructor, destructor и три стандартные директивы private, public И virtual.
Зарезервированное слово object служит для описания объекта. Описание объекта должно помещаться в разделе описания типов:
type
MyObject = object
(Поля объекта}
{Методы объекта}
end;
Если объект порождается от какого-либо родителя, имя родителя указывается в круглых скобках сразу за словом object:
type
MyDescendantObject =
end;
Любой объект может иметь сколько угодно потомков, но только одного родителя, что позволяет создавать иерархические деревья наследования объектов.
Для нашей учебной задачи создадим объект-родитель TGraphobject, в рамках которого будут инкапсулированы поля и методы, общие для всех остальных объектов:
type
TGraphObj = object
Private. объекта будут скрыты от
Integer;точки)
Color: Word;фигуры)
объекта будут доступны пользователю)
Constructor Init(aX,aY: Integer; aColor:
/Создает экземпляр
ProcedureVirtual;
объект заданным цветом aColor)
Procedure Show;
объект — вычерчивает его цветом
Procedure Hide;
объект — вычерчивает его цветом
Procedure
объект в точку с координатами и
end;описания объекта
В дальнейшем предполагается создать объекты-потомки отреали
зующие все специфические свойства точки, линии, окружности и прямоугольника. Каждый из этих графических объектов будет характеризоваться положением на экране (поля х и Y) и цветом (поле color). С помощью метода Draw он будет способен отображать себя на экране, а с помощью свойств "показать себя" (метод show) и "спрятать себя" (метод Hide) сможет перемещаться по экрану (метод MoveTo). Учитывая общность свойств графических объектов, мы объявляем абстрактный объекткоторый не связан с
конкретной графической фигурой. Он объединяет в себе все общие поля и методы реальных фигур и будет служить родителем для других объектов.
Директива Private в описании объекта открывает секцию описания скрытых полей и методов. Перечисленные в этой секции элементы объекта "не видны" программисту, если этот объект он получил в рамках библиотечного TPU-модуля. Скрываются обычно те поля и методы, к которым программист (в его же интересах!) не должен иметь непосредственного доступа. В нашем примере он не может произвольно менять координаты реперной точки (X, Y), т. к. это не приведет к перемещению объекта. Для изменения полей х и Y предусмотрены входящие в состав объекта методы и MoveTo. Скрытые поля и методы доступны в рамках той программной единицы (программы или модуля), где описан соответствующий объект. В дальнейшем предполагается, что программа будет использовать модуль Graphobj с описанием объектов. Скрытые поля будут доступны в модуле Graphob но недоступны в вызывающей его основной профамме. Разумеется, в рамках реальной задачи создание скрытых элементов объекта вовсе не обязательно. Я ввел их в объект TGraphObj лишь для иллюстрации возможностей ООП.
Директива public отменяет действие директивы private, поэтому все следующие за public элементы объекта доступны в любой программной единице. Директивы private и public могут произвольным образом чередоваться в пределах одного объекта.
Вариант объявления объекта TGraphObj без механизма private. . .public.' type
TGraphObj = object
X,Y: Integer;
Color: Word;
Constructor Init(aX,aY: Integer; aColor: Word);
Procedure Draw(aColor: Word); Virtual;
Procedure Show;
Procedure Hide;
Procedure MoveTo(dX, dY: Integer);
end;
Описания полей ничем не отличаются от описания обычных переменных. Полями могут быть любые структуры данных, в том числе и другие объекты. Используемые в нашем примере поля х и Y содержат координату реперной (характерной) точки графического объекта, а поле color — его цвет. Реперная точка характеризует текущее положение графической фигуры на экране и, в принципе, может быть любой ее точкой1.
Для описания методов в ООП применяются традиционные для Паскаля процедуры и функции, а также особый вид процедур ■■■■■ конструкторы и деструкторы. Конструкторы предназначены для создания конкретного экземпляра объекта, ведь объект — это тип данных, т. е. "шаблон", по которому можно создать сколько угодно рабочих экземпляров данных объектного типа (подобных TGraphObj, например). Зарезервированное слово constructor, используемое в заголовке конструктора вместо procedure, предписывает компилятору создать особый код пролога, с помощью которого настраивается так называемая таблица виртуальных методов (см. ниже). Если в объекте нет виртуальных методов, в нем может не быть ни одного конструктора, наоборот, если хотя бы один метод описан как виртуальный (с последующим словом virtual, см. метод Draw), в состав объекта должен входить хотя бы один конструктор и обращение к конструктору должно предшествовать обращению к любому виртуальному методу.
В нашем примере она совпадает с координатами точки в описываемом ниже объекте TPoint, с центром окружности в объекте TCircle, первым концом прямой в объекте TLine и с левым верхним углом прямоугольника в объекте TRect.
/
Типичное действие, реализуемое конструктором, состоит в наполнении объектных полей конкретными значениями. Следует заметить, что разные экземпляры одного и того же объекта отличаются друг от друга только содержимым объектных полей, в то время как каждый из них работает с одними и теми же объектными методами. В нашем примере конструктор Init объекта TGraphObj получает все необходимые для полного определения экземпляра данные через параметры обращения ах, aY и aColor.
Процедура Draw предназначена для вычерчивания графического объекта. Эта процедура будет реализовываться в потомках объекта TGraphObj по-разному. Например, для визуализации точки следует вызвать процедуру Put Pixel, для вычерчивания линии — процедуру Line и т. д. В объекте TGraphObj процедура Draw определена как виртуальная ("воображаемая"). Абстрактный объект TGraphObj не предназначен для вывода на экран, однако наличие процедуры Draw в этом объекте говорит о том, что любой потомок TGraphObj должен иметь собственный метод Draw, с помощью которого он может показать себя на экране.
При трансляции объекта, содержащего виртуальные методы, создается так называемая таблица виртуальных методов (ТВМ), количество элементов которой равно количеству виртуальных методов объекта. В этой таблице будут храниться адреса точек входа в каждый виртуальный метод. В нашем примере ТВМ объекта TGraphObj хранит единственный элемент — адрес метода Draw. Первоначально элементы ТВМ не содержат конкретных адресов. Если бы мы создали экземпляр объекта TGraphObj с помощью вызова его конструктора Init, код пролога конструктора поместил бы в ТВМ нужный адрес родительского метода Draw. Далее мы создадим несколько потомков объекта TGraphObj. Каждый из них будет иметь собственный конструктор, с помощью которого ТВМ каждого потомка настраивается так, чтобы ее единственный элемент содержал адрес нужного метода Draw. Такая процедура называется поздним связыванием объекта. Позднее связывание позволяет методам родителя обращаться к виртуальным методам своих потомков и использовать их для реализации специфичных для потомков действий.
Наличие в объекте TGraphObj виртуального метода Draw позволяет легко реализовать три других метода объекта: чтобы показать объект на экране в методе show, вызывается Draw с aCoior, равным значению поля color, a чтобы спрятать графический объект, в методе Hide вызывается Draw со значением цвета GetBkColor, т. е. с текущим цветом фона.
Рассмотрим реализацию перемещения объекта. Если потомок TGraphObj (например, TLine) хочет переместить себя на экране, он обращается к родительскому методу MoveTo. В этом методе сначала с помощью Hide объект стирается с экрана, а затем с помощью show показывается в другом месте. Для реализации своих действий и Hide, и show обращаются к виртуальному методу Draw. Поскольку вызов MoveTo происходит в рамках объекта TLine,
используется ТВМ этого объекта и вызывается его метод Draw, вычерчивающий прямую. Если бы перемещалась окружность, ТВМ содержала бы адрес метода Draw объекта TCircle и визуализация-стирание объекта осуществлялась бы с помощью этого метода.
Чтобы описать все свойства объекта, необходимо раскрыть содержимое объектных методов, т. е. описать соответствующие процедуры и функции. Описание методов производится обычным для Паскаля способом в любом месте раздела описаний, но после описания объекта. Например:
type
TGraphObj = object
end;
Constructor TGraphObj.Init;
begin
X := aX; '
Y := aY;
Color := aColor
end;
Procedure TGraphObj.Draw;
begin
{Эта процедура в родительском объекте ничего не делает, поэтому экземпляры TGraphObj не способны отображать себя на экране. Чтобы потомки объекта TGraphObj были способны отображать себя, они должны перекрывать этот метод}
end;
Procedure TGraphObj.Show;
begin
Draw(Color) end;
Procedure TGraphObj.Hide;
begin
Draw(GetBkColor)
end;
Procedure TGraphObj.MoveTo;
begin
Hide;
X := X+dX;
Y := Y+dY;
Show
end;
Отмечу два обстоятельства. Во-первых, при описании методов имя метода дополняется спереди именем объекта, т. е. употребляется составное имя метода. Это необходимо по той простой причине, что в иерархии родственных объектов любой из методов может быть перекрыт в потомках. Составные имена четко указывают принадлежность конкретной процедуры. Во-вторых, в любом объектном методе можно использовать инкапсулированные поля объекта почти так, как если бы они были определены в качестве глобальных переменных. Например, в конструкторе TGraph.Init переменные в левых частях операторов присваивания представляют собой объектные поля и не должны заново описываться в процедуре. Более того, описание
Constructor TGraphObj.Init;
var
X,Y: Integer;{Ошибка!)
Color: Word;{Ошибка!)
begin
end;
вызовет сообщение о двойном определении переменных х, Y и Color (в этом и состоит отличие в использовании полей от глобальных переменных: глобальные переменные можно переопределять в процедурах, в то время как объектные поля переопределять нельзя).
Обратите внимание, что абстрактный объект TGraphObj не предназначен для вывода на экран, поэтому его метод Draw ничего не делает. Однако методы Hide, show и MoveTo "знают" формат вызова этого метода и реализуют необходимые действия, обращаясь к реальным методам Draw своих будущих потомков через соответствующие ТВМ. Это и есть полиморфизм объектов.
Создадим простейшего потомка от TGraphObj — объект TPoint, с помощью которого будет визуализироваться и перемещаться точка. Все основные дей-
ствия, необходимые для этого, уже есть в объекте TGraphObj, поэтому в объекте Tpoint перекрывается единственный метод — Draw:
type
TPoint = object(TGraphObj)
Procedure Draw(aColor); Virtual;
end;
Procedure TPoint.Draw;
begin
PutPixel(X,Y,Color) (Показываемцветом Color пиксел
с координатами X и Y) .
end;
В новом объекте TPoint можно использовать любые методы объекта- родителя TGraphObj. Например, вызвать метод MoveTo, чтобы переместить изображение точки на новое место. В этом случае родительский метод TGraphObj .MoveTo будет обращаться к методу TPoint.Draw, чтобы спрятать и затем показать изображение точки. Такой вызов станет доступен после обращения к конструктору Init объекта TPoint, который нужным образом настроит ТВМ объекта. Если вызвать TPoint.Draw до вызова Init, его ТВМ не будет содержать правильного адреса и программа "зависнет".
Чтобы создать объект-линию, необходимо ввести два новых поля для хранения координат второго конца. Дополнительные поля требуется наполнить конкретными значениями, поэтому нужно перекрыть конструктор родительского объекта:
type
TLine = object(TGraphObj)
dX,dY: Integer;{Приращениякоординат второго конца}
Constructor Init (XI, Y1,X2, Y2: Integer; aColor: Word);
Procedure Draw(aColor: Word); Virtual;
end;
Constructor TLine.Init;
. /Вызывает унаследованный конструктор TGraphObj для инициализации полей X, Y и Color. Затем инициализирует поля dX и dY}
begin
(Вызываем унаследованный конструктор}
Inherited Init(XI,Yl,aColor);
/ Инициализируемполя dX и dY)
:=
:= Y2-Y1
end;
Procedure Draw;
begin
цвет
Line (X, Y,X+dX, Y+dY) {Вычерчиваемлинию} end;
В конструкторедля инициализации полей х, Y и Color, унасле
дованных от родительского объекта, вызывается унаследованный конструктордля чего употребляется зарезервированное слово
inherited (унаследованный):
Inherited
С таким же успехом мы могли бы использовать и составное имя метода:
TGraphObj.Init(XL,Yl,aColor);
Для инициализации полей dx и dY вычисляется расстояние в пикселах по горизонтали и вертикали от первого конца прямой до ее второго конца. Это позволяет в методевычислить координаты второго конца по ко
ординатам первого и смещениям dx и dY. В результате простое изменение координат реперной точки (X, в родительском методепе
ремещает всю фигуру по экрану.
Теперь не трудно реализовать объектдля создания и перемещения
окружности:
type
TCircie =
R: Integer;
ConstructorInteger; aColor:
Procedure
end;
Constructor
begin
Inherited
RaR
end;
Procedure TCircle.Draw;
begin
SetColor(aColor); {Устанавливаемцвет Color)
Circle (X, Y, R){Вычерчиваем окружность)
end;
В объекте TRect, с помощью которого создается и перемещается прямоугольник, учтем то обстоятельство, что для задания прямоугольника требуется указать четыре целочисленных параметра, т. е. столько же, сколько для задания линии. Поэтому объект TRect удобнее породить не от TGraphObj, a от TLine, чтобы воспользоваться его конструктором Init:
type
TRect = object(TLine)
.
Procedure Draw(aColor: Word);
end;
Procedure TRect.Draw;
begin
SetColor(aColor) ;
Rectangle(X,Y,X+dX, Y+dY){Вычерчива ем прямоугольник}
end;
Чтобы описания графических объектов не мешали созданию основной программы, оформим эти описания в отдельном модуле GraphObj:
Unit GraphObj;
• - Interface
(Интерфейсная часть модуля содержит только объявления объектов)
type
TGraphObj = object
end;
TPoint = object(TGraphObj)
end;
TLine = object(TGraphObj)
end;
TCircle = object(TGraphObj)
end;
TRect = object(TLine)
end;
Implementation
(Исполняемая часть содержит описания всех объектных методов}
Uses Graph;
Constructor TGraphObj.Init;
end.
В интерфейсной части модуля приводятся лишь объявления объектов, подобно тому как описываются другие типы данных, объявляемые в модуле доступными для внешних программных единиц. Расшифровка объектных методов помещается в исполняемую часть implementation, как если бы это были описания обычных интерфейсных процедур и функций. При описании методов можно опускать повторное описание в заголовке параметров вызова. Если они все же повторяются, они должны в точности соответствовать ранее объявленным параметрам в описании объекта. Например, заголовок конструктора TGraphObj.Init может быть таким:
Constructor TGraphObj .■ Init;
или таким:
Constructor TGraphObj.Init(aX, aY: Integer; aColor: Word);
Использование объектов
Идею инкапсуляции полей и алгоритмов можно применить не только к графическим объектам, но и ко всей программе в целом. Ничто не мешает нам создать объект-программу и "научить" его трем основным действиям: инициализации (init), выполнению основной работы (Run) и завершению (Done). На этапе инициализации экран переводится в графический режим работы, затем создаются и отображаются графические объекты (100 экземпляров TPoint и по одному экземпляру TLine, TCircle, TRect). На этапе Run осуществляется сканирование клавиатуры и перемещение графических объ-
ектов. Наконец, на этапе Done экран переводится в текстовый режим и завершается работа всей программы.
Назовем объект-программу именем TGraphApp И разместим его в модуле GraphApp (пока не обращайте внимание на точки, скрывающие содержательную часть модуля — позднее будет представлен его полный текст):
Unit GraphApp;
Interface
type
TGraphApp = object
Procedure Init;
Procedure Run;
Destructor Done;
end;
Implementation
Procedure TGraphApp.Init;
end;
end.
В этом случае основная программа будет предельно простой:
Program Graph_Objects;
Uses GraphApp;
.
var
App: TGraphApp;
begin
App.Init;
App.Run;
App.Done
end.
В ней мы создаем единственный экземпляр Арр объекта-программы TGrahpApp и обращаемся к трем его методам.
Создание экземпляра объекта ничуть не отличается от создания экземпляра переменной любого другого типа. Просто в разделе описания переменных мы указываем имя переменной и ее тип:
var
App: TGraphApp;
Получив это указание, компилятор зарезервирует нужный объем памяти для размещения всех полей объекта TGraphApp. Чтобы обратиться к тому или иному объектному методу или полю, используется составное имя, причем первым указывается не имя объектного типа, а имя соответствующей переменной:
Арр.Init;
Арр.Run;
Арр.Done;
Переменные объектного типа могут быть статическими или динамическими, т. е. располагаться в сегменте данных (статические) или в куче (динамические). В последнем случае мы могли бы написать такую программу:
Program Graph_Objects;
Uses GraphApp;
type
PGraphApp =ATGraphApp;
var
App: PGraphApp;
begin
App := New(PGraphApp,Init)
AppA.Run;
AppA.Done
end;
Инициализация динамической переменной App реализуется с помощью функции New. В этом случае первым параметром указывается имя типа инициализируемой переменной, а вторым осуществляется вызов метода- конструктора, который, я напомню, нужен для настройки таблицы виртуальных методов. Такой прием (распределение объектов в динамической памяти с одновременной инициализацией их ТВМ) характерен для техники ООП.
Ниже приводится возможный вариант модуля GraphApp для нашей учебной программы:
Unit GraphApp;
Interface
Uses GraphObj;
(Количество точек}(Количество точек}const
(Количество точек}
(Количество точек}
NPoints = 100;
type
{Объект-программа)
TGraphApp = object
Points: array [1..NPoints]
of TPoint;{Массив точек)
Line: TLine;
(Линия)
Rect: TRect;
{Прямоугольник)
Circ: TCircle;
{Окружность)
ActiveObj: Integer;
Procedure Init;
Procedure Run;
Procedure Done;
Procedure ShowAll;
{Активный объект)
Procedure MoveActiveObj(dX,
dY: Integer);
end;
Implementation
Uses Graph,CRT;'/
.
Procedure TGraphApp.Init;
{Инициализирует графический режим работы экрана. Создает и отображает NPoints экземпляров объекта TPoint, а также экземпляры объектов TLine, TCircle и TRect}
'
var
D,R,Err,k: Integer;
begin
{Инициализируем графику)
D := Detect;{Режим автоматического определения
типа графического адаптера)
InitGraph(D, R,1\tp\bgl’); {Инициализируемграфический режим.
Текстовая строка должна задавать путь к каталогу с графическими драйверами}
Err : = GraphResult;{Проверяемуспех инициализации графики)
if ErroO then
begin
GraphErrorMsg(Err);
Halt
end;
{Создаем точки)
for k := 1 to NPoints do
Points[k). Init (Random)GetMaxX), Random (GetMaxY), Random (15) +1) ;
{Создаем другие объекты)
Line. Init (GetMaxXdiv 3, GetMaxY div 3,2*GetMaxX div 3, 2*GetMaxY div 3,LightRed);
Circ. Init (GetMaxXdiv 2,GetMaxY div 2, GetMaxY div 5,White) ;
Rect.Init(2*GetMaxX div 5,2*GetMaxY div 5,3*GetMaxX div 5, 3*GetMaxY div 5, Yellow);
ShowAll;{Показываем все графические объекты)
ActiveObj := 1{Первым перемещаем прямоугольник)
end;{TGraphApp.Init)
(1
Procedure TGraphApp . Run ;
(Выбирает объект с помощью Tab и перемещает его по экрану) var
Stop: Boolean;{Признак нажатия Esc)
const
D = 5;{Шаг смещения фигур)
begin
Stop := False;
{Цикл опроса клавиатуры)
repeat
case ReadKey of{Читаем код нажатой клавиши}
27: Stop := True;{Нажата Esc)
9: begin{Нажата Tab)
inc(ActiveObj) ;
if ActiveObj >3 then ActiveObj3
end;
0: case ReadKey of
71: MoveActiveObj )-D,-D) ; {Влевои вверх)
72: MoveActiveObj (0,-D) ; (Вверх)
73: MoveActiveObj (D,-D); (Вправои вверх)
75: MoveActiveObj (-D, 0); {Влево)
77: MoveActiveObj (D, 0); {Вправо)
79: MoveActiveObj (-D, D) ; (Влево и вниз)
80: MoveActiveObj (О, D) ; (Вниз)
81: MoveActiveObj (D, D) ; (Вправо и вниз) end
end;
ShowAll;
Until Stop
ч
(;
Destructor TGraphApp.Done;
{Закрывает графический режим)
begin
CloseGraph
end;(TGraphApp. Done}
( —}
Procedure TGraphApp. ShowAll;
(Показывает все графические объекты}
var
Integer;
begin
for k := 1 to NPoints do Points [к] '. Show;
Line.Show;
Re ct. Show;
Circ.Show
end;
(;
Procedure TGraphApp . MoveActiveObj ;
(Перемещает активный графический объект}
begin
case ActiveObj of
Rect,MoveTo(dX,dY) ;
Circ.MoveTo(dX,dY) ;
Line.MoveTo (dX,dY)
end
end;
end.
В реализации объекта TGraphApp используется деструктор Done. Следует иметь в виду, ЧТО в отличие от конструктора, осуществляющего настройку
ТВМ, деструктор не связан с какими-то специфичными действиями: для компилятора слова destructor и procedure — синонимы. Введение в ООП деструкторов носит, в основном, стилистическую направленность — просто процедуру, разрушающую экземпляр объекта, принято называть деструктором. В реальной практике ООП с деструкторами обычно связывают процедуры, которые не только прекращают работу с объектом, но и освобождают выделенную для него динамическую память. И хотя в нашем примере деструктор Done не освобождает кучу, я решил употребить общепринятую стилистику и заодно обсудить с вами последнее еще не рассмотренное зарезервированное слово технологии ООП.
В заключение следует сказать, что формалистика ООП в рамках реализации этой технологии в Турбо Паскале предельно проста и лаконична. Согласитесь, что введение лишь шести зарезервированных слов, из которых действительно необходимыми ЯВЛЯЮТСЯ Три (object, constructor И virtual), весьма небольшая плата за мощный инструмент создания современного программного обеспечения.
Глава 11
Дополнительные возможности Турбо Паскаля
Внешние процедуры (функции)
С помощью внешних процедур (функций) можно осуществить вызов из программы процедур или функций, написанных на языке ассемблера. Ассемблер обеспечивает компиляцию программ, написанных на машинноориентированном языке программирования низкого уровня. В Турбо Паскале есть собственный встроенный ассемблер (см. главу 12). В этом разделе речь идет о программах, написанных и откомпилированных с помощью внешнего ассемблера, такого как, например, ассемблер фирмы Microsoft или Turbo Assembler фирмы Borland.
Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие возможности работы с различными аппаратно-программными средствами ПК. Ассемблерные программы выполняются значительно быстрее и занимают меньший объем памяти, чем программы, написанные на Турбо Паскале, однако низкий уровень языка ассемблера существенно снижает производительность труда программиста и резко усложняет отладку программ. Как правило, на языке ассемблера пишутся сравнительно небольшие фрагменты программ, в которых используются недоступные из Турбо Паскаля особенности архитектуры ПК.
Внешняя процедура (функция) в программе, написанной на Турбо Паскале, объявляется своим заголовком, за которым следует стандартная директива EXTERNAL, например:
Function LoCase (ch : char) : char; external;
Procedure Swapping (var a, b; N : word) ; external;'
I
Как видно из этих примеров, тело внешней процедуры (функции) отсутствует, — его заменяет директива EXTERNAL. Для подключения ассемблерной программы необходимо предварительно ее откомпилировать и получить объектный файл с расширением OBJ, содержащий перемещаемый код ассемблерной программы. Непосредственно перед описанием внешней процедуры
(функции) в тело основной программы вставляется директива компилятора ($L <имя файла>), где <имя файла> — имя OBJ-файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указываются опцией Options|Directories|Object Directories (см. приложение 1).
Перед передачей управления внешней процедуре (функции) программа помещает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры ВР, SP, ss и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления -в программу. Остальные регистры можно не сохранять и соответственно не восстанавливать.
Параметры могут передаваться по ссылке или по значению. Если параметр передается по ссылке, в стек помещается указатель, содержащий абсолютный адрес параметра, если по значению — в стек помещается сам параметр, точнее — его значение. Все параметры-переменные, т. е. параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Параметры-значения могут передаваться по ссылке или по значению в зависимости от длины внутреннего представления соответствующего параметра. В общем случае используется следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, он передается своим значением, т. е. его значение помещается в стек. Точно так же через стек передаются и все вещественные данные длиной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, начиная с версии 5.0 — через стек центрального процессора 8086/80486). Во всех остальных случаях, если длина внутреннего представления больше 4 байтов, соответствующий параметр передается по ссылке.
Ассемблерные функции в зависимости от длины внутреннего представления результата должны возвращать его через регистры центрального процессора или сопроцессора по следующим правилам:
длиной в 1 байт — в регистре AL;
длиной в 2 байта — в регистре АХ;
О длиной в 4 байта — в регистрах DX:AX (старшее слово в DX);
ТИП REAL (6 байт) — В регистрах DX:BX:AX;
ТИПЫ SINGLE, DOUBLE, EXTENDED И СОМР — через стек сопроцессора 8087/80486;
указатели — в регистрах DX:AX (сегмент в DX);
строки возвращаются по ссылке: адрес начала строки помещается в DX : АХ (сегмент вDX).
Все ассемблерные процедуры должны размещаться в сегменте с именем CODE или CSEG, или с именем, оканчивающимся на _техт; инициализированные локальные переменные помещаются в сегмент с именем CONST или с именем, оканчивающимся на _DATA. Все другие локальные переменные необходимо размещать в сегменте с именем DATA или DSEG, или с именем, оканчивающимся на _BSS. Любые другие объявления сегментов игнорируются. Все имена, объявленные в интерфейсной части модулей программы, написанной на Турбо Паскале, становятся доступными ассемблерной процедуре (функции) после их объявления директивой EXTRN. Точно так же все имена ассемблерных процедур и функций, которые должны быть доступны программе на Турбо Паскале, следует объявлять директивой PUBLIC.
Использование встроенных машинных кодов
В Турбо Паскале имеется возможность непосредственного включения в программу небольших фрагментов, написанных в машинных кодах. Для этого применяется стандартная директива INLINE, за которой в круглых скобках следует один или несколько элементов машинного кода, разделяемых косыми чертами. Элемент кода, в свою очередь, строится из одного или более элементов данных, разделенных знаками + или -.
Элементом данных может быть целая константа, идентификатор (переменной, константы или функции) или ссылка на счетчик адреса (*). Каждый элемент данных вызывает генерацию одного или двух байтов кода программы. Значение этого кода получается сложением или вычитанием элементов данных в соответствии с разделяющим их знаком. Значением идентификатора переменной, константы, функции служит адрес соответствующего объекта, значением ссылки на счетчик адреса является тот адрес, по которому будет размещаться следующий байт кода.
Элемент кода будет генерировать 1 байт кода, если этот элемент состоит только из целых констант и значение результата не превышает мощности одного байта, т. е. находится в диапазоне от О до 255. Если значение превышает 255 или элемент кода содержит ссылку на счетчик адреса, генерируются 2 байта. Знаки < и > могут использоваться для отмены автоматического выбора размера генерируемого кода. Если элемент кода начинается со знака <, в код заносится только 1 байт (с, младшей значимостью), даже если само значение занимает 2 байта. Наоборот, если элемент начинается со знака >, в код заносятся 2 байта (старший байт Может оказаться нулевым).
Значением идентификатора является смещение соответствующего объекта. Если переменная — глобальная, смещение задается относительно сегмента данных, хранящегося в регистре DS, если это локальная переменная, — от-
носительно сегмента стека (регистр SP). Базовым сегментом типизированной константы является сегмент кода (регистр cs).
В следующем примере приводятся две короткие процедуры, с помощью которых можно ввести или вывести данные через любой порт ПК.
FunctionWord;
var
pp: Word;
cc:Char;
begin
pp:=port;
inline (
$8b/$96/pp/{ mov DX,pp [bp] }
$EC/( IN AX,DX }
{ mov
InPort:=ord(cc);
end;/
Procedure
var
pp: Word;
cc:Char;
begin
pp:=port;
cc:=chr(Bt);
inline (
{ mov}
$8b/$96/pp/( mov DX,pp[bp] )
$EE){ OUT}
end;
Операторы INLINE могут произвольным образом смешиваться с другими операторами Турбо Паскаля, однако при выходе из процедуры (функции) содержимое регистров вр, SP, DS и ss должно быть таким как и при входе в нее.
С помощью директивы INLINE можно также задавать последовательность машинных кодов, которую необходимо несколько раз вставить в программу. Для этого используется описаниенапример:
Procedure
inline
INLINE-Процедура имеет обычный для Турбо Паскаля заголовок, в то время как тело процедуры пишется целиком с помощью оператора INLINE. Всякий раз, когда в программе будет встречаться оператор вызова INLINE- процедуры, компилятор Турбо Паскаля будет вставлять на это место не код вызова процедуры, а нужные машинные коды. Например, вместо вызова процедуры в операторе
Disablelnterrupt;'
компилятор вставит команду запрета прерываний CLI. Таким образом, INLINE-Процедуры служат своеобразным средством расширения возможностей стандартного компилятора Турбо Паскаля и подобны макросам ассемблера. Применение INLINE-Процедур увеличивает скорость исполнения программы, т. к. не осуществляется генерация (и исполнение) команд передачи управления в процедуру. По этой причине в inline-процедурах не следует использовать команды выхода из подпрограммы. INLINE-Процедура может иметь параметры, однако на них нельзя ссылаться в INLINE-Директивах (на другие символы Турбо Паскаля ссылаться можно). В следующем примере перемножаются два числа типа INTEGER, результат имеет тип longint:
FUNCTION LongMul(X,Y : Integer) : Longint;
inline (
$5A/{ POP AX; получить в АХ число Х }
$58/{ POP DX; получить в DX число Y )
$F7/$EA);{ IMUL DX; DX:AX := X * Y }
Отметим, что в силу упоминавшегося сходства с макросами ассемблера, имена INLINE-подпрограмм не могут быть аргументами в операторах @ или служить параметрами функций ADDR, OFS и SEG.
Обращение к функциям операционной системы
Турбо Паскаль предоставляет программисту практически неограниченные возможности использования любых функций стандартной операционной системы MS-DOS. При внимательном анализе материала этой книги вы, очевидно, заметите, что значительную его часть составляет описание многочисленных библиотечных процедур и функций. Собственно язык Паскаль весьма прост и лаконичен, что, по мнению многих специалистов, и послужило одной из причин его широкого распространения. Библиотечные же процедуры и функции в своей значительной части являются, по существу, своеобразным интерфейсом между языковыми средствами Турбо Паскаля и
функциями операционной системы. Разумеется, можно только приветствовать усилия разработчиков Турбо Паскаля по созданию мощных библиотек TURBO.TPL и GRAPH.TPU, однако ясно, что таким способом невозможно запрограммировать все допустимые обращения к средствам DOS. Вот почему в Турбо Паскаль включены две процедуры, с помощью которых программист может сам сформировать вызов той или иной функции дисковой операционной системы.
Следует учесть, что единственным механизмом обращения к функциям MS- DOS является инициация программного прерывания. Прерывание — это особое состояние вычислительного процесса. В момент прерывания нарушается нормальный порядок выполнения команд программы, и управление передается специальной процедуре, которая входит в состав DOS и называется процедурой обработки прерывания. Каждое прерывание характеризуется в рамках DOS порядковым номером и связано со своей процедурой обработки. В архитектуре центрального процессора ПК предусмотрены прерывания двух типов — аппаратные и программные. Аппаратные прерывания создаются схемами контроля и управления ПК и сигнализируют операционной системе о переходе какого-либо устройства в новое состояние или о возникновении неисправности. Программные прерывания инициируются при выполнении одной из двух специальных команд микропроцессора (INT или INTO) и служат для обращения к средствам MS-DOS.
Описываемые ниже процедуры входят в состав библиотечного модуля DOS.TPU и становятся доступными после объявления USES DOS. При возникновении программного прерывания в большинстве случаев необходимо передать процедуре обработки прерывания некоторые параметры, в которых конкретизируется запрос нужной функции. Эти параметры, а также выходная информация (результат обработки прерывания) передаются от программы к процедуре и обратно через регистры центрального процессора. В составе модуля DOS.TPU для этих целей определен специальный тип:
type
Registers = record
case integer of
0 : (AX,BX,CX,BP,SI,DI,DS,ES,Flags : word);
1 : (AL,AH,BL,BH,CL,CH,DL,DH: byte)
end;
Этот тип имитирует регистры центрального процессора и дает возможность обращаться к ним как к 16-битным или 8-битным регистрам.
Процедура INTR. С помощью этой процедуры инициируется программное прерывание с требуемым номером. Обращение:
INTR (<N>,<peгистры>)
Здесь:
<N> — выражение типа BYTE; номер прерывания;
<регистры> — переменная типа REGISTERS; в этой переменной процедуре обработки прерывания передается содержимое регистров и в ней же возвращается выходная информация.
Например, прерывание с номером 18 ($12) возвращает в регистре АХ объем оперативной памяти ПК. Короткая программа, представленная в примере
Uses DOS;varUses DOS;var
Uses DOS;
var
Uses DOS;
var
11.1, выведет на экран сообщение об этом объемр.
rregisters;
begin
Intr ($12, r);
writeln ('Объемпамяти = ', г.AX, * Кбайт') end.
Процедура MSDOS. Инициирует прерывание с номером 33 ($21). Формат обращения:
MSDOS (<регистры>)
Программное прерывание с номером 33 ($21) стоит особняком, т. к. оно дает доступ к большому количеству функций DOS (этим прерыванием вызывается 85 функций). Рассматриваемая процедура полностью эквивалентна вызову процедуры INTR с номером прерывания 33. Например, следующая программа (пример 11.2) выведет на экран версию операционной системы.
Пример 11.2Пример 11.2
Пример 11.2
Пример 11.2
Uses DOS;
var-
r : registers;
begin
r.AH := $30;
MsDos (r) ;'.
WriteLn('Версия операционной системы: ', r.AL, ' . ' r.AH)
end.
11.4. Поддержка процедур обработки прерываний
При написании процедур обработки прерываний существенными являются два обстоятельства. Во-первых, процедура обработки прерывания не должна искажать работу прерванной программы. Для этого необходимо сначала сохранить регистры центрального процессора, а перед выходом из процедуры — восстановить их. Во-вторых, процедура должна строиться по принципу реентерабельности (повторной входимости): ее работа может быть прервана в любой момент другими прерываниями и DOS может обратиться к соответствующей функции до завершения обработки предыдущего прерывания.
Турбо Паскаль предоставляет программисту возможность написания процедур обработки прерывания на языке высокого уровня, хотя обычно такие процедуры пишутся на языке ассемблера.’
Процедура обработки прерывания, написанная на Турбо Паскале, должна начинаться стандартной директивой INTERRUPT (прерывание), например:
Procedure IntProc (Flags, CS, IP, AX, BX, CX, DX,
SI, DF, DS, ES, BP : word) ; inermpt;
begin
end;
Формальные параметры в заголовке процедуры должны перечисляться в указанном порядке — через эти параметры все регистры прерванной программы становятся доступны процедуре обработки прерывания. Количество перечисляемых в заголовке процедуры параметров-регистров может быть любым, но не больше 12. Если в списке опущен какой-либо параметр, должны быть опущены также и все предшествующие ему параметры. Например, описание
Procedure IntProc (SI, DP, ES: word); interrupt;
будет неверным (опущеныпараметры DS и ВР); правильное описание:
Procedure IntProc (SI, DP,. DS, ES, BP: word); interrupt;
Заметим, что компилятор не контролирует порядок перечисления параметров в заголовке процедуры обработки прерывания.
Директива INTERRUPT вызывает генерацию специальных машинных кодов, обеспечивающих "заталкивание" регистров в стек при входе в процедуру и извлечение их из стека перед выходом из нее.
При входе в процедуру выполняются следующие действия:
push ах push bx
push
ex
push
dx
push
si
push
di
push
ds
push
es
push
bp
mov
bp,
si
sub
sp,
LocalSize
mov
ax,
SEG DATA
mov
ds,
ax
При выходе из процедуры:
mov sp, bp
pop bp
pop es
pop ds
pop di•
pop si pop dx pop ex pop bx pop ax irep
В самой процедуре обработки прерывания не рекомендуется обращаться к другим функциям MS-DOS, т. к. некоторые из них, в том числе все функции ввода/вывода, нереентерабельны.
Для связи с любыми процедурами прерываний, а следовательно, и с процедурами, написанными программистом, используются векторы прерываний — четырехбайтные абсолютные адреса точек входа в эти процедуры. Векторы прерываний располагаются в младших адресах оперативной памяти, начиная с нулевого адреса: прерывание номер 0 — по адресу 0, номер 1 — по адресу 1 x 4 = 4, номер N — по адресу N х 4. С помощью следующих двух процедур программист может прочитать содержимое любого вектора или установить его новое значение.
Процедура GETINTVEC. Возвращает вектор прерывания с указанным номером. Обращение:
GETINTVEC (<D>,<вектор>)
9 Зак. 941
Здесь:
<D> — выражение типа BYTE; номер прерывания;
<вектор> — переменная типа POINTER; адрес точки входа в процедуру обработки прерывания.
Представленная в примере 11.3 программа выводит на экран содержимое всех ненулевых векторов прерываний.
Uses DOS;
var
i : byte;
p : pointer;
begin
for i := 0 to 255 do
begin
GetlntVec (i, p) ;
if(Seg(pA)<>0)or(Ofs(pA) о0)then
WriteLn (' N i:3, 1 Seg =', Seg (pA):5, ' Ofs =', Ofs (pA) :5)
end
end.
Процедура SETINTVEC. Устанавливает новое значение вектора прерывания. Формат обращения:
SETINTVEC (<D>,<адрес>)
Здесь:
<D> — выражение типа BYTE; номер прерывания;
CD <адрес> - выражение типа POINTER; адрес точки входа в процедуру обработки прерывания.
При нормальном завершении программы она выгружается из памяти, что делает невозможным разработку резидентных в памяти процедур обработки прерываний. Вы можете прекратить работу программы и оставить ее резидентной в памяти, если воспользуетесь процедурой KEEP.
Процедура KEEP. Завершает работу программы и оставляет ее резидентной в памяти. Обращение:
KEEP (<код>)
Здесь <код> — выражение типа WORD; код завершения программы. Код завершения представляет собой фактически единственный механизм передачи сообщений от запущенной программы к программе, которая ее запустила. Он может быть проанализирован в вызывающей программе с помощью ФУНКЦИИ DOSEXITCODE.
Функция DOSEXITCODE. Возвращает значение типа WORD — код завершения подчиненной программы. Обращение:
DOSEXITCODE
Запуск внешних программ
Из программы, написанной на Турбо Паскале, можно запустить любую другую готовую к работе программу. Для этого предусмотрена процедура EXEC из библиотечного модуля DOS. Формат обращения к процедуре:
EXEC (<имя>, <параметры>)
Здесь:
<имя> — выражение типа STRING; имя файла с вызываемой программой;
<параметры> — выражение типа STRING; параметры вызова.
Имени запускаемой программы может предшествовать путь к файлу. Параметры передаются запускаемой программе в виде текстовой строки и могут быть проанализированы ею с помощью двух следующих функций.
Функция PARAMCOUNT. Возвращает общее количество параметров вызова программы (значение типа WORD). Обращение:
PARAMCOUNT
Параметры вызова обычно следуют в командной строке DOS сразу за именем вызываемой программы и отделяются от этого имени и друг от друга пробелами, например:
C:\TP\TURBO MYPROG.PAS
С:\SIAM А:\SYSTEM1.SIA
Здесь MYPROG.PAS и А:\SYSTEM1.SIA — параметры, передаваемые программам TURBO И SIAM.
При вызове программы непосредственно из среды Турбо Паскаля ей можно передать параметры с помощью опции Options | Parameters (см. приложение 1).
Функция PARAMSTR. Возвращает значение типа STRING, соответствующее нужному параметру вызова. Формат обращения:
PARAMSTR (<D>)
Здесь <D> — выражение типа WORD; порядковый номер параметра.
Заметим, что программе всегда передается параметр, соответствующий N 0. В этом параметре MS-DOS сообщает полное имя запущенной программы с указанием диска и каталога, откуда она была загружена.
Использование процедуры EXEC имеет ряд особенностей. Прежде всего необходимо отметить, что сама вызывающая программа остается резидентной в памяти, поэтому она не должна занимать всю оперативную память. Объем выделяемой программе памяти регулируется опцией Options | Memory sizes (см. приложение 1). По умолчанию параметры Low heap limit и High heap limit этой опции таковы (соответственно 0 и 655 360 байт), что вызывающая программа, написанная на Турбо Паскале, занимает весь доступный объем памяти, и вызываемая программа не будет загружена. Полезно включить в текст вызывающей программы директиву компилятора, в которой изменяются принятые по умолчанию размеры памяти. Например, так: {$м 2048, 0, 0}
Такая директива ограничивает предоставленную программе область стека величиной 2 Кбайт и исключает возможность использования в ней динамической памяти. Разумеется, вы можете установить и другие значения параметров в этой директиве.
Специфические особенности исполнения программ Турбо Паскаля требуют изменения стандартных значений некоторых векторов прерываний. К ним относятся векторы со следующими шестнадцатеричными номерами:
$00, $02/ $18, $23, $24, $34, $35, $36, $37, $38, $39, $ЗА, $ЗВ, $ЗС, $3D, $3E, $3F, $75.
Начальные значения этих векторов сохраняются в восемнадцати переменных с именами SAVEINTXX из библиотечного модуля SYSTEM, где хх — шестнадцатеричный номер прерывания. Поэтому непосредственно перед запуском внешней программы и сразу после возврата из нее рекомендуется вызывать библиотечную процедуру без параметров SWAPVECTORS, которая обменивает содержимое векторов прерывания и перечисленных переменных.
Программа из примера 11.4 читает с клавиатуры любую команду DOS, затем вызывает командный процессор COMMAND.COM операционной системы и передает ему эту команду.
Обратите внимание, что для указания файла COMMAND.COM и пути к нему используется обращение к библиотечной функции GETENV, с помощью которой можно получить параметры настройки операционной системы. В частности, параметр COMSPEC определяет спецификацию файла, содержащего командный процессор.
($M1024, О, 0}
Uses DOS;
var
ststring [79];
begin
writeкоманду DOS:
readln (st); if st <>then
begin
st :=
SwapVectors;
Exec (GetEnvst)
SwapVectors end ,
Функция ENVCOUNT. Возвращает значение типа INTEGER, в котором содержится общее количествоустановленных в DOS. Обращение:
ENVCOUNT
ФункцияВозвращает значение типа STRING, содержащее имя и зна
чение нужного параметра настройки операционной системы. Формат обращения:
ENVSTR (<D>)
Здесь— выражение типа INTEGER; номер параметра.
Эта функция возвращает строку типагде NAME — имя, а
VALUE — значение соответствующего параметра настройки.
ФункцияВозвращает значение типа STRING, в котором содержится
параметр настройки DOS. Формат обращения:
GETENV (<имя>)
Здесь— выражение типа STRING; имя параметра.
Эта функция имеет параметр обращения NAME, а возвращает значение VALUE (см. функцию ENVSTR).
Оверлей
Как отмечалось в главе 9, максимальный размер модуля не может превышать 64 Кбайт, однако количество модулей не ограничено, что дает возможность разрабатывать весьма крупные профаммы, занимающие, например, всю доступную оперативную память ПК (приблизительно 580 Кбайт). Тем не менее, в некоторых случаях и этот объем может оказаться недостаточным. Турбо Паскаль предоставляет в распоряжение профаммиста простой и достаточно эффективный механизм оверлея, с помощью которого можно создавать про- фаммы практически неофаниченной длины (следует оговориться, что речь идет только о длине кода профаммы; два важных размера — длина сегмента данных и размер профаммного стека — в Турбо Паскале не могут превышать 64 Кбайт независимо от структуры профаммы).
Оверлей — это такой способ использования оперативной памяти, при котором в один и тот же участок памяти, называемый оверлейным буфером, попеременно по мере надобности загружаются различные оверлейные (перекрывающиеся) модули. При этом все оверлейные модули в готовом к работе виде хранятся на диске, а в оперативной памяти в каждый момент находится лишь один активный модуль и, возможно, небольшое число неактивных.
Пусть, например, профамма (рис. 11.1) состоит из главной части MAIN и двух модулей А и в, a LM, LA и LB — соответственно длина главной части и обоих модулей, причем LA > LB. Тогда неоверлейная профамма займет в памяти LM + LA + LB байт, в то время как оверлейная профамма — лишь LM + LA байт.
При исполнении оверлейной профаммы в память первоначально загружается главная часть и один из модулей, например, модуль А. Если в процессе исполнения профаммы встретится обращение к модулю в, профамма приостановит свою работу, с диска в оверлейный буфер будет загружен модуль в (модуль А при этом частично уничтожается), после чего профамма продолжит свою работу. Если в дальнейшем встретится обращение к А, точно таким же образом будет загружен модуль А, причем зафузка нужных модулей в оверлейный буфер осуществляется автоматически и программисту не нужно об этом заботиться.
Описанный механизм выявляет главное преимущество оверлейной структуры: объем оперативной памяти, занимаемой оверлейной профаммой, определяется длиной ее главной части и наибольшего из перекрывающихся модулей, в то время как при неоверлейной структуре в этот объем входит суммарная длина всех модулей. Чем больше в профамме оверлейных модулей и чем меньше длина наибольшего из них, тем больший выигрыш в памяти дает оверлейная структура. Однако совершенно очевиден и главный недостаток таких структур: на каждую загрузку оверлейного модуля с диска
в оверлейный буфер требуется дополнительное время, поэтому оверлейная программа будет исполняться с меньшей скоростью.
Рис.Пример структуры программы:
а — неоверлейная;— оверлейная
Работа оверлейных программ обеспечивается с помощью процедур и функций библиотечного модуля OVERLAY, входящего в библиотечный файл TURBO.TPL.
При создании оверлейных программ нужно руководствоваться следующей последовательностью действий:
Выделить главную часть программы и разбить оставшуюся часть на несколько модулей. Отметим, что никаких дополнительных ограничений на модули, по сравнению с описанными в главе 9, не накладывается за одним исключением: в оверлейных модулях недопустимы процедуры обработки прерываний. Желательно продумать состав модулей таким образом, чтобы по возможности минимизировать количество их перезагрузок в оверлейный буфер в процессе исполнения программы.
В главной части программы указать с помощью директив компилятора видате модули, которые будут оверлейными, например:
Program Main;
CRT, DOS, Graph, Overlay,UnitB;
{$0 DOS)
($0 UnitA)
{$0 Units}
Примечание
Следует подчеркнуть, что из всех стандартных библиотечных модулей только один модуль DOS может быть оверлейным, остальные модули (CRT, Graph, Printer и т. д.) не могут объявляться оверлейными.
Предусмотреть перед первым по логике работы программы обращением к оверлейному модулю вызов процедуры инициализации оверлея OVRINIT. Здесь же, если это необходимо, следует установить размер оверлейного буфера и указать возможность использования- расширенной памяти (см. ниже).
В начале главной программы и каждого оверлейного модуля необходимо поместить директивы компилятора {$0+} и {$F+} или установить опции Options | Compile | Force far calls и Options | Compile | Overlays allowed (см. приложение 1) в активное состояние, после чего откомпилировать программу на диск. Программа готова к работе.
Таким образом, все процедуры и функции в оверлейной программе должны использовать дальнюю модель вызова — это обязательное условие. Отметим, что попытка компиляции оверлейного модуля, в начале которого отсутствует директива {$0+} (предполагается, что опция среды Options | Compile | Overlays allowed неактивна), будет обнаружена компилятором, в то время как неправильная (ближняя) модель вызова оверлейных подпрограмм компилятором не контролируется и может привести к непредсказуемым результатам при исполнении программы,
Далее, инициализация оверлея (осуществляется вызовом процедуры OVRINIT, см. ниже) должна происходить до вызова любого из оверлейных модулей. Это требование кажется тривиальным, однако множество проблем в оверлейных программах обычно связано именно с ним. Дело в том, что обращение к оверлейному модулю может происходить еще до начала работы основной программы: напомню, что любой модуль (в том числе и оверлейный) может иметь инициирующую часть, которая исполняется перед началом работы основной программы. В связи с этим рекомендую придерживаться следующего простого правила: никогда не используйте оператор BEGIN в конце модуля, если вам нет нужды в инициирующих действиях; пустая инициирующая часть содержит пустой оператор, которому будет передано управление на этапе инициализации. Таким образом, пустая инициирующая часть оверлейного модуля очень часто может вызывать сообщение об ошибке периода исполнения с кодом 208 (не установлена система управления оверлеем). Как же быть, если в оверлейном модуле все-таки нужна инициирующая часть? В этом случае можно рекомендовать следующий прием. Создайте лишний модуль, в котором будут пустыми все части, кроме инициирующей. В этой части разместите команды инициализации оверлея. Новый модуль не должен быть оверлейным и его имя должно стоять в предложении USES основной программы перед именем любого оверлейного модуля. После компиляции такой программы инициализация оверлея будет осуществляться перед выполнением любой другой инициирующей части и проблема будет решена.
Процедура OVRINIT. Инициализирует оверлейный файл. Обращение:
OVRINIT (<ИМЯ>)
Здесь <имя> — выражение типа STRING; имя файла с оверлейной частью программы.
При компиляции оверлейной программы создается специальный файл с именем, совпадающим с именем главной программы, и расширением OVR. В этот файл компилятор помещает все оверлейные модули, из него же эти модули загружаются в оверлейный буфер в процессе исполнения программы. Файл с оверлейной частью программы должен размещаться в том же каталоге, что и файл с главной частью (с расширением ЕХЕ). Отметим, что имя оверлейного файла всегда необходимо дополнять расширением OVR.
Обычно размер оверлейного буфера определяется автоматически таким образом, чтобы в нем мог разместиться самый крупный из всех оверлейных модулей. Программист может увеличить размер буфера. Тогда при загрузке в буфер очередного модуля программа проверит, достаточно ли в буфере свободного места и, если достаточно, загрузит новый модуль сразу за старым, который, таким образом, не будет уничтожен. Такой механизм способствует минимизации потерь времени на перезагрузку модулей. Если установлен очень большой размер буфера, то в нем, возможно, смогут разместиться все оверлейные модули, однако в этом случае оверлейная структура становится просто ненужной.
Процедура OVRSETBUF. Устанавливает размер оверлейного буфера. Формат обращения:
OVRSETBUF (<длина>)
Здесь <длина> — выражение типа LONGINT; задает новую длину буфера в байтах не больше той, которую устанавливает сама система автоматически. Расширение буфера идет за счет соответствующего уменьшения доступной динамической памяти, поэтому к моменту вызова этой процедуры куча должна быть пустой.
Функция OVRGETBUF. Возвращает значение типа LONGINT, содержащее текущий размер оверлейного буфера. Обращение:
OVRGETBUF
Процедура OVRINITEMS. Обеспечивает работу с расширенной памятью. Если ваш ПК относится к классу компьютеров типа IBM PC/AT и в нем имеется так называемая .ЕМУ-память (Expanded Memory Specification — расширенная память, удовлетворяющая стандарту фирм Lotus/Intel/Microsoft), то вы можете использовать эту память для размещения в ней оверлейного файла
OVR. Поскольку время доступа к расширенной памяти значительно меньше времени чтения с диска, такое размещение увеличивает скорость исполнения оверлейной программы. Обращение:
OVRINITEMS
При обращении к этой процедуре программа прежде всего проверит, достаточен ли объем имеющейся в вашем ПК EMS-памяти для размещения оверлейного файла. Если это так, то оверлейный файл считывается в EMS- память, сам файл закрывается, и программа будет получать оверлейные модули из этой памяти. Если же EMS-память отсутствует или ее объем недостаточен, обращение к процедуре игнорируется, и программа будет считывать оверлейные модули с диска.
Все управление оверлеем осуществляется стандартной подпрограммой, которая называется администратором оверлея. Эта подпрограмма получает управление всякий раз, когда программа обращается к ресурсам оверлейного модуля, не размещенного в данный момент в буфере. Администратор оверлея сначала перемещает предыдущий оверлейный модуль из буфера в так называемую контрольную зону, а уже затем грузит с диска в буфер новый модуль. Если в момент, когда оверлей находится в контрольной зоне, программа вновь обратится к нему, он вернется на свое старое место и таким образом затраты времени на обмен данными с диском будут уменьшены. Программист может задать размер контрольной зоны с помощью обращения к процедуре OVRSETRETRY и получить этот размер с помощью функции OVRGETRETRY. Обычно размер контрольной зоны составляет от одной трети до половины размера оверлейного буфера. Вы можете подобрать этот размер экспериментально в ходе пробного прогона программы. Для этого используются две переменные модуля OVERLAY:
var
OvrTrapCount: Word; {Счетчик обращений к администратору}
OvrLoadCount: Word; {Счетчик загрузок в оверлейный буфе; '
Всякое обращение программы к оверлейному модулю, которого нет в оверлейном буфере (в том числе и к модулю, находящемуся в контрольной зоне), приводит к наращиванию содержимого переменной OVRTRAPCOUNT на единицу. Всякая загрузка оверлейного модуля из файла в буфер увеличивает на единицу счетчик OVRLOADCOUNT.
В интерфейсной части модуля OVERLAY объявлены еще три переменные, которые могут оказаться полезными для некоторых применений.
type
OvrReadFunc = Function (OvrSeg: Word): Integer;
var
OvrReadBuf : OvrReadFunc;OvrReadBuf : OvrReadFunc;{Функция чтения изоверлейного файла}
OvrReadBuf : OvrReadFunc;
OvrReadBuf : OvrReadFunc;
OvrResult : Integer;{Признак ошибки оверлея)
OvrFlleMode: Byte;{Способ доступа к оверлейному файлу)
Переменная OVRRESULT содержит код, указывающий на успех или неуспех каждой очередной операции администратора оверлея. Значения э|гой переменной могут быть такими:
□ О — операция прошла успешно;
О ■"“■’ 1 — общая ошибка;
О -2 — не найден оверлейный файл;
□ -3 — не хватает памяти для оверлейного буфера;
О —4 — ошибка чтения/записи оверлейного файла;
О—5 — не работает драйвер EMS-памяти;
О —6 — не хватает EMS-памяти.
Переменная OVRFILEMODE обычно содержит 0, что трактуется как возможность доступа к оверлейному файлу только для чтения информации. Перед вызовом процедуры OVRINIT программа может установить другое значение этой переменной и таким образом изменить доступ к файлу, что бывает необходимым, если ПК подключен к сети ЭВМ.
В переменной OVRREADBUF содержится имя функции, к которой обращается администратор оверлея при каждом чтении из оверлейного файла. Программа может перехватить обращение к этой функции и проанализировать результат операции. Для этого необходимо в основной программе сохранить имя стандартной функции чтения в глобальной переменной типа OVRREADFUNC и поместить в переменную OVRREADBUF имя новой функции. В эту новую функцию администратор будет передавать управление всякий раз, когда появится необходимость чтения из оверлейного файла. Программа может проверить состояние дисков перед исполнением операции (например, наличие нужного сменного диска), выполнить саму операцию (путем вызова функции, сохраненной в глобальной переменной), проверить результат обращения и предпринять необходимые действия. Отметим, Что нормальное завершение. операции чтения указывается нулевым значением функции чтения, ненулевое значение означает ту или иную ошибку; код ошибки стандартен для DOS (см. приложение 3) и/или для драйвера EMS-памяти.
При желании вы можете "пристыковать" оверлейный файл в конец ЕХЕ- файла основной программы. При этом следует учесть, что интегрированная среда обычно присоединяет в конец ЕХЕ-файла отладочные таблицы, поэтому программу и все ее модули следует компилировать в режиме отключенных опций Options | Compiler | Debug information и Options | Compiler | Local symbols (см. приложение 1). Для объединения ЕХЕ-файла с оверлейным файлом необходимо дать такую команду MS-DOS:
COPY /В NAME.EXE+NAME.OVR
Здесь:
NAME . EXE — имя ЕХЕ-файла;
NAME . OVR — имя оверлейного файла.
Чтобы оверлеи читались из ЕХЕ-файла, нужно просто указать имя этого файла при обращении к OVRINIT:
Ovrlnit(ParamStr(0));
(в программу всегда передается параметр ParamStr(0), в котором DOS сообщает полное имя запущенной программы — с указанием диска и каталога, откуда была загружена программа).
Прямое обращение к памяти
и портам ввода/вывода
В Турбо Паскале имеется пять предварительно объявленных массивов: MEM, MEMW, MEML, PORT и PORTW. Первые три обеспечивают доступ к любому участку оперативной памяти по абсолютному адресу, два других — доступ к портам ввода/вывода.
Компонентами массива MEM являются данные типа BYTE, массива MEMW — WORD, массива MEML — LONGINT. Обращение к элементам этих массивов, т. е. их индексация, имеет специальный вид: каждый индекс представляет собой абсолютный адрес и состоит из двух выражений типа WORD; первое дает сегментную часть адреса, второе — смещение; выражения разделяются двоеточием. Например:
T
Mem[$0000:$1000] := 0;
DataMem :=
MemLong :=
Как следует из технического описания операционной системы MS-DOS, в памяти по адресу $F000:$FFFE располагается байт-указатель типа компьютера. Следующая программа (пример 11.5) прочтет этот байт и выведет на экран тип вашего ПК.
Пример 11.5Пример 11.5
Пример 11.5
Пример 11.5
begin
Write (’ Тип компьютера: *) ; case Mem [$F000:$FFFE] of $FF : WriteLn CPC);
$FE : WriteLn('XT');
I
$FD : WriteLn('PCjr');
$FC : WriteLn('AT');
$F9 : WriteLn('совместимый с PC )
end
end.
Компонентами массива PORT являются байты (тип BYTE), а массива PORTW — слова (тип WORD). Индексами этих массивов должно быть выражение типа BYTE, указывающее номер нужного порта. Присвоение значения элементу массива PORT или PORTW приведет к записи в порт, упоминание элемента в выражении — к чтению из порта. Компоненты массивов PORT и PORTW нельзя передавать в качестве параметров процедурам или функциям. Эти идентификаторы не употребляются без индексных выражений.
Длинные строки
Среда Турбо Паскаль 7.0 обладает весьма интересным новшеством: в ней введена поддержка длинных строк, т. е. строк, длина которых может превышать 255 байт. Как известно, тип string в Турбо Паскале имеет максимальную длину 255 байт. Это связано с тем, что истинная длина строки в этом типе указывается первым байтом, мощность которого не позволяет определять строки большей длины. В то же время в языке Си используется другой подход к заданию текстовых строк: первый байт строки является ее первым символом, второй байт — вторым символом и т. д. пока не встретится байт с символом #0. Таким образом, длина строки Не указывается явно, как в типе string, а определяется по замыкающему символу #0. Ясно, что такие строки могут иметь произвольную длину, ограничиваемую лишь объемом оперативной памяти или принятой схемой ее адресации: в MS- DOS такой границей является длина сегмента, поэтому максимально возможная длина строки в Си для ПК составляет 65 535 символов. Такие строки в дальнейшем будем называть ASCIIZ-строками.
Для реализации операций над ASCIIZ-строками в язык введен новый тип pchar, определяемый как указатель на символ:
type
PChar =AChar;
Однако такой обычный для Паскаля тип-указатель в рамках Турбо Паскаля 7.0 трактуется необычным способом: считается, что он указывает на цепочку символов, заканчивающуюся терминальным нулем, т. е. на ASCIIZ-строку. Более того, с этим типом совместим любой одномерный символьный массив с нулевой левой границей, а переменные типа PChar можно индексиро-
вать, как если бы они были массивами символов. Следующая программа напечатает все заглавные буквы английского алфавита:
{$Х+}{$Х+}{Включаем расширенный синтаксис}
{$Х+}
{$Х+}
var
Chars : array [0. .26] of Char; ■ (Массив символов}
PChars: PChar;PChars: PChar;{Указатель на символ}{Указатель на символ}
PChars: PChar;
PChars: PChar;
{Указатель на символ}
{Указатель на символ}
k Integer;
begin
for k 0 to 25 do
Chars [к] :=(Наполняем массив}
PChars := Chars;PChars[26] := #0;WriteLn(PChars)PChars := Chars;PChars[26] := #0;WriteLn(PChars){Указателю присваиваем массив!}
PChars := Chars;
PChars[26] := #0;
WriteLn(PChars)
PChars := Chars;
PChars[26] := #0;
WriteLn(PChars)
{Индексируемуказатель!)
(Печатаем указатель!}
end.
Три последних оператора программы недопустимы в стандартном Паскале и в ранних версиях Турбо Паскаля, но возможны в версии 7.0, если включен расширенный синтаксис (директивой <$Х+} или опцией Options | Compiler | Extended syntax). Обратите внимание, что процедура WriteLn этой версии умеет работать с ASCIIZ-строками.
Для поддержки ASCIIZ-строк разработан модуль strings, в котором реализованы необходимые процедуры и функции.
Функция strCat. Объединяет строки. Заголовок:
Function StrCat(Dest, Source: PChar) : PChar;
Функция копирует строку source в конец строки Dest и возвращает указатель на начало Dest.
Функция StrComp. Сравнивает строки. Заголовок:
Function StrComp(Str1, Str2: PChar): Integer;
Побайтно сравнивает строку Strl со строкой str2 и возвращает следующий результат:
D О, если stri=str2;
больше 0, если stri>str2;
меньше 0, если strl<str2.
Функция strCopy. Копирует строку. Заголовок:
Function StrCopy(Dest, Source: PChar): PChar;i
Здесь функция копирует строку Source в строку Dest и возвращает указатель на Dest. strCopy не проверяет реальный размер памяти, связанный с Dest (он должен быть не меньше strLen
Процедура strDispose. Удаляет строку из кучи. Заголовок:
Procedure
Строкадолжна быть предварительно помещена в кучу функцией strNew.
Еслипроцедура ничего не делает.
Функция strECopy. Объединяет строки. Заголовок:
FunctionSource:PChar;
Эта функция работает в точности как strCat, но возвращает указатель на конец сцепленных строк, т. е. на терминальный ноль.
ФункцияВозвращает конец строки. Заголовок:
FunctionPChar;
Функция возвращает указатель на терминальный нольstr.
ФункцияСравнивает строки. Заголовок:
FunctionPChar;
Функция сравнивает строки, игнорируя возможную разницу в высоте букв. Возвращает такой же результат, как и Сотр. Замечу, что функция правильно работает лишь с латиницей. Для кириллицы ее нужно модифицировать (см. ниже).
Функция strLCat. Объединяет строки. Заголовок:
FunctionSource: PChar;PChar;
Копирует символы строки source в конец строки Dest до тех пор, пока не будут скопированы все символы или когда длина сцепленной строки Dest не достигнет значения MaxLen. Возвращает указатель на сцепленную строку.
В отличие от strCopy эта функция блокирует возможное переполнение области памяти, связанной с Dest. Обычно в качестве MaxLen используется выражениеНапример:
{$х+}
Uses
. var
S: arrayof Char;
begin
StrCopy(S, 'Turbo');
StrLCat(S, ' ’, SizeOf(S)-l);
StrLCat(S,
"Turbo Pas") end;
Функция strLComp. Сравнивает строки. Заголовок:
FunctionSource:PChar;
В отличие отсравнивает не более MaxLen символов строк. Возвра
щаемый результат такой же, как и у strcomp.
Функция strLCopy. Заголовок:
FunctionSource: PChar; MaxLen:PChar;
Эта функция копирует символы из строки Source в строку Dest до тех пор, пока не будет скопирована вся строка или пока не будет скопировано MaxLen символов. В отличие от strCopy блокирует возможное переполнение области памяти, связанной с Dest. В качествеобычно используется
выражение(Dest) Например:
($X+)
Uses Strings;
var
S: arrayof Char;
begin
StrLCopy(S, 'Turbo Pascal', SizeOf(S)-1);
WriteLn(S)"Turbo
end;
Функция strLen. Возвращает длину строки. Заголовок:
Function StrCat(Str:Word;
ФункцияСравнивает строки с учетом регистра. Заголовок:
FunctionStr2: PChar; MaxLen:PChar;
Сравнивает не более MaxLen символов строк, проверяя точное соответствие высоты букв. Возвращаемый результат такой же, как у strcomp. Функция правильно работает только с латиницей.
Функция strLower. Преобразует в строчные буквы. Заголовок:
FunctionPChar;
Преобразует заглавные буквы строкик строчным и возвращает указа
тель на результат. Функция правильно работает только с латиницей.
Функция strMove. Копирует строку. Заголовок:
FunctionSource: PChar; Count:PChar;
Копирует точно Count символов строки source в строку Dest и возвращает указатель на результат. Функция игнорирует действительные размеры строк и может выйти за их пределы.
Функция strNew. Помещает строку в кучу. Заголовок:
Function StrNew(Str: PChar) : PChar;
Функция strPas. Преобразует ASCIIZ-строку в строку string. Заголовок:
Function StrPas(Str: PChar): String;
Функция strPCopy. Преобразует строку string в ASCIIZ-строку. Заголовок:
Function StrPCopy(Str: PChar; S: String): PChar;
Возвращает указатель на str.
Функция StrPos. Ищет подстроку. Заголовок:
Function StrPos(Strl, Str2: PChar): PChar;
Ищет подстроку str2 в строке Strl и возвращает указатель на первое вхождение str2 или NIL, если подстрока не найдена.
Функция strRSoan. Ищет последний символ. Заголовок:
Function StrRScan (Str: PChar; Ch: Char): PChar;
Функция ищет символ Ch в строке str и возвращает указатель на последний обнаруженный символ Ch или NIL, если символ не найден.
Функция strscan. Ищет первый символ. Заголовок:
Function StrScan(Str: PChar; Ch: Char): PChar;
Ищет символ Ch в строке str и возвращает указатель на первый обнаруженный символ ch или NIL, если символ не найден.
Функция StrUpper. Заголовок:
Function StrLower(Str: PChar): PChar;
Функция преобразует строчные буквы строки str к заглавным и возвращает указатель на результат. Функция правильно работает только с латиницей.
Как уже упоминалось, четыре функции модуля strings (strLower, StrUpper, StrlComp и StrLIComp) используют преобразование высоты букв и работают корректно только для букв латинского алфавита (латиницы). Для русских букв эти функции можно изменить следующим образом:
(Этот модуль содержит модификацию функций стандартного
модуля Strings для работы с кириллицей (альтернативный
вариант кодировки} ■
($X+}
Unit StringsR;
INTERFACE
Function LoCase(Ch: Char): Char;
Function UpCase (Ch: Char): Char;
Function StrLower(Str: PChar) : PChar;
Function StrUpper (Str: PChar): PChar;
Function StrlComp(Strl, Str2: PChar): Integer;
Function StrLIComp(Strl, Str2: PChar; MaxLen: Word): Integer;
IMPLEMENTATION
Uses Strings;
Function LoCase(Ch: Char): Char;'
{Преобразует латинскую или русскую букву Ch к строчной} begin
case Ch of
'A'..'Z': LoCase :=Chr (ord('a') +ord(Ch)-ord('A') ) ;
'A'. . 'IT : LoCase := Chr(ord('a')+ord(Ch)-ord('A') ) ;
’P'..'Я':LoCase:=Chr(ord('p') +ord(Ch)-ord('P') ) ;
else LoCase := Ch
end
end;
{)
Function UpCase(Ch: Char): Char;
{Преобразует латинскую или русскую букву в переменной Ch к заглавной}
begin
case Ch of
'a'..' z':UpCase :=
i ’a1..’n’:UpCase:=Chr(ord( 'A') +ord(Ch) -ord('a') ) ;
'P'. . 'я': UpCase :=Chr(ord('P') +ord(Ch) -ord( 'p') ) ; else
UpCase := Ch
end
end;
{;
Function StrLower(Str: PChar): PChar;
(Преобразуетлатинские и русские буквы строки Str к строчным} var
k: Word;
begin
for k := 0 to StrLen(Str)-1 do Str[k] := LoCase(Str[к]);
StrLower := Str
end;
/J Function StrUpper(Str: PChar): PChar;
(Преобразует латинские и русские буквы строки Str к заглавным) var
Word;
begin
for k := 0 to StrLen(Str)-1 do Str[k] := UpCase(Str[k]);
StrUpper := Str end;
//
Function StrlComp(Strl, Str2: PChar): Integer;
{Сравнивает две строки, игнорируя возможную разницу в высоте латинских или русских букв)
var
Word;
Max: Word;
begin
{Определяем максимальное количество сравниваемых символов как минимум длин строк) Мах := StrLen(Strl);
if StrLen(Str2)<Max then
Max := StrLen(Str2);
{Проверяем символы до первого несовпадения.
Буквы преобразуем кч
for k := 0 to Max-1 do
if•then•
begin
(Строки не равны)
StrlComp :=
Exit
end;
(Разницынет — результат зависит от совпадения длин)
StrlComp :=
end;'
(;
Function StrLIComp(Strl, Str2: PChar; MaxLen: Word) : Integer;
(Сравнивает не более MaxLen символов строк, проверяя точное соответствие высоты букв) var
Max: Word; begin {Определяеммаксимальное количество сравниваемых символов как минимум длин строк и MaxLen) МахMaxLen;
if StrLen(Strl)<Max then Max := StrLen(Strl) ; if StrLen(Str2)<Max then Max := StrLen(Str2) ; (Проверяем символы до первого несовпадения.
Буквы преобразуем к заглавным) for k := 0 to Max do
if UpCase(Strl[к] ) oUpCase (Str2[k]) then begin
StrLIComp := ord(UpCase(Str2[k]) ) ;
Exit end;
(Разницы нет. Если проверено MaxLen СИМВОЛОВ, строки считаются равными, в противном случае результат зависит от совпадения длин строк) if Max=MaxLen then StrLIComp := О else
StrLIComp := StrLen(Strl)-StrLen(Str2) end; end.
Если вы будете использовать этот модуль, ссылайтесь на него в предложении Uses после ссылки на стандартный модуль strings:
Uses Strings, StringsR, • ■ ■
Глава 12
Встроенный ассемблер
Ассемблером называется машинно-зависимый компилятор, преобразующий специальным образом составленные текстовые строки в машинные инструкции. Как и любой другой компилятор, ассемблер упрощает разработку программ за счет того, что предоставляет пользователю доступ к кодам машинных инструкций и операндам с помощью символьных имен.
В этой главе рассматриваются приемы программирования с помощью ассемблера, встроенного в компилятор Турбо Паскаля. Встроенный ассемблер имеется в версиях 6.0 и 7.0 Турбо Паскаля и в руках опытного программиста представляет собой мощный инструмент, позволяющий "выжать" из ПК все возможное.
Общее описание МП 8086/8088
Встроенный ассемблер (далее — просто ассемблер) дает возможность программировать на уровне отдельных машинных инструкций. Это главное отличие ассемблера от Паскаля и в этом отличии сосредоточены все его достоинства и недостатки. Основное достоинство заключается в том, что, программируя на ассемблере, программист обычно выбирает последовательность машинных инструкций так, чтобы реализовать нужные вычисления с максимальной скоростью при минимальных затратах памяти, в то время как даже такой весьма совершенный компилятор, как компилятор Турбо Паскаля, неизбежно вносит в машинный код некоторую избыточность, уменьшающую скорость счета и увеличивающую затраты памяти. С другой стороны, программирование на уровне машинных инструкций — чрезвычайно хлопотное занятие и не может сравниться по скорости разработки программ с программированием на Паскале — в этом заключается главный недостаток ассемблера.
Чтобы использовать средства ассемблера, необходимо ясно представлять себе детали архитектуры микропроцессоров Intel 8086.
К этому семейству относятся микропроцессоры:
8086 — 16-разрядный микропроцессор, используемый в ПК IBM PC/XT;
8088 — аналог 8086, отличается от него только взаимодействием с памятью: 8086 может обмениваться с памятью как байтами, так и 16- разрядными словами, в то время как 8088 — только байтами;
О 80286 — улучшенный вариант 8086, используемый в ПК IBM AT; может работать в двух режимах: в реальном режиме, полностью эмулирующем работу МП 8086, и в защищенном режиме, в котором способен адресовать память до 16 Мбайт (в реальном — до 1 Мбайт);
80386 — 32-разрядный вариант 80286; способен адресовать до 4 Гбайт;
О 80486 — комбинация 80386/80387, т. е. имеет внутреннюю подсистему реализации операций с плавающей точкой;
80586 (Pentium) — имеет ряд усовершенствований, обеспечивающих ему увеличение производительности в 2—3 раза по сравнению с 80486, в том числе возможность обрабатывать 64-разрядные числа.
Микропроцессоры этого семейства наращивают свои возможности в перечисленном порядке, но строго совместимы от младших моделей к старшим: все, что может 8086/8088, реализует и Pentium, но не наоборот. Ниже обсуждается архитектура (внутреннее устройство, способы адресации и система команд) МП 8086/8088.
Регистры
В МП 8086/8088 имеется 14 регистров. В функциональном отношении они делятся на следующие группы:
регистры общего назначения (АХ, вх, сх, DX); предназначены для хранения операндов и выполнения основных команд; любой из них может использоваться как совокупность двух независящих друг от друга 8- разрядных регистров: старшего байта регистра (АН, вн, сн, он) и младшего байта (AL, BL, CL, dl); например, АХ состоит из АН и AL;
О сегментные регистры (cs, DS, ss, ES); служат для указания сегмента при адресации памяти;
П регистры-указатели (SP, ВР, IP); используются для указания смещения при адресации памяти;
индексные регистры (si и di);- применяются для индексной адресации;
регистр флагов; необходим для хранения признаков состояния процессора.
Внутри одной и той же функциональной группы регистры используются различным образом. Ниже описывается специфика регистров.
Регистр АХ. Является основным сумматором. Используется во всех арифметических операциях (сложить, умножить и т. п.). Только с помощью АХ и его полурегистров ah/al возможен обмен данными с портами ввода/вывода.
Регистр вх. Служит в качестве сумматора в арифметических операциях, а также как базовый регистр при индексной адресации.
Регистр сх. В основном работает как счетчик при выполнении операций повторения и сдвига. Может также участвовать в арифметических операциях.
Регистр DX. Используется как регистр данных в операциях ввода/вывода, а также как сумматор при обработке длинных целых чисел (32-разрядных).
Регистр cs. Содержит номер сегмента памяти (сегмента кода), в котором располагается текущая машинная инструкция. Для получения полного адреса следующей команды его содержимое сдвигается влево на 4 разряда и складывается с регистром-указателем IP. Содержимое cs автоматически изменяется в командах дальнего (межсегментного) перехода и вызова процедур.
Регистр IP. Определяет смещение относительно начала сегмента кода cs очередной исполняемой машинной инструкции. Содержимое IP автоматически изменяется в ходе исполнения инструкции, обеспечивая правильный порядок выборки команд из памяти.
Регистр DS. Содержит номер сегмента памяти (сегмента данных), в котором располагаются данные (константы и переменные). Все глобальные переменные и типизированные константы программы Турбо Паскаля всегда располагаются в единственном сегменте, адресуемом этим регистром.
Регистр ss. Содержит номер сегмента стека. Стек — это участок автоадресуе- мой памяти, предназначенный для временного хранения операндов. С помощью стека Турбо Паскаль организует обмен данными между программой и процедурами, кроме того, в нем он размещает все локальные переменные (т. е. переменные, объявленные внутри процедуры). Память стека используется по правилу "последним пришел — первым ушел": самый последний помещенный в стек операнд будет первым извлекаться из него.
Регистр SP. Указывает на вершину стека, т. е. совместно с регистром ss адресует ячейку памяти, куда будет помещаться операнд или откуда он будет извлекаться. Содержимое этого регистра автоматически уменьшается после размещения в стеке очередного операнда и увеличивается после извлечения операнда из стека.
Регистр ВР. Так называемый указатель базы. Облегчает создание и применение локального стека (т. е. стека для использования внутри процедуры).
Регистр ES. Дополнительный сегментный регистр ES используется для межсегментного обмена данными и в некоторых строковых операциях.
Регистр SI. Определяет адрес источника информации при индексной адресации данных (например, при обработке массивов). Обычно взаимодействует в паре с регистром DS.
Регистр DI. В паре с регистром ES определяет приемник информации при межсегментном обмене данными.
Регистр флагов. Отдельные разряды (биты) этого регистра имеют следующее назначение.
Флаг переноса CF. Содержит 1, если произошел перенос единицы при сложении или заем единицы при вычитании. Используется также в циклических операциях и операциях сравнения.
CD Флаг четности PF. Содержит 1, если в результате операции получено число с четным количеством значащих разрядов, т. е. дополняет результат до нечета — необходим в операциях обмена для контроля данных.
Флаг внешнего переноса AF. Контролирует перенос из третьего бита данных. Полезен при операциях над упакованными десятичными числами.
Флаг нуля ZF. Равен 1, если в результате операции получен ноль, и равен О в противном случае.
Флаг знака SF. Равен 1, если в результате операции получено отрицательное число (с единицей в старшем разряде).
П Флаг трассировки TF. Равен 1, если программа исполняется по шагам, с передачей управления после каждой выполненной команды по прерыванию с вектором 1.
П Флаг прерываний IF. Содержит 1, если микропроцессору разрешена обработка прерываний.
П Флаг направления DF. Управляет направлением передачи данных: если он содержит 0, то после каждой индексной операции содержимое индексных регистров увеличивается на 1, в противном случае — уменьшается на 1.
П Флаг переполнения OF. Устанавливается в единицу, если в результате операции получено число, выходящее за разрядную сетку микропроцессора.
Адресация
В архитектуре МП 8086/8088 адрес любого байта задается двумя 16-битовыми словами — сегментом и смещением. При формировании 20-разрядного полного адреса, необходимого для адресации в пределах 1 Мбайт, сегмент сдвигается влево на 4 разряда (умножается на 16) и складывается со смещением. Поскольку емкость 16-разрядного смещения составляет 65 536 значений, в пределах одного сегмента можно адресовать до 64 Кбайт.
Архитектура МП позволяет использовать семь различных способов адресации.
Регистровая
Извлекает операнд из регистра или помещает его в регистр. Примеры:
mov ax,bx add CX r axpush CXmov ax,bx add CX r axpush CX{Извлекаем из BX и помещаем в AX}
mov ax,bx add CX r ax
push CX
mov ax,bx add CX r ax
push CX
(Содержимое АХ прибавляем к СХ}
(Заталкиваем в стек содержимое CX}
Непосредственная
Операнд (8- или 16-разрядная константа) содержится непосредственно в теле команды. Примеры:
mov ах,100 add ах, 5 mov cx,$FFFFmov ах,100 add ах, 5 mov cx,$FFFF(Загружаем в АХ значение 100} {К содержимому АХ прибавляем 5} (Помещаем в СХ значение 65 535}
mov ах,100 add ах, 5 mov cx,$FFFF
mov ах,100 add ах, 5 mov cx,$FFFF
Прямая
Смещение операнда задается в теле программы и складывается с регистром DS; например:
var
X: Word;
В: Byte;
mov ах, X (Пересыпа ем значение переменной X в регистр АХ}
add ah, В {К содержимому регистра АН прибавляем
mov X, ахmov X, ахзначение переменной В}
mov X, ах
mov X, ах
{Пересылаем содержимое регистра АХ в область памяти переменной Х{
Косвенная регистровая
Исполнительный адрес операнда (точнее, его смещение) содержится в одном из регистров вх, ВР, SI или DI. Для указания косвенной адресации этот регистр должен заключаться в квадратные скобки, например:
mov ах,[Ьх] (Содержимое 16-разрядного слова, хранящегося
в памяти по адресу DS:BX, пересылаем в регистр АХ);
Каждый из регистров BX—DI по умолчанию работает со своим сегментным регистром:/
DS:BX, SS:BP, DS:SI, ES:DI
Допускается явное указание сегментного регистра, если он отличается от заданного по умолчанию, например:•
mov ax,es:[bx]
Адресация по базе
Базовый регистр вх (или ВР) содержит базу (адрес начала некоторого фрагмента памяти), относительно которой ассемблер вычисляет смешение, например:
mov ax, [bx] +10mov ax, [bx] +10{Загружаемв АХ 10-й по счетубайт от начала базы памяти
mov ax, [bx] +10
mov ax, [bx] +10
по адресу DS:BX};
I
Индексная адресация
Один из индексных регистров SI или DI указывает положение элемента относительно начала некоторой области памяти. Пусть, например, АОВ — имя массива значений типа Byte. Тогда можно использовать такие фрагменты:
mov si,15{Помещаем в SI константу 15}
mov ah, AOB [si](Пересылаемв АН 16-й по
порядку байт от начала массива}
mov si, 0
mov AOB[s i],ah{Пересыпа ем полученное в
самый первый элемент массива}
Адресация по базе с индексированием
Вариант индексной адресации для случая, когда индексируемая область памяти задается своей базой. Например:
mov ax, [bx] [si]
Этот тип адресации удобен при обработке двумерных массивов. Если, например, АОВ есть массив из 10x10 байт вида
var
АОВ: array [0..9,0..9] of Byte;
то доступ к элементу АОВ [2,3] можно осуществить с помощью такого фрагмента
mov bx,20{База строки 2}
mov si,2{Номер 3-го элемента }
mov ax,AOB[bx][si]{Доступ к элементу}
Система команд
В приведенных ниже табл. 12.1-12.8 указывается мнемоника всех допустимых инструкций для МП 8086/8088. Для удобства пользования все команды разбиты на 6 функциональных групп — пересылки данных, арифметические, битовые, строковые, передачи управления, прерываний. Внутри каждой группы команды объединяются в подгруппы по общим дополнительным признакам.
Детальный анализ всех команд МП 8086/8088 занял бы слишком много места, поэтому в идущих за таблицами пояснениях рассматриваются лишь наиболее популярные команды. Исчерпывающее описание всех команд вы найдете в [1], [20].
Таблица 12.1. Команды пересылки данных
Мнемо- ФорматПояснение
ника
Командыобщего назначения
MOV
MOV <приемник>, <источник>
Переслать значение
PUSH
PUSH <источник>
Поместить в стек
POP
POP <приемник>
Извлечь из стека
XCHG
XCHG <приемник, <источник>
Обменять значения
XLAT
XLAT <таблица>
Загрузить в AL байт из таблицы
Команды ввода/вывода
IN
IN <аккумулятор>, порт>
Читать из порта
OUT
OUT <порт>, аккумулятор>
Записать в порт
Команды пересылки адреса
LEA
LEA <регистр 16>, <память 16>
Загрузить исполнительный адрес
LDS
LDS <регистр 16>, <память 32>
Загрузить В DS: регистр! 6 ПОЛНЫЙ
адрес
LES
LES <регистр 16>, <память 32>
Загрузить В ES: регистр!6 ПОЛНЫЙ
адрес
Командыпересылкифлагов
LAHF
LAHF
Загрузить флаги в АН
SAHF
SAHF
Установить флаги из АН
PUSHF
PUSHF
Поместить флаги в стек
POPF
POPF
Извлечь флаги из стека
Одна из наиболее часто используемых команд — MOV — позволяет в защищенном режиме переслать байт или слово из регистра в регистр, из памяти в регистр или из регистра в память. Тип пересылаемых данных (байт или слово) определяется регистром, участвующим в пересылке. Ниже приводят-
ся примеры использования команды:
movmovmovmovmovmovax,Table Table,ah ds, axax,Table Table,ah ds, axmov es:[bx],axmov es:[bx],axmov ch,-17mov Table,$FFmov ch,-17mov Table,$FF(Пересылка слова из памяти в AX} (Пересылка байта из АН в память} (Пересылка в сегмент данных) (Пересылка слова в память: базовая адресация с заменой сегмента} (Переслать константу в регистр}(Переслать константу в память}(Пересылка слова из памяти в AX} (Пересылка байта из АН в память} (Пересылка в сегмент данных) (Пересылка слова в память: базовая адресация с заменой сегмента} (Переслать константу в регистр}(Переслать константу в память}
mov
mov
mov
mov
mov
mov
ax,Table Table,ah ds, ax
ax,Table Table,ah ds, ax
mov es:[bx],ax
mov es:[bx],ax
mov ch,-17
mov Table,$FF
mov ch,-17
mov Table,$FF
(Пересылка слова из памяти в AX} (Пересылка байта из АН в память} (Пересылка в сегмент данных) (Пересылка слова в память: базовая адресация с заменой сегмента} (Переслать константу в регистр}
(Переслать константу в память}
(Пересылка слова из памяти в AX} (Пересылка байта из АН в память} (Пересылка в сегмент данных) (Пересылка слова в память: базовая адресация с заменой сегмента} (Переслать константу в регистр}
(Переслать константу в память}
С помощью MOV нельзя пересылать:
из памяти в память, например, вместо
mov Meml,Mem2
следует использовать
mov ax,Mem2
mov Meml,ах
О константу или переменную в DS, например, нельзя
mov DS,Data_Seg■
нужно:
mov ах,Data_Seg
mov ds, ах
один сегментный регистр в другой, например, нельзя
moves,ds
НО МОЖНО
mov ах, ds
mov es, ax
П в регистр cs; значение этого регистра (сегмента кода) автоматически меняется при выполнении дальних команд CALL и JMP; кроме того, он загружается из стека при выполнении команды RETF (выход из дальней процедуры).
Для временного сохранения регистров и данных, а также для обмена значениями между регистрами широко используются стековые команды PUSH и POP. Каждая из них работает со словом, т. е. в стек нельзя поместить или извлечь из него одиночный байт. При выполнении PUSH вначале уменьшается на 2 содержимое указателя SP, а затем операнд помещается по адресу SS: SP.
При извлечении из стека сначала читается память по адресу S3: SP, а затем SP увеличивается на 2. Таким образом, при заполнении указатель вершины стека SP смещается к младшим адресам, а при освобождении — к старшим. При работе со стеком следует помнить о специфике стековой памяти ("последним пришел — первым ушел"), а также о том, что эта память интенсивно используется при вызове процедур, т. е. состояние стека к моменту выхода из процедуры должно быть строго согласовано с дальнейшей работой программы. Первое условие определяет порядок извлечения данных из стека — он должен быть обратным порядку, в котором эти данные помещались в стек. Второе условие фактически означает, что после выхода из процедуры указатель SP должен содержать то же смещение, что и к моменту входа в нее. Иными словами, процедура не должна "забыть" в стеке лишнее слово или взять из него больше нужного.
Команда загрузки адреса LEA загружает в регистр адрес (смещение) нужного участка памяти. Этого же можно достичь с помощью зарезервированного слова OFFSET, стоящего перед именем переменной. Например:
var
X: Word;
asm
mov ax, OFFSET X {Загружаем смещение X в AX}
lea ax,X{To же действие)
end;
Разница состоит в том, что в случае команды LEA разрешается использовать индексную адресацию, что особенно удобно при пересылке массивов данных.
Две другие команды адресной загрузки — LDS и LES загружают первое 16- разрядное слово из источника в регистр-приемник, а затем следующее сло- BO — В регистр DS или ES, т. е. они рассчитаны на загрузку полного адреса операнда (сегмента и смещения).
•
Таблица 12.2. Арифметические команды
Мнемо- ФорматКомментарий
ника
Команды сложения
ADDADD <приемник>, <источник> СЛОЖИТЬ
ADCADC <приемник^, <источник> Сложить, добавить перенос
ААААААСкорректировать сложение для таблицы
ASCII
Таблица 12.2 (окончание)
Мнемоника
Формат
Комментарий
Команды сложения
DAA
DAA
Скорректировать сложение для но-десятичных чисел
двоич-
INC
INC <приемник>
Увеличить на единицу
Команды вычитания
SUB
SUB <приемник>,
<источник> Вычесть
SBB
SBB <приемник>,
<источник> Вычесть с заемом
AAS
AAS
Скорректировать вычитание для цы ASCII
табли-
DAS
DAS
Скорректировать вычитание для но-десятичных чисел
двоич-
DEC
DEC <приемник>
Уменьшить на единицу
NEG
NEG <приемник>
Обратить знак
CMP
СМР <приемник>,
<источник> Сравнить
Команды умножения
MUL
MUL <источник>
Умножить без знака
IMUL
IMUL <источник>
Умножить со знаком
ЛАМ
ДАМ
Скорректировать умножение для цы ASCII
табли-
Команды деления
DIV
DIV <источник>
Делить без знака
IDIV
IDIV <ист,очник>
Делить со знаком
AAD
AAD
Скорректировать деление для таблицы ASCII
Команды расширения знака
CBW
CBW
Преобразовать байт в слово
CWD
CWD
Преобразовать слово в двойное слово
При работе с арифметическими командами следует помнить о том, что МП может обрабатывать знаковые числа, числа без знака, а также двоичнодесятичные числа. В беззнаковых числах для представления значения задействованы все биты, т. е. они эквивалентны типам Byte и word, в то время как знаковые числа в старшем разряде хранят знак числа и эквивалентны
типам Shortlnt и integer. Двоично-десятичные числа используют по 4 бита для каждого десятичного разряда и могут быть упакованными или неупакованными. В первом случае один байт хранит 2 десятичные цифры (старшая — в старшем полубайте), во втором — только одну (старший полубайт не используется). Основные арифметические команды МП (ADD, SUB, MUL, DIV) не учитывают двоично-десятичную форму представления чисел, поэтому в архитектуру МП включены команды коррекции результата.
Таблица 12.3. Битовые команды
Мнемо-ФорматКомментарий
ника
Логические команды
ANDAND <приемник>,<источник>Выполнить логическую операцию AND
OROR <приемник>, <источник>Выполнить операцию OR
XORXOR <приемник>,<источник>Выполнить операцию XOR
NOTNOT <приемник>Выполнить операцию NOT
TESTTEST <приемник>,<источник>Проверить
Сдвиговые команды
SAL/SHL
SAL
<приемник>,
<источник>
Сдвинуть влево
SAR/SHR
SAR
<приемник>,
<ис<еочник>
Сдвинуть вправо
ROL
ROL
<приемник>,
<источник>
Сдвинуть влево циклически
ROR
ROR
<приемник>,
<источник>
Сдвинуть вправо циклически
RCL
RCL
<приемник>,
<источник>
Сдвинуть влево с переносом
RCR
RCR
<приемник>,
<источник>
Сдвинуть вправо с переносом
Битовые команды используются при исчислении логических выражений, а также в тех случаях, когда необходимо изменить отдельные разряды операнда. Логические команды AND, OR, XOR и NOT эквивалентны соответствующим операциям Турбо Паскаля в случае, когда операндами являются целочисленные выражения. Команда TEST выполняет целочисленную операцию поразрядного суммирования AND, но не изменяет значения операндов, а лишь устанавливает флаги в соответствии со значением результата сравнения: обнуляет CF и OF, изменяет PF, ZF, SF и не меняет AF (флаг ZF установится в 1 в том случае, когда оба операнда содержат по единице хотя бы в одном соответствующем разряде). Команды сдвига SHL/SHR эквивалентны одноименным операциям Турбо Паскаля и отличаются от команд циклического сдвига ROL/ROR тем, что вытесненные в ходе их выполнения значащие раз-
ряды теряются, в то время как при циклическом сдвиге эти разряды появляются "с другой стороны". Например, если выполнить фрагмент
mov al,l {Загружаемв AL единицу}
shr al, 1{Сдвигаем вправо на 1 разряд}
то регистр AL будет содержать 0 (вытесненная вправо единица будет помещена в OF), в то время как после замены команды SHR на ROR в нем будет значение $80=128 (вытесненная единица будет помещена в старший бит регистра).
Заметим, что счетчиком в командах сдвига может быть цифра 1 или количество сдвигов, указываемое в регистре CL.
Таблица 12.4. Команды передачи управления
Мнемоника
Формат
Комментарий
Безусловные переходы
CALL
CALL <ИМЯ>
Войти в процедуру
RET
RET <количество_ параметров>
Вернуться из процедуры
JUMP
JUMP <имя>
Перейти
Условные
переходы
JA/JNBE
JA <близкая_метка>
Перейти, если выше (после сравнения беззнаковых операндов)
JAE/JNB
JAE <близкая_метка>
Перейти, если выше или равно
JB/JBAE/JC
JB <близкая_метка>
Перейти, если ниже
JBE/JNA
JBE <близкая_метка>
Перейти, если ниже или равно
JCXZ
JCXZ <близкая_метка>
Перейти, если СХ=0
JE/JZ
JE <близкая_метка>
Перейти, если равно
JG/JNLE
JG <близкая_метка>
Перейти, если больше (после сравнения знаковых операндов)
JGE/JNL
LGE <близкая_метка>
Перейти, если больше или равно
JL/JNGE
JL <близкая_метка>
Перейти, если меньше
JLE/JNG
JLE <близкая_метка>
Перейти, если меньше или равно
JNC
JNC <близкая_метка>
Перейти, если нет переноса
JNE/JNZ
JNE <близкая_метка>
Перейти, если не равно
JNO
JNO <близкая_метка>
Перейти, если нет переполнения
JNP/JPO
JNP <близкая_метка>
Перейти, если нечетный
Таблица 12.4 (окончание)
Мнемоника
Формат
Комментарий
Условные переходы
JO
JO <близкая_метка>
Перейти, если перенос
JP/JPE
JP
Перейти, если четный
JS
JS
Перейти, если отрицательный
Команды управления циклами
LOOPLOOPПовторить ЦИКЛ
LOOPE/LOOPZ LOOPEПовторять, пока равны
LOOPNE/LOOPNZ LOOPNEПовторять, пока не равны
Команды безусловных переходов CALL, RET, JMP могут использовать дальнюю или ближнюю модель памяти, в то время как команды условных ходов — только ближнюю (в пределах от -128 до +127 байтов). При дальней модели памяти (устанавливается опцией Options | Compiler | Force far calls среды Турбо Паскаля или директивой компилятора как внутрисегментная, так и межсегментная передача управления, при ближней — только внутрисегментная.
Инструкция CALL работает следующим образом. Вначале адрес следующей за CALL инструкции (адрес возврата) помещается в стек, затем в регистр IP (или в парупомещается адрес точки входа в процедуру, таким обра
зом сразу за командой CALL будет исполняться уже первая команда процедуры. Оба адреса (точки входа и возврата) будут 16-битовыми смещениями для внутрисегментного вызова или 32-битовыми полными адресами — для межсегментного. Все процедуры (функции) Паскаля, оттранслированные в режимеили содержащие зарезервированное слово FAR в заго
ловке, должны вызываться как дальние. Для этого за инструкцией CALL следует указать модель памяти:
ProcedureFar;
call FAR MyProccall FAR MyProcдальней процедуры)дальней процедуры)
call FAR MyProc
call FAR MyProc
дальней процедуры)
дальней процедуры)
end;
Таким же способом должны вызываться все библиотечные подпрограммы (т. е. объявленные в интерфейсных частях модулей). При дальнем вызове в
941
стек сначала "заталкивается" содержимое сегмента кода cs, а уже затем — смещение возврата.
При выходе из дальней процедуры команда RET извлекает из стека оба 16- разрядных слова и помещает первое в IP, а второе в cs, а при выходе из ближней извлекает из стека только смещение и помещает его в IP.
Команды условных переходов способны передавать управление на метку, расположенную в пределах ближайших плюс-минус 128 байтов от самой команды. Если нужно передать управление на метку, расположенную дальше в том же сегменте, или на метку в другом сегменте, сразу за командой условной передачи располагают безусловную команду JMP или CAL, например:
стр ах, 0jne @NotZerojmp IsZeroстр ах, 0jne @NotZerojmp IsZero{ Проверяемых}
стр ах, 0
jne @NotZero
jmp IsZero
стр ах, 0
jne @NotZero
jmp IsZero
(ЫХ=О ?!
(Да — переходим на дальнюю метку}
.(Нет — продолжаем работу}
В таблице термин "выше/ниже" используется применительно к сравнению беззнаковых операндов, а "больше/меньше" — знаковых.
Например:стр ах, 5ja @Above 5стр Ьх, -3 jle @LessM3Например:стр ах, 5ja @Above 5стр Ьх, -3 jle @LessM3Поскольку условные переходы реализуют ветвление программы на основе проверки флагов, обычно непосредственно перед ними располагаются команды, изменяющие эти флаги, чаще всего — команда сравнения СМР. В табл. 12.5 показаны варианты условных переходов для различных соотношений приемника и источника (первого и второго операнда) команды СМР.
Например:
стр ах, 5
ja @Above 5
стр Ьх, -3 jle @LessM3
Например:
стр ах, 5
ja @Above 5
стр Ьх, -3 jle @LessM3
Таблица 12.5. Условные переходы по команде СМР
Условие
Для беззнаковых чисел
Для чисел со знаками
Приемник больше источника
JA
JG
Приемник и источник равны
JE
JE
Приемник меньше источника
JB
JL
Приемник не меньше источника
JAE
JGE
Приемник не больше источника
JBE
JLE
Приемник и источник не равны
JNE
JNE
(АХ>5 ?}
(Да, больше — переходим}
(ВХ<=-3 ?}
(Да, меньше или равен}
Команды LOOP/LOOPE/LOOPNE служат для организации циклов. Все они используют содержимое регистра сх как счетчик числа повторений. Команда LOOP уменьшает сх на единицу и передает управление на метку начала цикла, если содержимое этого регистра отлично от нуля. Команды LOOPE/LOOPNE также уменьшают счетчик сх, но передают управление в начало цикла при совместном условии установки (или сброса) флага ZF и неравенства нулю счетчика сх.
Вот как, например, можно отыскать нулевой байт в массиве АОВ:
var
АОВ: array [1..1000] of Byte;
asm
mov ex,1000 lea bx,AOB dec bx
{Здесь начало @@Test: inc bx
emp BYTE PTR loopne @Test jnz @NotZero
end;
'{Инициируем счетчик CX)
{Помещаем адрес АОВ в BX)
{Готовим цикл) цикла проверки)
{Адрес очередного байта}
[Ьх], 0(Проверяем байт}
{Замыкаем цикл}
{Если не найден нулевой байт}
{Нашли нулевой байт)
Таблица 12.6. Строковые команды
Мнемоника
Формат
Комментарий
MOVSB
MOVSW
Пересылка строк
MOVSB■
MOVSW
Пересылать байты
Пересылать слова
CMPSB
CMPSW
Сравнение строк
CMPSB
CMPSW
Сравнивать байты
Сравнивать слова
SCASB
SCASW
Сканирование
SCASB
SCASW
Искать байт
Искать слово
Таблица 12.6 (окончание)
Мнемоника
Формат
Комментарий
LODSB
Загрузка и сохранение
LODSB
Загружать байты
LODSW
LODSW
Загружать слова
STOSB
STOSB
Сохранять байты
STOSW
STOSW
Сохранять слова
Строковые команды рассчитаны на обработку строк. Замечу, что термин "строка" здесь отнюдь не эквивалентен аналогичному термину Турбо Паскаля и означает произвольную цепочку байтов или слов длиной до 64 Кбайт. Эти команды оперируют пятью примитивами, каждый из которых обрабатывает лишь один байт или одно слово за раз. Перед примитивом обычно указывается префикс повторения REP/REPE/REPNE, заставляющий выполняться примитив до тех пор, пока не обнулится счетчик повторений сх или не будет нарушено соответствующее условие.
При работе со строковыми командами важно помнить два обстоятельства. Во-первых, эти команды всегда берут адрес строки-источника из пары DS:SI, а строки-приемника — из пары ES:DI. Таким образом, перед исполнением строковой команды необходимо инициировать сегментные регистры нужным образом. Во-вторых, строковые команды используют индексную адресацию с автоматическим изменением смещения в SI/DI после однократного исполнения примитива. Содержимое этих регистров изменяется на 1 при обработке байтов и на 2 при обработке слов, причем наращивается, если флаг направления DF сброшен, и уменьшается, если он равен 1.
Вот как можно осуществить пересылку массива А в массив в:
var
A,B: array [1..250] of Integer;
asm
lea si, A
{Смещение А — в SI (источник)}
push ds
pop es
(Инициируем ES := DS}
lea di,В
(Смещение В - в DI (приемник)}
mov ex,250
{Счетчик переноса)
eld
{Направление переноса — наращивать)
rep movsw
(Переносим 500 байт}
end;
В программе на Турбо Паскале регистр DS всегда содержит сегмент данных, поэтому инициировать его необязательно. Что касается регистра дополнительного сегмента ES, такого правила нет, и хотя в большинстве случаев он также ссылается на сегмент данных, рекомендуется проводить его инициализацию перед использованием строковой команды (см. выше команды push ds, pop es).
Таблица 12.7. Команды прерываний
Мнемоника
Формат
Комментарий
INT
INT <номер>
Выполнить прерывание
INTO
INTO
Выполнить прерывание по переполнению
IRET
IRET
Вернуться из прерывания
Выполнение прерываний во многом напоминает косвенный вызов дальней процедуры. По команде INT (INTO) в стек помещается регистр флагов, сегмент cs и указатель IP, а новые значения этих регистров берутся из 4- байтного вектора прерывания, соответствующего номеру прерывания в команде INT, или из вектора 4 — для команды INTO. Таким образом, единственным отличием от команды CALL является то, что в стек предварительно заносится регистр флагов. Следует, правда, оговориться: перед передачей управления программе обработки прерывания микропроцессор сбрасывает флаги трассировки TF и прерываний IF; сброс TF необходим для обеспечения нормальной работы отладчиков, использующих прерывание по вектору 1 или 4, сброс IF блокирует вмешательство других процессов в ход обработки прерывания.
Команда INTO представляет собой условное прерывание и выполняется, если в этот момент установлен в 1 флаг переполнения OF. Команда IRET реализует правильный выход из программы обработки прерывания: она считывает из стека 3 двухбайтных слова и помещает их в регистры IP, cs и регистр флагов.
Таблица 12.8. Команды управления
Мнемоника
Формат
Комментарий
Управление флагами
STC
STC
Установить перенос
CLC
CLC
Очистить перенос
CMC
CMC
Инвертировать CF
STD
STD
Установить направление
Таблица 12.8 (окончание)
Мнемоника
Формат
Комментарий
CLD
CLD
Управление флагами
Очистить направление
STI
STI
Разрешить прерывания
CLI
CLI
Запретить прерывания
HLT
HLT
Внешняя синхронизация
Остановить вычисления
WAIT
WAIT
Ждать активности на шине
ESC
ESC <код>, <источник> Передать команду
LOCK
LOCK
Захватить шину
NOP
NOP
Пустая команда
Нет операции
Команды внешней синхронизации работают следующим образом.
Команда HALT переводит МП в состояние останова, из которого его можно вывести только при перезагрузке системы или при наступлении немаскируемого прерывания.
Команда WAIT заставляет МП выполнять холостой режим работы и каждые 5 тактов проверять уровень сигнала на входной шине: пока на этой шине нет сигнала активности, процессор выполняет WAIT, но как только шина активизируется, он продолжит исполнение программы. Эта инструкция обычно используется для ожидания сигнала обслуживания (прерывания) высокоприоритетного устройства типа контроллера прямого доступа к памяти.
Команда ESC служит для передачи указанного в ней операнда на шину данных. Тем самым обеспечивается возможность передачи команд другим процессорам. Эта команда чаще всего применяется для управления работой арифметического сопроцессора. В этом случае код представляет собой код команды сопроцессора, а источник — используемый в этой команде операнд.
П Команда LOCK фактически представляет собой однобайтный префикс, который можно использовать совместно с любой другой командой микропроцессора. По этой команде МП активизирует одноименный сигнал на своей шине, что исключает доступ к этой шине для любого другого внешнего устройства (процессора).
12.2. Специфика встроенного ассемблера
Приведенное выше общее описание архитектуры МП 8086/8088 является базовым для любого ассемблера, в том числе и для встроенного ассемблера Турбо Паскаля. Однако ассемблеры содержат массу дополнительных возможностей, облегчающих разработку готовых к работе программ. Эти возможности отражаются. в директивах и макрокомандах ассемблера. Встроенный ассемблер не предназначен для написания законченных программ, поэтому в нем отсутствуют макрокоманды и директивы. Главной особенностью встроенного ассемблера является практически полное отсутствие в нем средств описания переменных и данных, т. к. эти объекты описываются средствами Турбо Паскаля.
Оператор ASM
Зарезервированное слово ASM открывает доступ к средствам встроенного ассемблера. Этот оператор может располагаться только внутри исполняемой части программы (подпрограммы). Область действия оператора ASM ограничивается ближайшим по тексту зарезервированным словом END. Таким образом, структура любого ассемблерного оператора такова:
asm
«Эдна или несколько команд встроенного ассемблера> end;
С точки зрения Турбо Паскаля пара asm. . . end считается операторными скобками, ограничивающими единственный оператор Паскаля, например:
if Х>10 then
asm
end
else
for k := 1 to 5 do
asm
end;
Тело ассемблерного оператора asm. . . end может быть пустым или содержать несколько ассемблерных команд. Каждая ассемблерная команда должна
располагаться на отдельной строке или отделяться от следующей за ней команды символом Ниже приводятся два разных способа написания одной и той же последовательности ассемблерных команд:
asm
mov ah, 0; int $16; mov ChCode, al; mov ScCode, ah
end;
asm
mov ah, 0
int $16
mov ChCode,al
" mov ScCode,ah
end;
В конце строки, содержащей единственную ассемблерную команду, или между двумя командами, располагающимися на одной строке, разрешается вставлять комментарий, который должен оформляться по обычным правилам Турбо Паскаля, т. е. ограничиваться символами {, } или (*, *). Таким образом, комментарии разрешены между ассемблерными командами, но не внутри них. Например, такой оператор будет правильным:
asm
(Инициируем регистры}
lea si,X; push ds;
pop es;(ES := DS}
•
lea di,Y; mov ex,100
old(Перенос —
rep {Выполняем Y : = X} movsw
{Здесь нет ошибки — комментарий можно вставлять между префиксом и командой} end; .
а такой — неправильным:
asm
(Готовим регистры}
lea si,X; push ds;
pop {ES:=DS} es;
{Ошибка! Комментарий разорвал
мнемонику команды и ее операнд}
lea di,Y; mov ex,100 (инаправление) eld
f
Глава 12. Встроенный ассемблер279
|
{Комментарий является разделителем команд, поэтому перед ним можно не ставить rep movsw
end;
В пределах ассемблерного оператора допускаются любые команды, но Турбо Паскаль требует выполнения следующего соглашения:
В начале ассемблерного оператора регистр DS содержит сегмент кода, ss — сегмент стека, ВР — текущий стек, SP указывает на вершину стека. Все эти регистры должны иметь точно такие же значения к моменту завершения работы ассемблерного оператора.
Программист не должен делать каких-либо предположений о содержимом остальных регистров, и эти регистры могут иметь произвольное значение после завершения работы ассемблерного оператора. Исключением является случай ассемблерной функции, которая должна использовать некоторые регистры для возврата своего значения (см. разд. 12.2.3).
Синтаксис ассемблерных команд
Здесь и далее ассемблерными командами называются команды на языке встроенного ассемблера, вставляемые в тело ассемблерного оператора asm...end.
Структура ассемблерной команды такова:
[Метка] [Префикс] [Код [Операнд [,Операнд] ] ]
В квадратных скобках указываются необязательные элементы структуры.
Метки
Любой команде ассемблерного оператора может предшествовать одна или несколько меток. В ассемблере существуют метки двух типов: глобальные и локальные. Глобальные метки — это обычные метки Турбо Паскаля. Они объявляются в разделе описаний после зарезервированного слова Label. С помощью глобальной метки можно передать управление в тело ассемблерного оператора оператором GOTO. Например:
Label
AltEnt;
begin
Goto AltEnd;Goto AltEnd;(Передаем управление внутрьа ссемблерного опера тора}
Goto AltEnd;
Goto AltEnd;
asmasm
asm
asm
AltEnd:AltEnd:можно передать управление
AltEnd:
AltEnd:
end;
Локальные метки объявляются непосредственно в теле ассемблерного оператора. Эти метки обязаны начинаться символом @. Поскольку этот символ нельзя использовать в именах Турбо Паскаля, он позволяет отличить локальную метку от глобальной. Локальная метка не известна нигде вне ассемблерного оператора, поэтому на нее нельзя передать управление тором GOTO. По этой же причине в разных ассемблерных операторах допустимы одноименные локальные метки.
Префиксы
Встроенный ассемблер поддерживает следующие префиксы команд:
LOCK — захват шины;
П REP/REPE/REPNE — ПОВТОр СТРОКОВОЙ
□ REPZ/REPNZ — СИНОНИМ REPE/REPNE;
SEGCS — перекрытие cs;
П SEGDS — перекрытие DS;
П SEGSS — перекрытие ss;\
П SEGES - перекрытие ES.
Префиксы/REP/REPE/REPNEВ разд. 12.1.3. Префиксы SEGxx
определяют сегментный регистр, который должен использоваться вместо заданного по умолчанию, и распространяются только на следующие за ними ассемблерные команды.
Если префикс указан без кода инструкции, он распространяет свое действие на следующую ассемблерную команду.
Код инструкции очень редко имеет более одного префикса и никогда — более трех: допускается следующая последовательность i
LOCK SEGxx REPxx
Замечу, что если при обработке строковой команды произошло аппаратное прерывание, МП 8086/8088 "забывает" префиксы LOCK и SEGXX, которые, возможно, определены в той же команде, так что создавать сложные префиксные конструкции не рекомендуется.
Коды инструкций
Встроенный ассемблер поддерживает мнемонику всех команд, перечисленных в разд. 12.1.3. Кроме того, в ассемблерных командах допускается мнемоника инструкций процессора 8087, а также команды процессоров 80286/80287. Замечу, что инструкции 8087 разрешены только при активном состоянии ($N+}, 80286 — при {$G+}, a 80287 — в случае {$G+,N+}.
Операнды
Операндами встроенного ассемблера могут быть выражения, состоящие из комбинации регистров, констант, имен и символов операций.
Регистры
Во встроенном ассемблере используется мнемоника регистров, указанная в разд. 12.1.1, а также имя ST для ссылки на регистры арифметического сопроцессора.
Константы
Ассемблер поддерживает строковые и числовые константы.
Строковые константы заключаются в апострофы или кавычки. Если константа объявлена с помощью кавычек, внутри нее символ апострофа рассматривается наравне с другими символами, т. е. не считается ограничителем константы, точно так же внутри константы, обрамленной апострофами, не считается ограничителем символ кавычки. Если внутри константы необходимо указать ограничивающий ее символ, он удваивается. Примеры:
' Строковая константа'
"Это — тоже строковая константа"
'Символ '' не считается ограничителем'
'внутри строки, обрамленной кавычками
Числовые константы могут быть только целыми и их значение не может превосходить емкости двойного слова, т. е. должно быть внутри диапазона от -2 147 483 648 до +4 294 967 295.
По умолчанию при записи числовых констант используется десятичная нотация, но ассемблер поддерживает также двоичные, восьмеричные и шестнадцатеричные константы. Двоичная константа составляется как комбинация единиц и нулей, заканчивающаяся символом в (от Binary — двоичный); при записи восьмеричной константы используются символы 0—7, а в ее конце ставится символ о (Octal — восьмеричный); шестнадцатеричная константа записывается по правилам Турбо Паскаля (начинается с символа tt) либо по правилам Турбо Ассемблера: начинается с цифры, в конце ставится символ н (от Hexadecimal — шестнадцатеричный).
Имена
Локальные метки — это единственные имена, которые разрешается определять внутри ассемблерного оператора. Имена остальных объектов программы — констант, переменных, подпрограмм — должны определяться только с помощью средств Турбо Паскаля.
Область определения имен подчиняется тем же правилам, что и в Турбо Паскале — имена должны быть "видны" в том месте, где они используются, и они локализуются в пределах блока, в котором описаны.
Во встроенном ассемблере существуют три предопределенных имени:
@@Code — текущий сегмент кода;
@Data — начальный сегмент данных;
@Result — ссылка внутри функции на ее результат.
Имена @Code и @Data могут употребляться только в сочетании с директивой SEG для ссылки на нужный сегмент. Например:
asm
mov ax, SEG @Data
mov ds,ax
end;
С помощью имени @Result осуществляется присвоение результата функции. Например:
Function Min(X,Y: Integer): Integer;
(Эта функция сравнивает два целых числа
и возвращает наименьшее из них)
begin
asm
mov ах,Х
[Помещаем X в АХ)
cmp ax,-Y jl @ mov ax,Y
(X<Y ?)
(Да — на выход}
(Нет — помещаем Y в АХ}
@ : mov @Result, ах end
(АХ содержит результат)
end;
Для доступа к полям записей разрешены составные имена. Например:
type
Point = record
X,Y: Integer
end;
Rect = record
А,В: Point
end;
var
P: Point;^
R: Rect;
begin
asm
mov ax,P.X
add ax,P.Y
mov R.A.X,ax
end
ч end.
Идентификаторы типов можно применять к операндам для уточнения данных, о которых идет речь. Каждая из следующих команд реализует одно и то же действие: загружает в регистр АХ слово по адресу ES : [di+4]:
mov ax,(Rect PTRes:[di]).B.X
mov ax,Rect(es:[di]).B.X
mov ax,es:Rect[di].B.X
mov ax,Rect[es:di].B.X-
mov ax,es:[di].Rept.B.X
Следующие имена не могут использоваться в операндах встроенного ассемблера:
стандартные процедуры и функции (например, WriteLn, Chr);
предопределенные массивы Mem, MemW, MeinL, Port, Portw;
константы с плавающей точкой, строковые и множественного типа;
макросы (процедуры и функции, полностью реализуемые одним оператором InLine);
символ 8Resu.lt вне функции.
Выражения
Во встроенном ассемблере имеются выражения трех классов: регистровые, ссылки на память и непосредственные.
Регистровое выражение — это выражение, состоящее из имени регистра. Все следующие команды содержат только регистровые выражения:
push ds
pop es
mov ah,bl
add CX, ax
Непосредственные выражения — это нетипизированные константы и имена типов. Примеры непосредственных выражений:
const
dec= 10;
asm
mov ax, dec
mov bx, О
add ex,2*dec+l
sub dh,-5
end;
Все остальные выражения относятся к ссылкам на память. Например: const
dec: Word = 10;
Step = 12;
var
X,Y: Byte;
asm
mov ax, dec
mov ex,[Step]
add ah,X
mov Y,bl mov ax, [bx] end;
Важным отличием ассемблерных выражений от выражений Турбо Паскаля является то обстоятельство, что они должны быть статическими, т. е. разрешены (вычислены) на этапе создания программы. Если выражение может быть полностью вычислено к моменту его трансляции, т. е. если оно состоит только из регистровых или непосредственных значений, такое выражение называется абсолютным, компилятор вычисляет его и использует для создания команды.
В ходе компиляции программы вырабатывается так называемый объектный код, который затем преобразуется компоновщиком в готовую к работе программу. При создании объектного кода компилятор не может вычислить значения выражений типа "ссылка на память", т. к. не знает окончательного положения в памяти меток, переменных, подпрограмм. В результате он соз-
дает так называемое перемещаемое выражение, которое затем компоновщиком преобразуется в нужную ссылку на память.
Встроенный ассемблер разрешает любую операцию над абсолютным значением (см. ниже), но ограничивает перемещаемые выражения до сложения или вычитания, одним из операндов которого должна быть константа.
Другое важное отличие ассемблерных выражений от выражений Турбо Паскаля заключается в способе интерпретации переменных. В выражениях Паскаля любая ссылка на переменную интерпретируется как текущее содержимое этой переменной. В ассемблерных выражениях это справедливо только тогда, когда все выражение в целом состоит из имени переменной. Во всех остальных случаях ссылка на переменную интерпретируется как адрес переменной. Например, выражение
Х+10 .
в Паскале означает: "к содержимому переменной х прибавить ю". В ассемблерной команде это означает: "к адресу (смещению) переменной х прибавить io”. Однако команда
mov ах,Х
означает: "поместить в регистр АХ первые два байта переменной х". Если бы нам понадобилось загрузить в АХ адрес переменной х, мы должны были бы написать
mov ах,OFFSET X
Замечу, что попытка "перехитрить" ассемблер командами типа
mov ах,Х+0
mov ах,Х+1-1
и т. п. не дает желаемого результата: ассемблер просто загружает в АХ содержимое (а не адрес) переменной х.
Как и в Паскале, ассемблерные выражения имеют тип, но отличаются тем, что этот тип определяет только размер объекта в памяти и не ограничивает применяемые к нему операции.
В табл. 12.9 перечислены предопределенные типы встроенного ассемблера.
Таблица 12.9. Предопределенные типы встроенного ассемблера
Тип
Длина в памяти
Тип
Длина в памяти
BYTE
1
TBYTE
10
WORD
2
NEAR
—
DWORD
4
FAR
i
QWORD
8
Имена предопределенных типов можно использовать для приведения типов выражений. Например, если определены переменные var
Flag: Boolean;
X : Word;
то такие ассемблерные выражения недопустимы:
mov Flag,bx
mov ah,X
Для корректного задания последней команды можно предложить следующие варианты:
mov ah,BYTE PTR X
mov ah,Byte(X)
mov ah,X.Byte
Во всех случаях в АН будет загружен первый (младший) байт переменной х.
Встроенный ассемблер поддерживает операции, перечисленные в табл. 12.10 (в порядке убывания приоритета).
Таблица 12.10. Операции встроенного ассемблера
Операция
Комментарий
&
Перекрытие идентификатора
()
Подвыражение
[ ]
Ссылка на память
. (точка)
Селектор структуры
HIGH LOW
Доступ к байту в слове
+ •
Унарные операции задания знака
Перекрытие сегмента
OFFSET SEG TYPE PTR *
/ MOD SHL SHR
+ •
Бинарные операции
NOT AND OR XOR
Операции над битами
Операция &
Осуществляет перекрытие идентификатора: следующий за знаком & идентификатор считается определенным в программе, даже если он совпадает с зарезервированным словом.
Например:
var
Ch: Byte;
mov0 в регистр СН}
&Ch, О0 в переменную
Операция ()
Круглые скобки используются обычным для Паскаля образом — для изменения порядка исчисления выражения (подвыражение, ограниченное скобками, вычисляется в первую очередь). Если перед скобками стоит имя типа, все выражение приобретает указанный тип. Например:
mov ах, (= 65}
mov= 27}
Операция []
Определяет ссылку на память. Выражение внутри скобок вычисляется в первую очередь. Обычно оно связывается с регистрами вх, ВР,DI. Для
указания индексации возможны операции + и -. Например:
mov= 100}
movв АН содержимое
байта по адресу
(точка)
Селектор элемента структуры. Результат — сумма выражений до и после точки с типом второго выражения. Например:
var
R: record
X: Word; Y: Byte
end;
mov
mov R.Y,al
Операции HIGH и LOW
HIGH возвращает старший, a LOW — младший байт выражения типа слова, следующего за символами операции. Выражение должно иметь абсолютное непосредственное зйачение. Например:
mov al,High $1000= $10}
Операция; (двоеточие)
Указывает ассемблеру, что выражение после операции должно относиться к сегменту, указанному до операции. Результат — ссылка на память со значением второго выражения. Например:
mov ах, [10] {АХ = слово по адресу DS:10}
movax,BS:[10](AX = слово по адресу■ BS:10}
Операция OFFSET
Возвращает смещение выражения, следующего за операцией. Результат имеет непосредственное значение. Например:
movax,X{AX = слово по адресу переменной X)
mov ax,offset X{AX = смещение адреса X}
Операция SEG
Возвращает сегмент выражения, следующего за операцией. Результат имеет непосредственное значение.,
Операция PTR
Осуществляет приведение типа. Результат — ссылка на память со значением выражения после операции и типом выражения до операции. Например:
Function Swap(X: Integer): Integer;
{Меняет местами байты в слове X) begin
asm
mov ax, X
mov BYTE PTR @Result,ah
mov BYTE PTR @Result+l, al
end;
end;
Операции * и /
Операция * означает умножение, / — целочисленное деление. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции. Например:
mov ax, 2*2{AX = 4}
mov ax,17/3 (АХ - 5}
Операция MOD
Возвращает остаток от целочисленного деления. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции. Например:
mov ax, 17 mod 3 (AX = 2}
Операции SHL и SHR
Осуществляют логический сдвиг влево (SHL) или вправо (SHR) выражения, стоящего до операции, на количество разрядов, определяемое выражением после операции. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции. Например: mov an, 1 shl 7$80 =
•
Бинарная операция +
Осуществляет сложение двух выражений. Выражения могут быть непосредственными значениями или ссылками на память, но только одно из них может быть перемещаемым. Если одно из выражений — ссылка на память, результат также определяет ссылку на память, а если одно из выражений — перемещаемое, результат будет перемещаемым.
Бинарная операция -
Вычитание двух выражений. Первое выражение может быть любого класса, а второе должно быть абсолютным непосредственным значением. относится к тому же классу, что и первое выражение.
Побитовые операции NOT, AND, OR, XOR
Имеют такой же смысл, что и одноименные операции Турбо Паскаля над целыми числами. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции.
Директивы ассемблера
Встроенный ассемблер не поддерживает никаких директив, обычно используемых в других ассемблерах, за исключением DB, и DD.
Структура директив такова: Dx
Здесь:
DX — директива DB, или DD;
□— ассемблерная константа или константное выражение.
Директива DB определяет цепочку байтов, DW — слов, DD — двойных слов. Например:
db'Турбо Паскаль', 13,10
dw 0, $ FFFF, NearProc
dd'ABCD',999999999, FarProc
В качестве константных выражений разрешается использовать любые ассемблерные константы со значением, не выходящим из диапазона байта (DB), слова (dw) или двойного слова (DD). В любой директиве можно определять строковую константу, которая приводит к побайтовому заполнению памяти ASCII-кодами символов. Поскольку слово (двойное слово) размещается в памяти, начиная со своего младшего байта, старший (старшие) байт в директивах DW и DD при размещении строковой константы может остаться неопределенным и заполняется нулем. Например, два следующих объявления эквивалентны: dw* 5’
dw$35 ($35 - ASCII-код символа ' 5 '}
В директивах DW и DD разрешается также указывать имена, которые в этом случае интерпретируются как адреса соответствующих объектов, причем для DW это ближний адрес (смещение), а для DD — дальний. Например:
dw X{Размещает смещение переменной X}
dd Proc (Размещает FAR-адрес процедуры Proc)
Данные, определяемые директивами DX, всегда размещаются в текущем кодовом сегменте. Разместить таким образом данные в сегменте данных (т. е. определить константу или типизированную константу) невозможно — это можно выполнить стандартными средствами Турбо Паскаля. Более того, директивы не могут снабжаться именами, а поэтому использовать размещаемые с их помощью данные не так-то просто. В следующем примере на экран выводится текстовое сообщение. Для этого используется функция 9 вызова ДОС, в соответствии с которой в регистрах DS: DX должен содержаться адрес текстовой строки, а сама строка должна заканчиваться символом $: . asm
jmp @NextCode{Обходим фрагмент данных}
@:
db ’Текстовая строка, 13,10, ’$’
@NextCode:
push
ds
{Сохраняем DS}
push
cs
pop
ds
(DS =
mov
dx,OFFSET 8
{DS:DX - адрес строки}
mov
ah, 9
(AH — код функции вывода)
int
21h
(Выводим строку)
pop
ds
(Восстанавливаем DS)
end;
Обратите внимание на использование регистра DS. В соответствии с требованиями функции 9, он должен содержать сегмент выводимой строки. В нашем случае строка располагается в кодовом сегменте, поэтому мы вынуждены сначала сохранить значение DS в стеке, а затем восстановить его. Если бы мы этого не сделали, по завершении ассемблерного оператора регистр DS указывал бы на сегмент кода, и была бы потеряна связь программы Турбо Паскаля с глобальными переменными и константами.
Ассемблерные подпрограммы
Ассемблерные подпрограммы — это процедуры и функции, объявленные директивой Assembler. В' таких подпрограммах исполняемая часть не содержит begin...end и состоит из единственного ассемблерного оператора asm. . .end. Например:
Function LongMul(X,Y:Integer):Longlnt; Assembler;
asm
mov ax, X
imul Y (DX/AXсодержат "длинный"результат)
end;,
При компиляции ассемблерных подпрограмм выполняется ряд оптимизаций кода, в том числе:
параметры-значения строкового типа, а также длиной в 1, 2 и 4 байта не копируются во временную память, т. е. внутри подпрограммы они считаются параметрами-переменными;
компилятор не создает переменную @Result для результата функции, и ссылка на эту переменную в ассемблерной функции недопустима; исключением являются функции, возвращающие значения строкового типа, для них ссылка на @Result допустима;
генерируются следующие команды на входе в подпрограмму и на ее вы-
ходе:
push
bp
{Сохраняется BP)
mov
bp, sp
(BP содержит текущую границу стека/
I sub
sp,Locals
{Резервируется часть стека для размещения локальных переменных}
mov
sp,bp
{Восстанавливается граница стека}
pop
bp
{Восстанавливается ВР)
ret
Params'
{Из стека удаляются параметры
подпрограммы и осуществляется выход из нее)
Здесь:
Locals — общая длина в байтах всех объявленных в подпрограмме локальных переменных;
Params — длина (в байтах) всех формальных параметров.
Если Locals и Params равны нулю, входной код не создается, а выходной содержит единственную инструкцию RET.
Все локальные переменные Турбо Паскаль размещает в стеке. Это относится как к обычным, так и к ассемблерным подпрограммам. Для ссылки на локальные переменные используется адресация по базе, задаваемой парой DS : ВР, поэтому при входе в процедуру всегда создается так называемый локальный стек: в регистр ВР помещается текущая граница стека, а сама эта граница смещается вверх на суммарную длину всех локальных переменных, чтобы работа со стеком внутри подпрограммы не разрушила локальные переменные. Например:
Procedure . . . .; Assembler;
var
X: Word;
Y: Byte; asm
mov X, ax {Компилируется в mov■ [ВР-2], ax) mov ah, Y {Компилируется в mov■ ah,[ВР-3]) end;
Ассемблерные функции должны следующим образом возвращать результат своей работы:
длиной 1 байт (Byte, char и т. п.) — в регистре al;
длиной 2 байта (integer, word) — в регистре АХ;
длиной 4 байта (pointer, Longlnt) — в регистрах DX (старшее слово) и АХ (младшее слово);•
типа Real — в регистрах DX, вх, АХ (старшее слово — в DX, младшее в АХ);
Вещественных ТИПОВ Single, Double, Extended, Comp — В регистре ST (0) сопроцессора;
П строкового типа — во временной области памяти, на которую ссылается @Result.
УУ
У
У
Глава 13
Работа с библиотекой CRT
Во многих случаях стандартные для Паскаля возможности ввода/вывода данных С ПОМОЩЬЮ Процедур Read, ReadLn, Write, WriteLn оказываются ЯВНО недостаточными для разработки удобных в использовании диалоговых программ. Например, процедуры Read/ReadLn вводят с клавиатуры только типизированные данные, причем с обязательным эхо-повтором набираемых символов на экране. С их помощью нельзя определить факт нажатия какой- либо специальной клавиши (функциональной клавиши, клавиши управления курсором и т. п.). Процедуры Write/writeLn выводят сообщения, начиная с того места на экране, где в данный момент находится курсор, причем по мере вывода курсор автоматически сдвигается на экране, а если очередной символ выводится в самом нижнем правом углу экрана, осуществляется "прокрутка" экрана: его содержимое сдвигается вверх на одну строку. Все это сильно затрудняет создание и обновление различного рода окон, меню и других атрибутов современных диалоговых программ.
Разработчики Турбо Паскаля предусмотрели несколько подпрограмм, существенно увеличивающих возможности текстового ввода/вывода. Эти подпрограммы сосредоточены в библиотеке (модуле) CRT, входящей в комплект поставки Турбо Паскаля. В модуль включены также процедуры sound, NoSound и Delay, которые позволяют программировать звуковой генератор ПК. В этой главе обсуждается использование подпрограмм модуля CRT.
Программирование клавиатуры
Дополнительные возможности управления клавиатурой реализуются двумя функциями: KeyPressed И ReadKey.
Функция Keypressed. Возвращает значение типа Boolean, указывающее состояние буфера клавиатуры: False означает, что буфер пуст, a True — что в буфере есть хотя бы один символ, еще не прочитанный программой.
В MS-DOS реализуется так называемый асинхронный буферизованный ввод с клавиатуры. По мере нажатия на клавиши соответствующие коды помещаются в особый буфер, откуда они могут быть затем прочитаны программой. Стандартная длина буфера рассчитана на хранение до 16 кодов символов. Если программа достаточно долго не обращается к клавиатуре, а пользователь нажимает клавиши, буфер может оказаться переполненным. В этот момент раздается звуковой сигнал и "лишние" коды теряются. Чтение из буфера обеспечивается процедурами Read/ReadLn и функцией ReadKey. Замечу, что обращение к функции KeyPressed не задерживает исполнения программы: функция немедленно анализирует буфер и возвращает то или иное значение, не дожидаясь нажатия клавиши.
Функция ReadKey. Возвращает значение типа char. При обращении к этой функции анализируется буфер клавиатуры: если в нем есть хотя бы один не прочитанный символ, код этого символа берется из буфера и возвращается в качестве значения функции, в противном случае функция будет ожидать нажатия на любую клавишу. Ввод символа с помощью этой функции не сопровождается эхо-повтором и содержимое экрана не меняется.
Пусть, например, в какой-то точке программы необходимо игнорировать все ранее нажатые клавиши, коды которых еще не прочитаны из буфера, т. е. необходимо очистить буфер. Этого можно достичь следующим способом:
Uses CRT;
var
С: Char;
begin
while KeyPressed do
С ReadKey;
end.
При использовании процедуры ReadKey необходимо учесть, что в клавиатурный буфер помещаются так называемые расширенные коды нажатых клавиш. Если нажимается любая алфавитно-цифровая клавиша, расширенный код совпадает с ASCII-кодом соответствующего символа. Например, если нажимается клавиша с латинской буквой а (в нижнем регистре), функция ReadKey возвращает значение chr(97), а если А (в верхнем регистре) — значение chr(65). При нажатии функциональных клавиш <F1>—<F10>, клавиш управления курсором, клавиш <Ins>, <Home>, <Del>, <End>, <PgUp>, <PgDn> в буфер помещается двухбайтная последовательность: сна-
чала символ #0, а затем расширенный код клавиши. Таким образом, значение #0, возвращаемое функцией ReadKey, необходимо только для того, чтобы указать программе на генерацию расширенного кода. Получив это значение, программа должна еще раз обратиться к функции, чтобы прочитать расширенный код клавиши2.
Следующая простая программа позволит вам определить расширенный код любой клавиши. Для завершения работы программы нажмите клавишу <Esc>.
Uses CRT;
var
С: Char;
begin
repeat
С := ReadKey;
if С<>#0 then
WriteLn(ord(С))
else
WriteLn('O',ord(ReadKey):8)
until C=I27 (27 - расширенный код клавиши Esc} end.
Если вы воспользуетесь ЭТОЙ программой, то обнаружите, что нажатие на некоторые клавиши игнорируется функцией ReadKey. Это прежде всего так называемые сдвиговые клавиши — <Shift>, <Ctrl>, <Alt>. Сдвиговые клавиши в MS-DOS обычно служат для переключения регистров клавиатуры и нажимаются в сочетании с другими клавишами. Именно таким способом, например, различается ввод прописных и строчных букв. Кроме того, функция игнорирует переключающие клавиши <Caps Lock>, <Num Lock>, <Scroll Lock>, а также "лишние" функциональные клавиши <F11> и <F12> клавиатуры IBM AT, не имеющие аналога на клавиатуре ранних моделей IBM PC/XT (в этих машинах использовалась 84-клавишная клавиатура, в то время как на IBM AT — 101-клавишная).
В табл. 13.1 приводятся расширенные коды клавиш, возвращаемые функцией ord (ReadKey). Для режима ввода кириллицы приводятся коды, соответствующие альтернативному варианту кодировки.
То есть код сканирования клавиши. Этот код определяется порядком, в соответствии с которым микропроцессор клавиатуры Intel 8042 периодически опрашивает (сканирует) состояние клавиш.
Таблица 13. 1. Расширенные коды клавиш
Код
Клавиша или комбинация клавиш
Код
Клавиша или комбинация клавиш
ПервыйВторой
ПервыйВторой
байтбайт
байтбайт
Алфавитно-цифровые клавиши
8
<Backspace>
9
<ТаЬ>
V
(Забой)
(Табуляция)
13
<Enter>
32
<Пробел>
33
<!>
34
<">
35
<#>
36
<$>
37
<%>
38
<&>
39
< '>
40
<(>
41
<)>
42
<*>
43
<+>
44
<,>
45
<->
46
<.>
47
</>
48-57
<0>—<9>
58
<:>
59-
<;>
60
«>
61
<=>
62
<»
63
<?>
64
<@>
65-90
<A>—<Z>
91
< [>
92
<\>
93
<]>
94
<~>
95
<_>
96
<'>
97-122
<a>-<z>
123
<{>
124
</>
125
<}>
126
<~>
128-159
<А>-<Я>
160-175
<a>—<п>
224-239
<p>—<я>
Таблица 13.1 (окончание)
Код
Клавиша или комбинация клавиш
Код
Клавиша или комбинация клавиш
Первый
Второй
Первый
Второй
байт
байт
байт
байт
Управляющие клавиши и их сочетания со сдвиговыми
0
3
<Ctrl>+<2>
0
15
<Shift>+<Tab>
0
16-25
<Alt>+<Q>—
<Alt>+<P> (верхнийряд букв)
0
30-38
<Alt>+<A>—
<Alt>+<L> (среднийряд букв)
0
44-50 .
<Alt>+<Z>-
<Alt>+<M> (нижний ряд букв)
0
59-68
<F1>-<F10>
0
71
<Ноте>
0
72
<t>
0
73
<PgUp>
0
75
«->
0
77
<-»
0
79
<End>
0
80
<4>
0
81
<PgDn>
0
82
<lns>
0
83
<Del>
0
84-93
<Shift>+<F1>—
<Shift>+<F10>
0
94-103
<Ctrl>+<F1>-
<Ctrl>+<F10>
0
104-113
<Alt>+<F1>—
<Alt>+<F10>
0
114
<Ctrl>+<PrtScr>
0
1.15
<Ctrl>+«-> '
0
116
<Ctrl>+<-»
0
117
<Ctrl>+<End>
0
118
<Ctrl>+<PgDn>
0
119
<Ctrl>+<Home>
0
120-131
<Alt>+<1>-
<Alt>+<=> (верхний ряд клавиш)
0
132
<Ctrl>+<PgUp>
-
Текстовый вывод на экран
Библиотека Turbo Vision соответствует самым высоким требованиям, и я настоятельно рекомендую обращаться к ней при программировании сложных текстовых изображений (меню, окон и т. п.). Тем не менее вполне возможно, что некоторые из читателей захотят использовать значительно более простые, но достаточно эффективные средства модуля CRT, описываемые в этом разделе.
Используемое в ПК устройство визуального отображения информации — дисплей — состоит из двух основных частей: монитора, содержащего экран (электронно-лучевую трубку или жидкокристаллическую панель) с необходимыми компонентами (устройствами развертки изображения), и блока управления, который чаще называют дисплейным адаптером или просто адаптером. Обычно оба устройства согласуются друг с другом, но в отдельных случаях этого согласования может не быть (например, цветной монитор может работать с монохромным адаптером и наоборот). Будем считать оба устройства согласованными, поэтому, упоминая о различных дисплеях, я буду говорить только о различных адаптерах, т. к. именно в них сосредоточены основные отличия дисплеев друг от друга.
Исторически первым адаптером (1981 г.), использованным на IBM PC, был так называемый монохромный адаптер (MDA). Его возможности были очень скромны, он позволял выводить только текстовые сообщения в одном из двух форматов: 25 строк по 40 или по 80 символов в строке. Символы выводились в прямом изображении (светлые символы на темном фоне), причем их ширина оставалась одинаковой в обоих режимах, поэтому при выводе в режиме 40x25 была задействована только левая половина экрана. В MDA применялись два символьных шрифта — обычный и с подчеркиванием.
В 1982 году фирма Hercules выпустила адаптер HGC (от англ. Hercules Graphics Card — графическая карта Геркулес), который полностью эмулировал MDA в текстовом режиме, но в отличие от него мог еще воспроизводить и графические изображения с разрешением 720x350 точек (пикселов).
Примерно в это же время IBM выпустила цветной графический адаптер CGA (Color Graphics Adapter) и впервые на экране ПК появился цвет. CGA позволял выводить как текстовые сообщения, так и графические изображения (с разрешением 320x200 или 640x200 пикселов). В текстовом режиме выводились 40x25 или 80x25 символов как в монохромном, так и в цветном изображениях. При работе в монохромном режиме символы, в отличие от MDA, не могли подчеркиваться, зато их можно было выводить в негативном изображении (черные символы на светлом фоне). При выводе в цветном режиме использовалось 16 цветов для символов и 8 — для окружающего их фона.
Текстовые возможности CGA стали стандартом де-факто и поддерживаются во всех последующих разработках IBM — адаптерах EGA, MCGA, VGA и
SVGA. Возможности модуля CRT рассматриваются применительно к адаптерам этого типа.
Процедура TextMode. Служит для задания одного из возможных текстовых режимов работы адаптера. Заголовок процедуры:
Procedure TextMode(Mode: Word);
Здесь Mode — код текстового режима. В качестве значения этого выражения могут использоваться следующие константы, определенные в модуле CRT:
const
BW40
=
(Черно-белыйрежим 40x25)
Со40
= 1;
(Цветной режим 40x25)
BW80
= 2;
(Черно-белыйрежим 80x25)
Со80
= 3;
(Цветной режим 80x25)
Mono
=
{Используется с MDA)
Font8x8
= 256;
{Используе тся для загружаемого шрифта
режиме 80x43 или 80x50 с адаптерами EGA или VGA)
Код режима, установленного с помощью вызова процедуры TextMode, запоминается в глобальной переменной LastMode модуля CRT и может использоваться для восстановления начального состояния экрана.
Следующая программа иллюстрирует действие этой процедуры в различных режимах. Замечу, что при вызове TextMode сбрасываются все ранее сделанные установки цвета и окон, экран очищается и курсор переводится в его левый верхний угол.
Uses CRT;
Procedure Print(S: String);
{Выводит сообщение S и ждет инициативы пользователя)
begin
WriteLn(S) ;{Выводим сообщение)
WriteLn('Нажмите клавишу Enter...');
ReadLn{ЖДем нажатия клавиши Enter)
end; (Print)
var
LM: Word;(Начальныйрежим экрана)
begin
LM := LastMode; (Запоминаем начальный режим работы дисплея)
TextMode(Co40);
Print('Режим 40x25');
TextMode(C08O);
Print('Режим 80x25');■
TextMode(Co40+Font8x8);
Print('Режим Co40+Font8x8');
TextMode(Co80+Font8x8);
Print('Режим Co80+Font8x8');
(Восстанавливаем исходный режим работы:)
TextMode(LM)
end.
/
Процедура TextColor. Определяет цвет выводимых символов. Заголовок процедуры:
Procedure TextColor(Color: Byte);
Процедура TextBackground. Определяет цвет фона. Заголовок:
Procedure TextBackground(Color: Byte);
Единственным параметром обращения к этим процедурам должно быть выражен ие типа Byte, задающее код нужного цвета. Этот код удобно определять с помощью следующих мнемонических констант, объявленных в модуле CRT:
const
Black=0;{Черный}
Blue=1;{Темно - синий}
Green=2;(Темно-зеленый}
Cyan3; {Бирюзовый}
Red=4;{Красный}
Magenta=5;{Фиолетовый}
Brown=6;{Коричневый}
LightGray=7;{Светло-серый}
DarkGray=8;{Темно -серый}
LightBlue=9;{Синий)
LightGreen=10;{Светло-зеленый)
LightCyan=11;{Светло-бирюзовый)
LightRed=12;(Розовый)
LightMagenta =13;(Малиновый)
Yellow=14;{Желтый)
White='15;(Белый)
Blink= 128; {Мерцание символа)
Следующая программа иллюстрирует цветовые возможности Турбо Паскаля. Uses CRT; const
Col: array [1..15] of String [16] = ('темно-синий', ' темно-зеленый1, ' бирюзовый’, ' красный ’, 1 фиолетовый1,1 коричневый1,1 светло-серый1,1 темно-серый’, 1 синий’,'зеленый','светло-бирюзовый','розовый', 'малиновый','желтый','белый'); var k: Byte;
begin for k := 1 to 15 dot
begin(Выводим 15 сообщений различными цветами}
Textcolor(к);
WriteLn('Цвет ', к, ' -'
end; TextColor(White+Blink); {Белые мигающие символы} WriteLn('Мерцание символов'); {Восстанавливаем стандартный цвет} TextColor(LightGray);,
WriteLn end.
Обратите внимание на последний оператор writeLn: если его убрать, режим мерцания символов сохранится после завершения программы, несмотря на ТО, ЧТО перед НИМ СТОИТ оператор TextColor (LightGray) .
Дело в том, что все цветовые определения предварительно заносятся в специальную переменную TextAttr модуля CRT и используются для настройки адаптера только при обращении к процедурам write/WriteLn.
Процедура ClrScr. Очищает экран или окно (см. ниже процедуру window). После обращения к ней экран (окно) заполняется цветом фона, и курсор устанавливается в его левый верхний угол. Например:
Uses CRT; var
С: Char begin
TextBackground(red);
{Заполняем экран красным цветом){Заполняем экран красным цветом)CirScr;
{Заполняем экран красным цветом)
{Заполняем экран красным цветом)
WriteLn ('Нажмите любую клавишу. ■ - ');
С := ReadKey;{ЖЛем нажатия любой клавиши)
TextBackground(Black);
ClrScr .{Восстанавливаем черный фон экрана)
end.
Процедура window. Определяет текстовое окно — область экрана, которая в дальнейшем будет рассматриваться процедурами вывода как весь экран. Сразу после вызова процедуры курсор помещается в левый верхний угол окна, а само окно очищается (заполняется цветом фона). По мере вывода курсор, как обычно, смещается вправо и при достижении правой границы окна переходит на новую строку, а если он к этому моменту находился на последней строке, содержимое окна сдвигается вверх на одну строку, т. е. осуществляется "прокрутка" окна. Заголовок процедуры:
Procedure Window(XI,Y1,X2,Y2: Byte);
Здесь XI, Yl, X2, Y2 — координаты левого верхнего и правого нижнего углов окна. Они задаются в координатах экрана, причем левый верхний угол экрана имеет координаты (1, 1), горизонтальная координата увеличивается слева направо, а вертикальная — сверху вниз.
В следующем примере иллюстрируется вывод достаточно длинного сообщения в двух разных окнах.
Uses CRT;
var
k: integer;
begin
{Создаем левое окно - желтые символы на синем фоне:}
TextBackground(Blue);
Window(5,2,35,17);
TextColor(Yellow) ;
for k := 1 to 100 do
Write (' Нажмите клавишу Enter...');
ReadLn;(Ждемнажатия Enter)
ClrScr;{Очищаем окно)
{Создаем правое окно - белые символы на красном фоне:)
TextBackground(Red) ;
TextColor(White);
Window(40,2,70,17);
for k1 to 100 do
Write ( ' Нажмите клавишу Enter; . . 1);
11 Зак 941
ReadLn;
TextMode(COSO){Сбрасываем все установки)
end.
Обращение к процедуре window игнорируется, если какая-либо из координат выходит за границы экрана или если нарушается одно из условий: X2>X1 и Y2>Y1. Каждое новое обращение к window отменяет предыдущее определение окна. Границы текущего окна запоминаются в двух глобальных переменных модуля CRT: переменная WindMin типа word хранит XI и Y1 (XI — в младшем байте), а переменная того же типа windMax — Х2 и Y2 (х2 — в младшем байте). При желании вы можете изменять их нужным образом без обращения к window. Например, вместо оператора
Window(40,2,70,17);
можно было бы использовать два оператора
WindMin := 39+ (1 shl 8);■
WindMax := 69+(16 shl 8);
(в отличие от обращения к window координаты, хранящиеся в переменных windMin и windMax, соответствуют началу отсчета (О, 0)).
Процедура GotoXY. Переводит курсор в нужное место экрана или текущего окна. Заголовок процедуры:
Procedure GotoXY(X,Y: Byte);
Здесь х, Y — новые координаты курсора. Координаты задаются относительно границ экрана (окна), т. е. оператор
GotoXY(1,1);
означает указание перевести курсор в левый верхний угол экрана (или окна, если к этому моменту на экране определено окно). Обращение к процедуре игнорируется, если новые координаты выходят за границы экрана (окна).
Функции WhereX и whereY. С помощью этих функций типа Byte можно определить текущие координаты курсора: WhereX возвращает его горизонтальную, a whereY — вертикальную координату.
В следующей программе сначала в центре экрана создается окно, которое обводится рамкой, затем в окне выводится таблица из двух колонок.
Uses CRT;
const
LU = tt218; (Левый верхний угол рамки)
RU = #191; (Правый верхний угол)
LD = #192; (Левый нижний)
RD = #217;{Правый нижний)
Н = #196;
V =
= 14;
Y1 = 5;
Х2 = 66;
Y2 =
Txt =клавишу
var k: г : ,•.,
begin
ClrScr; окно в центре экрана - желтые символы на синем
TextBackground(Blue);
TextColor(Yellow);
Window(XI,Y1,X2,Y2);
ClrScr; окно верхний угол) (Горизонтальная линия} for k:=to X2-1 do
Write (RU);(Верхнийправый угол}
for k := to Y2-1 do
begin
к левой
Write(V);(Левая черта}
GotoXY (X2-Xl+l,WhereY);{Правая граница}
Write (V){Правая черта}
end; нижний
Window(XI,Yl,Х2,Y2+1);(Расширяем вниз на одну
строку координаты окна, иначе вывод в правый нижний угол вызовет прокрутку окна
нижнийнижнийGotoXY (2,Y2-Y1+1); {Возвращаем курсор из левого верхнего угла окна на нужное (Горизонтальная рамка} for k:= Xl+1 to X2-1 do
нижний
нижний
Write(RD);
{Определяемвнутреннюю часть окна}
Window(X1+1,Y1+1,X2-1,Y2-1) ;
(Выводимлевый столбец):
for k := Y1 + 1 to Y2-2 do
WriteLn('Левый столбец, строка ',k-Yl);
(Ждем нажатия любой клавиши)
Write V ' Нажмите любую клавишу. .. ');
k := ord(ReadKey) ;
if k=0 then
k : = ord(ReadKey);■
DelLine;{Стираем приглашение I
{Выводим правый столбец}
for kYl+1 to Y2-2 do
.
begin
GotoXY((Х2-Х1) div 2,k-Yl);
Write('Правый столбец, строка ',k-Yl) end;
{Выводим сообщение и ждем нажатия клавиши Enter)
GotoXY((X2-Xl-Length(Txt)) div 2,Y2-Y1-1);
TextColor(White);
Write(Txt);
ReadLn;
{Восстанавливаем стандартный режим}
TextMode(COSO)
end.
Три следующие процедуры без параметров могут оказаться полезными при разработке текстовых редакторов.
Процедура ClrEOL. Стирает часть строки от текущего положения курсора до правой границы окна (экрана). Положение курсора не меняется.
Процедура DelLine. Уничтожает ВСЮ строку с курсором в текущем окне (или на экране, если окно не создано). При этом все строки ниже удаляемой (если они есть) сдвигаются вверх на одну строку.
Процедура InsLine. Вставляет строку. Строка с курсором и все строки ниже ее сдвигаются вниз на одну строку; строка, вышедшая за нижнюю границу окна (экрана), безвозвратно теряется; текущее положение курсора не
меняется.
Процедуры LowVideo, NormVideo И HighVideo. С ПОМОЩЬЮ ЭТИХ Процедур без параметров можно устанавливать соответственно пониженную, нормальную и повышенную яркость символов. Например:
Uses CRT;
begin
LowVideo;
WriteLn('Пониженная яркость');
NormVideo;
WriteLn('Нормальная яркость');
HighVideo;
WriteLn('Повышенная яркость')
end.
Замечу, что на практике нет разницы между пониженной и нормальной яркостью изображения.
Процедура AssignCRT. Связывает текстовую файловую переменную F с экраном с помощью непосредственного обращения к видеопамяти (т. е. к памяти, используемой адаптером для создания изображения на экране). В результате вывод в такой текстовый файл осуществляется значительно (в 3— 5 раз) быстрее, чем если бы этот файл был связан с экраном стандартной процедурой Assign. Заголовок процедуры:
Procedure AssignCRT(F: Text);
В следующей программе измеряется скорость вывода на экран с помощью стандартной файловой процедуры и с помощью непосредственного обращения к видеопамяти. Вначале файловая переменная F связывается "медленной" процедурой Assign со стандартным устройством CON (т. е. с экраном) и подсчитывается количество N1 циклов вывода некоторого текста за 5 х 55 = = 275 миллисекунд системных часов. Затем файловая переменная связывается с экраном с помощью процедуры быстрого доступа AssignCRT и точно так же подсчитывается количество N2 циклов вывода. В конце программы счетчики N1 и N2 выводятся на экран.
Замечу, что показания системных часов хранятся в оперативной памяти компьютера в виде четырехбайтного слова по адресу [$0040:$006C] и наращиваются на единицу каждые 55 миллисекунд.
Uses CRT;
var
F: Text;
t: Longlnt;{Начало отсчета времени)
N1,N2: Word;{Счетчики вывода)
const
txt = ' Text';
begin
{Стандартный вывод в файл )
Assign(F,'CON');
Rewrite(F);
N1 := 0;{Готовим счетчик вывода}
IIClrScr;{Очищаем экран}
I
I
{Запоминаем начальный момент:}
t := MemL[$0040:$006С];
{Ждем начала нового 5 5-мс интервала, чтобы
ИСКЛЮЧИТЬ погрешность в определении времени:)
while MemL[$0040:$006C]=t do;
{Цикл вывода за 5 интервалов)
while MemL[$0040:$006C]<t+6 do
begin
inc (Nl);
Write(F,txt)
end;
Close(F);
{■ Вывод с помощью быстрой процедуры
прямого доступа к экрану)
AssignCRT(F);
Rewrite(F);
N20;
ClrScr;
t := MemL[$0040:$006C];
while MemL[$0040:$006C]=t do;
while MemL[$0040:$006C]<t+6 do
begin
inc(N2);
Write(F,txt)
end;
Close(F);
{Пе ча та ем резуль тат)
ClrScr;
WriteLn(Nl,N2:10)
end.
Следует учесть, что вывод на экран обычным образом без использования файловой переменной (например, оператором write (txt)) также осуществляется с помощью непосредственного доступа к видеопамяти, поэтому ценность процедуры AssignCRT весьма сомнительна. Прямой доступ к видеопамяти регулируется глобальной логической переменной DirectVideo модуля CRT: если эта переменная имеет значение True, доступ разрешен, если False — доступ к экрану осуществляется с помощью относительно медленных средств операционной системы MS-DOS. По умолчанию переменная DirectVideo имеет значение True.
•
13.3. Программирование звукового генератора
Звуковые возможности ПК (не оснащенного специальной звуковой картой) основаны на одноканальном управляемом звуковом генераторе, вырабатывающем электромагнитные колебания звуковой частоты. Колебания подаются на встроенный в ПК динамик и заставляют его звучать.
В модуль CRT включены три процедуры, с помощью которых вы сможете запрограммировать произвольную последовательность звуков.
Процедура Sound. Заставляет динамик звучать с нужной частотой. Заголовок процедуры:
Procedure Sound(F: Word);
Здесь F — выражение типа word, определяющее частоту звука в герцах. После обращения к процедуре включается динамик, и управление немедленно возвращается в основную программу, в то время как динамик будет звучать впредь до вызова процедуры NoSound.
Процедура NoSound. Выключает динамик. Если он к этому моменту не был включен, вызов процедуры игнорируется.
Процедура Delay. Обеспечивает задержку работы программы на заданный интервал времени. Заголовок процедуры:
Procedure Delay(T: Word);
Здесь т — выражение типа word, определяющее интервал времени (в миллисекундах), в течение которого задерживается выполнение следующего оператора программы.
Для генерации звукового сигнала обычно используется вызов описанных процедур по схеме Sound—Delay—NoSound. Следующая программа заставит ПК воспроизвести простую музыкальную гамму. Массив F содержит частоты всех полутонов в первой октаве от "до" до "си". При переходе от одной октавы к соседней частоты изменяются в два раза.
Uses CRT;
const
F: array [1..12] of Real =
(130.8, 138.6, 146.8, 155.6, 164.8,
185.0, 196.0, 207.7, 220.0,
246.9);{Массив частот 1-й октавы}
Temp = 100;{Темп исполнения}
var
к, п: Integer;
begin
{Восходящая гамма}
for k := 0 to 3 do
for п := 1 to 12 do
begin
Sound(Round(F[n]*(1 shl к)));
Delay(Temp);
NoSound
end;
{Нисходящая гамма}
for k := 3 downto 0 do
for n := 12 downto 1 do
begin
Sound(Round(F[n]*(1 shl k)));
Delay(Temp);
NoSound
end
end.
Глава 14
Применение библиотеки GRAPH
Начиная с версии 4.0, в состав Турбо Паскаля включена мощная библиотека графических подпрограмм Graph, остающаяся практически неизменной во всех последующих версиях. Библиотека содержит в общей сложности более 50 процедур и функций, предоставляющих программисту самые разнообразные возможности управления графическим экраном. Для облегчения знакомства с библиотекой все входящие в нее процедуры и функции сгруппированы по функциональному принципу.
Примечание
Материал этой главы был написан в 1989 г., когда я писал свою первую книгу по Турбо Паскалю. Так как с тех пор библиотека Graph не изменялась, я оставил эту главу без изменения.
Переход в графический режим и возврат в текстовый
Стандартное состояние ПК после его включения, а также к моменту запуска программы из среды Турбо Паскаля соответствует работе экрана в текстовом режиме, поэтому любая программа, использующая графические средства компьютера, должна определенным образом инициировать графический режим работы дисплейного адаптера. После завершения работы программы ПК возвращается в текстовый режим.
Краткая характеристика графических режимов работы дисплейных адаптеров
Настройка графических процедур на работу с конкретным адаптером достигается за счет подключения нужного графического драйвера. Драйвер — это специальная программа, осуществляющая управление теми или иными техническими средствами ПК. Графический драйвер, как легко догадаться,
управляет дисплейным адаптером в графическом режиме. Графические драйверы разработаны фирмой Borland практически для всех типов адаптеров. Обычно они располагаются на диске в отдельном подкаталоге BGI в виде файлов с расширением BGI (от англ. Borland Graphics Interface — графический интерфейс фирмы Borland). Например, CGA.BGI — драйвер для CGA-адаптера, EGAVGA.BGI — драйвер для адаптеров EGA и VGA и т. п.
Выпускаемые в настоящее время ПК оснащаются адаптерами, разработанными фирмой IBM, или совместимыми с ними. Если не учитывать уже упоминавшийся в главе 13 монохромный адаптер MDA, все они имеют возможность работы в графическом режиме. В этом режиме экран дисплея рассматривается как совокупность очень близко расположенных точек — пикселов, светимостью которых можно управлять с помощью программы.
Графические возможности конкретного адаптера определяются разрешением экрана, т. е. общим количеством пикселов, а также количеством цветов (оттенков), которыми может светиться любой из них. Кроме того, многие адаптеры могут работать с несколькими графическими страницами. Графической страницей называют область оперативной памяти, используемую для создания "карты" экрана, т. е. содержащую информацию о светимости (цвете) каждого пиксела. Ниже приводится краткая характеристика графических режимов работы наиболее распространенных адаптеров.
Адаптер CGA (Color Graphics Adapter — цветной графический адаптер) имеет 5 графических режимов. Четыре режима соответствуют низкбй разрешающей способности экрана (320 пикселов по горизонтали и 200 по вертикали, т. е. 320x200) и отличаются только набором допустимых цветов — палитрой. Каждая палитра состоит из трех цветов, а с учетом черного цвета несветящегося пиксела — из четырех: палитра 0 (светло-зеленый, розовый, желтый), палитра 1 (светло-бирюзовый, малиновый, белый), палитра 2 (зеленый, красный, коричневый) и палитра 3 (бирюзовый, фиолетовый, светло-серый). Пятый режим соответствует высокому разрешению 640x200, но каждый пиксел в этом случае может светиться либо каким-то одним заранее выбранным и одинаковым для всех пикселов цветом, либо не светиться вовсе, т. е. палитра этого режима содержит два цвета. В графическом режиме адаптер CGA работает только с одной страницей.
Адаптер EGA (Enhanced Graphics Adapter — усиленный графический адаптер) может полностью эмулировать графические режимы адаптера CGA. Кроме того, в нем возможны режимы: низкого разрешения (640x200, 16 цветов, 4 страницы) и высокого разрешения (640x350, 16 цветов, 1 страница). В некоторых модификациях есть также монохромный режим (640x350, 1 страница, 2 цвета).
Адаптер MCGA (Multi-Color Graphics Adapter — многоцветный графический адаптер) совместим1 с CGA и имеет еще один режим — 640x480, 2 цвета, 1 страница. Такими адаптерами оснащались младшие модели серии ПК PS/2
фирмы IBM. Старшие модели этой серии оснащаются более совершенными адаптерами VGA (Video Graphics Array — графический видеомассив). Адаптер VGA эмулирует режимы адаптеров CGA и EGA и дополняет их режимом высокого разрешения (640x480, 16 цветов, 1 страница).
Не так давно появились так называемые cynep-VGA-адаптеры (SVGA) с разрешением 800x600 и более, использующие 256 и более цветовых оттенков. В настоящее время эти адаптеры получили повсеместное распространение, однако в библиотеке Graph для них нет драйверов. Поскольку SVGA совместимы с VGA, для управления современными графическими адаптерами приходится использовать драйвер EGAVGA.BGI и довольствоваться его относительно скромными возможностями.
Несколько особняком стоят достаточно популярные адаптеры фирмы Hercules. Адаптер HGC имеет разрешение 720x348, его пикселы могут светиться одним цветом (обычно светло-коричневым) или не светиться вовсе, т. е. это монохромный адаптер. Адаптер HGC+ отличается несущественными усовершенствованиями, а адаптер HICC (Hercules In Color Card) представляет собой 16-цветный вариант HGC+.
Процедуры и функции
Процедура initGraph. Инициализирует графический режим работы адаптера. Заголовок процедуры:
Procedure InitGraph(var Driver, Mode: Integer; Path: String);
Здесь:
О Driver — переменная типа integer, определяет тип графического драйвера;
О Mode — переменная того же типа, задающая режим работы графического адаптера;
О Path — выражение типа string, содержащее имя файла драйвера и, возможно, маршрут его поиска.
К моменту вызова процедуры на одном из дисковых носителей информации должен находиться файл, содержащий нужный графический драйвер. Процедура загружает этот драйвер в оперативную память и переводит адаптер в графический режим работы. Тип драйвера должен соответствовать типу графического адаптера. Для указания типа драйвера в модуле предопределены следующие константы:
const
Detect = 0; {Режим автоопределения типа)
CGA = 1;
MCGA = 2;
EGA = 3;
EGA64 =
EGAMono = 5;
IBM8514 = 6;
HercMono = 7;
ATT400 = 8;
VGA = 9;
PC3270=10;
Большинство адаптеров могут работать в различных режимах. Для того чтобы указать адаптеру требуемый режим работы, используется переменная Mode, значением которой в момент обращения к процедуре могут быть такие константы:
const
{Адаптер
CGA: }
CGACO
= 0; {Низкое разрешение, палитра 0}
CGAC1
= 1; {Низкое разрешение, палитра 1}
CGAC2
2; {Низкоеразрешение, палитра 2}
CGAC3
= 3; {Низкоеразрешение, палитра 3)
CGAHi
= 4; {Высокое разрешение}
{Адаптер
MCGA:}
MCGACO
= 0; {Эмуляция CGAC0}
MCGAC1
1; /Эмуляция CGAC1}
MCGAC2
= 2; {Эмуляция CGAC2}
MCGAC3
= 3; (Эмуляция CGAC3}
MCGAMed
=
MCGAHi=5;(640x480)
(Адаптер EGA:}
EGALo=16цветов}
EGAHi=1;(640x350,16цветов}
EGAMonoHi =3;(640x350,2 цвета)
{Адаптеры HGC и HGC+:)
HercMonoHi = 0; (720x348) {Адаптер ATT400:}
АТТ400СО=0;{ Аналог режимаCGACO)
АТТ400С1=1;{Аналог режимаCGAC1}
АТТ400С2=2;{АналогрежимаCGAC2)
АТТ400СЗ3; {Аналогрежима CGAC3,}
ATT400Med =режима CGAHi)
ATT400Hi=5;{640x400, 2 цвета)
(Адаптер VGA:}
VGALo=0;{640x200}
VGAMed=1;(640x350)
VGAHi=2;{640x480}
PC3270Hi=
{Адаптер IBM8514)
IBM8514LO = 0; (640x480, 256 цветов)
IBM8514H1 = 1; {1024x768, 256 цветов)
Пусть, например, драйвер CGA.BGI находится в каталоге TP\BGI на диске С: и устанавливается режим работы 320x200 с палитрой 2. Тогда обращение к процедуре будет таким:
Uses Graph;
var
Driver, Mode : Integer;
begin
Driver := CGA;(Драйвер)
Mode:= CGAC2;{Режимработы)
InitGraph(Driver, Mode, 'C:\TP\BGI');
Если тип адаптера ПК неизвестен или если программа рассчитана на работу с любым адаптером, используется обращение к процедуре с требованием автоматического определения типа драйвера:
DriverDetect;
InitGraph(Driver, Mode, 'C:\TP\BGI');
После такого обращения устанавливается графический режим работы экрана, а при выходе из процедуры переменные Driver и Mode содержат целочисленные значения, определяющие тип драйвера и режим его работы. При этом для адаптеров, способных работать в нескольких режимах, выбирается старший режим, т. е. тот, что закодирован максимальной цифрой. Так, при работе с CGA-адаптером обращение к процедуре со значением Driver = Detect вернет в переменной Driver значение 1 (CGA) и в Mode — значение 4 (CGAHi), а такое же обращение к адаптеру VGA вернет Driver = 9 (VGA) и Mode = 2 (VGAHi).
Функция GraphResult. Возвращает значение типа integer, в котором закодирован результат последнего обращения к графическим процедурам. Если
ошибка не обнаружена, значением функции будет ноль, в противном случае — отрицательное число, имеющее следующий смысл:
const
grOk
= 0;
(Нет ошибок}
grInitGraph
=-l;
{He инициализирован графический режим)
grNotDetected
=-2;
(He определен тип драйвера} '
grFileNotFind
=-3;
{Не найден графический драйвер)
grlnvalidDriver
=-4;
(Неправильный тип драйвера}
grNoLoadMem
=-5;
{Нет памяти для размещения драйвера)
grNoScanMem
=-6;
{Нет памяти для просмотра областей)
grNoFloodMem
=-7;
{Нет памяти для закраски областей}
grFontNotFound
=-8;
(Не найден файл со шрифтом)
grNoFontMem
=- 9;
{Нет памяти для размещения шрифта}
grlnvalidMode
=-10;
{Неправильный графический режим)
grError
=-11;
(Общая ошибка)
grIOError
=-12;
{Ошибка ввода/вывода/
grlnvalidFont
=-13;
{Неправильный формат шрифта)
grInvalidFontNum=-14;
(Неправильный номер дрифта/
После обращения к функции GraphResult признак ошибки сбрасывается, поэтому повторное обращение к ней вернет ноль.
Функция GraphErrorMsg. Возвращает значение типа string, в котором по указанному коду ошибки дается соответствующее текстовое сообщение. Заголовок функции:
Function GraphErrorMsg (Code: Integer) : String;
Здесь code — код ошибки, возвращаемый функцией GraphResuit.
Например, типичная последовательность операторов для инициации графического режима с автоматическим определением типа драйвера и установкой максимального разрешения имеет следующий вид: var
Driver, Mode, Error : Integer;
begin
Driver := Detect;{Автоопределение драйвера}
InitGraph(Driver, Mode, '');(Инициируемграфику)
Error := GraphResuit;{ Получа ем резуль тат)
if Error <> grOk then{Проверяем ошибку)
beginв процедуре
WriteLn(GraphErrorMsg(Error));{Выводим сообщение}
end
else(Нет ошибки}
Чаще всего причиной возникновения ошибки при обращении к процедуре является неправильное указание местоположения файла с драйвером графического адаптера (например, файла CGA.BGI для адаптера CGA). Настройка на местоположение драйвера осуществляется заданием маршрута поиска нужного файла в имени драйвера при вызове процедуры initGraph. Если, например, драйвер зарегистрирован в подкаталоге DRIVERS каталога PASCAL на дискето нужно использовать вызов:
Mode,
^'Примечание
Во всех следующих примерах процедура InitGraph вызывается с параметром Driver в виде пустой строки. Такая форма обращения будет корректна только в том случае, когда нужный файл графического драйвера находится в текущем каталоге. Для упрощения повторения примеров скопируйтесоответст
вующий адаптеру вашего ПК, в текущий каталог.
Процедура closeGraph. Завершает работу адаптера в графическом режиме и восстанавливает текстовый режим работы экрана. Заголовок:
Procedure CloseGraph;
ПроцедураСлужит для кратковременного возврата в тек
стовый режим. В отличие от процедуры CloseGraph, не сбрасываются установленные параметры графического режима и не освобождается память, выделенная для размещения графического драйвера. Заголовок:
Procedure
ФункцияВозвращает значение типа integer, в котором
содержится код установленного режима работы графическогоЗа
головок:
Function
ПроцедураУстанавливает новый графический режим работы
адаптера. Заголовок:
Procedure
Здесь Mode — код устанавливаемого режима.
Следующая профамма (пример 14.1) иллюстрирует переход из графического режима в текстовый и обратно.
Tlh./i.;../<«« ;K xtivKvfJfJk;!.. •J?iw^^^^Wm^№^^^^^^^W^www^rafl^ttwT.v .. .<7. i Л iX« пй..; .'гл'; j; Л.-.Л.. i... Г.. X.. rA.........{.,...'.. .4/..... •
Uses Graph;
var
Driver, Mode, Error : Integer;
begin
(Инициализируем графический режим)
Driver := Detect;
InitGraph(Driver, Mode, ");
Error := GraphResult;{Запоминаем резуль тат)
if Error <> grOk then{Проверяем ошибку)
WriteLn(GraphErrorMsg(Error)) (Есть ошибка) else
begin{Нет ошибки)
WriteLn ('Это графический режим');
WriteLn ('Нажмите "Enter"...':20);
ReadLn;
{Переходим в текстовый режим)
RestoreCRTMode;
WriteLn (’ А это текстовый...');
ReadLn;
{Возвраща емся в графический режим)
SetGraphMode (GetGraphMode);
WriteLn ('Опять графический режим...');
ReadLn;
CloseGraph
end
end.
В этом примере для вывода сообщений как в графическом, так и в текстовом режиме применяется стандартная процедура WriteLn. Если ваш ПК оснащен нерусифицированным адаптером CGA, вывод кириллицы в фафическом режиме таким способом невозможен, в этом случае замените соответствующие сообщения так, чтобы использовать только латинские буквы.
Процедура DetectGraph. Возвращает тип драйвера и режим его работы. Заголовок:> ''
Procedure DetectGraph(var Driver, Mode: Integer);
Здесь:
О Driver — тип драйвера;
□ Mode — режим работы.
В отличие от функции GetGraphMode, описываемая процедура возвращает в переменной Mode максимально возможный для данного адаптера номер графического режима.
Функция GetDriverName. Возвращает значение типа string, содержащее имя загруженного графического драйвера. Заголовок:
Function GetDriverName: String;
Функция GetMaxMode. Возвращает значение типа integer, содержащее количество возможных режимов работы адаптера. Заголовок:
Function GetMaxMode: Integer;
Функция GetModeName. Возвращает значение типа String, содержащее разрешение экрана и имя режима работы адаптера по его номеру. Заголовок:
Function GetModName(ModNumber: Integer): String;
Здесь ModNumber — номер режима.
Следующая программа (пример 14.2) после инициализации графического режима выводит на экран строку, содержащую имя загруженного драйвера, а также все возможные режимы его работы.
Uses Graph;
var
a, b: Integer;
begin
a := Detect;
InitGraph(a, b, ");
WriteLn(GetDriverName);
for a := 0 to GetMaxMode do
WriteLn(GetModeName(a):10);
ReadLn;
CloseGraph
end.
Процедура GetModeRange. Возвращает диапазон возможных режимов работы заданного графического адаптера. Заголовок:
Procedure GetModeRange(Drv: Integer; var Min, Max: Integer);
Здесь:
Drv — тип адаптера;
Min — переменная типа integer, в которой возвращается нижнее возможное значение номера режима;
мах — переменная того же типа, верхнее значение номера.
Если задано неправильное значение параметра Drv, процедура вернет в обеих переменных значение ”1. Перед обращением к процедуре можно не устанавливать графический режим работы экрана. Следующая профамма выводит на экран названия всех адаптеров и диапазоны возможных номеров режимов их работы.
Uses Graph;
var
D,L,H: Integer;
const
N: array [1..11] of String [8] =
('CGA','MCGA','EGA',
'EGA64','EGAMono',ЧВМ8514',
'HercMono','ATT400','VGA',
'PC3270','Ошибка');
begin
WriteLn('АдаптерМин.Макс.');
for D := 1 to 11 do
begin
GetModeRange(D, L, H);
WriteLn(N[DJ, L:7, H:10)
end end.
Координаты, окна, страницы
Многие графические процедуры и функции используют указатель текущей позиции на экране, который в отличие от текстового курсора невидим. Положение этого указателя, как и вообще любая координата на фафическом экране, задается относительно левого верхнего угла, который, в свою оче-
редь, имеет координаты (0, 0). Таким образом, горизонтальная координата экрана увеличивается слева направо, а вертикальная — сверху вниз.
Функции GetMaxX и GetMaxY. Возвращают значения типа word, содержащие максимальные координаты экрана в текущем режиме работы соответственно по горизонтали и вертикали. Например:
Uses Graph;
var
a,b: Integer;
begin
a := Detect;
InitGraph(a, b, ");
WriteLn(GetMaxX, GetMaxY:5);
ReadLn; s
CloseGraph
end.
Функции GetX и GetY. Возвращают значения типа integer, содержащие текущие координаты указателя соответственно по горизонтали и вертикали. Координаты определяются относительно левого верхнего угла окна или (если окно не установлено) экрана.
Процедура setviewPort. Устанавливает прямоугольное окно на графическом экране. Заголовок:
Procedure SetViewPort(XI,Y1,X2,Y2: Integer; ClipOn: Boolean);
Здесь:
XI, Yl, X2, Y2 — координаты левого верхнего и правого нижнего углов окна;
ClipOn — выражение типа Boolean, определяющее "отсечку" не умещающихся в окне элементов изображения.
Координаты окна всегда задаются относительно левого верхнего угла экрана. Если параметр ClipOn имеет значение True, элементы изображения, не умещающиеся в пределах окна, отсекаются, в противном случае границы окна игнорируются. Для управления этим параметром можно использовать такие определенные в модуле константы:
const
ClipOn = True; {Включить отсечку}
ClipOff = False;(He включать отсечку)
Пример 14.3 иллюстрирует действие параметра ciipOn. Программа строит два прямоугольных окна с разными значениями параметра и выводит в них несколько окружностей. Для большей наглядности окна обводятся рамками (рис. 14.1).
Uses Graph,CRT;
var
x, у, e: Integer;
xll,yll,xl2,yl2,{Координаты 1-го окна)
x21,x22,(Левый верхний угол 2-го}
R,(Начальный радиус}
k: Integer;
begin
DirectVideo := False;{Блокируемпрямой доступ
к видеопамяти в модуле CRT)
{Инициализируем графический режим)
х := Detect;
InitGraph(x, у, '') ;
(Проверяем результат)
e GraphResult;
if e <> grOk then WriteLn(GraphErrorMsg(е) ;{Ошибка}
else
begin,(Нет ошибки)
{Вычисляем координаты с учетом разрешения экрана}
Х11 :=
х12 : = GetMaxX div 3;
yll := GetMaxY div 4;
y!2 :=
R := (xl2-xll) div 4;
x21 := xl2*2;
x22 := x21+x!2-xll;
{Рисуем окна}
WriteLn('ClipOn:' :10,'ClipOff:':40);
Rectangle (xll, yll, xl2, yl2);
Rectangle(x21, yll, x22, У12) ;
{Назначаем 1-е окно и рисуем четыре окружности)
SetViewPort(xll, yll, xl2, yl2, ClipOn);
for k := 1 to 4 do
Circle(0,yll,R*k);
{Назначаем 2-е окно и рисуем окружности}
SetViewPort(x21, yll, x22, yl2, ClipOff);
for k 1 to 4 do
Circle(0,yll,R*k) ;
{Ждем нажатия любой клавиши)
if ReadKey=#0 then k := ord(ReadKey);
CloseGraph
end
end.
Процедура GetviewSettings. Возвращает координаты и признак отсечки текущего графического окна. Заголовок:
Procedure GetViewSettings(var Viewinfo: ViewPortType);
Здесь Viewinfo — переменная типа ViewPortType. Этот тип в модуле Graph определен следующим образом:
type
ViewPortType = record
xl,yl,x2,y2: Integer; {Координа ты окна)
Clip: Boolean {Признак отсечки)
end;
Процедура MoveTo. Устанавливает новое текущее положение указателя. Заголовок:
Procedure MoveTo(X, Y: Integer) ;
Здесь х, Y — новые координаты указателя соответственно по горизонтали и вертикали.
Координаты определяются относительно левого верхнего угла окна или (если окно не установлено) экрана.
Процедура MoveRei. Устанавливает новое положение указателя в относительных координатах.
Procedure MoveRel(DX, DY: Integer);
Здесь DX, DY — приращения новых координат указателя соответственно по горизонтали и вертикали.
Приращения задаются относительно того положения, которое занимал указатель к моменту обращения к процедуре.
Процедура ClearDevice. Очищает графический экран. После обращения к процедуре указатель устанавливается в левый верхний угол экрана, а сам экран заполняется цветом фона, заданным процедурой SetBkColor. Заголовок:
Procedure CiearDevice;
Процедура ClearViewPort. Очищает графическое окно, а если окно не определено к этому моменту — весь экран. При очистке окно заполняется цветом с номером 0 из текущей палитры. Указатель перемещается в левый верхний угол окна. Заголовок:
Procedure ClearViewPort;
В следующей программе (пример 14.4) на экране создается окно, которое затем заполняется случайными окружностями (рис. 14.2). После нажатия на любую клавишу окно очищается. Для выхода из программы нажмите <Enter>..
Рис. 14.2. Окно со случайными окружностями
Uses CRT,Graph;
var
xl,yl,x2,y2,Err: Integer;
begin
{Инициализируем графический режим}
xl := Detect;
InitGraph (xl, x2, ' ');
ErrGraphResult;
if ErrOgrOk then
WriteLn(GraphErrorMsg(Err))
else
begin
{Определяем координаты окна
с учетом разрешения экрана}
xl := GetMaxX div 4;
yl : = GetMaxY div 4;
x2 := 3*xl;
y2 := 3*yl;
(Создаем окно}
Rectangle(xl,yl,x2,y2);
SetViewPort(xl+1,yl+l,x2-l,y2-l,ClipOn);,
{Заполняем окно случайными окружностями}
repeat
Circle(Random(GetMaxX),Random(GetMaxX),
Random(GetMaxX div1 50)
until KeyPressed;
{Очищаем окно и ждем нажатия Enter)
ClearViewPort ;
OutTextXY(0,0,'Press Enter...');
ReadLn;
CloseGraph
end
end.
,
Процедура GetAspectRatio. Возвращает два числа, позволяющие оценить соотношение сторон экрана. Заголовок:
Procedure GetAspectRatio(var X, Y: Word);
.\
■ Пример 14.5■ Пример 14.5
■ Пример 14.5
■ Пример 14.5
Здесь х, Y — переменные типа word. Значения, возвращаемые в этих переменных, позволяют вычислить отношение сторон графического экрана в пикселах. Найденный с их помощью коэффициент может использоваться при построении правильных геометрических фигур, таких как окружности, квадраты и т. п. Например, если вы хотите построить квадрат со стороной L пикселов по вертикали, то это можно сделать с помощью операторов GetAspectRatio (Xasp, Yasp);
Rectangle(xl, yl, xl+L*round (Yasp/Xasp), yl+L);
а если L определяет длину квадрата по горизонтали, то правильным будет оператор
Rectangle(xl,yl,xl+L,yl+L*round(Xasp/Yasp));
Процедура SetAspectRatio. Устанавливает масштабный коэффициент отношения сторон графического экрана. Заголовок:
Procedure SetAspectRatio(X, Y: Word);
Здесь x, Y — устанавливаемые соотношения сторон.
Следующая программа (пример 14.5) строит 20 окружностей с разными соотношениями сторон экрана (рис. 14.3).
Рис. 14.3. Окружности при разных отношениях сторон экрана
Uses Graph,CRT;
const
R = 50;
dx = 1000;
var
d,m, е, к : Integer;
Xasp,Yasp: Word;
begin
d := Detect;
InitGraph(d, m, '');
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
GetAspectRatio(Xasp, Yasp);
for k := 0 to 20 do
begin
SetAspectRatio(Xasp+k*dx,Yasp);
Circle(GetMaxX div 2,GetMaxY div 2,R)
end;
if ReadKey=#0 then k := ord(ReadKey);
CloseGraph
end
end.
.'
Процедура SetActivePage. Делает активной указанную страницу видеопамяти. Заголовок:
Procedure SetActivePage(PageNum: Word);
Здесь PageNum — номер страницы.
Процедура пригодна только для адаптеров, поддерживающих многостраничную работу (EGA, VGA и т. п.). Фактически процедура просто переадресует графический вывод в другую область видеопамяти, однако вывод текстов с помощью Write/writeLn всегда осуществляется только на страницу, которая является видимой в данный момент (активная страница может быть невидимой). Нумерация страниц начинается с нуля.
Процедура SetVisualPage. Делает видимой страницу с указанным номером. Обращение:
Procedure SetVisualPage(PageNum: Word);
Здесь PageNum — номер страницы.
Процедура может использоваться только с адаптерами, поддерживающими многостраничную работу (EGA, VGA и т. п.). Нумерация страниц начинается с нуля.
Следующая программа (пример 14.6) сначала рисует квадрат в видимой странице и окружность — в невидимой. После нажатия на <Enter> происходит смена видимых страниц.
Пример146Пример146
Пример146
Пример146
Uses Graph;
var
d,m, е: Integer;
s: String;
begin
dDetect;
InitGraph(d,m, ' ' );
e := GraphResult;
if e <> grOk then
WriteLn (GraphErrorMsg(e))
else {Нет ошибки. Проверяем, поддерживает ли драйвер многостраничную работу с видеопамятью:}
if d in [HercMono,EGA, EGA64,MCGA, VGA] then begin{Используеммногостраничный режим}
if dOHercMono then
SetGraphMode(m-1);'
{Заполняем видимую страницу)
Rectangle(10,10,GetMaxX div 2,GetMaxY div 2);
OutTextXY(0,0,'Page 0. Press Enter...');
(Заполняем невидимую)
SetActivePage(1);
Circle(GetMaxX div 2, GetMaxY div 2, 100);
OutTextXY(0,GetMaxY-10, 'Page 1. Press Enter...') ; {Демонстрируем страницы)
ReadLn;
SetVisualPage(1);
ReadLn;
SetVisualPage(0);
ReadLn;
CloseGraph
end
else
begin(Драйвер не поддерживает многостраничный режим}
s := GetDriverName;
CloseGraph;
WriteLn('Адаптер ',з, ' использует только 1 страницу '•) end end.
*
Обратите внимание на оператор
if dOHercMono then
SetGraphMode(m-1);
С его помощью гарантированно устанавливается многостраничный режим работы на адаптерах EGA, MCGA, VGA. Как уже говорилось, после инициализации графики с Driver=Detect устанавливается режим работы с максимально возможным номером; перечисленные адаптеры в этом режиме могут работать только с одной графической страницей; чтобы обеспечить работу с двумя страницами, следует уменьшить номер режима.
Линии и точки
Процедура PutPixel. Выводит заданным цветом точку по указанным координатам. Заголовок:
Procedure PutPixel(X, Y: Integer; Color: Word);
Здесь:
x, Y — координаты точки;
color — цвет точки.
Координаты задаются относительно левого верхнего угла окна или, если окно не установлено, относительно левого верхнего угла экрана.
Следующая программа (пример 14.7) периодически выводит на экран "звездное небо" и затем гасит его. Для выхода из программы нажмите любую клавишу.
Uses CRT, Graph;
type
PixelType = record
х, у : Integer;
end; const
N = 5000;{Количество "звезд"}
var
d, г,е, к: Integer;
xl,yl,x2,y2: Integer;
a: array [1..N] of PixelType; (Координаты)
begin
(Инициализируем графику) d := Detect;
InitGraph(d, r, 11);
e := GraphResult;
if eogrOk then
WriteLn(GraphErrorMsg(e))
else
begin
(Создаем окно в центре экрана}
xl : = GetMaxX div 4;
yl :=div 4;
x2 :- 3*xl;
У2 := 3*yl;
Rectangle(xl,yl,x2,y2);
SetViewPort(xl+1,yl+l,x2-l,y2-l,ClipOn);
(Создаем и запоминаем координаты всех "звезд")
for k := 1 to N do
with a[k] do
begin
x := Random(x2-xl);
у := Random(y2-yl) end;
(Цикл вывода)
repeat
for k 1 to N do
with a[k] do(Зажигаем "звезду")
PutPixel(x,у,white);
if not KeyPressed then
for k := N' downto 1 do
with а [к] do{Гасим "звезду")
PutPixel(х,у,black)
until KeyPressed;
while KeyPressed do k := ord(ReadKey);
CloseGraph
end;
end.
Функция GetPixel. Возвращает значение типа Word, содержащее цвет пиксела с указанными координатами. Заголовок:
Function GetPixel(X, Y: Integer): Word;
Здесь x, Y — координаты пиксела.
Процедура Line. Вычерчивает линию с указанными координатами начала и конца. Заголовок:
Procedure Line(XI, Yl, Х2, Y2: Integer);
Здесь XI, Yl, X2, Y2 — координаты начала и конца линии.
Линия вычерчивается текущими стилем и цветом. В следующей программе (пример 14.8) в центре экрана создается окно, которое затем расчерчивается случайными линиями. Для выхода из программы следует нажать любую клавишу.
Uses CRT, Graph;
var
d, r, e : Integer;
xl,yl,x2, y2: Integer;
begin
{Инициализируем графику) d := Detect;
InitGraph(d, r, ' e := GraphResult; if e <> grOk then WriteLn(GraphErrorMsg(e)) else begin
{Создаем окно в центре экрана) xl :=4;
yl : = GetMaxY div 4;
х2 := 3*xl;
у2 := 3*yl;
Rectangle(xl,yl,x2,y2);
SetViewPort(xl+l,yl+l,x2-l,y2-l,ClipOn);
{Цикл вывода случайных линий} repeat
SetColor(succ(Random(16)));(Случайныйцвет}
Line(Random(x2-xl) , Random(y2-yl) , Random(x2-xl), Random(y2-yl)) until KeyPressed;
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph end end.
Процедура LineTo. Вычерчивает линию от текущего положения указателя до положения, заданного его новыми координатами. Заголовок:
Procedure LineTo(X, Y: Integer);
Здесь x, Y — координаты нового положения указателя, они же — координаты второго конца линии.
Процедура LineRel. Вычерчивает линию от текущего положения указателя до положения, заданного приращениями его координат. Заголовок:
Procedure LineRei (DX, DY: Integer);
Здесь DX, DY — приращения координат нового положения указателя.
В процедурах LineTo и LineRei линия вычерчивается текущими стилем и цветом.
Процедура SetLineStyle. Устанавливает новый стиль вычерчиваемых линий. Заголовок:
Procedure SetLineStyle(Type, Pattern, Thick: Word)
Здесь Type, Pattern, Thick — соответственно тип, образец и толщина линии. Тип линии может быть задан с помощью одной из следующих констант: const
SolidLn= 0; {Сплошная линия}
DottedLn= 1; {Точечная линия}
CenterLn= 2; {Штрих -пунктирная линия}
DashedLn = 3; {Пунктирная линия)
UserBitLn = 4; (Узорлинии определяет пользователь)
Параметр pattern учитывается только для линий, вид которых определяется пользователем (т. е. в случае, когда туре = UserBitLn). При этом два байта параметра Pattern определяют образец линии: каждый установленный в единицу бит этого слова соответствует светящемуся пикселу в линии, нулевой бит — несветящемуся пикселу. Таким образом, параметр Pattern задает отрезок линии длиной в 16 пикселов. Этот образец периодически повторяется по всей длине линии.
Параметр Thick может принимать одно из двух значений:
const
Normwidth= 1; (Толщина в один пиксел}
Thickwidth = 3; {Толщина в три пиксела}
Отметим, что установленный процедурой стиль линий (текущий стиль) используется при построении прямоугольников, многоугольников и других фигур.
В примере 14.9 демонстрируются линии всех стандартных стилей, затем вводятся слово-образец и линия с этим образцом заполнения (рис. 14.4). Для выхода из программы введите ноль.
SolidLn
Pat tern:
1 ' ‘ .
DottedLn
Pattern:
7■ • •
•
*
■
■
•
■
• <
Pattern"
127 - -
CenterLn
Pattern:
65520
DashedLn
Pattern:
SolidLn
1DottedLn
^^■^^" CentdrLn
DashedLn
Рис. 14.4. Образцы линий
Uses CRT, Graph;
const
style: array [0..4] of String [9] = ( 'SolidLn'DottedLn ', ’CenterLn’,
'DashedLn1, 'UserBitLn');
var
d, r, e, i,j, dx, dy: Integer;
p: Word;
begin
(Инициализируем графику)
d := Detect; , InitGraph(d, r, 11 );
e := GraphResult;
if e <> grOk then
WriteLn (GraphErrorMsg(e))
else
begin
{Вычисляем смещение линий)
dx : = GetMaxX div 6;
dy := GetMaxY div 10;
(Выводим стандартные линии)
for j := 0 to 1 do(Для двух ТОЛЩИН }
begin
for i := 0 to 3 do(Четыре типа линий)
begin
SetLineStyle(i, 0, j*2+l);
Line(0,(i+j*4+l)*dy,dx,(i+j*4+l)*dy);
OutTextXY(dx+10,(i+j *4+1)*dy, style[i])
end
end;
{Вводим образец и чертим линию)
j0;
dy := (GetMaxY+1) div 25;
repeat
OutTextXY(320,j*dy,'Pattern: ');
GotoXY(50,j+1);
ReadLn(p);
if p <> 0 then begin
SetLineStyle(UserBitLn,p,NormWidth);
Line(440,j*dy+4, 600, j*dy+4); inc (j)
end
until р = 0;
CloseGraph
end end.
Процедура GetLineSettings. Возвращает текущий стиль линий. Заголовок:
Procedure GetLineSettings(var Styleinfo: LineSettingsType)
Здесь Styleinfo — переменная типа LineSettingsType, в которой возвращается текущий стиль линий.
Тип LineSettingsType определен в модуле Graph следующим образом: type
LineSettingsType record
LineStyle: Word; {Тип линии)
Pattern : Word; (Образец)
Thickness: Word {Толщина}
end;
Процедура SetWriteMode. Устанавливает способ взаимодействия вновь выводимых линий с уже существующим на экране изображением. Заголовок:
Procedure SetWriteMode(Mode) ;
Здесь Mode — выражение типа integer, задающее способ взаимодействия выводимых линий с изображением.
Если параметр Mode имеет значение 0, выводимые линии накладываются на существующее изображение обычным образом (инструкцией MOV центрального процессора). Если значение равно 1, то это наложение осуществляется с применением логической операции XOR (исключающее ИЛИ): в точках пересечения выводимой линии с имеющимся на экране изображением светимость пикселов инвертируется на обратную, так что два следующих друг за другом вывода одной и той же линии на экран не изменят его вид.
Режим, установленный процедурой SetwriteMode, распространяется на процедуры Drawpoly, Line, LineRel, LineTo И Rectangle. Для задания параметра Mode можно использовать следующие определенные в модуле константы:
const
Copy Put = 0; {Наложение операцией MOV)
XORPut = 1; {Наложение операцией XOR)
Программа, приведенная в примере 14.10, создает на экране изображение часового циферблата (рис. 14.5). Для наглядной демонстрации темп хода "часов" ускорен в 600 раз (см. оператор Delay (100)). При желании вы смо-
12 Зак. 941
жете легко усложнить программу, связав ее показания с системными часами и добавив секундную стрелку. Для выхода из программы нажмите на любую клавишу.
Рис. 14.5. Часовой циферблат
Uses Graph, CRT;
var
d,г, rl,r2,rr, к, х!,у1,х2,у2,х01,у01: Integer;
Xasp,Yasp : Word;
begin
{Инициализируем графику)
d := detect;
InitGraph(d, r, ");
k := GraphResult;
if k 0 grOK then
WriteLn(GraphErrorMSG(к))
else
begin
{Определяем отношение сторон и размеры экрана) xl :=2;
yl :=div 2;
GetAspectRatio(Xasp, Yasp);
{Вычисляем радиусы:)
r:= round(3*GetMaxY*Yasp/8/Xasp);
rl := r2•
циферблат) Circleвнешняя
Circle(xl,yl,round(1.02*r)); {Вторая окружность) for k := 0 to 59 do(Деления циферблата)
begin if k5=0 then
rrrlделения)
else rrделения)
координаты концов делений) := := x2 := y2 := деление) end; вывод стрелок) SetWriteMode(XORPut); SetLineStyle(SolidLn,0,Thickwidth); r 0;минут в одном часе)
вывода repeat for k := 0 to 59 do= минуты)
if notthen
begin часовой x2 := y2 := минутной xOl := xl+Round(r2*sin(2*pi*k/60)); yOl := {Изображаем стрелки) Line (xl, yl,x2,y2); Line(xl,yl,x01,y01); Delay(100); (Дляимитации реального темпа нужно установить задержку 60 000}
{Для удаления стрелок выводим их еще раз!} Line(xl,yl,x01,y01);
Line (xl, yl,x2, у2) ;
{Наращиваем и корректируем счетчик минут в часе) inc (г);
if г=12*60 then г := О end until KeyPressed; if ReadKey=#O then k := ord(ReadKey); CloseGraph end
end.
Многоугольники
Процедура Rectangle. Вычерчивает прямоугольник с указанными координатами углов. Заголовок:
Procedure Rectangle(XI, Yl, X2, Y2: Integer);
Здесь XI, Yl, X2, Y2 — координаты левого верхнего и правого нижнего углов прямоугольника. Прямоугольник вычерчивается с использованием текущего цвета и текущего стиля линий.
В следующей программе (пример 14.11) на экране вычерчиваются 10 вложенных друг в друга прямоугольников.
I. Пример 14.11I. Пример 14.11
I. Пример 14.11
I. Пример 14.11
Uses Graph, CRT;
var
d, r, e, xl, yl, x2,y2,dx,dy: Integer;
begin
{Инициализируем графику} d :'= Detect;
InitGraph(d, r,'');
e := GraphResult;
if e <> grOK then
WriteLn(GraphErrorMsg(e))
else
begin
{Определяем приращения сторон}
dxGetMaxX div 20;
dy : = GetMaxY div 20;
(Чертим вложенные прямоугольники} for d := 0 to 9 do
Rectangle(d*dx,d*dy,GetMaxX-d*dx,GetMaxY-d*dy);
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
Процедура DrawPoly. Вычерчивает произвольную ломаную линию, заданную координатами точек излома.
Procedure DrawPoly(N: Word; var Points)
Здесь:
N — количество точек излома, включая обе крайние точки;
Points — переменная типа pointType, содержащая координаты точек излома.
Координаты точек излома задаются парой значений типа word: первое определяет горизонтальную, второе — вертикальную координаты. Для них существует следующий определенный в модуле тип:
type
PointType = record
х, у : Word
end;
Вычерчивание осуществляется текущим цветом и стилем линий. Вот как, например, можно с помощью этой процедуры вывести на экран график синуса:
Uses Graph;
const
N = 100;{Количество точек графика}
var
d, r, e: Integer;
m : array [O..N+1] of PointType;
k : Word;
begin
{Инициализируем графику}
d := Detect;
InitGraph(d, r, '');
е := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
{Вычисляем координаты графика}
for k 0 to N do with m[k] do
begin
x := trunc(k*GetMaxX/N);
у := trunc(GetMaxY*(-sin(2*Pi*k/N)+1)/2)
end;
(Замыкаем график прямой линией}
m[succ(N)].x :=
m[succ(n) ] .y := m[0].y;
DrawPoly(N + 2, m) ;
ReadLn;
CloseGraph
end
end.
В этом примере проведение горизонтальной прямой реализовано путем "замыкания" ломаной — первая и последняя координаты ее точек излома совпадают.
Замечу, что хотя количество точек излома N — выражение типа word, на ca- мом деле внутри процедуры на этот параметр накладываются ограничения, связанные с конечным размером используемой буферной памяти. Вы можете убедиться в этом с помощью, например, изменения N в предыдущем примере: при N=678 график перестанет выводиться на экран, а функция GraphResult будет возвращать значение -б (не хватает памяти для просмотра областей). Таким образом, для этой программы пороговое значение количества точек излома составляет 679. В то же время для программы
Uses Graph;
const
N=510; {Предельное значение, при котором на экране еще видна диагональная линия}
var
d, к: Integer;
Coo: array [1..N] of PointType;
begin
d := Detect;
InitGraph (d, k, ");
for k := 1 to N do with Coo[k] do
if odd(k) then
begin
X := 0;
Y := 0
end
else
begin
X := GetMaxX;
Y := GetMaxY
end;
DrawPoly(N,Cqo);
ReadLn;
CloseGraph
end.
это значение равно 510. В этой программе ломаная задается в виде многократно накладывающихся друг на друга диагональных линий.
14.5. Дуги, окружности, эллипсы
Процедура circle. Вычерчивает окружность. Заголовок:
Procedure Circle (Xv Y: Integer; R: Word);
Здесь:
x, Y — координаты центра;
R — радиус в пикселах.
Окружность выводится текущим цветом. Толщина линии устанавливается текущим стилем, вид линии всегда SolidLn (сплошная). Процедура вычерчивает правильную окружность с учетом изменения линейного размера радиуса в зависимости от его направления относительно сторон графического экрана, т. е. с учетом коэффициента GetAspectRatio. В связи с этим параметр R определяет количество пикселов в горизонтальном направлении.
В примере 14.12 в центре экрана создается окно, постепенно заполняющееся случайными окружностями. Для выхода из программы нажмите на любую клавишу.
! Пример 14.12 ..<•.'
у X l- ч;: хй^Ц; •—•;::v. ^.zkA"-"* ьШКК' ¥«l-^ 1 »•-—- 'J,'"-v 4HSs^ *
Uses Graph, CRT;•
var d,r,e,x,y: Integer; begin {Инициализируем графику) d := Detect;
InitGraph(d, r, ’ ’);
e := GraphResult;
if e <> grOK then
WriteLn(GraphErrorMsg(e))
else begin
{Создаем окно в центре экрана}
х := GetMaxX div 4;
у := GetMaxX div 4;
Rectangle(x,y,3*x,3*y);
SetViewPort(x+1,y+1,3*x-l,3*y-l,ClipOn);
{Цикл вывода случайных окружностей) repeat
SetColor(succ(Random(white)));{Случайный цвет)
SetLineStyle(0,0,2*Random(2)+1); (и стиль линии} x := Random (GetMaxX) ;{Случайное положение)
у := Random(GetMaxX);{центра окружности)
Circle(x,у,Random(GetMaxX div 4));
until KeyPressed;
if ReadKey=#0 then x := ord(ReadKey);
CloseGraph
end end.
Процедура Arc. Чертит дугу окружности. Заголовок:
Procedure Arc(X, Y: Integer; BegA, EndA, R: Word);
Здесь:
□ х, Y — координаты центра;
О BegA, EndA — соответственно начальный и конечный углы дуги;
ОR — радиус.
Углы отсчитываются против часовой стрелки и указываются в градусах. Нулевой угол соответствует горизонтальному направлению вектора слева направо. Если задать значения начального угла 0 и конечного — 359, то будет выведена полная окружность. При вычерчивании дуги окружности используются те же соглашения относительно линий и радиуса, что и в процедуре circle.
Вот как выглядят две дуги: одна с углами 0 и 90, вторая 270 и 540 градусов (рис. 14.6).
Рис. 14.6. Иллюстрация результатов работы процедуры Arc
Следующая программа (пример 14.13) создает это изображение.
Uses Graph, CRT;
var
d, r, е : Integer;
Xasp,Yasp: Word;
begin
{Инициализируем графику) d := Detect;
InitGraph(d, r, ' ’);
e := GraphResult;
if e о grOK then
WriteLn(GraphErrorMsg(e))
else
begin
GetAspectRatio(Xasp,Yasp);
{R = 1/5 от в ертикального размера экрана} г :=
d : = GetMaxX div 2; (Смещение второго графика) e := GetMaxY div 2; {Положение горизонтальной оси) {Строим левый трафик)
Line (0,е,5*г div 2,е);(Горизонтальная ось)
Line (5*г div 4, е div 2,5*г div 4,3*е div 2);
Arc (5*г div 4,е, 0,90,R);(Дуга)
div-
{Правый график) Line (d, е, d+5*'r div 2,е) ;
Line (d+5*r div 4,е div 2, d+5*r div 4,3*е div 2);
Arc (d+5*r div 4,е,270,540,R) ;
OutTextXY(d,е+е div 8, '270 - {ЖДем нажатия на любую клавишу) if ReadKey=#0 then d := ord(ReadKey) ; CloseGraph end end.
Процедура GetArccoords. Возвращает координаты трех точек: центра, начала и конца дуги. Заголовок:
Procedure GetArcCoords(var Coords: ArcCoordsType);
Здесь Coords — переменная типа ArcCoordsType, в которой процедура возвращает координаты центра, начала и конца дуги.
Тип ArcCoordsType определен в модуле GRAPH следующим образом:
type
ArcCoordsType = record
X,Y: Integer; {Координатыцентра)
Xstart,Ystart: Integer; (Начало дуги)
Xend,Yend: Integer; {Конец дуги)
end;
Совместное использование процедур Arc и GetArcCoords позволяет вычерчивать сопряжения двух прямых с помощью дуг. Обратите внимание на коррекцию длины радиуса в примере 14.14, в котором вычерчивается прямоугольник со скругленными углами.
; Пример 14.14; Пример 14.14
; Пример 14.14
; Пример 14.14
Uses Graph,CRT;
const
RadX = 50;(Горизонтальный радиус)
lx= 400;{Ширина)
iy = 100;{Высота}
var
d, r, e: Integer;
coo : ArcCoordsType;
xl,yl: Integer;
xa,ya: Word;
RadY : Integer;{Вертикальныйрадиус)
begin
{Инициализируем графику}
d Detect;
InitGraph(d, r, 1 1);
e := GraphResult;
if e <> grOK then
WriteLn(GraphErrorMsg(e))
else
begin
GetAspectRatio(ха,ya); (Получаемотношение сторон) {Вычисляем вертикальный радиус и положение фигуры с учетом отношения сторон экрана) RadY := round(RadX*(xa/ya));
xl :=2;
yl := (GetMaxY-2*RadY-ly) div'2;
{Вычерчиваем фигуру)
Line (xl,yl,xl+lx,yl);{Верхняя горизонтальная)
Arc (xl+lx,yl+RadY,0,90,RadX); (Скругление) GetArcCoords(coo);
with coo do
begin
Line(Xstart,Ystart,Xstart,Ystart+ly);
(Правая вертикальная}
Arc(Xstart-RadX,Ystart+ly,270,0, RadX);
GetArcCoords (coo);
Line(Xstart,Ystart,Xstart-lx,Ystart);
(Нижняя горизонтальная)
Arc(Xstart-lx,Ystart-RadY,180,270,RadX);
GetArcCoords(coo);
Line(Xstart,Ystart,Xstart,Ystart-ly);
Arc(Xstart+RadX,Ystart-ly,90,180,RadX) end;
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
Процедура Ellipse. Вычерчивает эллипсную дугу. Заголовок:
Procedure Ellipse(X, Y: Integer; BegA, EndA, RX, RY: Word);
Здесь:
x, Y — координаты центра;
BegA, EndA — соответственно начальный и конечный углы дуги;
RX, RY — горизонтальный и вертикальный радиусы эллипса в пикселах.
При вычерчивании дуги эллипса действуют те же соглашения относительно линий, что и в процедуре circle, и те же соглашения относительно углов, что и в процедуре Arc. Если радиусы согласовать с учетом масштабного коэффициента GetAspectRatio, будет вычерчена правильная окружность.
В следующей программе (пример 14.15) вычерчиваются три эллипсных дуги (рис. 14.7) при разных отношениях радиусов. Замечу, что чем выше разрешение фафического экрана, тем ближе к единице отношение сторон и тем меньше первый график отличается от третьего.
Рис. 14.7. Эллипсные дуги
V*•^•'*'*>s4^Ч^V,V,*v“tt•^*•^•'•w•^*^•WV?'j6VЧt•^^*,^^*ч*tftйS>vw•K58|•-,eA!‘#e>ц>W!)УЛ1№A^•^sM|l^Ый**^Д'*.*4'Ч2M5iiM№ЭДf•^®te■lИЗBC^lMtiKA-^Ki,fl^,•
I Пример 14.15
1 •'...*4.»..«е.е •*>•••«• (• К . о ........... I •....•••• е• • «V» Л^К«/«»
Uses Graph, CRT; var
d, r,e: Integer;
ха,ya: Word;
begin
(Инициализируем графику)
d := Detect;
InitGraph(d, r, * *);
e := praphResult;
if e <> grOK then
WriteLn(GraphErrorMsg(e))
else
begin
(Первый график)
OutTextXY(50,40, 'RX =
Line (0,100,160,100);(ОсьХ)
Line (80,55,80,145);(Ось Y)
Ellipse (80,100,180,90,40,40) ;
(Второй график)
OutTextXY(260,40,'RX = Line. (190,100,410,100); Line (300, 55,300, 145) ; Ellipse (300,100,0,359,100,20);
(Третий график),
OutTextXY(465,40,'Aspect Ratio'); Line;
Line (520,55,520,145);
GetAspectRatio(xa, ya);
Ellipse (520,100, 0, 270, 40,round(40*(xa/ya))); if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
Краски, палитры, заполнения
ПроцедураУстанавливает текущий цвет для выводимых линий и
символов. Заголовок:
Procedure
Здесь color — текущий цвет.
В модуле Graph определены точно такие же константы для задания цвета, как и в модуле CRT (см. разд. 13.2).
ФункцияВозвращает значение типа word, содержащее код теку
щего цвета. Заголовок:
Function GetColor: Word;
ФункцияВозвращает значение типа word, содержащее макси
мальный доступный код цвета, который можно использовать для обращения к setcoior. Заголовок:
У '
Function GetMaxCoior: Word;
ПроцедураУстанавливает цвет фона. Заголовок:
Procedure
Здесь Color — цвет фона.
В отличие от текстового режима, в котором цвет фона может быть только темного оттенка, в графическом режиме он может быть любым. Установка нового цвета фона немедленно изменяет цвет графического экрана. Это означает, что нельзя создать изображение, два участка которого имели бы разный цветДляв режиме высокого разрешения установ
ка цвета фона изменяет цвет активных пикселов. Замечу, что после замены цвета фона на любой, отличный от 0 (Black) цвет, вы не сможете более использовать цвет 0 как черный, он будет заменяться на цвет фона, т. к. процедуры модуля GRAPH интерпретируют цвет с номером 0 как цвет фона. Это означает, в частности, что вы уже не сможете вернуть фону черный цвет!
Если ваш ПК оснащен цветным экраном, следующая программа (пример продемонстрирует работу процедуры SetBkColor. Программа выводит десять вложенных друг в друга прямоугольников, после чего циклически меняет цвет фона. Для выхода из программы достаточно нажать на любую клавишу.
•UMLVMi£W?MiyMM^№^^^^^^M^^^^^M^^^^tt^^M&'*^lM1^^5^^^yW^^^^ti^^^^^^^^^^^^^W^^WjliMi^^^^^^^^^4Ki^toy8#ytiKw?,*
Пример 14.163.ШВ^ A i
Uses Graph, CRT;
const
NC: array [0..15] of String [12] =
('Black','Blue','Green','Cyan','Red','Magenta* л
' Sr own * , ' jightGray',.' DarkGray', ' jightBlue', 'LightGreen','LightCyan','LightRed',
'LightMagenta','Yellow','White'); var
d, r, e, k, color, dx, dy: Integer;/
begin
(Инициализируем графику}
dDetect;
InitGraph(d, r, 1?
e := GraphResult;
if e <>then.
WriteLn(GraphErrorMsg(e))
else
begin
{Выводим текст в центре экрана I
OutTextXY(200,GetMaxY div 2,'BACKGROUND COLOR');
dxGetMaxXdiv 30;{Приращение длины}
dy:=GetMaxYdiv 25;{Приращение высоты}
fork:= 0 to9 do{ Выводим 10 прямоугольников}
Rectangle(k*dx,k*dy,GetMaxX-k*dx,GetMaxY-k*dy);
color := black;(Начальныйцвет фона}
repeat(Цикл смены фона}
SetBkColor(color) ;
SetFillStyle(0,Color) ;
Bar(3 4 5,GetMaxY div 2,440,GetMaxY div 2+8) ;
OutTextXY(345,GetMaxY div 2,NC[color]);
delay(lOOO);
inc(color);
if color > White then
colorBlack
until KeyPressed;
if ReadKey=#O then k := ord(ReadKey);
CloseGraph
end
end.
Функция GetBkColor. Возвращает значение типа word, содержащее текущий цвет фона. Заголовок:
Function GetBkCoior: Word;
Процедура SetPalette. Заменяет один из цветов палитры на новый цвет. Заголовок:
Procedure SetPalette(N: Word; Color: ShortInt);
Здесь:
□ N — номер цвета в палитре;
О color — номер вновь устанавливаемого цвета.
Данная процедура может работать только с адаптерами EGA или VGA. Она не должна использоваться с IBM8514 или 256-цветным вариантом VGA — для этих адаптеров предназначена особая процедура SetRGBPalette (см. ниже). Первоначальное размещение цветов в палитрах EGA/VGA соответствует последовательности их описания константами Black, ..., white, т. е. цвет с индексом 0 — черный, 1 — синий, 2 — зеленый и т. д. После обращения к процедуре все фрагменты изображения, выполненные цветом с индексом N из палитры цветов, получат цвет Color. Например, если выполнить оператор
SetPalette (2,White);
то цвет с индексом 2 (первоначально это — бирюзовый цвет Cyan) будет заменен на белый. Замечу, что цвет с индексом 0 отождествляется с цветом фона и может изменяться наряду с любым другим цветом.
Следующая программа (пример 14.17) выводит на экран ряд прямых разного цвета и затем случайным образом меняет цвета палитры.
: Пример 14.17"’ /":
Uses Graph, CRT;
var
d,r,e,N,k,color: Integer;
Palette: PaletteType;
begin
{Инициализируем графику} d := Detect;
InitGraph(d, r, ' ') ;
е := GraphResult;
if e <> grOK then
WriteLn(GraphErrorMsg(e))
else
begin
{Выбираем толстые сплошные линии}*
SetLineStyle(SolidLn, 0, Thickwidth);
GetPalette(Palette);(Текущаяпалитра)
for Color := 0 to Palette.Size-1 do
begin
SetColor(Color);
Line(GetMaxX div 3,Color*10,2*GetMaxX div 3,Color*10) end;
(Меняем палитру и ждем инициативы пользователя} while not KeyPressed do
for e := 0 to Palette.Size-1 do .....
if ReadKey=#0 then d := ord(ReadKey); CloseGraph
end
end. ,
Процедура GetPalette. Возвращает размер и цвета текущей палитры. Заголовок:
Procedure GetPalette (var Paletteinfo: PaletteType);
Здесь Palettelnfo — переменная типа PaletteType, возвращающая размер и цвета палитры.
\
В модуле Graph определена константа const
MaxColors = 15;
ИТИП,
•
type
PaletteType = record
Size :(Количество цветов в палитре}
Colors : array [0..MaxColors] of Shortlnt
{Номера входящих в палитру цветов)
end;
С помощью следующей программы (пример 14.18) можно вывести на экран номера всех возможных цветов из текущей палитры.
:— ........у.............. ;уу."-.■■■-:
; Пример 14,18
Uses Graph;
var
Palette: PaletteType;
d, г, е,к: Integer;
begin
{Инициализируем графику)
d := Detect;
InitGraph(d, r, ’ *);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
GetPalette (Palette) ;{Получаемпалитру)
CloseGraph;{Возвращаемся в текстовый режим)
with Palette do{Выводим номера цветов)
for k := 0 to pred(Size) do
Write(Colors[к]:5);
end
end.
Процедура SetAllPalette. Изменяет одновременно несколько цветов палитры. Заголовок процедуры:
Procedure SetAllPalette(var Palette);
Параметр Palette в заголовке процедуры описан как нетипизированный. Первый байт этого параметра должен содержать длину N палитры, остальные байты — номера вновь устанавливаемых цветов в диапазоне от -1 до MaxColors. Код -1 означает, что соответствующий цвет исходной палитры не меняется.
В следующей программе (пример 14.19) происходит одновременная смена сразу всех цветов палитры.
•';."•';."
•';."
•';."
Uses Graph, CRT;
var
Palette: array [0..MaxColors] of Shortlnt;
d, r, e, k: Integer;
begin
{Инициилизируем графику}
d := Detect;
InitGraph(d, r, ’');
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e) )
else
begin
(Выбираем толстые сплошные линии} SetLineStyle(SolidLn, 0, ThickWidth);
{Выводим линии всеми доступными цветами}
for k := 1 to GetMaxColor do
begin
SetColor(k);
Line(GetMaxX div 3,k*10,2*GetMaxX div 3,k*10)
end;
Palette[0] := MaxColors;{Размер палитры}
\repeat{Цикл смены палитры}
for k1 to MaxColors do
Palette[к] :=
SetAllPalette(Palette)
until KeyPressed;
if ReadKey=|0 then k := ord(ReadKey);
CloseGraph
end
end.
Function GetPaletteSize: Integer;Function GetPaletteSize: Integer;Функция GetPaletteSize. Возвращает значение типа integer, содержащее размер палитры (максимальное количество доступных цветов). Заголовок: (
Function GetPaletteSize: Integer;
Function GetPaletteSize: Integer;
Процедура GetDefaultPalette. Возвращает структуру палитры, устанавливаемую по умолчанию (в режиме автонастройки). Заголовок:
Procedure GetDefaultPalette(var Palette: PaletteType);
Здесь Palette — переменная типа PaletteType (см. Процедуру GetPalette), в которой возвращаются размер и цвета палитры.
Процедура SetFillStyle. Устанавливает стиль (тип и цвет) заполнения. Заголовок:
Procedure SetFillStyle(Fill,Color: Word);
Здесь:
G Fill —тип заполнения;
О Color — цвет заполнения.
С помощью заполнения можно покрывать какие-либо фрагменты изображения периодически повторяющимся узором. Для указания типа заполнения существуют следующие предварительно определенные константы:
const
EmptyFill= 6;
{Заполнение фоном (узор отсутствует))
SolidFill= 1;
(Сплошное заполнение)
LineFill- 2;
{Заполнение)
LtSlashFill= 3;
(Заполнение ///////)
SlashFill= 4;
{Заполнение утолщенными ///}
BkSlashFill '= 5;
{Заполнение утолщенными \ \ \}
LtBkSlashFill * 6;
{Заполнение \ \ \ \ \ \ \)
HatchFill- 7;
(Заполнение +++++++)
XHatchFill= 8;
{Заполнение ххххххх)
InterleaveFill= 9;
{Заполнение прямоугольной клеточки)
WideDotFill = 10
{Заполнение редкими точками)
CloseDotFill11
{Заполнение частыми точками)
UserFill= 12;
(Узор определяется пользователем)
Программа из примера 14.20 продемонстрирует вам все стандартные типы заполнения.
Пример 14.20Пример 14.20
Пример 14.20
Пример 14.20
Uses Graph, CRT;
var
d, r, e, k,j, x, y: Integer;
begin
(Инициализируем графику) d : = Detect;
InitGraph(d, r, *');
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
x := GetMaxX div 6;{Положение графика}
у := GetMaxY div 5;(на экране)
for j := 0 to 2 do/Два ряда)
for k := 0 to 3 do(по четыре квадрата I
begin•
Rectangle((к+1)*х,(j+1)*у,(к+2)*х,(j+2) *у);
SetFillStyle(k+j*4,j+1);
\\Bar((k+1)*x+l,(j+l)*y+l,(k+2)*x-l,(j+2)*y-l) end;
\
\
if ReadKey=#0 then k := ord(ReadKey); CloseGraph
end end.
Если параметр Fill имеет значение 12 (userFill), то рисунок узора определяется программистом путем обращения к процедуре SetFillPattern.
Процедура SetFiiiPattern. Устанавливает образец рисунка и цвет штриховки. Заголовок:
Procedure SetFillPattern(Pattern: FillPatternType; Color: Word);
Здесь:
□ Pattern — выражение типа FillPatternType; устанавливает образец рисунка для Fill = UserFill В Процедуре SetFillStyle;
О color — цвет заполнения.
Образец рисунка задается в виде матрицы из 8x8 пикселов и может быть представлен массивом из 8 байт следующего типа:
type
FillPatternType = array [1..8] of Byte;
Каждый разряд любого из этих байтов управляет светимостью пиксела, причем первый байт определяет 8 пикселов первой строки на экране, второй байт — 8 пикселов второй строки и т. д.
На рис. 14.8 показан пример двух образцов заполнения. На рисунке черточкой обозначается несветящийся пиксел, а прямоугольником — светящийся. Для каждых 8 пикселов приводится шестнадцатеричный код соответствующего байта.
Следующая программа (пример 14.21) заполняет этими образцами две прямоугольных области экрана.
ОбразецОбразецЗначениебайта$49$92 $49 $92$49$92$49$92Значениебайта$49$92 $49 $92$49$92$49$92ОбразецОбразец3начениебайта$00$18$24$42$42$24$18§003начениебайта$00$18$24$42$42$24$18§00
Образец
Образец
Значениебайта
$49
$92 $49 $92
$49
$92
$49
$92
Значениебайта
$49
$92 $49 $92
$49
$92
$49
$92
Образец
Образец
3начениебайта
$00
$18
$24
$42
$42
$24
$18
§00
3начениебайта
$00
$18
$24
$42
$42
$24
$18
§00
Рис. 14.8. Образцы заполнения и их кодыРис. 14.8. Образцы заполнения и их кодыUses Graph, CRT;constpattl: FillPatternType= ($49,$92,$49,$92,$49,$92,$49,$92);patt2: FillPatternType= ($00,$18,$24,$42,$42,$24,$18,$00); vard, r,e: Integer;beginUses Graph, CRT;constpattl: FillPatternType= ($49,$92,$49,$92,$49,$92,$49,$92);patt2: FillPatternType= ($00,$18,$24,$42,$42,$24,$18,$00); vard, r,e: Integer;begin
Рис. 14.8. Образцы заполнения и их коды
Рис. 14.8. Образцы заполнения и их коды
Uses Graph, CRT;
const
pattl: FillPatternType= ($49,$92,$49,$92,$49,$92,$49,$92);
patt2: FillPatternType= ($00,$18,$24,$42,$42,$24,$18,$00); var
d, r,e: Integer;
begin
Uses Graph, CRT;
const
pattl: FillPatternType= ($49,$92,$49,$92,$49,$92,$49,$92);
patt2: FillPatternType= ($00,$18,$24,$42,$42,$24,$18,$00); var
d, r,e: Integer;
begin
(Инициализируем графику}
d := Detect;
InitGraph(d, r, ' *);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin if d=CGA then
SetGraphMode(0);{Устанавливаемцвет для CGA)
SetFillStyle(UserFill,White) ;
{Левый верхний квадрат)
SetFillPattern(Patti,1);
Bar(O,O,GetMaxX div 2, GetMaxY div 2);
(Правый нижний квадрат)
SetFillPattern(Patt2, 2) ;
Bar(GetMaxX div 2,GetMaxY div 2, GetMaxX, GetMaxY) ;
" if ReadKey=#O then d := ord(ReadKey) ;
CloseGraph
end
end.
/
Если при обращении к процедуре указан недопустимый код цвета, вызов процедуры игнорируется и сохраняется ранее установленный образец заполнения. В частности, если в предыдущем примере убрать оператор
if d=CGA then
SetGraphMode(0);
устанавливающий цветной режим работы CGA-адаптера, на экран ПК, оснащенного адаптером этого типа, будут выведены два одинаковых прямоугольника, т. к. обращение
SetFillPattern(patt2, 2 ) ;
содержит недопустимо большой для данного режима код цвета и вызов этой процедуры игнорируется. Сказанное, однако, не относится к процедуре SetFillStyle для значения параметра Fill в диапазоне от о до n: программа будет нормально работать и в режиме высокого разрешения CGA- адаптера, причем все цвета палитры, кроме цвета фона, при этом заменяются на белый.
Процедура SetFillPattern. Возвращает образец заполнения, установленный ранее процедурой SetFillPattern. Заголовок:
Procedure GetFillPattern (var Pattern: FillPatternType);
Здесь Pattern — переменная типа FillPatternType, в которой возвращается образец заполнения.
Если программа не устанавливала образец с помощью процедуры SetFillPattern, то массив Pattern заполняется байтами со значением 255 ($FF).
Процедура GetFillSettings. Возвращает текущий стиль заполнения. Заголовок:
Procedure GetFillSettings (var Pattlnfo: FillSettingsType) ;
Здесь Pattlnfo — переменная типа FillSettingsType, в которой возвращается текущий стиль заполнения.
В модуле GRAPH определен тип:
type
FiiiSettingsType = record
Pattern: Word;{Образец)
Color : Word.
end;
Поля Pattern и Color в этой записи имеют то же назначение, что и аналогичные параметры при обращении к процедуре SetFillStyle.
Процедура SetRGBPaiette. Устанавливает цветовую гамму при работе с дисплеем IBM 8514 и адаптером VGA. Заголовок:
Procedure SetRGBPalette(ColNum,RedVal, GreenVal,BlueVal: Integer);
Здесь:
.
ColNum — номер цвета;
Redval, Greenval, Blueval — выражения типа integer, устанавливающие интенсивность соответственно красной, зеленой и синей составляющих цвета.
Эта процедура может работать только с дисплеем IBM 8514, а также с адаптером VGA, рассчитанным на видеопамять объемом 256 Кбайт. В первом случае параметр ColNum задается числом в диапазоне 0-255, во втором — в диапазоне 0-15. Для установки интенсивности используются 6 старших разрядов младшего байта любого из параметров Redval, Greenval, Blueval.
В следующей программе (пример 14.22) в центре экрана выводится прямоугольник белым цветом, после чего этот цвет случайно изменяется с помощью процедуры SetRGBPaiette. Для выхода из программы нужно нажать любую клавишу.
Пример 14.22
Uses Graph,CRT;
var
Driver, Mode, Err, xl, yl: Integer;
begin
( Инициализируем графический режим) Driver := Detect;
InitGraph(Driver, Mode, fl J .
Err := GraphResult;
if Err<>0 then
WriteLn(GraphErrorMsg(Err))
else if Driver in [IBM8514, VGA] then
begin
{Выводим прямоугольник в центре экрана}
xl := GetMaxX div 4 ;
yl : = GetMaxY div 4;
SetColor(15);
Bar(xl,yl,3*xl,3*yl);
{Изменяем белый цвет на случайный}
while not KeyPressed do
SetRGBPalette(15,Random(256),Random(256),Random(256));
CloseGraph
end
else
begin
CloseGraph;
WriteLn(’Адаптер не поддерживает
RGB-режимуправления цветами')
end
end.
Процедура FloodFill. Заполняет произвольную замкнутую фигуру, используя текущий стиль заполнения (узор и цвет). Заголовок:
Procedure FloodFill(X, Y: Integer; Border: Word);
Здесь:
x, Y — координаты любой точки внутри замкнутой фигуры;
Border — цвет граничной линии.
Если фигура не замкнута, заполнение "разольется" по всему экрану.
Следует учесть, что реализованный в процедуре алгоритм просмотра границ замкнутой фигуры не отличается совершенством. В частности, если выводятся подряд две пустые строки, заполнение прекращается. Такая ситуация обычно возникает при заполнении небольших фигур с использованием типа LtSlashFill. В фирменном руководстве по Турбо Паскалю рекомендуется, ПО ВОЗМОЖНОСТИ, вместо Процедуры FloodFill использовать FillPoly (заполнение прямоугольника).
Следующая программа (пример 14.23) демонстрирует заполнение случайных окружностей. Сначала в центре экрана создается окно, в котором заполня-
ется небольшой прямоугольник. Часть прямоугольника останется незаполненной, в чем вы можете убедиться, т. к. программа в этот момент приостанавливает работу, ожидая нажатия на клавишу <Enter>. Затем осуществляется вывод и заполнение случайных окружностей до тех пор, пока не будет нажата любая клавиша. Замечу, что прямоугольник заполняется полностью, если вместо типа LtSlashFill (косая штриховка линиями обычной толщины) используется slashFill (штриховка утолщенными линиями). Если программа будет работать достаточно долго, она может "зависнуть", что лишний раз свидетельствует о несовершенстве реализованного в ней алгоритма.
Uses Graph, CRT;
var
d, r, e, х, у, с : Integer;
begin
{Инициализируем графику)
dDetect;
InitGraph(d, r, 11 );
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
{Создаем прямоугольное окно}
х := GetMaxX div 4;
у := GetMaxY div 4;
Rectangle(х,у,3*x, 3*y)
SetViewPort(x+1,y+1,3*x-l,3*y-l,ClipOn);
{Демонстрируем заливку маленького прямоугольника) SetFillStyle(LtSlashFill,GetMaxColor);
Rectangle(0,0,8,20);
FloodFill(1,1,GetMaxColor);
OutTextXY(10,25,'Press Enter.. . ') ;
ReadLn;{Ждемнажатия Enter}
{Выводим окружности до тех пор, пока не будет нажата любая клавиша} repeat
{Определяем случайный стиль заливки) SetFillStyle(Random(12),Random(GetMaxColor+1));
{Задаем координаты центра и цвет окружности}
х := Random (GetMaxX div 2) ;
у := Random (GetMaxY div 2);
с := Random (succ(GetMaxColor));
SetColor(c);
(Выводим и заливаем окружность}
Circle (x, у, Random(GetMaxY div 5) );
FloodFill (x, у, с) until•
if ReadKey=#0 then x := ord(ReadKey); CloseGraph end end.
Процедура Bar. Заполняет прямоугольную область экрана. Заголовок:
Procedure Bar(Xl, Yl, X2, Y2: Integer);
Здесь Xl, Yl, X2, Y2 — координаты левого верхнего и правого нижнего углов закрашиваемой области.
Процедура закрашивает (но не обводит) прямоугольник текущим образцом узора и текущим цветом, которые устанавливаются процедурой SetFillStyle.
Следующая программа (пример 14.24) дает красивые цветовые эффекты (закраска случайных прямоугольников).
Uses Graph, CRT;
var
d, r, e : Integer;
begin
(Инициализируем графику)
d := Detect;
InitGraph(d, r, * ’);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
(Создаем.окно в центре экрана)
d := GetMaxX div 4;
Г :=div 4;
Rectangle(d,г,3*d,3*r);
SetViewPort(d+1,r+1,3*d-l,3*r-l,ClipOn);
{Цикл вывода и закраски случайных многоугольников)
repeat
SetFlllStyle(Random(12),Random(succ(GetMaxColor)));
Bar(Random(GetMaxX),Random(GetMaxY),
Random(GetMaxX),Random(GetMaxY));
until KeyPressed;
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
Процедура Bar3D. Вычерчивает трехмерное изображение параллелепипеда и закрашивает его переднюю грань. Заголовок:
Procedure Bar3D (Xl, Yl, X2, Y2, Depth: Integer; Top: Boolean);
Здесь:
Xl, Yl, X2, Y2 — координаты левого верхнего и правого нижнего углов передней грани;
Depth — третье измерение трехмерного изображения ("глубина") в пикселах;
Тор — способ изображения верхней грани.
Если параметр Тор имеет значение True, верхняя грань параллелепипеда вычерчивается, в противном случае — не вычерчивается (этот вариант используется для изображения поставленных друг на друга параллелепипедов, см. следующий пример). Значением этого параметра может быть одна из следующих констант, определенных в модуле Graph:
const
TopOnTrue;
TopOff = False;
При вычерчивании используется текущий стиль линий (setLinestyle) и текущий цвет (SetColor). Передняя грань заливается текущим стилем заполнения (SetFillStyle).
Процедура обычно применяется при построении столбиковых диаграмм. Следует учесть, что параллелепипед "прозрачен", т. е. за его незакрашенными фанями могут быть видны другие элементы изображения.
Следующая программа (пример 14.25) иллюстрирует различные аспекты применения процедурыBar3D.
Пример 14.25
Uses Graph,'CRT;
var
d, r, е: Integer;
begin
{Инициализируем графику)
d := Detect;
InitGraph(d, r,' ') ;
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
' {Столбик с верхней гранью:)
Bar3D(80,100,120,180,15,TopOn);
{Столбик без верхней грани:)
Bar3D(150,150,190,180,15,TopOff);
{Этот столбик "стоит" на следующем и прозрачен:)■
Bar3D(230,50,250,150,15,TopOn);
Bar3D(220,150,260,180,15,TopOn);
fy этого столбика нет верхней грани, и поэтому он
не мешает поставленному на него сверху:}
Bar3D (300, 150, 340, 180, 15, TopOff);
SetLineStyle(3,0,1);
SetColor(Yellow) ;
SetFillStyle(LtSlashFill,Yellow) ;
Bar3D (300, 50,340, 150, 15, TopOn);
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph;
end
end.
Процедура FillPoly. Обводит линией и закрашивает замкнутый многоугольник. Заголовок:
Procedure FillPoly(N: Word; var Coords);
Здесь:
□ N — количество вершин замкнутого многоугольника;
О coords — переменная типа PointType, содержащая координаты вершин.
Координаты верщин задаются парой значений типа integer: первое определяет горизонтальную, второе — вертикальную координаты. Для них можно использовать следующий определенный в модуле тип:
type
PointType = record
х, у : Integer
end;
Стиль и цвет линии контура задаются процедурами SetLineStyle и SetColor, тип и цвет заливки — процедурой SetFillStyle.
В примере 14.26 на экран выводятся случайные закрашенные многоугольники.
Uses Graph, CRT; var
d, r, e: Integer;
p : array [1. .6] of PointType;
n, k : Word;
begin
{Инициализируем графику) d := Detect;
InitGraph(d, r,11);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e) ) else
begin
{Создаем окно в центре экрана} d := GetMaxX div 4;
r := GetMaxY div 4 ;
Rectangle(d,r,3*d,3*r);
SetViewPort(d+1,r+1,3*d-l,3*r-l,ClipOn);
{Цикл вывода случайных закрашенных многоугольников} repeat
{Выбираем случайный цвет и узор)
SetFillStyle(Random(12),Random(succ(GetMaxColor)));
SetColor (Random(succ(GetMaxColor)));
{Назначаем случайные координаты}
nRandom (4) + 3;
for k := 1 to n do with p[k] do
begin
xRandom (GetMaxX div 2) ;
у := Random (GetMaxY div 2)
end;
FillPoly (n, p)until KeyPressed;FillPoly (n, p)until KeyPressed;{Выводими закрашиваем){Выводими закрашиваем)
FillPoly (n, p)
until KeyPressed;
FillPoly (n, p)
until KeyPressed;
{Выводими закрашиваем)
{Выводими закрашиваем)
if ReadKey=#0 then к :=
CloseGraph
end
end.
Процедура FillEllipse. Обводит линией и заполняет эллипс. Заголовок:
Procedure FillEllipse(X, Y, RX, RY: Integer);
Здесь:
□ x, Y — координаты центра;
О RX, RY — горизонтальный и вертикальный радиусы эллипса в пикселах.
Эллипс обводится линией, заданной процедурами SetLineStyle и SetColor, и заполняется с использованием параметров, установленных процедурой SetFillStyle.
Процедура sector. Вычерчивает и заполняет эллиптический сектор. Заголовок:
Procedure Sector(X, Y: Integer; BegA, EndA, RX, RY: Word);
Здесь BegA, EndA — соответственно начальный и конечный углы эллиптического сектора. Остальные параметры обращения аналогичны параметрам процедуры FillEllipse.
В следующей программе (пример 14.27) на экран выводятся случайные закрашенные эллипсы и секторы. Для выхода из программы нажмите любую клавишу.
iПример 14.27iПример 14.27
iПример 14.27
iПример 14.27
Uses Graph, CRT; var
d, r, e : Integer;
begin
(Инициализируем графику}
d Detect;
InitGraph(d, r, 11);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
(Создаем окно в центре экрана}
d : = GetMaxX div 4;
r := GetMaxY div 4;
Rectangle(d,r,3*d,3*r);
SetViewPort(d+1,r+1,3*d-l,3*r-l,ClipOn);
(Цикл вывода}
repeat
SetFillStyle(Random(12) , Random(succ(GetMaxColor)));
SetColor (Random(succ(GetMaxColor)));
Sector(Random(GetMaxX div),Random(GetMaxY div 2), Random(360),Random(360),Random(GetMaxX div 5), Random (GetMaxY div 5));
FillEllipse(Random(GetMaxX div 2),
Random(GetMaxY div 2),Random(GetMaxX div 5), Random(GetMaxY div 5))
until KeyPressed;
if ReadKey=#0 then d := ord(ReadKey) ;
CloseGraph
end
end.
Процедура PieSlice. Вычерчивает и заполняет сектор окружности. Заголовок: Procedure PieSlice(X, Y: Integer; BegA, EndA, R: Word);
В отличие от процедуры sector, указывается лишь один горизонтальный радиус R, остальные параметры аналогичны параметрам процедуры Sector.
Сектор обводится линией, заданной процедурами setLinestyle и Setcolor, и заполняется с помощью параметров, определенных процедурой SetFillStyle. Процедуру удобно использовать при построении круговых диаграмм, как, например, в программе из примера 14.28 (рис. 14.9).
Пример 14.28Пример 14.28
Пример 14.28
Пример 14.28
Uses Graph, CRT;
var
d, r, е : Integer;
begin
(Инициализируем графический режим) d := Detect;
Рис. 14.9. Иллюстрация процедуры PieSlice
InitGraph(d, r, * *);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e) )
else
begin
{Выводим маленький сектор) SetFillStyle(WideDotFill, White);
PieSlice(GetMaxX div 2+5,GetMaxY div 2+4,270,360,100);
{Выводим большой сектор) SetFillStyle (SolidFill, Red) ;
13 Зак 94 ’
PieSlice (GetMaxX div 2,GetMaxY div 2, 0,270,100);
(Выводим надписи}
OutTextXY (GetMaxX div 2+90,GetMaxY div 2+70, '25%');
OutTextXY(GetMaxX div 2-50,GetMaxY div 2-20,'75%');
(ЯСдем нажатия на любую клавишу)
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
14.7. Сохранение и выдача изображений
Функция Imagesize. Возвращает размер памяти в байтах, необходимый для размещения прямоугольного фрагмента изображения. Заголовок:
Function ImageSize(XI, Yl, X2, Y2: Integer): Word;
Здесь XI, Yl, X2, Y2 — координаты левого верхнего и правого нижнего углов фрагмента изображения.
Процедура Getlmage. Помещает в память копию прямоугольного фрагмента изображения. Заголовок:
Procedure Getlmage(XI, Yl, Х2, Y2: Integer; var Buf)
Здесь:
XI, Yl, X2, Y2 — координаты углов фрагмента изображения;
Buf — переменная или участок кучи, куда будет помещена копия видеопамяти с фрагментом изображения.
Размер Buf должен быть не меньше значения, возвращаемого функцией imageSize с теми же координатами XI, Yl, Х2, Y2.
Процедура Putimage. Выводит в заданное место экрана копию фрагмента изображения, ранее помещенную в память процедурой Getimage. Заголовок:
Procedure PutImage(X, Y: Integer; var Buf; Mode: Word);
Здесь:
x, Y — координаты левого верхнего угла того места на экране, куда будет скопирован фрагмент изображения;
Buf — переменная или участок кучи, откуда берется изображение;
Mode — способ копирования.
Как видим, координаты правого нижнего угла не указываются, т. к. они полностью определяются размерами вновь выводимой на экран копии изо-
бражения. Координаты левого верхнего угла могут быть какими угодно, лишь бы. только выводимая копия уместилась в пределах экрана (если копия не может разместиться на экране, она не выводится и экран остается без изменений).
Параметр Mode определяет способ взаимодействия вновь размещаемой копии с уже имеющимся на экране изображением. Взаимодействие осуществляется путем применения кодируемых этим параметром логических операций к каждому биту копии и изображения. Для указания применяемой логической операции можно выбрать одну из следующих предварительно определенных констант:
const
N о ппа 1 Put= 0; (Замена существующего изображения на копию)
XorPut = I; {Исключительное ИЛИ}
OrPut= 2; {Объединительное ИЛИ)
AndPut = 3;{Логическое И)
NotPut = 4;{Инверсия изображения)
Наиболее часто используются операции NormalPut, XORPut и NotPut. Первая из них просто стирает часть экрана и на это место помещает копию из памяти в том виде, как она там сохраняется. Операция NotPut делает то же самое, но копия выводится в инверсном виде. Для монохромного режима это означает замену светящихся пикселов на темные и наоборот. В цветном режиме операция NotPut применяется к коду цвета каждого пиксела.
Рис. 14.10. Иллюстрация работы процедур Get Image/Put Image
Например, для white (код 15 или в двоичном виде 1111) эта операция даст КОД 0000 = 0 = Black, ДЛЯ Red = 4 = 0100 ПОЛУЧИМ 1011 = 11 = LightCyan и т. д. Операция xoRPut, примененная к тому же месту экрана, откуда была получена копия, сотрет эту часть экрана. Если операцию применить дважды к одному и тому же участку, вид изображения на экране не изменится. Таким способом можно довольно просто перемещать изображения по экрану, создавая иллюзию движения.
Следующая программа (пример 14.29) рисует "Неопознанный летающий объект" — летающую тарелку на звездном фоне (рис. 14.10).
®SiS
Uses Graph, CRT;
const
г = 20; {Характерныйразмер НЛО)
pause ■■■ 50; {Длительность паузы} var
d,m, е, xm,ym,x,y, 1х,1у,rx, ry, Size,i,dx,dy,Width,Height: Integer;
Saucer : Pointer;
label
loop;
begin
{Инициализируем графику)
d Detect;
InitGraph(d, m, ' 1;
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
x := r*5;
у :=-r*2;
xm := GetMaxX div 4;
ym := GetMaxY div 4;
{Создаем "тарелку" из двух эллипсов с "усами" антенн} Ellipse (х,у, О, 360, Г, г div 3+2)
Ellipse (х, у-4,190, 357, г, г div 3) ;
Line (х+7,у-6,х+10, у-12) ;
\
Line (x-7,y-6, Circle Circle
FloodFill(x+1, y+4,White); габариты НЛО и помещаем его в 1х := 1у := := :=div3+3;
Width-+1;
ry —+1;
:=ly, rx, ry)
(Saucer, ly, rx, ry, {Стираем построенное} ly, звездное
Rectangle(xm,ym,3*xm,3*ym);
SetViewPort(xm+1,ym+1,3*xm-l,3*ym-l, ClipOn); xm := ym := for i:=l to 200 do PutPixel начальное положение НЛО и направление х := xm div • у := ym div dx10;
:= {Основной цикл} repeat НЛО
Delay (pause)месте и
Putlmage (х, у,стираем
новые loop: . х := у достиг границы if (x<0) or (x+Width+l>xm) or ''
(у<0) or (y+Height+l>ym) then
begin (Да — НЛО достиг границы: меняем направление его перемещения} х := x-dx; :=
dx GetMaxX div 10 - Random(GetMaxX div 5);
dy := GetMaxY div 30 - Random(GetMaxY div 15);
goto loop
end
until KeyPressed;
if ReadKey=#0 then x : = ord(ReadKey);
CloseGraph
end
end.
I
14.8. Вывод текста
Описываемые ниже стандартные процедуры и функции поддерживают вывод текстовых сообщений в графическом режиме. Это не одно и то же, что использование процедур Write или WriteLn. Дело в том, что специально для графического режима разработаны процедуры, обеспечивающие вывод сообщений различными шрифтами в горизонтальном или вертикальном направлении, с изменением размеров и т. д. Однако в стандартных шрифтах, разработанных для этих целей фирмой Borland, отсутствует кириллица, что исключает вывод русскоязычных сообщений.
С другой стороны, процедуры write и WriteLn после загрузки в память второй половины таблицы знакогенератора (а эта операция легко реализуется в адаптерах EGA и VGA) способны выводить сообщения с использованием национального алфавита, но не обладают мощными возможностями специальных процедур.
Ниже описываются стандартные средства модуля Graph для вывода текста.
Процедура OutText. Выводит текстовую строку, начиная с текущего положения указателя. Заголовок:
Procedure OutText(Txt: String);
Здесь Txt — выводимая строка.
При горизонтальном направлении /вывода указатель смещается в конец выведенного текста, при вертикальном — не меняет своего положения. Строка выводится в соответствии с установленный стилем и выравниванием. Если текст выходит за границы экрана, то при употреблении штриховых шрифтов он отсекается, а в случае стандартного шрифта не выводится.
Процедура OutTextXY. Выводит строку, начиная с заданного места. Заголовок:
Procedure OutTextXY (X, Y: Integer; Txt: String);
Здесь:
x, Y — координаты точки вывода;
Txt — выводимая строка.
Отличается от процедуры outText только координатами вывода. Указатель не меняет своего положения.
Процедура SetTextStyle. Устанавливает стиль текстового вывода на графический экран. Заголовок:
Procedure SetTextStyle(Font, Direct, Size: Word);
Здесь:
Font — код (номер) шрифта;
Direct — код направления;
Size — код размера шрифта.
Для указания кода шрифта имеются следующие предварительно определенные константы:
const
DefaultFont = 0; {Точечный шрифт 8x8}
TriplexFont = 1 ; {Утроенный шрифт TRIP.CHR)
Small Font= 2; { Уменьшенный шрифт LITT.CHR)
SansSerifFont = 3; (Прямой шрифт SANS.CHR}
GothicFont= 4 ; {Готический шрифт GOTH.CHR}
Замечу, что эти константы определяют все шрифты для версий 4.0, 5.0, 5.5 и 6.0. В версии 7.0 набор шрифтов значительно расширен, однако для новых шрифтов не предусмотрены соответствующие мнемонические константы. В этой версии помимо перечисленных вы можете при обращении к процедуре SetTextstyie задать номера шрифтов, перечисленные в табл. 14.1.
Таблица 14.1. Варианты шрифтов для SetTextstyie
Номер
Файл
Краткое описание
5
scri.chr
"Рукописный" шрифт
6
simp.chr
Моноширинный шрифт типа Courier
7
tscr.chr
Красивый наклонный шрифт типа Times начертания Italic
8
Icom.chr
Шрифт типа Times New Roman
Таблица 14.1 (окончание)
Номер
Файл
Краткое описание
9
euro.chr
Шрифт типа Courier увеличенного размера
10
bold.chr
Крупный двухштриховый шрифт
Шрифт DefaultFont ВХОДИТ В МОДУЛЬ GRAPH И доступен В ЛЮбоЙ момент. Это — единственный матричный шрифт, т. е. его символы создаются из матриц 8x8 пикселов. Все остальные шрифты — векторные: их элементы формируются как совокупность векторов (штрихов), характеризующихся направлением и размером. Векторные шрифты отличаются более богатыми изобразительными возможностями, но главная их особенность заключается в легкости изменения размеров без существенного ухудшения качества изображения. Каждый из этих шрифтов размещается в отдельном дисковом файле. Если вы собираетесь использовать какой-либо векторный шрифт, соответствующий файл должен находиться в вашем каталоге, в противном случае вызов этого шрифта игнорируется и подключается стандартный.
Замечу, что шрифт DefaultFont создается графическим драйвером в момент инициации графики на основании анализа текстового шрифта. Поэтому, если ваш ПК способен выводить кириллицу в текстовом режиме, то вы сможете с помощью этого шрифта выводить русскоязычные сообщения и в графическом режиме. В случае других шрифтов эта возможность появляется только после их модификации.
Для задания направления выдачи текста можно использовать константы:
const/
HorizDir = 0;{ Слева направо)
VertDir = 1; {Снизу вверх}
Как видим, стандартные процедуры outText и outTextXY способны выводить сообщения лишь в двух возможных направлениях — слева направо или снизу вверх. Зная структуру векторных шрифтов, не трудно построить собственные процедуры вывода, способные выводить сообщения в любом направлении.
Каждый шрифт способен десятикратно изменять свои размеры. Размер выводимых символов кодируется параметром size, который может иметь значение в диапазоне от 1 до 10 (точечный шрифт — в диапазоне от 1 до 32). Если значение параметра равно 0, устанавливается размер 1, если больше ю — размер 10. Минимальный размер шрифта, при котором еще отчетливо различаются все его детали, равен 4 (для точечного шрифта — 1).
Следующая программа (пример 14.30) демонстрирует различные шрифты. Их размер выбран так, чтобы строки имели приблизительно одинаковую
‘W MWAYT^l^t vWVi^ V‘W MWAYT^l^t vWVi^ Vвысоту. Перед исполнением профаммы скопируйте все шрифтовые файлы с расширением CHR в текущий каталог.
‘W MWAYT^l^t vWVi^ V
‘W MWAYT^l^t vWVi^ V
14.30
TC*w «1 ее• « «Йе e* • e". . ... Л . • *Wй ЛИТСееЛТОе'Я jto Ле eWei?*» Лее Л .V.
Graph,
const
arrayof String [4] =
('TRIP','LITT','SANS','GOTH',
'SCRI','SIMP','TSCR','LOOM','EURO','BOLD');
50;
Tab2 = 150;
Tab3 = 220;
var
для
вывода и ее
Size,{Размер символов}
номер
k: Integer;
String;
с: Char;
1
Procedure
строкис учетом позиций табуляции
begin
MoveTo ((ТаЫ-TextWidth (SI)) div2,Y) ;
OutText(SI);
div
OutText(32);
div
OutText(S3);
ifthenколонки
div
else
MoveTo(Tab3+3,Y);
OutText(S4)
end;
/ — -
begin
{Инициализируем графику)
InitGraph (d, г, ' ’);
Err := GraphResult;
if ErrogrOk then
WriteLn(GraphErrorMsg(Err))
else
begin
{Определяем количество шрифтов:)
($IFDEF VER70)
MaxFont : = 10;
(SELSE).
MaxFont 4;
($ENDIF)
SetTextStyle(1,0,4);
Y := 0;
OutTextWithTab ('N', 'Name', 'Size' / Symbols') ;
{Определяем высоту Y линии заголовка}
Y : = 4*TextHeight (' Z') div 3;
Line(0,Y,GetMaxX,Y);
{Определяемначало Y таблицы и высоту dY каждой строки)
Y := 3*TextHeight(' Z') div 2;
dY := (GetMaxY-Y) div (MaxFont);
(Готовим строку символов)
SymbT : = 1 1 ;
for с := ’ a1 to 1 z1 do
SymbT := SymbT+c;
(Цикл вывода строк таблицы) .
for k := 1 to MaxFont do
begin
Size := 0;
{Увеличиваемразмер до тех пор, пока высота строки не станет приблизительно равна dY) repeat
inc(Size) ;
SetTextStyle(к,0,Size+1);
until (TextHeight('Z')>=dY) or (Size=10) or (Textwidth(FontNames[k])>(Tab2-Tabl));
{Готовим номер NT и размер SizeT шрифта} Str (к, NT);
Str (Size,SizeT);
{Выводим строку таблицы}
SetTextStyle(к,HorizDir,Size);
OutTextWithTab(NT,FontNames[k],SizeT,SymbT); inc(Y,dY) end;
{Рисуем линии рамки}
Rectangle(0,0,GetMaxX,GetMaxY);
Line(Tabi,0,Tabi,GetMaxY);
Line(Tab2,0,Tab2,GetMaxY);
Line(ТаЬЗ,0,ТаЬЗ,GetMaxY);
{Ждем инициативы пользователя}
ReadLn;
CloseGraph
end
end.
Процедура SetTextJustify. Задает выравнивание выводимого текста по отношению к текущему положению указателя или к заданным координатам. Заголовок:
Procedure SetTextJustify(Horiz, Vert: Word);
Здесь:
□ Horiz — горизонтальное выравнивание;
О vert — вертикальное выравнивание.
Выравнивание определяет, как будет размещаться текст — левее или правее указанного места, выше, ниже или по центру. Для этого предусмотрены такие константы:
const
LeftText = 0; (Указатель слева от текста}
CenterText=1; {Симметрично слева и справа, сверху и снизу}
RightText = 2; {Указатель справа от текста}
BottomText= 0; (Указатель снизу от текста}
TopText= 2; {Указатель сверху от текста}
Обратите внимание на неудачные, с моей точки зрения, имена мнемонических констант: если, например, вы зададите LeftText, что в переводе озна-
чает "Левый Текст", сообщение будет расположено справа от текущего положения указателя (в случае процедуры outTextxY — справа от заданных координат). Так же "наоборот" трактуются и остальные константы.
Следующая программа (пример 14.31) иллюстрирует различные способы выравнивания относительно центра графического экрана.
*y*^i^yi e^j^t^y •’' * V4|Wy I^WWttWWWjSiil^MWyy WiMVf ? 1УГ2У1'j* У 1ДК2 У ‘У^К^^ Цi6’1»"'*.'*"^«v; * ’Ц^*' *■"•'•'’ *• w у у * *^y ^ yj •jjj^y ।* * ‘
: Пример 14.31;
n*KkWf*.*K JR«flVnMCwHy«^wv№i^^^M Й^ЛеЛИнЭМкЯ i ri 7 Ял • «7г? Ял,.
Uses Graph, CRT; var
d, г, е : Integer;
begin
(Инициализируем графику}
d := Detect;
InitGraph(d, r, ’ ’) ;
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
{Выводим перекрестие линий в центре экрана)Line(0,GetMaxY div 2,GetMaxX,GetMaxY div 2);
Line(GetMaxX div 2,0,GetMaxX div 2,GetMaxY);
{Располагаем текст справа и сверху от центра)
SetTextStyle(TriplexFont,HorizDip, 3) ;
SetTextJustify(LeftText,BottomText);
OutTextXY (GetMaxX div 2, GetMaxY div 2, 1LeftText,BottomText');
{Располагаем текст слева и снизу} SetTextJustify (RightText, TopText);
OutTextXY (GetMaxX div 2, GetMaxY div 2, 'RightText, TopText');
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
Процедура setusercharsize. Изменяет размер выводимых символов в соответствии с заданными пропорциями. Заголовок:
Procedure SetUserCharSize(XI, Х2, Yl, Y2: Word);
Здесь XI, Yl, Х2, Y2 — выражения типа Word, определяющие пропорции по горизонтали и вертикали.
Процедура применяется только по отношению к векторным шрифтам. Пропорции задают масштабный коэффициент, показывающий, во сколько раз увеличится ширина и высота выводимых символов по отношению к стандартно заданным значениям. Коэффициент по горизонтали находится как отношение XI к Х2, по вертикали — как отношение Yl к Y2. Чтобы, например, удвоить ширину символов, необходимо задать Xl=2 и Х2=1. Стандартный размер символов устанавливается процедурой setTextstyle, которая отменяет предшествующее ей обращение к setusercharsize.
В примере 14.32 демонстрируется изменение пропорций уменьшенного шрифта.
v^** i**e*n* **i ••**Mfiiiiti**“iiii*iiMiiiiiiMiiiMMm*iMii*iiViMi6***iii,““*,,e**if.•**мм***“**ммт“*м1****“"*н11ввмммг*11*1г те еммееемееешымеен ; Пример 14.32
•.... .v^^^?f«.vH№f>f«Z?1vm^WfN^^?!w9^W^^WWntT«nW)«^r?nf.1X. if ЛкЯж •. "Лт1»»еЯ#ЯК1«Ж • •mvwttKWwMiW^W^^^^W^^R^Ki^ff^^^^^^^^^^^^^
Uses Graph, CRT;
var
d, r, е : Integer;
begin
(Инициализируем графику}
d Detect; InitGraph(d, r, '1);
e := GraphResult;
if e <> grOk then
WriteLn(GraphErrorMsg(e))
else
begin
MoveTo (0, GetMaxY div 2); .
SetTextstyle (SmallFont, HorizDir, 5);
SetTextJustify (LeftText, BottomText) ;
{Выводим сообщение стандартной высотой 5}
OutText ('Normal Width, ') ;
(Удваиваем ширину шрифта)
SetUserCharSize (2, 1, 1, 1);
OutText (' Double Width, ') ;
(Удваиваем высоту, возвращаем стандартную ширину}
SetUserCharSize (1, 1, 2, 1);
OutText (' Double Height, 1) ;
SetUserCharSize (2, 1, 2, 1) ;
OutText (' Double Width and Height');
if ReadKey=#0 then d := ord(ReadKey);
CloseGraph
end
end.
Функция Textwidth. Возвращает длину в пикселах выводимой текстовой строки. Заголовок:
Function Textwidth (Txt: String): Word;
Учитываются текущий стиль вывода и коэффициенты изменения размеров символов, заданные соответственно процедурами SetTextstyle и SetUserCharSize.
Функция TextHeight. Возвращает высоту шрифта в пикселах. Заголовок:
Function TextHeight(Txt: String): Word;
Процедура GetTextsettings. Возвращает текущий стиль и выравнивание текста. Заголовок:
Procedure GetTextSettins(var Textinfo: TextSettingsType);
Здесь Textlnfo — переменная типа TextSettingsType, который в модуле GRAPH определен следующим образом:
type
TextSettingsType = record
Font
: Word;
(Номер шрифта}
Direction:
Word;
{Направление}
CharSize
: Word;
{Код размера)
Horiz
: Word;
(Горизонтальное выравнивание}
Vert
: Word
{Вертикальное выравнивание)
end;
Функция InstallUserFont. Позволяет программе использовать нестандартный векторный шрифт. Заголовок функции:
Function InstallUserFont(FileName: String): Integer;
Здесь FileName — имя файла, содержащего векторный шрифт.
Как уже говорилось, в стандартную поставку Турбо Паскаля версий 4.0— 6.0 включены три векторных шрифта, для версии 7.0 — 10. Функция instaiiuserFont позволяет расширить этот набор. Функция возвращает
идентификационный номер нестандартного шрифта, который может использоваться при обращении к процедуре SetTextStyle.
Функция InstallUserDriver. Включает нестандартный графический драйвер в систему BGI-драйверов. Заголовок функции:
Function InstallUserDriver(FileName: String; AutoDetectPtr: Pointer): Integer;
Здесь:
FileName — имя файла, содержащего программу драйвера;
AutoDetectPtr — адрес точки входа в специальную процедуру автоопределения типа дисплея, которая в числе прочих процедур должна входить в состав драйвера.
Эта функция расширяет и без того достаточно обширный набор стандартных графических драйверов и предназначена в основном для разработчиков аппаратных средств.
14.9. Включение драйвера и шрифтов в тело программы
В Турбо Паскале имеется возможность включения графического драйвера и штриховых шрифтов непосредственно в тело программы. Такое включение делает программу независимой от местоположения и наличия на диске драйверов и шрифтов, а также ускоряет подготовку графических программ к работе (шрифты и драйвер загружаются вместе с программой).
Включение драйвера и шрифтов осуществляется по следующей общей схеме. Сначала с помощью вспомогательной программы BINOBJ.EXE, входящей в комплект поставки Турбо Паскаля, драйвер и шрифты преобразуются в OBJ-файл. Для этого вне среды Турбо Паскаля необходимо вызвать утилиту BINOBJ с тремя параметрами: именем преобразуемого файла, именем получаемого OBJ-файла и глобальным именем процедуры. Эти имена, в принципе, могут быть произвольными, правильными для MS-DOS именами. Например:
c:\tp\binobj cga.bgi cga cgadrv
В результате такого обращения из каталога ТР на диске С: будет вызвана программа BINOBJ и ей будут переданы следующие параметры:
□ CGA.BGI — имя файла с преобразуемым драйвером;
□ CGA — имя файла с расширением OBJ, т. е. CGA.OBJ, который будет получен в результате исполнения программы BINOBJ;
□ CGADRV — глобальное имя, под которым этот драйвер будет известен программе.
382Часть I. Ядро Турбо Паскаля
—— . —. f
После этого можно написать следующий фрагмент программы:
Uses Graph;
Procedure CGADRV; external;
($L CGA.OBJ}
var
d, r, e : Integer;
begin
if RegisterBGIDriver (@CGADRV) < 0 then
begin
WriteLn ('Ошибка при регистрации драйвера'); /
halt
end;
d := CGA; r := CGAHi;
InitGraph (d, гЛ ");
Как видно из этого примера, в программе объявляется внешняя процедура с именем CGADRV (глобальное имя, указанное при обращении к BINOBJ), причем дается директива компилятору отыскать в текущем каталоге и загрузить файл CGA.OBJ, в котором находится эта процедура. Затем осуществляется регистрация драйвера путем обращения к функции RegisterBGIDriver. Единственным параметром этой функции является адрес начала драйвера в памяти (@ CGADRV). Функция возвращает значение типа integer, которое служит для контроля правильности завершения процедуры регистрации драйвера: если это значение меньше нуля, обнаружена ошибка, в противном случае функция возвращает номер зарегистрированного драйвера. В примере контролируется правильность регистрации драйвера и, если ошибка не обнаружена, инициируется графический режим работы экрана.
Аналогичным образом можно присоединить к программе стандартные штриховые шрифты (матричный шрифт 8x8 входит в состав модуля GRAPH и поэтому присоединять его не надо). Присоединение шрифта строится по описанной схеме за тем исключением, что для его регистрации вызывается функция RegisterBGiFont. Например, после преобразования
c:\Pascal\binobj litt.chr litt litt
можно использовать операторы
Procedure Litt; External;
{ $L Litt.obj}
if RegisterBGiFont (@litt) < 0 then
Обратите внимание, что регистрация и драйвера, и шрифтов должна предшествовать инициализации графического режима.
Регистрировать можно также драйверы (шрифты), "которые не компилируются вместе с программой, а зафужаются в динамическую память. Например:
Uses Graph;
var
р: Pointer;
f: file;
begin
Assign(f,'Litt.chr');(Открываем файл}
Reset(f,l);(LITT.CHRдля чтения}
GetMem(p,Filesize(f));{Резервируемдля него
область кучи нужного размера)
(Читаем файл}{Регистрируем шрифт)(Читаем файл}{Регистрируем шрифт)BlockRead(f,pA,Filesize(f)Y;
(Читаем файл}
{Регистрируем шрифт)
(Читаем файл}
{Регистрируем шрифт)
WriteLn(RegisterBGIFont(p))
end.
ЧАСТЬ II
БИБЛИОТЕКА
TURBO VISION
Глава 15
Введение в Turbo Vision
В этой главе мы попробуем разработать программу, которая использует некоторые возможности Turbo Vision. Пусть, например, нам необходимо создать простейшую информационную систему — нечто вроде электронной записной книжки. Предполагается, что данные, необходимые этой системе, будут храниться в виде записей в дисковом файле. Наша задача — разработать удобную диалоговую программу, облегчающую доступ к файловым данным.
Разработка программы, разумеется, не является самоцелью — ведь для нас это только повод для знакомства с Turbo Vision. Поэтому мы будем создавать программу постепенно, каждый раз фиксируя достигнутые результаты. Если вас интересует собственно информационная программа, то вы можете сразу ознакомиться с ее окончательным вариантом, приведенным в приложении 5.
Простейшая программа в Turbo Vision
Работа большинства прикладных программ проходит в три этапа: подготовка к работе, собственно работа и, наконец, ее завершение. В нашем случае к подготовительному этапу можно отнести такие действия, как анализ существования файла данных и его (файла) подготовка к работе. На этапе завершения мы должны обеспечить необходимые действия по сохранению файла. Все остальные действия относятся к среднему этапу. С учетом этого можно написать следующую простейшую программу:
begin
(Подготовить работу программы}
{Выполнить необходимые действия)
{Завершить исполнение программы) end.
Если вы попытаетесь выполнить эту программу, ничего не произойдет — ведь мы еще никак не конкретизировали необходимые действия. Так обстоит дело в Турбо Паскале, но не так — в Turbo Vision! Для любой прикладной программы Turbo Vision сразу же создает некоторую минимальную про-
граммную реализацию, которую вы можете затем постепенно наращивать в ходе детализации программы. Вот начальный вариант программы с использованием Turbo Vision:
Uses Арр; {Используетсямодуль АРР библиотеки Turbo Vision)
var
Notebook: TApplication;
begin
Notebook.Init;{Подготовить работу программы)
Notebook.Run;(Выполнить необходимые действия}
Notebook.Done {Завершить исполнение программы) end.
В этой программе объявляется использование стандартного для Turbo Vision модуля Арр (от application — приложение, прикладная программа). Такое объявление открывает доступ прикладной программе к мощным возможностям Turbo Vision. Чтобы реализовать эти возможности, мы объявили переменную Notebook (notebook — записная книжка) типа TApplication. Как вы вскоре заметите, на букву Т в Turbo Vision начинаются идентификаторы объектов. Таким образом, Notebook — это экземпляр объекта TApplication, т. е. объединение данных (полей) и методов обработки этих данных (процедур, функций, конструкторов, деструкторов). В объекте TApplication предусмотрены методы Init, Run и Done. Вызов этих методов и составляет исполняемую часть нашей программы. Если вы подготовите И запустите программу, на экране ПК появится изображение, показанное на рис. 15.1.
Рис. 15.1. Вид экрана для простейшей программы
Для выхода из программы необходимо, как это следует из надписи в левом нижнем углу экрана, нажать клавиши <Alt>+<X> или подвести к этой надписи указатель мыши (если, разумеется, ваш ПК оснащен этим устройством) и нажать ее левую кнопку.
Как видите, даже простейшая программа "знает", как создать экран, распознает команду <Alt>+<X> и может работать с мышью. Совсем не плохо для трех исполняемых операторов, не так ли? Такие возможности доступны потому, что в объекте TAppiication предусмотрены соответствующие методы. В этом смысле работа с объектами напоминает использование подпрограмм из библиотек. Однако в отличие от подпрограммы любой объект имеет все необходимые ему данные. Говоря об объектах, я часто буду употреблять такие слова, как "знает", "умеет", "может", подчеркивая тем самым главную отличительную особенность объектов от традиционных подпрограмм — их "разумность". Последовательное проведение в жизнь принципа инкапсуляции (объединения) данных и всех необходимых для их обработки методов придает объекту определенную независимость от других элементов программы; объекты как бы "живут" в программе своей независимой жизнью.
Простейшая программа не может выполнять никаких других действий, кроме уже перечисленных, т. к. именно эти действия запрограммированы в методах Init и Run объекта TApplication. В ходе их выполнения на экране создается изображение, имеющее три зоны: верхняя строка, нижняя строка и вся остальная часть экрана. Верхняя строка обычно служит для размещения опций главного меню (не забывайте, что Turbo Vision — это оболочка для диалоговых программ!). Нижняя строка — строка статуса: в ней указываются так называемые командные клавиши, т. е. клавиши или комбинации клавиш, которые вызывают нужные действия без перехода к промежуточному диалогу. Вся остальная часть экрана составляет "рабочий стол" профам- мы — сюда будут помещаться сообщения, здесь будут размещаться окна, "всплывающие" меню (меню нижнего уровня) и т. п.
Формирование строки статуса
Стандартный вид экрана, показанный на рис. 15.1, можно изменять. Попробуем придать ему некоторый специфический для нашей программы вид. Например, заменим в строке статуса стандартное сообщение
Alt-X Exit
на русифицированное
Alt-X Выход
Таким образом, нам необходимо модифицировать стандартное поведение объекта Notebook. Для этого мы должны отыскать в типе TAppiication метод, ответственный за создание строки статуса. Если мы обратимся к при-
ложению 5, то обнаружим, что объект типа TApplication содержит методы Init и Done, с помощью которых создаются и уничтожаются экземпляры объекта, но в нем нет метода, ответственного за строку статуса. Однако из таблицы наследования нетрудно определить, что этот метод (initStatusLine) он наследует от своего родителя TProgram. Как изменить работу метода? В рамках объектно-ориентированной библиотеки для этого поступают следующим образом: объявляется объект-потомок от стандартного объекта, поведение которого необходимо изменить, и в новом объекте описывается свой метод, ответственный за это поведение.
Изменим программу следующим образом:
Uses App, Objects, Menus, Drivers, Views;
type
TNotebook = object (TAppiication) {Создаем объект-потомок
от TApplication}
Procedure InitstatusLine; Virtual; {Перекрываем старый
метод InitstatusLine новым}
end;
г!
Procedure TNotebook.InitStatusLine;
{Описание нового метода, с помощью которого
создается строка
var
R: TRect; {Границы строки статуса)
begin
GetExtent (R) ;{Получаемв R координаты всего экрана}
R.A.Y := pred(R.B.Y) ; {Помещаемв R координаты строки
статуса}
(Создаем строку статуса:).
StatusLine :=
{Определяем один вариант строки статуса:}'
NewStatusDef(О, $FFFF,{Устанавливаемдля этого варианта
максимальный диапазон контекстной справочной службы} { Определяем единственную клавишу Alt-X:}
NewStatusKey('~Alt-X~ Выход',kbAltX,cmQuit,
NIL), (Нет других клавиш}
NIL){Нет других строк статуса}
))
end; {TNotebook.InitStatusLine}
{)
var
Notebook: TNotebook; {.Изменен тип переменной!}
begin
Notebook.Init;
Notebook.Run;
Notebook.Done.
end.
Как видим, программа сразу же усложнилась. Во-первых, в ней имеются идентификаторы, которые определены в других модулях Turbo Vision, — эти модули мы перечислили в предложении uses. Во-вторых, нам потребовалось объявить новый объект TNotebook как потомок от объекта TApplication. Объект-потомок наследует от своего объекта-родителя все поля и методы и при необходимости может их дополнять своими полями и методами, а также перекрывать методы родителя. Как раз для того, чтобы перекрыть унаследованный от TProgram стандартный метод InitStatusLine, ответственный за создание строки статуса, нам и понадобилось объявление нового типа TNotebook. Строка
Procedure InitstatusLine; Virtual;
в объявлении этого типа указывает, что новый объект будет пользоваться одноименным, но иным, чем объект-родитель, методом. Возможность замены методов на одноименные, но с другим содержанием, называется полиморфизмом.
Процедура TNotebook. initstatusLine раскрывает суть нового метода. В ней используется обращение к методам NewstatusDef и NewStatusKey, с помощью которых создается динамический объект типа TStatusLine. Программа TAppiication обращается к методам этого объекта для обслуживания строки статуса.
Turbo Vision позволяет определять несколько вариантов строки статуса. Каждый вариант создается с помощью метода NewStatusDef. В зависимости от текущего состояния программы (от контекста программы) Turbo Vision автоматически помещает в строку статуса нужный вариант. Так как в нашей программе определен единственный вариант строки статуса, мы указали максимально возможный диапазон контекста программы при обращении к Методу NewStatusDef.
С помощью метода NewStatusKey в строке статуса определяется очередная командная клавиша. При обращении к методу сначала указывается текст, высвечиваемый в строке статуса, причем символом - выделяется та часть сообщения, которая будет подсвечена в строке статуса другим цветом: таким способом в Turbo Vision указываются командные клавиши. Идентификатор kbAltx задает комбинацию клавиш, a cmQuit — связанную с ней команду.
В программе объявляется переменная R типа TRect. С помощью такого типа переменных программист задает координаты прямоугольного участка экрана. Эта переменная необходима нам для указания того места на экране, где будет помещено сообщение
Alt-X Выход
определяющее командные клавиши <Alt>+<X>. Для правильного задания этих координат мы дополнительно ввели два предложения:
GetExtent(R);
R.A.Y :=
В первом вызывается стандартный метод Turbo Vision GetExtent, с помощью которого в R помещаются координаты доступной в данный момент части экрана. Во втором — номер той строки (строки статуса), куда будет выводиться сообщение. В Turbo Vision тип TRect объявляется в виде следующей записи:
type
TRect = record
record {Координаты верхнего левого угла}
X: Byte;
Y: Byte
end;
record {Координатыправого нижнего угла}
X: Byte;
Y: Byte
end
end;
Таким образом, второе предложение лишь уменьшает на единицу вертикальную координату самой нижней доступной строки и устанавливает полученное значение в поле R.A.Y (это поле задает вертикальную координату верхнего левого угла прямоугольного участка). Заметим, что в Turbo Vision минимальные координаты задаются значением 0, в то время как в стандартном модуле CRT Турбо Паскаля минимальные координаты имеют значение 1.
Обратите внимание на характерный прием, широко используемый в Turbo Vision: при обращении к методам NewStatusDef и NewStatusKey последним параметром указывается переменная типа Pointer. Внутри методов эта переменная трактуется как ссылка на новый метод, что позволяет организовать цепочку последовательных определений. Вложенная последовательность вызовов заканчивается зарезервированной константой NIL, указывающей на конец цепочки. Если бы мы, например, захотели добавить
в строку статуса определение клавиши <F10>, связав ее с закрытием активного окна, мы могли бы использовать такую конструкцию:
NewStatusDef(О, $FFFF,
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,
NewStatusKey('~F10~ Закрыть окно', kbFlO, cmClose,
NIL) ) ,(Нет других клавиш)
NIL){Нет других определений)
Описанный пример позволяет нам сделать очень важный вывод: чтобы модифицировать стандартное поведение объекта, необходимо создать объект- потомок от этого объекта и перекрыть в нем нужный метод.
Формирование меню
Вернемся к смысловой части нашего примера и подумаем о том, какие еще свойства следует придать программе. Поскольку мы предполагаем работу с файлом, можно включить в программу код, реализующий строку меню с опцией Файл, связав с этой опцией такие действия, как открытие уже существующего файла данных и/или создание нового. Здесь же можно предусмотреть возможность альтернативного выхода из программы. Кроме того, в главное меню следует поместить еще одну опцию, назовем ее Работа. Эта опция должна открыть доступ к содержательной части программы. С учетом сказанного программу нужно дополнить следующими строками:
const
{Команды для обработчиков событий:}
cmWork = 203; {Обработать данные}
cmDOS = 204; {Временно выйти в DOS}
WinComl: TCommandSet =
временно недоступных команд}
Эти строки следует вставить сразу после предложения uses; они определяют коды команд, с помощью которых будут затем вызываться соответствующие части профаммы. Кроме того, объявление объекта TNotebook нужно дополнить строкой
type
л TNotebook = object (TApplication)
Procedure InitMenuBar; Virtual; {Перекрыва ем стандартный
метод InitMenuBar)
end;
в которой перекрывается прежний метод InitMenuBar, ответственный за формирование строки меню. И, Наконец, в разделе объявлений программы следует поместить описание метода initMenuBar и видоизменить описание
Метода InitStatusLine:у
Procedure TNotebook.InitMenuBar;
{Создание верхнего меню}
var
R: TRect;
begin
GetExtent(R);
R.B.Y :=— координаты строки меню}
MenuBar := New(PMenuBar, Init(R,
NewMenu (l Создаем меню /
{Первый элемент нового меню представляет собой
подменю (меню второго уровня) . Создаем его)
NewSubMenu(1~F~/ Файл', hcNoContext,
{Описываем элемент главного меню)
NewMenu({Соз да ем подменю}.
Newl tem ({Первый элемент)
' ~1~/ Открыть', 'F3',kbF3,cmOpen,hcNoContext,
Newl tem ({Второй элемент}
' ~2~/ Закрыть',' F2',kbF2,cmSave,hcNoContext,
Newltem ({ Третий элемент}
'~3~/ Сменить диск','',0,cmChangeDir,hcNoContext,
NewLine({Строка-разделитель}
Newltem('~4~/ Вызов ДОС,'',0,cmDOSShell,hcNoContext,
Newltem('~5~/ Конец работы','Alt-X',
kbAltX,cmQnit, hcNoContext,
*
NIL)))))){Нетдругих элементов подменю}
) ,
{Создаем второй элемент главного меню}
Newltem('~W~/ Работа','',kbF4, cmWork,hcNoContext,
NIL)(Нет других элементов главного меню)
))))
end; {TNotebook.InitMenuBar}
сI
1/
Procedure TNotebook.InitStatusLine;
{Формирует строку статуса}
var
R: TRect; (Границыстроки статуса}
begin
GetExtent(R);(Получаем в R координаты всего экрана)
R.A.Y :=
StatusLine :=; New(PStatusLine,
Init(R,{Создаем строку статуса)
NewStatusDef(0, $FFFF,{Устанавливаем максимальный
диапазон контекстной справочной службы)
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,
NewStatusKey('~F2~ Закрыть', kbF2, cmSave,
NewStatusKey('~F3~ Открыть', kbF3, cmOpen,
NewStatusKey(’~F4~ Работа', kbF4, cmWork,»
NewStatusKey(''FlO' Меню', kbFlO, cmMenu,
NIL))))), (Нет других клавиш)
NIL)(Нет других определений}
));
DisableCommands(WinComl) {Запрещаем недоступные команды)
end; {TNotebook.InitStatusLine)
В новом варианте программы мы продвинулись дальше по пути конкретизации ее действий. Если вы запустите программу и нажмете клавиши <Alt>+<F> (вызов опции Файл главного меню), на экране появится изображение, показанное на рис. 15.2.
Рис. 15.2. Вид окна с развернутым меню опции Файл
Определение опций меню во многом напоминает определение командных клавиш в строке статуса. Отличие заключается лишь в том, что с любой опцией меню может быть при необходимости связана встроенная справочная служба. В нашей программе мы не используем эту возможность, для чего задаем стандартный идентификатор hcNoContext (нет контекстнозависимой справки) при описании каждой опции.
Подобно клавишам строки статуса командные клавиши меню выделяются символом ~. Заметим, что не имеет смысла назначать в качестве командных клавиш клавиши кириллицы, т. к. при их анализе Turbo Vision игнорирует коды 128-255. Если бы, например, мы задали в качестве командной клавиши для опции Файл клавишу <Ф>, нажатие <Ак>+<Ф> не вызвало бы развертывания подменю, связанного с этой опцией (как и в Турбо Паскале, в Turbo Vision опции главного меню вызываются комбинацией <Ак>+<клавиша>, а опции меню нижнего уровня — просто нажатием нужной командной клавиши).
Команды
Необходимо пояснить назначение вновь введенных констант cmXXXX. Это так называемые команды, точнее их коды (шифр). Сразу же замечу, что префикс cm в идентификаторах команд не является следствием каких-либо требований со стороны Turbo Vision, просто он принят для предопределенных (стандартных) команд, таких как cmQuit и cmClose. Вновь вводимые команды не являются предопределенными, при их описании я заимствовал стандартный префикс только по соображениям стилистики.
Что же такое команды Turbo Vision? Внимательный анализ предыдущего варианта программы показывает, что эти коды еще никак не используются, они понадобились лишь для синтаксически правильного обращения к стандартным методам инициации строк меню и статуса. В новом варианте программной реализации вы можете вызвать любую опцию главного меню или нажать любую командную клавишу это не приведет ни к каким последствиям. Пока работает только комбинация клавиш <Alt>+<X>, завершающая работу программы, и клавиши <F2>, <F3> и <F10>. Так происходит потому, что эти клавиши мы связали со стандартными командами cmQuit, cmsave, спЮреп и cmMenu и обрабатываются они где-то внутри Turbo Vision. Новые команды пока не известны системе, и их обработку мы должны взять на себя.
Как мы увидим дальше, в Turbo Vision есть средства контроля командных клавиш. Эти средства определяют факт нажатия на клавишу с помощью генерации кода соответствующей команды, который (код) будет в этом случае передан нашей программе. Таким образом, обработка команд заключается в расшифровке получаемых от Turbo Vision кодов и передаче управления соответствующим частям программы.
Для шифровки команд в Turbo Vision используется 16-разрядное слово, что позволяет определить до 65 535 различных команд. Некоторые из этих кодов зарезервированы для использования внутри Turbo Vision, остальные доступны программисту (табл. 15J).
Таблица 15.1. Коды зарезервированных и доступных команд
Код команды
Зарезервировано
Можно запретить
0-99
Да
Да
100-255
Нет
Да
256-999
Да
Нет
1000-65535
Нет
Нет
Команды с кодами от 0 до 255 при необходимости могут быть временно запрещены, остальные команды запретить нельзя — вот почему имеется два диапазона доступных для программиста кодов команд. Временное запрещение команд связано с тем очевидным свойством диалоговых программ, что отнюдь не любая команда может исполняться в каждом возможном состоянии программы. Например, бессмысленно использовать команду Закрыть файл, если файл еще не открыт. Наоборот, если файл уже открыт, команда Открыть файл может стать временно недоступной пользователю. Механизм маскирования (временного запрещения) команд позволяет избавиться от многочисленных проверок контекстуальной корректности тех или иных команд: программист может их запретить впредь до наступления какого-либо события, а запрещенные команды игнорируются средствами Turbo Vision и в программу пользователя не передаются.
В нашей программе имеет смысл запретить команды cmSave и cmWork до тех пор, пока пользователь не откроет нужный файл с данными. Запрет команд достигается обращением к стандартной процедуре DisableCommands (см. предыдущий вариант программы). Указанные в обращении к ней команды задаются в виде множества кодов (мощность любого множества в Турбо Паскале не может превышать 256, вот почему могут быть запрещены только первые 256 команд) и становятся недоступны впредь до обращения к процедуре EnableCommands (разрешить команды).
Запрещенные опции меню (как и временно недоступные командные клавиши) выделяются на экране оттенком (пониженной яркостью).
События и их обработка
Весьма важным в Turbo Vision является принцип отделения процесса создания видимых изображений от процесса обработки данных. Это означает, что
все действия по созданию разнообразных окон, меню и прочих видимых элементов можно осуществлять, не заботясь о тех командах (действиях пользователя), которые будут связаны с ними. Именно так мы поступили при определении меню и строки статуса — коды команд дают возможность распознать соответствующие действия пользователя, однако сами эти действия пока еще никак не раскрыты. И наоборот, мы можем разрабатывать части программы, ответственные за обработку действий пользователя, не связывая прямо эти части с созданием нужных видимых элементов.
Turbo Vision поддерживает два возможных способа действия пользователя — с помощью клавиш клавиатуры и с помощью мыши. Любое такое действие пользователя с точки зрения Turbo Vision приводит к появлению события, т. е. к созданию небольшого информационного пакета, описывающего вновь возникшую ситуацию. События распространяются от одной части программы к другой до тех пор, пока не обнаружится подпрофамма, ответственная за обработку данного события. Эта подпрофамма обычно очищает информационный пакет и таким образом блокирует дальнейшее перемещение события.
Пожалуй, именно механизм событий кардинально отличает Turbo Vision от других библиотек Турбо Паскаля. На первых порах это может вызвать определенные трудности, связанные с отладкой профамм. Принцип независимости обработки событий от процесса создания видимых элементов приводит фактически к появлению двух параллельных процессов в рамках одной программы: процесса создания видимых элементов и процесса обработки событий. Говоря о профаммах Turbo Vision, следует помнить, что эти программы управляются событиями. Их трассировка (прослеживание работы) в среде Турбо Паскаль обычно достигается установкой и использованием контрольных точек.
Подпрограммы, ответственные за обработку действий пользователя, называются обработчиками событий. Любой стандартный для Turbo Vision объект, обеспечивающий создание видимого элемента, имеет собственный обработчик событий (виртуальный метод HandleEvent), который вы можете перекрыть своим собственным методом, если вас не устраивает стандартная реакция объекта на то или иное событие. Существует такой метод и в объекте TNotebook. По умолчанию этот объект использует обработчик событий, унаследованный им от объекта-родителя ТApplication. Стандартный обработчик знает, как реагировать на команды cmQuit и cmMenu, но ему не известны новые команды cmWork, cmOpenFile и др. Чтобы профамма смогла правильно обработать эти команды, мы должны перекрыть стандартный метод HandieEvent объекта TNotebook новым. Добавим в описание объекта TNotebook еще одну строку
type
TNotebook = object (TAppiication)
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
и поместим в раздел объявлений текст новой подпрограммы:
Procedure TNotebook.HandleEvent(var Event: TEvent);
{Обработчик событий программы)
begin{TNotebook.HandleEvent)
Inherited HandleEvent(Event);{Обработка стандартных
команд cmQuit и cmMenu} if Event.What =then
case Event.Command of {Обработка новых команд:)
.cmOpen:(Открыть файл)
cmSave:
cmChangeDir: ChangeDir;{Сменить диск)
cmDOSShell : DOSCall;{Временныйвыход в ДОС)
cmWork : Work;{Обработать данные}
else
exit/Не обрабатывать другие команды)
end;
ClearEvent(Event) {Очистить событие после обработки)
end;{TNotebook.HandleEvent)
Чтобы новый вариант программы можно было выполнить, следует предусмотреть "заглушки для несуществующих пока процедур FileOpen, FileSave и т. д. Например:
Procedure FileOpen;
begin
end;
Поведение вновь созданного варианта профаммы внешне ничем не отличается от предыдущего: также будут созданы меню, строка статуса и основное поле экрана, профамма по-прежнему будет распознавать клавиши <Alt>+ +<Х> и <F10>. Однако теперь она будет реагировать и на новые команды. Чтобы убедиться в этом, установите контрольные точки в заглушках FileOpen и FileSave и запустите профамму вновь: нажатие на клавишу <F3> вызовет останов в контрольной точке FileOpen — ведь именно с этой клавишей мы связали команду cmOpen в процедуре InitStatusLine, в то время как нажатие на клавишу <F2> не приведет к срабатыванию контрольной точки FileSave, поскольку команда cmSave пока еще запрещена, и обработчик HandleEvent ее просто не "увидит".
14 Зак. 941
тип{ "Пустое " событие}от{Состояние кнопок)(Признак двойногонажатия кнопкикурсора(Событие от{Код клавиши}(Событие-сообщение:} (Кодтип{ "Пустое " событие}от{Состояние кнопок)(Признак двойногонажатия кнопкикурсора(Событие от{Код клавиши}(Событие-сообщение:} (Код
тип
{ "Пустое " событие}
от
{Состояние кнопок)
(Признак двойного
нажатия кнопки
курсора
(Событие от
{Код клавиши}
(Событие-сообщение:} (Код
тип
{ "Пустое " событие}
от
{Состояние кнопок)
(Признак двойного
нажатия кнопки
курсора
(Событие от
{Код клавиши}
(Событие-сообщение:} (Код
Примечание
Чтобы использовать нестандартные команды меню или статуса, мы должны перекрыть обработчик событий программы, в новом обработчике выделить из потока событий команды и распознать их коды.
Чтобы стали более понятны действия обработчика событий, отметим, что тип TEvent в Turbo Vision определен как запись такого вида:
type
TEvent = record
What: Word;
case Word of
evMouse: (
Buttons: Byte;
Boolean;
Where:
evKeyDown: (
case Integer of
0:
(CharCode: Byte; ScanCode: Byte)) ;
(
Word;
case Word of
0: (InfoPtr:
1: (InfoLong:
(InfoWord:
: (Inf oByte : Byte ) ;
(InfoChar:
end;
Стандартная маскапозволяет выделить из потока событий
только те, которые связаны с передачей команд между различными обработчиками событий. Именно таким способом стандартный обработчиксообщаетновомуобработчику
о возникновении события, связанного с вновь определенной командой. Если бы мы не предусмотрели вызов стандартного обработчика с помощью оператора
Inherited
нам пришлось бы самим анализировать положение мыши или нажатую клавишу и интерпретировать их как соответствующие команды. Включение вызова TApplication.HandleEvent в тело нашего обработчика событий избавляет нас от этой рутинной работы.
В конце обработчика мы вызвали стандартную процедуру ClearEvent, с помощью которой в переменную Event помещается сообщение Nothing ("пустое" событие). Это событие игнорируется всеми обработчиками, так что программа будет повторять проверку состояния мыши и клавиатуры до тех пор, пока не произойдет нового события. Фактически тело процедуры TApplication.Run (см. исполняемую часть нашей программы) состоит из бесконечно повторяющегося цикла проверки мыши и клавиатуры и передачи событий по цепи обработчиков событий. После получения любого события обработчик должен либо обработать это событие и очистить переменную Event, либо просто вернуть управление обработчику верхнего уровня, если эта команда не предназначена для него, либо, наконец, сформировать и передать новое событие для реализации команд, которые распознаны им, но которые он выполнять не умеет или не должен.
Программирование диалоговых запросов
В обработчике событий TNotebook.HandleEvent мы предусмотрели вызовы нескольких процедур, с помощью которых реализуются конкретные действия программы. Настала пора запрограммировать эти действия.
Начнем с процедуры FileOpen. Ее задача — выбрать один из возможных файлов с данными и подготовить его к работе. Конечно, программу можно было бы сделать менее гибкой, раз и навсегда "привязав" ее к какому-то одному файлу, скажем, с именем NOTEBOOK. DAT. Но даже и в этом случае следует решить проблему с местоположением файла данных, а также определить, что должна делать программа, если нужный файл не найден. Наша программа будет весьма гибкой в этом отношении: она позволит указать интересующий нас файл мышью или клавишами курсора, либо ввести имя файла с помощью клавиатуры или взять его из буфера ранее введенных имен. Иными словами, поведение нашей программы будет в точности повторять поведение среды Турбо Паскаль в момент нажатия на клавишу <F3>.
Если вы когда-либо программировали подобные действия в Турбо Паскале, то по достоинству оцените простоту их реализации в Turbo Vision"
Procedure FileOpen;
(Открывает файл данных)
var
PF: PFileDialog; {Диалоговое окно выбора файла)
Control: Word;
PathStr;
begin
{Создаем экземпляр динамического объекта:}
New(PF, Init (' *.dat','Выберите нужный файл:',
'Имяфайла',fdOpenButton,0}) ;
(С помощью следующего оператора окно выводится на экран и результат работы пользователя с ним помещается в переменную Control:}
Control := DeskTopA.ExecView(PF) ;
{Анализируемрезультат запроса:}•
case Control of
StdDlg.cmFileOpen,cmOk:
begin{Пользователь указал имя файла:}
PFA.GetFileName(s); (s содержит имя файла}
{}{Открыть файл)
end;
end; {case Control)
Dispose(PF, Done){Уничтожаем экземпляр}
end; {FileOpen}
Для реализации этого фрагмента необходимо указать имя модуля stdDlg в предложении uses — в этом модуле описан тип PFileDialog и предусмотрены все необходимые методы для работы с ним. Кроме ТОГО, в программе используется переменная s типа Pathstr. Этот тип описан в модуле DOS — сошлитесь также и на него. Сделайте нужные изменения в тексте программы, не раскрывая пока сущности действий
{Открыть файл}
запустите программу на счет и нажмите клавишу <F3> — экран приобретет вид, показанный на рис. 15.3.
Тип PFileDialog — это указатель на объект TFileDialog, создающий и обслуживающий стандартное диалоговое окно выбора файлов. Все действия по созданию и использованию диалогового окна, показанного на рис. 15.3, реализуются двумя операторами:
New(PF, Init('*.dat', 'Выберите нужный файл:',
'Имя файла',fdOpenButton,0));
Control := DeskTopA.ExecView(PF);
Рис. 15.3. Диалоговое окно выбора файлов
Первый оператор инициирует новый экземпляр объекта TFileDiaiog. Три строковых параметра обращения к конструктору Init этого объекта задают соответственно маску выбираемых файлов *.dat, заголовок диалогового окна ('Выберите нужный файл:') и заголовок поля ввода ('Имя файла'). Параметр fdOpenButton указывает на необходимость включить в диалоговое окно кнопку Open. Последним параметром задается идентификатор протокола ввода. Доступ к этому протоколу открывается кнопкой [Jr] справа от окна ввода. Сам протокол хранится в куче в виде последовательности вводившихся ранее текстовых строк. Идентификатор протокола ввода позволяет при необходимости использовать один и тот же протокол в разных диалоговых окнах.
Второй оператор
Control := DeskTopA.ExecView(PF);
помещает вновь созданное окно в основное поле экрана программы (ссылка DeskTops) и инициирует диалог с пользователем. Результат диалога возвращается в переменной control, значение этой переменной анализируется оператором
case Control of
end;
Если Control содержит КОДЫ команд cmOk ИЛИ cmFileOpen, ТО С ПОМОЩЬЮ метода GetFileName объекта TFileDiaiog в переменную s записывается полное имя файла (с предшествующим путем).
Примечание
В методеName:параметр об
ращения должен иметь тип Pathstr. Этот тип определен в модуле DOS, — вот почему нам понадобилось сослаться на этот модуль в предложении uses. Если указать компилятору на необходимость смягчить проверку строковых типов (директива компиляторато при обращении кможно ис
пользовать переменную любого строкового типа, в том числе String.
Перед выходом из процедурыэкземпляр объекта
уничтожается (удаляется из кучи) обращением к деструктору Done.
По описанной схеме в Turbo Vision создаются и используются любые другие диалоговые окна.
Внимание
Для реализации диалогового запроса необходимо создать диалоговое окно и с помощью функции Execview объекта-владельца (программы) инициировать диалог с пользователем. Результат, возвращаемый этой функцией, будет содержать выбранную пользователем команду.
Чтобы запрограммировать действия, связанные с открытием файла, следует вначале решить, какие именно данные он будет содержать. Напомню, что мы разрабатываем диалоговую программу управления "записной книжкой". Структура типичной записи в такой книжке состоит из трех полей: имя, телефон, адрес. Учитывая это, будем считать, что данные в файле хранятся в виде следующих записей:
const
поля
LPhone= 11;поля
= 40;поля Addr)
type
=recordданных в
Name :
Phone:
Addr :
end;
Поместим эти строки в начале программы, а перед описанием процедуры
FiieOpen вставим определения следующих глобальных переменных:
var
DataFile: file of DataType;
OpFileF : Boolean;открытого
Дополним текст процедуры FileOpen такими строками:
case Control of
StdDlg.cmFileOpen,cmOk:
begin
PFA.GetFileName(s);
• начинаются новые
1
Reset(DataFile);
if TOResult <> 0 then
Rewrite(DataFile);
OpFileF :=
{$!+)
if OpFileF then
begin
DisableCoinmands (WinCom2) ;
EnableCommands(WinComl)
end
end;
end;
С помощью операторамы временно запрещаем набор
команд, указанный в константеЭта константа в нашем случае
должна содержать команду cmopen; ее определение нужно включить сразу за определением константы const
WinComl:=
TConmandSet =
Обращение к процедуре EnableCommands разрешает использовать команды И cmWork.
Инкапсуляция
новых полей и методов
При попытке откомпилировать полученный вариант программы Турбо Паскаль сообщит о неизвестном идентификатореНа первый
взгляд это кажется странным — ведь аналогичное обращение в обработчике событийне вызывало проблем! Все дело в том, что
мы работаем с объектами, а следовательно, здесь очень важным становится контекст программ. Обработчик— это метод объек-
та TNotebook, который унаследовал от своих родителей многие свойства, в том числе и метод DisableCommands. Процедура FileOpen не является потомком объектов Turbo Vision и не имеет доступа к их полям и методам.
Внимание
В Turbo Vision все новые процедуры обычно инкапсулируются в объекты, если в них необходимо получить доступ к специфическим средствам этих объектов.
Поскольку процедура FileOpen вызывается из обработчика событий объекта TNotebook, нам следует включить ее в виде нового метода этого объекта:
type
TNotebook = object (TApplication)
Procedure FileOpen;
Procedure FileSave;
Procedure.ChangeDir;
Procedure DOSCall;
Procedure Work;
end;
В этом фрагменте мы инкапсулировали в объект все методы, используемые обработчиком событий. Разумеется, необходимо соответствующим образом изменить заголовок процедуры FileOpen, поскольку она теперь стала методом объектаTNotebook:
Procedure TNotebook.FileOpen;
Аналогичным образом следует изменить и заголовки других инкапсулированных процедур. Теперь трансляция пройдет успешно, а после открытия файла команда <F3> станет недоступной.
Тексты двух других новых методов объекта TNotebook не нуждаются в особых комментариях:
Procedure TNotebook.FileSave;
{Закрывает файл данных)
begin
Close(DataFile);
OpFileF := False;
EnableCommands(WinCom2); {Разрешаем открыть файл)
DisableCommands(WinComl) {Запрещаем работу и сохранение}
end;{TNotebook.FileSave)
{1
Procedure TNotebook.ChangeDir;
(Изменяет текущий каталог) var
PD: PChDirDialog; (Диалоговое окно смены каталога/диска) Control: Word;
begin
New(PD, Init(cdNormal,0));(Создаемдиалоговое окно)
Control := DeskTopA. ExecView (PD) ; (Используем окно}
ChDir (PD' . DirinputA. DataA);ChDir (PD' . DirinputA. DataA);(Устанавливаемновый каталог)
ChDir (PD' . DirinputA. DataA);
ChDir (PD' . DirinputA. DataA);
Dispose(PD, Done)Dispose(PD, Done){ Удаляемокно из кучи){ Удаляемокно из кучи)
Dispose(PD, Done)
Dispose(PD, Done)
{ Удаляемокно из кучи)
{ Удаляемокно из кучи)
end;{TNotebook.ChangeDir)
Несколько слов по поводу реализации процедуры TNotebook.ChangeDir. В ней есть объект TChDirDialog, входящий в модуль StdDlg. С помощью этого объекта создается диалоговое окно, позволяющее выбрать новый диск или каталог. После создания и использования экземпляра объекта TChDirDialog в его поле
DirinputA.DataA
устанавливается строка типа Pathstr, задающая новый каталог (и, возможно, новый диск).
Чуть сложнее обстоит дело c процедурой DOSCall, которая должна реализовать временный выход в ДОС. Дело в том, что перед выходом необходимо сохранить в куче текущее состояние программы, а после возврата нужно восстановить состояние программы, в том числе и вид экрана. Чтобы реализовать имеющиеся в Turbo Vision средства сохранения и восстановления программы, в предложение Uses необходимо добавить ссылку на модуль Memory. Вот текст Метода TNotebook. DOSCall:
Procedure TNotebook.DOSCall;
(Временный выход в ДОС)
const
beginDoneEvents;DoneVideo;beginDoneEvents;DoneVideo;txt = 'Для возврата введите EXIT в ответ'+ ' на приглашение ДОС...';
begin
DoneEvents;
DoneVideo;
begin
DoneEvents;
DoneVideo;
DoneMemory;SetMemTop(HeapPtr);WriteLn(txt);SwapVectors;DoneMemory;SetMemTop(HeapPtr);WriteLn(txt);SwapVectors;(Освободить кучу)(Освободить кучу)(Закрыть обработчик событий) (Закрыть монитор экрана) (Закрыть монитор памяти)
DoneMemory;
SetMemTop(HeapPtr);
WriteLn(txt);
SwapVectors;
DoneMemory;
SetMemTop(HeapPtr);
WriteLn(txt);
SwapVectors;
(Освободить кучу)
(Освободить кучу)
/Сообщить о выходе)
{ Установить стандартные векторы)
{Передать управление командному процессору' ДОС:)
Exec (GetEnv( 'COMSPEC'),");
{Вернуться из ДОС:;
SwapVectors;(Восстановить векторы)
SetMemTop(HeapEnd);{Восстановить кучу)
InitMemory;
{Открыть монитор памяти)
InitVideo;
{Открыть монитор экрана}
InitEvents;
{Открыть обработчик событий)
InitSysError;
Redraw
{Открыть обработчик ошибок}
{Восстановить вид экрана)
end;{DOSCall}
I
Процедуры Donexxxx завершают работу отдельных частей Turbo Vision, а процедуры InitXXXX осуществляют обратные действия. С помощью процедуры SetMemTop в ДОС передается информация о фактически используемой динамической памяти (по умолчанию программе предоставляется вся доступная память). Этот вызов освобождает неиспользуемую в данный момент часть кучи для размещения в ней командного процессора COMMAND.COM. После возврата из ДОС вызов SetMemTop осуществляется еще раз — для того, чтобы зарезервировать за программой всю ранее выделенную ей память. Процедура Redraw восстанавливает все видимые элементы экрана.
Создание и использование групп
Пора заняться основной содержательной частью нашей программы — процедурой Work. Прежде всего следует продумать способ взаимодействия пользователя с данными (интерфейс пользователя). От удачного выбора интерфейса во многом зависит успех разработки диалоговых программ: неудобный способ доступа, связанный с необходимостью ввода каких-либо команд или ответов на многочисленные вопросы, надолго отобьет у пользователя всякое желание работать с программой. Turbo Vision предоставляет в ваше распоряжение все необходимые средства для разработки современного объектно-ориентированного диалога. В ходе такого диалога пользователь видит на экране объекты, о которых идет речь, он может указать на любой объект и выбрать те действия, которые нужно осуществить над ним.
При работе с электронной записной книжкой хотелось бы, чтобы на экране появилось сразу несколько записей, отсортированных в алфавитном порядке. Пользователь должен иметь возможность "листать" книжку, отыскивать в ней нужную запись, добавлять новые и исключать ненужные записи, редактировать их (вносить изменения). Таким образом, ядром диалога должно стать окно с текстом. При необходимости пользователь может смещать текст в окне в ту или иную сторону, перемещать само окно относительно границ
экрана, менять его размеры. Все эти возможности типичны для многочисленных текстовых редакторов, систем программирования, систем управления базами данных и т. п.
Для реализации этих действий в Turbo Vision предусмотрен специальный объект TWindow, экземпляры которого отображаются на экране в виде прямоугольного окна с рамкой и стандартными кнопками изменения размера и закрытия окна. Попробуем создать такое окно в нашей программе. Для ЭТОГО изменим текст процедуры work следующим образом:
Procedure TNotebook.Work;
/Работа с данными}
var
R: TRect;
begin
R.Assign(0,0,80,23);
DesktopA. Insert (New(PWindow, Init (R, ' ', 0) ) )
end; (Work)
После запуска программы нажмите клавишу <F3>, укажите в диалоговом окне имя несуществующего файла (файл данных пока еще не создан), нажмите клавиши <Enter> и <F4> — экран приобретет вид, показанный на рис. 15.4.
Рис. 15.4. Окно просмотра данных
Вы также можете перемещать это окно по экрану с помощью мыши (надо "схватить" мышью верхнюю рамку окна, т. е. подвести к ней указатель мы-
ши, нажать левую кнопку и, удерживая кнопку нажатой, перемещать мышь), изменять его размеры ("схватить" правый нижний угол), использовать стандартные кнопки изменения размера (справа на верхней рамке) и закрытия окна (слева). Ничего другого окно не умеет. А как загрузить в него текст? Как получить хорошо знакомые по среде Турбо Паскаль полосы прокрутки и управлять с их помощью положением текста? Для этих целей можно было бы использовать объект Tscroller, представляющий собой окно с текстом и с двумя полосами прокрутки. Однако по умолчанию такое окно неимеетрамки,а потомунеможетизменять своегоразмера,внемнет
стандартных кнопок изменения размера и закрытия окна. Таким образом, и объект Tscroller не решает всех проблем. Каков же выход? Нужно создать новый объект, объединяющий в себе свойства и TWindow, и Tscroller! В терминах Turbo Vision такие составные объекты называются группами.
Введем в программу следующий объект:
type
PWorkWin =ATWorkWin;
TWorkWin = object (TWindow)
Constructor Init(Bounds: TRect);
end;
Новый объект является потомком TWindow и, следовательно, наследует все свойства родителя, в том числе рамку и способность перемещения по экрану. Дополнительные свойства ему должен придать новый конструктор TWorkWin.Init, которому мы в качестве параметра передаем начальное положение и размеры создаваемого окна:
Constructor TWorkWin.Init(Bounds: TRect);
{Создание окна данных)
var
HS,VS: PScrollBar;{Полосы прокрутки)
Interior: PScroller;{Указатель на
управляемое текстовое окно)
begin
TWindow.Init(Bounds, ' ',0) ; (Создаем новое окно с рамкой)
GetClipRect(Bounds) ;{Получаем в BOUNDS координаты
минимальной перерисовываемой части окна)
Bounds.Grow(-1, -1) ;{Устанавливаемразмеры окна с текстом)
{Включаем стандартные по размеру и положению полосы прокрутки:)
VS := StandardScrollBar(sbVertical+sbHandleKeyBoard);
HS :=
текстовое
InteriorHS,
его в end; С помощью вызова процедурымы получаем размеры мини
мального прямоугольника, который следует обновлять при любых перемещениях окна или изменениях его размера. Такой вызов позволяет до минимума сократить время вывода. Процедура Bounds. Grow изменяет вертикальный и горизонтальный размеры прямоугольника Bounds: при положительном параметре соответствующий размер увеличивается, при отрицательном — уменьшается. Параметрыучитывают рамку основного
окна. Функциясоздает указатель на полосу
стандартного размера. При обращении к ней параметр sbvertical (sbHorizontal)определяетположениеполосы,апараметр
разрешает использование клавиатуры для управления ею (если этот параметр не включить, полоса будет управляться только с помощью мыши). Наконец, процедура insert включает вновь созданное окно в основное окно Twindow, так что теперь оба окна будут функционировать как одно целое.
Примечание
Для создания группы необходимо в объект-потомок от TGroup (обычно — это объект Twindow или потомок от него) вставлять нужные элементы с помощью метода Insert.
Осталось лишь нужным образом изменить процедуру work:
Procedure с var
R: TRect;
PWorkWin;
begin
R.Assign(0,0,80,23);
PW
DeskTopA.Insert(PW)
end;
Если исполнить подготовленную таким образом программу, на экране появится изображение, показанное на рис. 15.5.
РаботаРабота
Работа
Работа
fllt-X Выход F2F3 Открыть Г4
Рис. 15.5. Окно с полосами прокрутки
Вывод текста
По сравнению с рис. 15.4 мы добились немногого, ведь пока еще не решена главная проблема — вывод нужного текста. Разумеется, в вашем распоряжении всегдаимеетсяпроцедураоднаковыводтекста"влоб"спо
мощью этой процедуры практически никогда не используется в Turbo Vision, т. к. в этом случае выведенный текст не будет связан с окнами.
В объектедля вывода текста предусмотрен абстрактный метод
Draw. Абстрактным он называется потому, что не выполняет никакой полезной работы. Однако именно к этому методу обращается обработчик событий объекта TScroiier всякий раз, когда понадобится обновить на экране вид окна. Чтобы объект выполнял все заложенные в него функции, нам необходимо перекрыть этот метод новым. Мы уже знаем, что для этого нужно объявить новый объект:
type
PInterior =ATInterior;
TInterior = object (TScroiier)
ConstructorBounds:HS,VS:
Procedure Draw;
Procedure ReadFile;
end;
Мы перекрыли абстрактный метод Draw, стандартный конструктори
инкапсулировали в объект новый метод ReadFile. Новый конструктор пред-
назначен для инициализации экземпляра объектаКроме того, с
помощью методаон должен прочитать все записи файла данных и
подготовить соответствующий массив строк — это сократит время на обновление текста процедурой Draw.
Перед тем, как двигаться дальше, подумаем о способе хранения строк для процедуры Draw. Если все необходимые действия по чтению нужной записи из файла и преобразования ее к текстовому формату возложить на процедуру Draw, наша программа станет слишком медленной, в особенности, если файл данных записан на дискете. Поэтому предусмотрим такие глобальные переменные:
const
MaxLine = 300;(iЧдлинмалъная длина массива;
=
var
Word;длина массива
arrayof String
Теперь нетрудно подготовить процедуру ReadFile:
Procedure
содержимое файла данных в массив
var
k: Integer;
String;
Data:
begin
seek(DataFile,0);
NLines :=
if NLines > MaxLine then
NLines := MaxLine;
for k := 1 to NLines do
begin
Read(DataFile, data);
with data do
begin
sName;
while<do
s :=
ss+Phone;
while Length(s) < LName+LPhone do
s := s+' ’;
s :- s+Addr
end;
:= s
end;
end;
В этой процедуре из записей файла данных готовится массив строк Lines, причем начало каждого поля выравнивается так, чтобы поля образовали колонки — такая форма вывода поможет легко найти на экране каждое поле.
Теперь займемся процедурой Draw:
Procedure данные в окно var п,строка
Integer;строка
В: TDrawBuffer; Color: Byte; begin Color :=цвет основного
for n := 0 to pred(Size.Y) do — количество строк begin k :=— номер первой выводимой
MoveChar(B,' ',Color,Size.X); MoveStr(B, Copy(Lines[k],Delta.X+l,Size.X),Color);
WriteLine(0,N,Size.X,1,В) end
end;
Работа процедуры основана на использовании текущих размеров и положения текстового окна относительно текста. Эти параметры хранятся
size и Delta объектаи обновляются всякий раз, когда пользова
тель манипулирует полосами управления или изменяет размеры окна. Для вывода текста служат три процедуры:Movestr, WriteLine. Каждая
из них оперирует переменной в типа TDrawBuffer,собой
последовательности кодов выводимых символов и их атрибутов. Процедура заполняет переменнуюуказанным символом' и атрибутом
color. Процедуракопирует строку в переменную в, а с помощью
осуществляется вывод буфера в на экран.
Для вывода изображений (текста) перекрывайте и используйте метод Draw объекта-владельца нужной части экрана. Это обеспечит автоматическое изменение изображения и его прорисовку при изменении границ или положения поля вывода.
Цветовая палитра
В процедуре Draw переменная color задает атрибуты (цвет символов и цвет фона) символов, выводимых с помощью методов MoveChar и Movestr. С помощью функции она устанавливается таким образом, чтобы символы на экране отображались цветовым сочетанием с номером 1. В Turbo Vision существует гибкая система установки цвета отдельных видимых элементов. Элемент изображения связывается не с каким-то конкретным цветом, а с индексом в таблице цветов, называемой палитрой. Количество элементов палитры зависит от количества цветовых сочетаний, используемых при выводе элемента изображения. Например, в TScroller предусмотрена двухэлементная палитра цветов: первый элемент устанавливает цвет нормального текста, второй — выделенного текста (рис. 15.6).
12
Палитра TScroller
Подсвеченный текст Нормальный текст
Рис. 15.6. Палитра объекта TScroller
Числа 6 и 7 в этой палитре указывают не конкретные цвета, номера позиций в палитре объекта-владельца. Для нашего случая объектом-владельцем будет Таким образом, цвет номер палитры TScroller лишь указывает на шестое по счету цветовое сочетание в палитре Twindow (рис. 15.7).
Шестой элемент палитры Twindow в свою очередь ссылается на 13-й элемент палитры своего владельца — TProgram. Объект — это начальный видимый элемент любой программы в Turbo Vision. На нем заканчивается любая цепочка ссылок, т. е. его палитра содержит конкретные атрибуты символов. Значение 13-го элемента этой палитры равно что соответствует выводу желтого символа на синем фоне — именно таким образом ото-
бражается нормальный текст в окне TScroller, если это окно вставлено в TWindow. Если бы объект TScroiier был помещен непосредственно на панель экрана, то значение 6 в первом элементе палитры TScroiier указывало бы на шестой элемент палитры т Program, содержащий атрибут $28 (темносерые символы на зеленом фоне). Цветовые палитры в Turbo Vision содержат такие значения по умолчанию, чтобы любая комбинация цветов давала приятную цветовую гамму. При необходимости пользователь может изменить любую цветовую палитру.
Рис. 15.7. Связь палитр TScroiier и TWindow
Вернемся к нашему примеру И рассмотрим реализацию конструктора init:
Constructor TInterior.Init(var Bounds: TRect;
HS,VS: PScrollBar);
{Создает окно для данных}
begin
Inherited Init(Bounds, HS, VS) ;
ReadFile;
GrowMode := gfGrowHiX + gfGrowHiY;
SetLimit(LLine,NLines)
end; {Tin terior.Init)
Объект TScroiier имеет поле GrowMode, которое определяет, как элемент будет изменять свои размеры, если пользователь потребует этого. Параметр gfGrowHix предписывает окну TScroiier изменяться таким образом, чтобы правая его граница всегда находилась на постоянном расстоянии от правой
границы владельца. Точно так жезадает неизменным расстояние
нижней границы окна TScroiier от нижней границы владельца. Таким образом, окно TScroiier всегда будет занимать всю внутреннюю часть окна- владельцаС помощью процедурымы задаем гори
зонтальную х и вертикальную границы перемещения окна относительно текста. Эти границы будут выдерживаться при управлении окном с помощью клавиш или мыши: какими бы не были текущие размеры окна, нажатие на клавишу <End>, например, смещает его вправо так, чтобы самым правым видимым символом былсимвол текста. Нажатие на клавиши
<Ctrl>+<PgDn> смещает окно вниз по тексту таким образом, чтобы самая нижняя строка окна соответствовала 7-й строке текста. Иными словами, параметры х и задают координаты правого нижнего угла виртуального (воображаемого) экрана неограниченных размеров, на котором находится текст и по которому "скользит" окно. Левый верхний угол виртуального экрана всегда имеет координаты (О, О).
Осталось отредактировать конструкторнужно изменить тип
переменной interior
var"
Interior: PInterior;
и обращение к конструктору:1
Interior :=HS,
Но не спешите запускать программу на счет: ведь файла данных пока еще нет, а поэтому вы ничего не увидите на экране. Чтобы все-таки оценить достигнутые результаты, измените текст процедуры ReadFile, добавьте в него следующие строки:
Procedure
содержимое файла
var
text;
begin s :=
assign(f, s);
файл с текстом
NLines := 0;
while notand (NLines < MaxLine) do
begin
inc(NLines);
ReadLn(f,Lines[NLines])
end;
close(f) ;
exit;
end; {ReadFile}
Добавленные строки заставят процедуру прочитать в массив Lines текст самой программы (если вы будете запускать программу из среды Турбо Паскаль, не забудьте установить компиляцию в дисковый файл опцией Compile | Destination, иначе оператор
s:=copy(ParamStr(0),l,pos('.',ParamStr(0)))+'pas';
не сможет установить в s правильное имя файла с текстом вашей программы). После запуска программы нажмите клавишу <F3>, задайте имя несуществующего файла, нажмите клавиши <Enter> и <F4> — на экране появится окно, показанное на рис. 15.8.
■m-u■m-uТ/ to й n U / Рабата
■m-u
■m-u
Uses Йрр, Objects, Menus, Drivers, Немоту,crt, Views, StdDlg, DOS, Dialogs;
{Длина поля имени}{Длина поля телефона}{длина поля адреса}{Длина поля имени}{Длина поля телефона}{длина поля адреса}const
{Длина поля имени}
{Длина поля телефона}
{длина поля адреса}
{Длина поля имени}
{Длина поля телефона}
{длина поля адреса}
LNane = 25;
■ LPhone= 11;
LAddr = 40;
MaxLine= 300;
LLine =
282;==282;=={Команды для обработчиков событий:} cnOpenFile = 2801 cnSaveFile =
282;
=
=
282;
=
=
cnChDir cnWork cnDOS
=
[cnSaveFlie,cnUorkl;
Wincon2: TConnandSet = [cnOpenFile];
OpFilcF Boolean = False; {Признак открытого файла}
fllt-X Выход F2 Закрыть F3 Открыть F4 Работа F10 Мено
Рис. 15.8. Окно с текстом программы
Это окно откликается на нажатие клавиш управления курсором, нажатие клавиш <PgUp>, <PgDn>, <Ctrl>+<PgUp> и т. д. подобно тому, как ведет себя окно редактора в среде Турбо Паскаль. С помощью мыши вы можете перемещать его по экрану, изменять размеры, закрывать — все эти действия реализует стандартный обработчик событий объекта TScroller.
Использование коллекций
Вывод текста мы выполнили с помощью глобального массива Lines. Как известно, длина любого массива вПаскале не может превышать дли
ну сегмента данных (64 Кбайт). Это ограничение можно убрать, если воспользоваться еще одним механизмом Turbo Vision — коллекциями. Подобно массивам, коллекции представляют собой набор элементов, в которых можно хранить любые данные, включая экземпляры любых объектов. К элементам коллекции можно обращаться по индексу, однако, в отличие от массива, коллекция размещается в куче, поэтому ее суммарная длина ограничивается всей доступной памятью ибыть больше 64 Кбайт. Кроме того, размер
коллекции не лимитируется при ее создании и может динамически изменяться в процессе работы программы.
Коллекции обладают целым рядом новых свойств. В частности, к любой коллекции можно применить метод ForEach, который осуществит заданные вами действия над каждым элементом коллекции. Таким способом можно, например, быстро отыскать элемент, удовлетворяющий заданным требованиям. Наконец, в Turbo Vision определены отсортированные коллекции, элементы которых упорядочиваются по заданному ключу. Все это делает коллекции более предпочтительным способом хранения данных, чем массивы Турбо Паскаля.
Попробуем заменить массив Lines на отсортированную коллекцию. Введем в объектновое поле PS:
type
Tinterior =(TScroller)
PS: PStringCollection;
end;
Тип PStringCollection в Turbo Vision определен как указатель на экземпляр объектапредставляющий собой отсортированную
коллекцию строк. Сортировка строк осуществляется по обычным правилам сравнения строк по ASCII-кодам. Если вновь помещаемая строка уже существует в коллекции, она не дублируется (при желании программист может разрешить дублирование одинаковых строк), поэтому в общем случае количество элементов коллекции может оказаться меньшим количества помещенных в нее строк.
Для создания коллекции удалите ненужные теперь глобальные объявления Lines и NLines (в коллекции есть другие средства доступа к элементам) и измените методследующим образом:
Procedure
var
begin
PS :=
s :=
assign(f,s);
файл с текстом
while notordo
begin
ReadLn(f,s);
if s <> ' ' then
end;
Close(f);
Exit;
Seek(DataFile,O);
while notor LowMemory) do
begin
Read(DataFile, data);
with data do
begin
end;
if so" then
end;
end;
В приведенном фрагменте мы предусмотрительно изменили только ту часть программы, которая стоит после оператора Exit и зависит от удаленных глобальных определений. Вы должны сделать эти изменения (они все равно нам пригодятся) или ограничить скобками эту часть текста, чтобы получить синтаксически правильный вариант программы.
С помощью оператора
PS
инициализируется экземпляр коллекции, причем параметр 100 определяет начальный размер коллекции, а параметр 10 — шаг наращивания коллекции, если ее размер превысит 100 элементов. Оператор
if so" then(s) )
вставляет очередную непустую строку в коллекцию. Заметим, что коллекции передается не строка s, а лишь указатель на нее, т. к. функция Newstr размещает строку в куче и возвращает ее адрес. Функция NewStr не может
разместить в куче пустую строку, поэтому мы вставляем в коллекцию только непустые строки.
Функция LowMemory используется для контроля за размерами динамической памяти: она возвращает значение True, если в куче осталось менее 4 Кбайт.
В последний оператор метода Interior.Init внесите следующее изменение:
Constructor TInterior.Init(var Bounds: TRect;
HS,VS: PScrollBar);
begin
SetLimit(LLine,PSA.Count)
end; (Tinterior.Init}
Другим станет также и реализация метода TInterior. Draw:
Procedure TInterior.Draw;
var
k: Integer;
TDrawBuffer;
p: PString;
Color: Byte;
begin
Color := GetColor(1);
for n := 0 to pred(Size.Y) do
begin
k := Delta.Y+n;
MoveChar(B, 1 ',Color,.Size.X) ;
if k < pred(PSA.Count) then
begin
p := PSA.At(k);
MoveStr(B,Copy(pA,Delta.X+l,Size.X),Color)
end;
WriteLine(0,N,Size.X,1,B)
end
end;{Tin terior.Draw)
Элементы коллекции нумеруются, начиная с нуля. Длина коллекции (общее количество ее элементов) хранится в поле PSA.Count. Функция PSA.At(k) возвращает указатель на ^—И элемент коллекции.
Созданная коллекция размещается в динамической памяти, поэтому после использования ее следует удалить из кучи. Для этого перекроем стандартный деструктор Done:
type
TInterior = object (TScroller)
Destructor Done; Virtual;
end;
Destructor TInterior.Done;
begin
Dispose (PS, Done); {Удаляем коллекцию)
Inherited Done{Выполняем стандартный деструктор}
end;
F' Файл U/ Работа
r-U 1Веберите нужную запись. ESC - переход к диалогу ■’—"Щч
располагаться в теле обработчика событий}4
диапазон контекстной справочнойI
службы}
статуса Menus}fdOpenButton,0));hs.us: PScrolIBar);
DisableConnands(UinCon2) EnableCennands(UinConl); FileOpeni
NIL)))))){Нет других эленентов подменю}
New I ten("А"/ Открыть', ’ F3',kbF3,CH0penFНе,hcNoContext, Neu I ten (’ ~В~/Закрыть'/ F2' ,kbF2,cnSaueFile,hcNoContext, NeuIten('~C~/ Сменить диск',",0,cnChDir,hcNoContext, New I ten ('*D"/' Вызов ДОС',", 0,cnDOS,hcNoContext, New ItenСТ/ Конец работы' ,'Alt-X',kbAltX,cnQuit,hcNoContext, NewLinet{Вставить строку-разделитель}
Rewrite(DataFile);
begin end if OpFileF then Work s := Mane; Я
Alt-X Выход F2 Закрыть F3 Открыть F4 Работа F10 Меню
Рис. 15.9. Окно с отсортированным тестом программы
Еще раз хочу обратить ваше внимание на особенность программирования в среде Turbo Vision: вы определяете метод, но не указываете, когда он должен быть выполнен. Правильно сконструированный объект уже "знает", когда ему понадобится метод! Так было в случае метода Draw, так же обстоит дело и с деструктором Done: обработчик событий окна TWindow вызовет этот метод, как только он получит событие cmCancel (закрыть окно). Чтобы убедиться в этом, установите контрольную точку в строке
Dispose(PS, Done); {Удаляем коллекцию}
и запустите программу. Останов в контрольной точке произойдет только в том случае, если вы загрузите окно с текстом и попытаетесь выйти из программы. Если из программы выйти сразу после ее запуска, контрольная точка не сработает.
Вид экрана с окном просмотра отсортированного файла показан на рис. 15.9.
Указатель на элемент списка
Как уже отмечалось, с помощью процедуры Draw можно выводить обычный текст и выделенный текст. Попробуем использовать это обстоятельство для того, чтобы поместить в окно просмотра указатель на текущий элемент данных. Для этого добавим в TInterior еще одно поле:
type
Tinterior = object (TScroller)
Location: Word;
end;
Поле Location будет хранить номер той строки, которая отождествляется с выбранной строкой и которая на экране должна выделяться цветом. Добавьте в конце метода ReadFile строку
Location := 0;
и измените метод Draw:
Procedure TInterior.Draw;
{Выводит данные в окно просмотра}
var
n, k: Integer;
В: TDrawBuffer;
р: PString;
Color: Byte;
begin
if Delta.Y > Location then
Location := Delta.Y;
if Location > Delta.Y+pred(Size.Y) then
Location := Delta.Y+pred(Size.Y);
for n := 0 to pred(Size.Y) do
begin
k := Delta.Y+n;
if k=Location then
Color := GetColor(2)
else
Color := GetColor(1);
end
- end; {Tin terior.Draw}
Вначале проверяется, попадает ли строка с номером, хранящимся в Location, в число выводимых строк. Если это не так, значит пользователь изменил размеры окна или сдвинул его относительно текста; в этом случае нужным образом корректируется значение Location. Такая проверка гарантирует, что в окне всегда будет выводиться текущая строка. Перед выводом очередной строки сравнивается значение ее номера с величиной Location и, если величины совпадают, строка выводится цветом 2 из палитры TScroller (темно-синими символами на сером фоне).
Создав указатель в окне, нужно предусмотреть и средства воздействия на него. Для этого нам понадобится проверять действия пользователя с мышью и клавиатурой и изменять положение указателя. Вы не забыли, что все действия программы в Turbo Vision выполняются с помощью обработчика событий? Перекроем стандартный метод HandleEvent в объекте TInterior:
type
Tinterior = object(TScroller)
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
Procedure TInterior.HandleEvent(var Event: TEvent);
{Обработчик событий для окна данных}
var
R: TPoint;
begin
Inherited HandleEvent(Event);
case Event.What of
evMous eDown:{Peaкция на щелчок мышью}
begin
MakeLocal(MouseWhere, R);{Получаемв R локальные
координаты указателя мыши) Location ':= Delta.Y+R.Y;
Draw
end;
evKeyDown:{Реакция на клавиши + - }
case Event. KeyCode of
kbGrayMinus: if Location > Delta.Y then
begin
dec(Location);
Draw
end;
kbGrayPlus: if Location < Delta.Y+pred(Size.Y) then begin ' inc(Location);
Draw
end;
end
end
end;{Tin terlor.HandleEvent}
В новом методе вначале вызывается унаследованный обработчик событий TScroller.HandleEvent, с помощью которого обрабатываются все стандартные действия с окном (смещение текста, изменение размеров и т. д.). Затем обрабатываются события от нажатия кнопки мыши и от нажатия клавиш <+> и <—> из зоны цифровых клавиш (на клавиатуре ПК они выделяются серым цветом). С клавишей <+> связывается действие "сместить указатель вниз на одну строку", с клавишей <—> — "сместить вверх". Выбор серых клавиш (из зоны дополнительных цифровых клавиш) <+> идля сме
щения указателя вызван тем, что клавиши управления курсором используются для смещения окна и обрабатываются стандартным обработчиком событий. Заметим, что нажатие кнопки мыши будет обрабатываться в TScroller.HandleEvent только в том случае, если указатель мыши находится на рамке окна или на полосах управления. Если указатель сместить внутрь окна, нажатие на кнопку мыши будет преобразовано в событие evMouseDone и передано в наш обработчик. В этом случае глобальная переменная Mousewhere содержит абсолютные координаты указателя мыши (т. е. координаты относительно левого верхнего угла экрана). Чтобы получить номер соответствующей строки текста, мы сначала с помощью оператора
MakeLocal(MouseWhere, R);
получаем в переменной R локальные координаты мыши относительно границ окна TScroller. Оператор
Location := Delta.Y+R.Y;
устанавливает в поле Location номер той строки текста, на которой располагается указатель мыши.
Диалоговое окно выбора режима
Подведем некоторые итоги. Мы создали программу, которая погружает пользователя в среду объектно-ориентированного диалога Turbo Vision: она поддерживает командные клавиши, работу с мышью, может сменить каталог или диск, выбрать нужный файл и загрузить его в окно просмотра. Не так плохо для 300 строк программного текста! Наша дальнейшая задача — реализовать другие режимы работы (поиск нужной строки, добавление и уничтожение строк, их изменение). Для двух из них (уничтожение и редактирование строки) в программе необходимо каким-то образом указать ту строку, с которой будет работать пользователь. Мы уже реализовали эту возможность, предусмотрев в окне просмотра текста управляемый указатель. Поэтому режим просмотра можно принять в качестве основного режима работы с данными. В связи с этим следует несколько изменить метод TNotebook.HandleEvent, добавив автоматический переход в режим просмотра данных в случае'успешного открытия файла с данными:
Procedure TNotebook.HandleEvent(var Event: TEvent);
(Обработчик событий программы}
begin
Inherited HandleEvent(Event);
if Event.What =then
case Event.Command of
cmOpenFile:
begin
FileOpen;
if OpFileF then Work
end;
end;{TNotebook.HandleEvent)
Как из режима просмотра данных перейти к другим режимам? Возможно несколько решений. Я предлагаю для этих целей воспользоваться командой cmClose (закрыть окно просмотра): в момент, когда пользователь в режиме просмотра данных нажмет клавишу <Esc> или воздействует мышью на кнопку Закрыть окно, на экране должно раскрыться диалоговое окно выбора режима, предлагающее одно из пяти возможных продолжений:
закрыть окно просмотра;
удалить текущую запись;
О искать нужную запись;
редактировать текущую запись;
добавить запись (записи).
Для реализации этой идеи в уже созданный нами обработчик событий TInterior.HandleEvent следует ввести обработку события cmClose: const
{Команды для обработчиков событий:)
cmCan = 205; cmDelete = cmSearch = cmEdit= 208;
cmAdd= 209;
Function Control : Word;
{Создает и использует диалоговое окно выбора режима работы) begin Control := cmCan.
end; (Control}
(;
Procedure TInterior.HandleEvent(varEvent: TEvent) ;
{Обработчик событий для окна данных)
Procedure Deleteltem;
(Удаляетуказанный в Location элемент данных) begin end; (Deleteltem)
Procedure Addltem(Edit: Boolean);
(Добавляет новый или редактирует старый элемент данных)
begin
end; (AddItem)
(•)
Procedure Searchltem;
(Ищет нужный элемент)
begin
end; (Searchitem}
var
R: TPoint;
label Cis;
begin(Tinterior.Handl eEvent}
Inherited HandleEvent (Event);
case Event.What of evCommand:
case Event.Command of cmClose: begin
Cis:
case Control of{Получить команду из
основного диалогового окна)
cmCan, cmCancel: EndModal(cmCancel); cmEdit : cmDelete: Deleteltem;
cmSearch: Searchitem; cmAdd: Additem(False);
end end; cmZoom: exit;
end;
evMouseDown:{Позиционировать мышью)
evKeyDown:{Позиционировать клавишами + -}
case Event.KeyCode of kbEsc: goto Cis;
KbGrayMinus: if Location > Delta.Y then
end; {anterior.HandleEvent}
В этом фрагменте мы расширили набор нестандартных команд (константы cmCan—cmAdd), ввели новую функцию Control и предусмотрели необходимые процедуры в теле обработчика событий. Заметим, что режимы редактирования записи и добавления новой записи очень схожи по организации диалога с пользователем, поэтому они реализуются в рамках одной процедуры AddItern и управляются параметром обращения к ней.
Функция control используется для создания диалогового окна выбора продолжения. В качестве значения этой функции будет возвращаться одна из пяти новых команд. В начальном варианте функция возвращает команду cmCan, что интерпретируется обработчиком событий как указание на завершение работы с диалоговым окном. Поэтому, если вы вставите указанный текст в программу и запустите ее, поведение программы останется прежним.
Займемся реализацией функции control. Она должна создать диалоговое окно выбора режима, получить с его помощью команду, идентифицирующую выбранный режим, и вернуть эту команду в качестве своего значения:
Function Control: Word;
{Получает команду из основного диалогового окна)
const
X = 1;
L = 12;
DX= 13;
But: array [0..4] of String [13] = {Надписи на кнопках:}
С~1~ Выход'~2~ Убрать ','~3~ Искать
• ~,4~ Изменить'~5~Добавить 1) ;
Txt: array [0..3] of String [52] = (
{Справочный текст:}
'Убрать — удалить запись, выделенную цветом*,
’Искать — искать запись, начинающуюся нужными буквами',
'Изменить — изменить поле (поля) выделенной записи’,
’Добавить — добавить новую запись');
var
R: TRect;,
D: PDialog;
k: Integer;
begin
R.Assign(7,6, 74,15) ;
D := New(PDialog, Init (R, 1 Выберите продолжение: ')) ;
with D~ do
begin
for k := 0 to 3 do {Вставляем поясняющий текст)
begin
R. Assign (1, l+k,65, 2+k) ;
Insert(New(PStaticText, Init(R,#3+Txt[k]) ))
end;
for k := 0 to 4 do {Вставляем кнопки:)
begin
R.Assign(X+k*DX,6,X+k*DX+L,8);
Insert(New(PButton,
Init(R,But[k],cmCan+k,bfNormal)))
end;
SelectNext(False); {Активизируем первую кнопку) end;
Control := DeskTopA.ExecView(D) ; (Выполняем диалог) end; {Con trol)
Сначала создается диалоговое окно с заданными размерами (чтобы программе стал доступен тип TDialog, укажите в предложении uses модуль Dialogs). Затем в цикле for k 0 to 3 do
в окно вставляется поясняющий текст (рис. 15.10).
Г/ Файл Ц/ РаботаГ/ Файл Ц/ РаботаВыберите нужную запись. ESCВыберите нужную запись. ESC--переход к диалогупереход к диалогуИскатьИскать--Убрать искатьУбрать искать--= Выберите продолжение: =- удалить запись, выделенную цветом= Выберите продолжение: =- удалить запись, выделенную цветомзапись, начинающуюся на нужную букву (буквы)запись, начинающуюся на нужную букву (буквы)Изменить - изменить попе (поля) выделенной записиИзменить - изменить попе (поля) выделенной записиДобавитьДобавить--добавить новую записьдобавить новую запись
Г/ Файл Ц/ Работа
Г/ Файл Ц/ Работа
Выберите нужную запись. ESC
Выберите нужную запись. ESC
-
-
переход к диалогу
переход к диалогу
Искать
Искать
-
-
Убрать искать
Убрать искать
-
-
= Выберите продолжение: =- удалить запись, выделенную цветом
= Выберите продолжение: =- удалить запись, выделенную цветом
запись, начинающуюся на нужную букву (буквы)
запись, начинающуюся на нужную букву (буквы)
Изменить - изменить попе (поля) выделенной записи
Изменить - изменить попе (поля) выделенной записи
Добавить
Добавить
-
-
добавить новую запись
добавить новую запись
■1ЕШЛ1 F-EBUQI IIKBEE0I, МЕШЗИ 1ЕВШ
Alt-X Выход F2 Закрыть F3 Открыть F4 Работа F1B Пеню
Рис. 15.10.Диалоговое окно функции Control
Этот текст не связан с диалогом и называется статическим. Для вставки статической строки в любой видимый элемент служит конструктор TStaticText. Init, которому в качестве параметров передаются координаты строки и сама строка. Как вы уже могли заметить, идентификаторы объектов в Turbo Vision начинаются на букву Т, а идентификаторы типов- указателей на экземпляры этих объектов начинаются на букву Р. Таким образом, PStaticText — это тип-указатель на экземпляр объекта TStaticText, поэтому оператор
Insert(New(PStaticText, Init(R,'Текст'))
помещает строку "Текст" на место, заданное координатами переменной R. Отметим, что если строка начинается с символа #3, то при выводе на экран
она будет размещаться в центре прямоугольника R. Мы учтем это соглашение и дополним каждую выводимую строку этим символом.
В цикле
for k := 0 to 4 do {Вставить кнопки:}
в окно вставляются пять кнопок. При их инициализации используется то обстоятельство, что определенные нами команды cmCan—cmAdd образуют непрерывное множество [205-209].
Особо следует остановиться на операторе
SelectNext(False); {Активизируем1-ю кнопку}
Дело в том, что по умолчанию активизируется тот элемент диалогового окна, который задан (вставлен в окно) последним. Чтобы изменить активность по умолчанию, необходим вызов процедуры SelectNext, которая смещает активность к следующему элементу. Так как элементы образуют замкнутую цепь (от последнего элемента активность переходит к первому), параметр обращения к этой процедуре указывает направления смещения: если он имеет значение False, активным станет следующий в цепи элемент, если True — предыдущий.
Прежде чем вы попробуете запустить эту программу на счет, внесем в нее несколько изменений. Во-первых, пора убрать имитацию данных, показываемых в окне просмотра. Для этого в процедуре TInterior.ReadFile необходимо удалить строки
s : = copy(ParamStr(0),1,pos('.',ParamStr(0))) +'pas'; assign(f,s);
exit;
Надеюсь, что вы заблаговременно подготовили остальной текст этого метода, если это не так, вставьте операторы
seek (DataFile, 0);
while not (EOF (DataFile) or LowMemory) do
begin
end;
Location := 0
(см. разд. 15.11).
Во-вторых, обратили ли вы внимание на то, что в процедуре TNotebook.Work (см. разд. 15.8) указатель PW инициализируется оператором
PW :=
15 5ак. 941
а динамическая память, выделенная для размещения экземпляра объекта TWorkWin, не возвращается обратно в кучу? Если да, то у вас есть хорошие шансы избежать многих неприятностей при программировании в среде Turbo Vision. Конечно же, нам следовало где-то в программе позаботиться об удалении ненужного нам экземпляра объекта. Чтобы не усложнять программу, я не стал этого делать: если вставить оператор
Dispose(PW, Done)
сразу за оператором
DeskTops.Insert(PW)
то вновь созданное окно будет тут же удалено с экрана, поэтому оператор Dispose НУЖНО разместить В обработчике событий TNotebook.HandleEvent (подумайте, где именно).
После включения диалогового окна в цепочку действий, связанных с инициализацией PW, появилась возможность приостановить исполнение программы в процедуре work: вместо оператора
DeskTopA . Insert (PW)
вставьте следующие строки:
Control := DeskTopA.ExecView(PW);
Dispose(PW, Done)
и добавьте описание переменной control:
var,
Control: Word;
В отличие от процедуры insert, процедура Execview не только помещает видимый элемент на экран, но и приостанавливает дальнейшее исполнение программы work до тех пор, пока не закончится диалог с пользователем.
И, наконец, еще одно усовершенствование. Работа с программой станет удобнее, если сразу после чтения файла с данными она перейдет к их показу. Реализовать это очень просто. Добавьте вызов процедуры work в процедуру FileOpen следующим образом:
Procedure TNotebook.FileOpen;
begin
if OpFileF then begin
Work(Переходим к работе)
end;
end; {FileOpen}
Если вы внесете в программу все описанные изменения и запустите ее на счет1, то при попытке выйти из режима просмотра на экране будет развернуто диалоговое окно, показанное на рис. 15.10. Щелчок на любой кнопке этого окна не приводит ни к каким последствиям — наше окно пока откликается только на стандартную команду cmClose, связанную с клавишей <Esc>.
Обработка команд пользователя
Обработчик событий диалогового окна поддерживает только стандартные команды cmciose, cmOk, cmCancel. Как заставить его реагировать на команды пользователя? Вы правы: нужно перекрыть стандартный обработчик событий.
Введем новый объект TDigWin как потомок объекта TDialog и перекроем его метод HandleEvent:
type
PDlgWin =ATDlgWin;
TDigWin = object (TDialog)
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
В новом методе следует сначала вызвать стандартный обработчик, а затем проанализировать событие, если оно не очищено и содержит команду, значит была нажата какая-то командная кнопка, и нам следует заставить обработчик закрыть окно и вернуть эту команду как результат диалога с пользователем:
Procedure TDlgWin.HandleEvent(var Event: TEvent);
(Обработчик событий для основного диалогового окна) begin
Inherited HandleEvent(Event);
if Event.What =then
EndModal(Event.Command) (Закрыть окно и вернуть команду)
end;
। Файл с данными DataType пока еще не существует. Чтобы программа смогла нормально работать, в диалоговом окне открытия файла укажите произвольное имя, например MYDATA. После завершения работы программы будет создан пустой файл MYDATA.DAT.
Метод предназначен для того, чтобы завершить работу с диалоговым окном и вернуть команду в программу, использующую это окно. Измените описание переменной в функции Control на
var
D:>
и обращение к методу
DInit
и вновь запустите программу: теперь нажатие на любую кнопку диалогового окна приведет к его закрытию.
Редактирование
и добавление записей
Для редактирования и добавления записей создадим окно, показанное на рис. 15.11.
Рис. 15.11. Окно ввода/редактирования записей
Режим редактирования отличается от режима добавления записей двумя обстоятельствами. Во-первых, в режиме редактирования поля ввода данных окна должны содержать текст, взятый из редактируемой записи, а в режиме ввода эти поля пусты. Во-вторых, режим редактирования завершается сразу после нажатия на клавишу в то время как в режиме ввода нажатие
на эту клавишу означает добавление к файлу текущей записи и переход к вводу следующей. Режим ввода завершается командой cmClose (клавиша <Esc>). С учетом этого оба режима реализуются в рамках одной процедуры Additem (Edit), а параметр Edit указывает нужный режим: если Edit = = True, реализуется режим редактирования, если False — режим добавления записей. В листинге 15.1 приведен текст этой процедуры.
Procedure Additem(Edit: Boolean);
/Добавляет НОВЫЙ или редактирует старый элемент1 данных} const
у = 1;
dy= 2;* (
L = LName+LPhone+LAddr;
var
Data: DataType;
R: TRect;
InWin: PDialog;
BName,BPhone,BAddr: PInputLine;
Control: Word;
OldCount: Word;
s: String;
p: PString;
begin
Seek(DataFile,Filesize(DataFile)); [Добавляем записи в конец файла}
repeat {Цикл ввода записей} if Edit then (Готовим заголовок) s := 'Редактирование:' else begin
Str(FileSize(DataFile)+1, s);
while Length(s) < 3 do
s := 10’+s;
s := 'Вводится запись N ' +s
end;
- FillChar(Data,SizeOf(Data),' ');{Заполняемполя пробелами)
R.Assign(15,5,65,16) ;
InWinNew (PDialog,. Init (R, s) );{Создаем окно}
\
with InWinA do
begin{Формируем окно:}
R.Assign(2,у+1,2+LName,у+2);
BName := New(PInputLine, Init(R,LName)); Insert (BName);(Поле имени}
R.Assign(2,y,2+LName,y+1);
Insert(New(PLabel, Init(R, 'Имя',BName)));
R.Assign(2,y+dy+1,2+LPhone, y+dy+2) ;
BPhone :=New(PInputLine, Init(R,LPhone)); Insert (BPhone) ;(Поле телефона}
R.Assign(2,y+dy,2+LPhone,y+dy+1) ;
Insert(New(PLabel, Init(R, 'Телефон',BPhone)));
R.Assign(2,y+2*dy+l,2+LAddr,y+2*dy+2);
BAddr : =New(PInputLine, Init (R, LAddr) ) ;
Insert (BAddr);{Поле адреса}
R.Assign(2,y+2*dy,2+LAddr,y+2*dy+l);
Insert(New(PLabel,
Init(R, 'Адрес',BAddr)));
(Вставляем две командные кнопки:}
R.Assign(2,y+3*dy+l,12,y+3*dy+3);
Insert(New(PButton, Init(R, 'Ввести',cmOK,bfDefault)));.
R.Assign(2+20,y+3*dy+l,12+20,y+3*dy+3);
Insert(New(PButton, Init(R, 'Выход',cmCancel,bfNormal)));
SelectNext(False)(Активизируемпервую кнопку)
end; (Конец формирования окна) if Edit then with Data do begin (Готовим начальный текст:} p := РЗЛ.At(Location) ; (Читаемданные из записи} s := рА;
Nameсору Qs, 1, LName) ;
Phone:= copy(s,succ(LName),LPhone);
Addr : = copy(s,succ(LName+LPhone),LAddr);
InWinA.SetData(Data) (Вставляем текст в поля ввода} end;
Control := DeskTopA.ExecView(InWin) ; {Выполняемдиалог}
if Control=cmOk then with Data do
begin if Edit then
Deleteltem;(Удаляем старую запись}
Name :=BNameA . DataA;
Phone : = BPhoneA.DataA;
Addr := BAddrA.DataA; s[0] := FillChar (s fl] ,L, ' '); move (Name[ 1 ] , s [ 1 ] , Length (Name) ) ; move (Phone [1] ,s[succ(LName)],Length(Phone) ); move (Addr [1], s [succ(LName+LPhone)] ,Length(Addr)) ; OldCount := PSA. Count; (Прежде количество записей} PSA. Insert (NewStr(s)); (Добавляемв коллекцию} (Проверяем добавление}
if OldCount <> PSA. Count then
Write (DataFile,Data) (Да — добавляем в файл}endV
until Edit or (Control=cmCancel);
Draw
end;{Additem}
Вначале указатель файла смещается в самый конец, подготавливая добавление записей (судя по всему, режим добавления будет использоваться гораздо чаще, чем режим редактирования). Затем формируется заголовок окна и само окно. Операторы , if Edit then with Data do .
begin {Готовим начальный текст:}
end;
готовят начальное состояние полей ввода в режиме редактирования. Оператор
1пИ1пл.SetData(Data)
помещает подготовленный текст в нужные поля. При обращении к процедуре SetData данные должны быть предварительно подготовлены в строгом соответствии с порядком создания диалоговых полей в окне и типом их данных. Поскольку в нашем случае формат данных в полях ввода окна совпадает с форматом файловых данных, одна и та же переменная может служить как для работы с файлом, так и для установки начальных значений диалоговых полей.
В самом общем случае программист должен объявить новый тип, соответствующий формату помещаемых в окно данных, и использовать выражение этого типа в качестве параметра обращения к процедуре SetData. Например, если бы в нашем окне было предусмотрено только одно поле ввода Телефон, то установку данных можно было бы осуществить таким оператором:
InWinA. SetData (DataType.Phone)
где DataType . Phone — выражение типа String [LPhone].
Контроль за соответствием типа устанавливаемых данных порядку объявления и типу данных диалоговых полей полностью возлагается на программиста.
В операторах
if Control=cmOk then with Data do
begin
end
данные, полученные из диалогового окна, помещаются сначала в отсортированную коллекцию, а затем — в файл. С помощью оператора
if OldCount <> PSA.Count then
проверяется изменение количества данных в коллекции (напомню, что в отсортированную коллекцию можно поместить только уникальную запись). Если количество записей в коллекции изменилось, значит новая запись не совпадает ни с одной из уже имеющихся и ее следует поместить в файл.
Операторы
if Edit then
Deleteltem; {Удаляем старую запись}
предварительно удаляют старую запись с помощью обращения к процедуре Deleteltem.
Удаление записи
При реализации режима удаления записи нам нужно учесть тот факт, что порядок следования записей в файле и коллекции может быть различным. Поэтому в процедуре Deleteltem организуется цикл поиска в файле удаляемой записи:
Procedure Deleteltem;
(Удаляет указанный в Location элемент данных)
var
D: Integer;
PStr: PString;
String;
Data: DataType;
begin
PStr := PSA.At(Location); {Получаем текущую запись}
s := copy(PStrA,1,LName);
seek(DataFile,0);
D := -1;{D — номер записи в файле}
repeat{Цикл поиска по совпадению поля Name:}
inc(D);
read(DataFile,Data);
with Data do while Length(Name) < LName do Name := Name+' '
until Data.Name=s;
seek(DataFile,pred(FileSize(DataFile)));
read(DataFile,Data);{Читаемпоследнюю запись)
seek(DataFile,D);
write(DataFile,Data); (Помещаем ее на место удаляемой}
seek(DataFile,pred(FileSize(DataFile)));
truncate(DataFile); (Удаляем последнюю запись)
with PSA do D := IndexOf(At(Location));
PS л.At Free(D);{Удаляем строку из коллекции}
Draw{Обновляем окно }
end; {Deleteltem}
Режим поиска записи
Для поиска нужной записи сформируем диалоговое окно, показанное на рис. 15.12.
С помощью этого окна пользователь может задать несколько начальных букв, являющихся ключом для поиска записи. Получив данные из этого окна, процедура Searchitem организует поиск первой от начала коллекции строки, для которой не выполняется условие
PatternItem
где:
□ Pattern — образец поиска;
— текущая строка коллекции.
Найденная строка указывается как текущая в поле Location и организуется вывод соответствующего текста в окне просмотра.
W Файл W/ РаботаW Файл W/ РаботаВыберитезапись. ESCft.114-43-32284-12-89Выберитезапись. ESCft.114-43-32284-12-89--переход к диалогупереход к диалогу• f>.• f>.Бабаджанян БабкинБалтянБирюковБубнов Б.А.Бурак ft.Веселов А.П.Вибке Л.Воронов Воронов ГагинГадушкин А.И.ГалузаГладилина Г.А.Гриднев Г.Ф.ГусевГусевДачаБабаджанян БабкинБалтянБирюковБубнов Б.А.Бурак ft.Веселов А.П.Вибке Л.Воронов Воронов ГагинГадушкин А.И.ГалузаГладилина Г.А.Гриднев Г.Ф.ГусевГусевДача263-56-68263-56-68кв. 87кв. 87267-26-20227-26-70203-95-97268-14-26593-22-20267-26-20227-26-70203-95-97268-14-26593-22-20299-27-92 141-69-03 237-95-23 240-04-69299-27-92 141-69-03 237-95-23 240-04-69Выход F2 Закрыть F3 Открыть F4 Работа F10Выход F2 Закрыть F3 Открыть F4 Работа F10
W Файл W/ Работа
W Файл W/ Работа
Выберитезапись. ESC
ft.114-43-32
284-12-89
Выберитезапись. ESC
ft.114-43-32
284-12-89
-
-
переход к диалогу
переход к диалогу
• f>.
• f>.
Бабаджанян Бабкин
Балтян
Бирюков
Бубнов Б.А.
Бурак ft.
Веселов А.П.
Вибке Л.
Воронов Воронов Гагин
Гадушкин А.И.
Галуза
Гладилина Г.А.
Гриднев Г.Ф.
Гусев
Гусев
Дача
Бабаджанян Бабкин
Балтян
Бирюков
Бубнов Б.А.
Бурак ft.
Веселов А.П.
Вибке Л.
Воронов Воронов Гагин
Гадушкин А.И.
Галуза
Гладилина Г.А.
Гриднев Г.Ф.
Гусев
Гусев
Дача
263-56-68
263-56-68
кв. 87
кв. 87
267-26-20
227-26-70
203-95-97
268-14-26
593-22-20
267-26-20
227-26-70
203-95-97
268-14-26
593-22-20
299-27-92 141-69-03 237-95-23 240-04-69
299-27-92 141-69-03 237-95-23 240-04-69
Выход F2 Закрыть F3 Открыть F4 Работа F10
Выход F2 Закрыть F3 Открыть F4 Работа F10
Рис. 15.12..Окно ввода шаблона поиска
В реализации процедурыуказанная проверка осуществляется
для строк,преобразованных к прописным буквам с помощью внутренней процедурыт. е. поиск игнорирует возможную
разницу в высоте букв шаблона и строк коллекции.
Procedure Searchitem;
нужный
(s:String;
строку в верхний var(
k: Integer; begin k := 1 to Lengthdo
' if s[k] inthen
s[k] :=
else if s[k] inthen
s[k] :=
else if s[k] inthen
s[k] :=
UpString := s
end;
var
PDialog;
R: TRect;
s: String;
p: PInputLine;
k: Word;
begin {Searchitem}
R.Assign(15,8,65,16)
InWin :=
Init(R,'Поиск записи:'));
with InWinA do
begin
R. Assign(2,2,47,3);
p : = New(PInputLine, Init(R,50));
Insert(p);
R.Assign(1,1,40,2);
Insert(New(PLabel, Init(R, 'Введите образец для поиска: ', p) ) ) ;
R. Assign(10,5,20,7);
Insert (New(PButton,
Init(R,'Ввести', cmOk,bfDefault)));
R.Assign(25,5,35,7);
Insert(New(PButton,
Init(R,'Выход',cmCancel,bfNormal)));
SelectNext(False)
end;
if DeskTopA.ExecView(InWin) = cmCancel then exit;
s := pA.DataA;
Location := 0;
while (UpString(s) >= UpString(PString(PSA.At(Location))A)) and (Location < pred(PSA.Count)) do
inc(Location);
if (Location < Delta.Y) or
(Location > Delta.Y+pred(Size.Y)) then
ScrollTo(Delta.X,Location)
else
Draw
end; {Searchitem}
15.18. Итоги ,
Итак, мы завершили создание диалоговой программы, обслуживающей электронную "записную книжку". В ходе ее реализации вы познакомились с некоторыми возможностями диалоговой среды Turbo Vision. Я не ставил себе целью дать здесь подробное описание всех или даже использованных в программе средств Turbo Vision — этому посвящены остальные главы этой части книги. В частности, вне рамок примера остались такие важные механизмы, как потоки, ресурсы, и постпроцессорные события и многие другие возможности Turbo Vision. Однако уже рассмотренные средства свидетельствуют о том, что программирование в Turbo Vision существенно отличается от традиционных методов создания программ, и это отличие является следствием широкого применения в Turbo Vision механизмов объектно-ориентированного программирования.
Наша программа, насчитывающая всего около 600 строк, обеспечивает весьма высокий уровень диалога с пользователем: в ней есть командные клавиши, "всплывающие" меню, удобные диалоговые окна, поддержка мыши. Думаю, что вряд ли каким-либо другим способом мы смогли бы создать столь сложную диалоговую среду программой такого объема. Таким образом, Turbo Vision является превосходным инструментом для разработки диалоговых программ, ориентированных на текстовый режим работы экрана. Использование этой среды в ваших программах резко сократит сроки их создания и повысит качество.
Разумеется, созданная программа далека от совершенства, однако даже в этом виде она, как показывает мой собственный опыт, может быть достаточно полезной. При желании ее можно взять за основу создания более сложной информационно-поисковой системы.
Глава 16
Общая характеристика объектов
Все свойства Turbo Vision заключены в полях и методах входящих в библиотеку объектов. Каждый объект предназначен для тех или иных целей, поэтому, если вы хотите реализовать какие-то возможности Turbo Vision, вы должны создать и использовать экземпляры объектов с нужными вам свойствами. Этот процесс не представляет никакой сложности: вспомните обычные переменные или константы Турбо Паскаля; если, например, в программе понадобится осуществить преобразование строковых данных, вы объявляете переменную типа string, для реализации математических вычислений — переменную типа Real и т. д. Точно так же обстоит дело и с объектами: для создания окна вы можете объявить переменную типа TWindow, для использования отсортированной коллекции строк — переменную типа TStringcollection и т. д. Например:
var,
.
MyWindow: TWindow;
MyCollection: TStringCoiiection;
Однако для многих практических применений вы можете не найти среди объектов Turbo Vision такого, который бы имел все нужные вам свойства. В этом случае вы должны создать новый объект как потомок от какого-либо стандартного объекта Turbo Vision и наделить его дополнительными СВОЙСТвами. Если, например, вновь создаваемое окно предназначено для вывода отсортированных текстовых строк, вы можете изменить его конструктор Init, обеспечив в нем создание и использование экземпляра объекта TStringCoiiection:
type
MyWindowType = object (TWindow)
MyCollection: PStringCollection;
Constructor Init;
end;
Constructor MyWindowType.Init;
begin
MyCollection := New(PStringCollection, Init (...));
end;
Чтобы вновь создаваемый объект был по возможности простым в реализации, он должен наследовать от объекта-родителя максимальное количество нужных ему полей и методов. Таким образом, ключом к работе с Turbo Vision является знание тех свойств, которыми обладают ее стандартные объекты. Изучению объектов посвящена фактически вся эта часть книги, в этой главе мы дадим наиболее общую их характеристику.
Структура объектов
Все множество объектов Turbo Vision строится на базе трех примитивных объектов: TPoint, TRect и TObject. Примитивными они называются потому, что не являются потомками каких-либо других объектов, но используются или могут использоваться как основа порождаемых от них деревьев родственных типов (рис. 16.1).
TPoint
TRect
TObject
Рис. 16.1. Примитивные объекты Turbo Vision
Объекты TPoint и TRect служат для задания координат точки (TPoint) и прямоугольника (TRect) на экране. В Turbo Vision эти объекты не имеют потомства, но используются всеми другими объектами, связанными с созданием изображений на экране ПК. Объект TObject является основой иерархии всех других объектов Turbo Vision, кроме TPoint и TRect (рис. 16.2).
Объект TObject не имеет данных (полей) и содержит три метода: Init, Free и Done. Конструктор Init осуществляет распределение памяти и служит основой всех других конструкторов Turbo Vision. Метод Free освобождает память, распределенную конструктором. Деструктор Done не выполняет никакой работы — это абстрактный метод, который всегда перекрывается в объектах-потомках.
! Object -p! S trListM aker
-Т
-!ResourceFile
!S tream -j- !EmsS tream
L!DosStream—!BufStream
! Collection —! SortedCollection -y ! StringCollection —! Resourcecollection
-TValidator—pT LookupValidator—TStringLookupValidator L!FilterValidatorTRangeValidator LT PXPictureValidator
View
-!Scroller—TT extDevice—!! erminal
„TScrollBar
-!SlatusLine
T Hi story
Flame
T Button
-!lnputLine
-TListViewet-pTH istoryViewer ListBT
!Cluster -j-!MultiCheckBoxes I—TCheckBoxes I— !RadioButtons
T Static! ext-y! Param! ext
ox
L!MenuBar
-! Editor—!FileEditor
Application
LFWindow—|—!Dialog—j—!FileDialog
Lt Desk! opL-!ChDirDialc
L ! H istoryWindow L!EditWindow
Рис. 16.2. Иерархия объектов Turbo Vision
Абстрактные объекты и методы
Объект TObject никогда не используется самостоятельно и является основой для начинающегося с него дерева объектов. В Turbo Vision есть и другие объекты, не предназначенные для самостоятельного применения в программах. Такие объекты называются абстрактными. В правильно сконструированной программе никогда не создаются и не используются экземпляры абстрактных объектов. Эти объекты просто объединяют в себе некоторые общие для всех своих потомков поля и методы.
Во многих объектах Turbo Vision используются абстрактные методы, т. е. методы, которые не содержат исполняемых операторов и служат заготовками для аналогичных методов в объектах-потомках. Абстрактные методы всегда являются виртуальными и должны перекрываться в потомках. абстрактными являются используемый для удаления
экземпляра объекта из кучи, метод TSortedCollection.Compare, с ПОМОЩЬЮ которого осуществляется сортировка коллекций, метод TCluster. Press, ответственный за обработку события, связанного с выбором элемента управления в диалоговом окне. В большинстве абстрактных методов предусмотрен вызов глобальной процедуры Abstract, предназначенной для аварийного завершения работы программы и выдачи соответствующего диагностического сообщения.
Некоторые методы не являются абстрактными, но предоставляют лишь минимум возможностей и поэтому почти всегда перекрываются в потомках. Такие методы называются псевдоабстрактными. Примером псевдоабстракт- ного метода может служить метод TView.Draw, ответственный за перерисовку видимого элемента: по умолчанию он ничего не делает и оставляет выделенную ему часть экрана без изменений.
Функциональность объектов
В функциональном отношении все потомки объекта TObject делятся на две группы: видимые элементы и невидимые объекты (рис. 16.3).
Видимые элементы могут быть терминальными видимыми объектами или группами видимых элементов. Любой элемент группы в свою очередь может быть группой элементов, однако все группы в конце концов заканчиваются терминальными видимыми объектами.
Рис. 16.3. Видимые и невидимые объекты
Все терминальные видимые объекты (или просто видимые объекты) являются потомками объекта Tview, который придает им специальные свойства, отсутствующие у невидимых объектов. Объект Tview — это прямой потомок TObject (см. рис. 16.2), представляющий собой пустую прямоугольную область экрана. В программе почти никогда не используются экземпляры этого объекта, однако от него прямо или косвенно порождаются все видимые элементы — как стандартные для Turbo Vision, так и создаваемые программистом. Каждый порождаемый от Tview видимый объект владеет прямоугольной частью экрана и наследует его псевдоабстрактные методы Draw и HandleEvent. Метод Draw является основой специализированных методов, обеспечивающих создание изображений. Метод HandieEvent — это центральный метод, с помощью которого реализуется цепочка связанных друг с другом обработчиков событий. Каждый видимый элемент может обрабатывать связанные с ним события — это является одним из важнейших отличительных свойств видимых элементов.
Семейство невидимых объектов включает в себя потоки, ресурсы, контролеры, коллекции и списки строк. Невидимые объекты по существу представляют собой обобщение таких важных механизмов Турбо Паскаля, как файлы и массивы, учитывающее особенности объектно-ориентированных программ. Вы можете создавать невидимые объекты в любых программах, в том числе и не пользующихся изобразительными возможностями Turbo Vision. В частности, они могут оказаться полезными при разработке программ, рассчитанных на графический режим работы дисплея (видимые объекты TurboVision рассчитаны исключительно на текстовый режим работы).
Обзор видимых элементов
Видимый объект — это любой объект, который является потомком Tview и может быть изображен на экране в текстовом режиме работы дисплея. Важным свойством видимых объектов является возможность объединения нескольких видимых объектов в группу, рассматриваемую как единое целое. Любым элементом группы в свою очередь может быть новая группа, в которую могут входить другие группы и т. д., однако любая группа видимых элементов всегда заканчивается терминальными видимыми объектами.
Группы видимых элементов
Абстрактная группа i
Абстрактная группа — это объект TGroup. Этот объект, вообще говоря, не является абстрактным, однако его экземпляры практически никогда не используются в программах: главное назначение объекта — служить основой иерархии родственных объектов пользователя. Хотя группа владеет прямоугольным участком экрана, она становится видимой только за счет визуализации своих
элементов. Элементы группы организуются в связанный список: каждый эле- мейт имеет поле owner, содержащее указатель на своего владельца, и поле Next, с помощью которого указывается следующий элемент списка.
Панель экрана
Панель экрана — это объект TDesktop, создающий фоновое изображение рабочей зоны экрана. TDesktop является прямым потомком TGroup и, следовательно, может владеть произвольным набором видимых элементов. Заполнение панели экрана осуществляется другими видимыми элементами, такими Как TWindow, TDialog И Т. П. Обычно владельцем Группы TDesktop является программа — объект ТApplication или его потомки.
Программы
Все программы, работающие со средствами Turbo Vision, должны порождаться от объекта TProgram или его единственного потомка TAppiication. Оба объекта создают на экране стандартное изображение строки меню, панели экрана и строки статуса, т. е. являются владельцем группы, состоящей ИЗ TMenuBar, TDesktop И TStatusLine (рис. 16.4).
TAppiication отличается ОТ своего родителя только методами Init и Done, с помощью которых осуществляется инициализация основных подсистем Turbo Vision и их закрытие:
Constructor TApplication.Init;
begin
InitMemory;
InitVideo;
InitEvents;
InitSysError;
InitHistory;
TProgam.Init
end;
Destructor TApplication.Done;
begin
TProgram.Done;
DoneHistory;
DoneSysEfror;
DoneEvents;
DoneVideo;
DoneMemory
end;
Рис. 16.4. Стандартная группа
Обычно программа пользователя связывается с экземпляром
или его потомком. Если вас не устраивает стандартная последовательность инициализации и закрытия подсистем Turbo Vision, вы должны создать и использовать потомка отTProgram.
Окна
Для создания окон в Turbo Vision предусмотрен объектОбычно
этот объект владеет объектом т Frame и поэтому очерчивается прямоугольной рамкой со стандартными кнопками изменения размера и закрытия. Вы можете перемещать окно, изменять его размеры и закрывать, используя методы Tview или перекрывая их собственными методами. Если окно имеет номер от 1 до 9, его можно сделать активным (выбрать) командными клавишамигденомер окна. Если окно имеет несколько
видимых элементов, его обработчик событий интерпретирует нажатие на клавишиикак команду активизации следующего
(предыдущего) видимого элемента.
Диалоговые окна
Объектпорожден оти предназначен для реализации взаи
модействия с пользователем. В отличие от Twindow, диалоговое окно не может изменять свой размер (но может перемещаться по экрану). Его обработчик событий генерирует командув ответ на нажатие клавиши
<Esc> (или воздействие мышью на кнопку закрытия) и командув
ответ на нажатие
Терминальные видимые объекты
Рамки
Для создания рамки окна и кнопок изменения размера окна и его закрытия предназначен объект(рис. 16.5).
КнопкаКнопкаКнопкаКнопкаРис. 16.5. Рамка окна (объект TFrame)Рис. 16.5. Рамка окна (объект TFrame)
КнопкаКнопка
КнопкаКнопка
Рис. 16.5. Рамка окна (объект TFrame)
Рис. 16.5. Рамка окна (объект TFrame)
Этот объект никогда не используется самостоятельно, но только в группе, связанной с объектом TWindow. Последний имеет поле Frame с указателем на ассоциированный объект TFrame.
Кнопки
Объект TButton — это прямоугольник с надписью, имитирующий кнопку панели управления. Обычно TButton является элементом группы TDialog и "нажатие" на кнопку инициирует событие, связанное с какой-либо стандартной командой или командой пользователя.
Кластеры
Кластеры представляют собой прямоугольные видимые элементы, имитирующие несколько зависимых или независимых кнопок (рис. 16.6).
Устройство вывода".( ) Экран(•) Принтер( ) ФайлУстройство вывода".( ) Экран(•) Принтер( ) ФайлIид копии:Iид копии:[X] Инверсная[X] Горизонтальная[ ] Перевернутая[X] Инверсная[X] Горизонтальная[ ] Перевернутая
Устройство вывода".
( ) Экран
(•) Принтер
( ) Файл
Устройство вывода".
( ) Экран
(•) Принтер
( ) Файл
Iид копии:
Iид копии:
[X] Инверсная
[X] Горизонтальная
[ ] Перевернутая
[X] Инверсная
[X] Горизонтальная
[ ] Перевернутая
ббa
б
б
Рис. 16.6. Примеры кластеров:
а — с зависимыми кнопками; б — с независимыми кнопками
Для создания кластера и работы с ним предусмотрен абстрактный объект TCluster И его ПОТОМКИTCheckBoxes И TMultiCheckBoxes.
Объект TCluster имеет 16-разрядное поле value, каждый разряд которого связан со своей кнопкой в кластере. Его потомки отличаются способом изменения состояния этих разрядов: TRadioButtons устанавливает содержимое разряда выбранной кнопки в 1, предварительно очистив поле value, a TCheckBoxes переключает его содержимое, сохраняяразряды не
изменными. Многопозиционная кнопка TMultiCheckBoxes может иметь от одного до 8 связанных с ней разрядов в поле value. Количество разрядов зависит от количества возможных состояний кнопки, которых может быть до 256.
Таким образом, кластер может содержать до 16 кнопок и выбирать один из возможных вариантов (объект TRadioButtons)несколько вариантов
(TCheckBoxes и TMultiCheckBoxes). Переключение кнопок осуществляется с помощью мыши или клавиатуры. В последнем случае клавишейвы
бирается нужный кластер, а клавишами смещения курсора — нужная кнопка; состояние выбранной кнопки меняется нажатием на клавишу <Пробел>.
Меню
Для выбора одного из нескольких возможных продолжений в диалоговых программах широко применяются меню. Объект TMenuView и его потомки исущественно упрощают создание и использование ме
ню в среде Turbo Vision (рис. 16.7).
Рис. 16.7. Пример меню
Любая программа в Turbo Vision всегда владеет строкой меню. Перекрывая ВЫИСПОЛЬЗОВать методы
чтобы реализовать свою систему вложенных меню.
Строки ввода
Для ввода различных текстовых строк с клавиатуры ПК в Turbo Vision предусмотрен тип(рис. 16.8). Этот объект предоставляет в
ние пользователя мощные возможности встроенного редактора, обрабатывающего алфавитно-цифровые клавиши, клавиши перемещения курсора вле- во/вправо, а также клавиши <Backspace>, <Del>, <Insert>, <Home>, <End>.
Введите имя еайла:
I* .dat
Рис. 16.8. Пример строки ввода
Протокол ввода
Абстрактный объект THistory реализует механизм выбора строки ввода из списка ранее введенных строк (из протокола ввода). Объект THistory обычно ассоциируется с объектом TinputLine и становится доступным с помощью клавиши смещения курсора вниз (см. рис. 16.8). Протоколы ввода запоминаются в динамической памяти в виде связанных списков и имеют идентификатор (поле HistorylD), позволяющий использовать один и тот же протокол в нескольких объектах TinputLine.
Скользящие окна (скроллеры)
С помощью объекта TScroller реализуется так называемое скользящее окно, т. е. окно с текстом, положение которого (текста) относительно границ окна может меняться. Условимся для краткости такие объекты в дальнейшем называть скроллерами, а процесс смещения текста в окне — прокруткой. В общем случае размеры окна скроллера могут быть значительно меньше полных размеров просматриваемого текста: средства скроллера дают возможность вывести на экран любой фрагмент текста. Обычно скроллеры объединяются в одну группу с объектами TScrollBar, представляющими собой горизонтальную или вертикальную полосу. Эти полосы (полосы прокрутки) указывают положение окна относительно полных границ текста и служат для реализации прокрутки с помощью мыши. Прокрутка текста осуществляется также в ответ на нажатие клавиш смещения курсора и клавиш <PgUp>, <PgDn>, <Home>, <End>, <Ctrl>+<PgUp>, <Ctrl>+<PgDn>.
Потомки TScroller — объекты TTextDevice и TTerminal облегчают реализацию скользящих окон специального вида: объекты содержат методы, обеспечивающие запись текстовых строк в скроллер и чтение этих строк из него. Объект TTerminai отличается от своего родителя наличием внутреннего кольцевого буфера, предназначенного для запоминания текстовых строк, и соответствующих методов доступа к буферу. Размер буфера не может быть больше 64 Кбайт. Типичное применение этих объектов — реализация так называемых драйверов текстовых устройств. Например, можно связать существующий текстовый файл с объектом TTerminai и обеспечить непосредственное считывание строк файла в окно скроллера.
Просмотр списков
Абстрактный объект TListviewer предоставляет в ваше распоряжение средства просмотра списка строк и выбора из этого списка нужной строки. Списки строк выводятся в окне, управляемом полосами прокрутки. Типичное применение объектов TListviewer — просмотр списка файлов. Обычно для этих целей используется потомок TListviewer — объект TListBox, с помощью которого списки файлов выводятся в одну или несколько колонок.
Статический текст
Объекты TStaticText — это терминальные видимые объекты, используемые для вывода текстовых сообщений. В отличие от строк, созданных непосредственным обращением к процедуре WriteLn, текстовая строка объекта TStaticText может входить в группу видимых элементов (например, окно) и управляться этой группой. Статический текст игнорирует любые посланные к нему сообщения о событиях. Типичное применение TStaticText — создание различного рода информационных окон или поясняющего текста в диалоговых окнах.
Строки статуса
Самая нижняя строка экрана в Turbo Vision представляет собой строку статуса. Эта строка создается с помощью объекта TStatusLine. Строка статуса обычно содержит список наиболее важных командных клавиш. Элементы строки можно также выбирать мышью. Содержание строки статуса может изменяться в зависимости от контекста программы. Для этих целей в объекте предусмотрено поле Defs, содержащее указатель на связанный список типа TStatusDef. В каждом элементе этого списка есть два поля Min и мах, задающие диапазон контекста для данного варианта строки. Контекст программы определяется полем TView.HelpCtx.
Невидимые элементы
Потоки
-
Поток — это обобщение процедур ввода/вывода данных. Потоки имеют все необходимые методы, позволяющие им правильно обрабатывать любые наборы данных, в том числе — экземпляры объектов Turbo Vision. Базовый абстрактный объект TStream служит основой для специализированных потоков. Он имеет поле status, определяющее режим доступа к данным (только чтение, только запись или и чтение и запись одновременно). В объекте предусмотрено 7 абстрактных методов: Flush, GetPos, Getsize, Read, Seek, Truncate и write. Эти методы должны перекрываться в потомках TStream для того, чтобы придать потокам необходимые свойства. Перед ис-
пользованием любого вновь созданного объекта в потоке его необходимо зарегистрировать. В ходе регистрации объекта ему приписывается уникальный идентификатор, позволяющий Turbo Vision автоматически распознавать тип циркулирующих в потоке данных. Каждому стандартному объекту Turbo Vision уже приписан уникальный номер в диапазоне от 0 до 99. Для регистрации вновь создаваемых объектов программисту предоставлены номера от 100 до 65 535.
Потоки DOS
TDOSStream — это специализированный поток, реализующий небуферизо- ванный ввод/вывод. Метод Init этого объекта позволяет создать новый или открыть уже существующий дисковый файл, задав его имя и режим доступа. В объекте перекрываются все абстрактные методы TStream за исключением TStream. Flush. В большинстве программ удобнее использовать буферизованный ПОТОК TBufStream, порожденный ОТ TDOSStream.
Буферизованные потоки
TBufStream реализует буферизованную версию TDOSStream. Наличие внутреннего буфера существенно увеличивает скорость доступа к данным. Объект перекрывает абстрактный метод TStream. Flush, используемый для "выталкивания" данных из буфера. Выталкивание данных означает чтение (запись) данных из буфера перед закрытием потока и очисткой буфера.
Потоки EMS
Объект TEMSstream реализует обмен данными с так называемой EMS- памятью (EMS — от Expanded Memory Specification — спецификация расширенной памяти, т. е. оперативная память, превышающая основные для MS- DOS 640 Кбайт). Новые поля этого объекта содержат обработчик EMS, число страниц, размер потока и текущую позицию внутри него.
Ресурсы
Файл ресурсов (объект TResourceFile) — это специальный вид потока, элементы которого могут индексироваться с помощью строковых ключей. При сохранении в потоке очередного элемента (объекта) ему приписывается текстовая строка, которая идентифицирует этот элемент. Для быстрого поиска нужного элемента поток сохраняет в дисковом файле строки-индексы в виде отсортированной коллекции строк вместе с указателем на положение в файле индексируемого элемента и его размером.
Основное назначение файла ресурсов — обеспечение относительно простого способа доступа к диалоговым элементам. Например, вместо того, чтобы создавать диалоговое окно в точке ветвления программы, можно прочитать это окно из заранее подготовленного файла ресурсов.
Коллекции
TCollection реализует набор элементов, подобный массивам языка Турбо Паскаль. В отличие от массивов, содержащих элементы одного какого-либо типа, коллекции обладают свойством полиморфизма, т. е. могут хранить данные разного типа, в том числе и разные объекты. Коллекции размещаются в динамической памяти, поэтому их размер может быть больше 64 Кбайт. Кроме того, размер коллекции может динамически изменяться в ходе работы программы. К каждому элементу коллекции можно обратиться по его номеру (индексу).
Отсортированные коллекции
С помощью объектов TSortedCollection организуются коллекции, элементы которых упорядочены (отсортированы) по какому-либо признаку. Объект содержит абстрактный метод compare, с помощью которого осуществляется упорядочение. Этот метод должен перекрываться для задания нужного способа сортировки элементов коллекции. Метод insert обеспечивает вставку новых элементов в коллекцию с учетом принятого упорядочения. Метод search осуществляет поиск заданного элемента методом двоичного поиска.
Коллекции строк
TStringcollection представляет собой модификацию своего родителя TSortedColiection, обеспечивая лексикографическое упорядочение строк. Метод Freeltem удаляет нужную строку, сохраняя упорядочение строк. Для записи новых строк в коллекцию и чтения строк из нее объект имеет методы Putltem И Getltem.
Коллекции ресурсов
Объект TResourceCollection порожден ОТ TStringCoiiection И используется в объекте TResourceFile (файл ресурсов) для реализации коллекции ресурсов. При этом создается и поддерживается индексная служба в виде отсортированных строк, т. е. метод compare этого объекта поддерживает лексикографическое упорядочение строк.
Списки строк
Объект TStringList реализует специальный вид строкового ресурса, в котором к строкам можно обращаться с помощью числового индекса. Поле Count содержит количество строк в объекте. Этот объект упрощает создание многоязычных диалоговых программ, т. к. с его помощью можно обращаться к любой текстовой строке по ее индексу. В объекте предусмотрен метод Get, осуществляющий чтение нужной строки. Для создания списка строк и добавления к нему новых строк используется объект TStrListMaker.
Контролеры
Абстрактный объект TValidator и его потомки образуют группу объектов- контролеров. Общее назначение этих объектов — осуществление контроля за клавиатурным вводом пользователя. Они связываются с объектами типа TEditor и активизируются при выполнении метода TEditor.Valid. Получив управление, контролеры проверяют соответствие введенных данных определенным требованиям и блокируют завершение ввода, если обнаружена ошибка. Объекты-контролеры избавляют программиста от рутинной работы, связанной с программированием контроля наиболее популярных форматов вводимых данных.
TVaiidator
Абстрактный объект TVaiidator инкапсулирует основные свойства всех объектов-контролеров. Его метод valid вызывается из одноименного метода редактора TEditor и обращается к абстрактному методу TValidator.IsValid, чтобы осуществить необходимый контроль.
TPXPicture Validator
Объект TPXPictureValidator проверяет введенную пользователем строку на соответствие некоторому эталонному образцу ввода. Образец задается в виде текстовой строки, подготовленной в соответствии со спецификацией СУБД Paradox корпорации Borland.
TFilterValidator
Этот-объект проверяет все символы, введенные пользователем, на соответствие набору допустимых символов. Если хотя бы один введенный символ не соответствует ни одному из символов эталонного набора, метод TFilterValidator.IsValid вернет значение False и таким образом будет блокирована попытка пользователя закрыть объект TEditor.
TRangeValidator
Объект TRangeValidator порожден ОТ TFilterValidator. Он преобразует символьный ввод в целое число и проверяет, находится ли оно в заданном диапазоне Min—Max значений. Его метод isvalid возвращает True только в том случае, когда введенные пользователем символы соответствуют внешнему представлению целых чисел, а само введенное число N удовлетворяет ус- ловиюМах >= N >= Min.
TLookup Validator
Абстрактный объект TLookupValidator предназначен для контроля соответствия введенной пользователем строки набору эталонных строк. Фактически он модифицирует поведение TValidator в случае, когда проверяется произвольная текстовая строка. В его потомках должен перекрываться метод Lookup, осуществляющий нужный контроль.
TStringLookup Validator
Объект TStringLookupValidator порожден ОТ TLookupVaiidator И осуществляет контроль введенной пользователем строки, сравнивая ее с набором допустимых строк из отсортированной коллекции строк.
Глава 17
Видимые элементы
Любой видимый элемент Turbo Vision имеет два важнейших свойства: он полностью контролирует изображение в пределах выделенного ему участка экрана и знает, как обрабатывать связанные с этим участком события (нажатие на клавиши или отметку мышью). Эти свойства определяются двумя псевдоабстрактными методами объекта TView (этот объект является родителем всех остальных видимых элементов Turbo Vision): Draw и HandleEvent. Метод Draw знает, как рисовать объект, но не знает, когда это нужно делать. Метод HandieEvent, наоборот, знает когда, но не знает как. Эти методы в наибольшей степени воплощают основной принцип программ, управляемых событиями: процесс создания изображений и процесс обработки событий — это два разных процесса в том смысле, что в первом случае мы сообщаем программе, как создается изображение, а во втором — когда это нужно делать. Обработке событий посвящена следующая глава.
В этой главе мы рассмотрим некоторые детали технической реализации видимых элементов, которые дадут нам возможность разобраться в том, что именно делает видимый элемент и как он это делает. Эти сведения помогут вам правильно использовать видимые элементы в вашей программе.
Территориальность
Видимый элемент владеет прямоугольным участком экрана. Правильно сконструированный элемент обязан заполнять нужным изображением всю выделенную ему область, иначе на экране останется "мусор". Чтобы элемент мог заполнить область, ему должны быть известны координаты закрепленного за ним участка. Эти координаты хранятся в двух полях — origin и size, каждое из которых имеет тип TPoint. Поле origin задает координаты левого верхнего угла области, выделенной элементу, size — ра'змер этой области, т. е. показывает на каком расстоянии от Origin находится его правый нижний угол. Минимальный по размеру видимый элемент, который может хоть что-то вывести на экран, имеет Size.X = Y = 1.
Объект TPoint крайне прост, он только определяет координаты некоторой точки на экране и не имеет никаких методов:
type
TPoint = object
X: Integer;{Горизонтальная координа та}
Y: Integer;{Вертикальная координата}
end;
Координаты в Turbo Vision имеют две особенности. Во-первых, они указывают позицию на экране, начиная с 0, а не с 1, как это принято в стандартных для Турбо Паскаля подпрограммах работы с текстовым экраном (например, GotoXY из модуля CRT). Во-вторых, все координаты задаются относительно границ той группы видимых элементов, в которой создается и используется новый элемент. В Turbo Vision любой видимый элемент входит в состав какой-то группы, поскольку все элементы в конечном счете принадлежат программе, которая сама по себе является группой.
Для указания всех четырех координат видимого элемента и действий над ними используется тип TRect следующего вида:
type
TRect = object
TPoint; (Левый верхний угол }
TPoint; (Правый нижний угол)
Procedure Assign(ХА, YA, XB, YB: Integer);.
(Назначает значения параметров полям А и В }
Procedure Copy(R: Trect);
{Устанавливает все поля, равными прямоугольнику R} •
Procedure Move(ADX, ADY: Integer);
{Смещает прямоугольник, добавляя ADX к А.Х, В.Х и
ADY к A. Y, В. Y)
Procedure Grow(ADX, ADY: Integer);
(Изменяетразмер, вычитая ADX из А.Х'и прибавляя ADX к В.Х;
вычитая ADY из A. Y и прибавляя ADY к В. Y)
Procedure Intersect(R: TRect);
(Изменяет положение и размер прямоугольника до области, определенной пересечением R и текущего положения элемента}
Procedure Union(R: Trect);
(Изменяет прямоугольник до области, определяемой
объединением R и текущего положения элемента}
Function Contains(P: TPoint): Boolean;
(Возвращает TRUE, если точка принадлежит элементу)
Function Equals(R: Trect): Boolean;
(Возвращает True, если положение и размеры прямоугольника R
и элемента одинаковы),
Function Empty: Boolean;
(Возвращает TRUE, если элемент пустой, т. е. если его поля
Size.X и Size.Y равны нулю)
end;
С помощью полей OwnerA.Origin и OwnerA.Size видимый элемент может определить положение и размер своего владельца, т. е. той группы, в состав которой он входит, а с помощью метода
Procedure GetExtend(var R: Trect)
получить в R свои текущие координаты (напомню, что положение и размеры большинства видимых элементов могут меняться в ходе работы программы). Обычно обращение к GetExtend осуществляется перед тем, как задать максимально возможные координаты вновь создаваемого видимого элемента. Например, если внутри окна TWindow нужно поместить скроллер так, чтобы он занял всю внутреннюю часть окна, можно использовать такой фрагмент:
type
MyWindow = object (TWindow)
Constructor Init;
end;
Constructor MyWindow.Init;
var
(Указатель на скроллер){Указателина полосы скроллера)(Указатель на скроллер){Указателина полосы скроллера)(Получаемкоординаты окна) (Уменьшаемих на 1}(Получаемкоординаты окна) (Уменьшаемих на 1}S: PScroller
(Указатель на скроллер)
{Указателина полосы скроллера)
(Указатель на скроллер)
{Указателина полосы скроллера)
(Получаемкоординаты окна) (Уменьшаемих на 1}
(Получаемкоординаты окна) (Уменьшаемих на 1}
R: TRect;
HS, VS: PScrollBar;
begin
GetExtend(R);
R.Grow(-l,~l);
S :=New(PScroller,
Init(S, HS, VS)); {Создаем скроллер)
Insert(S);{Помещаем скроллер в окно}
end;
Вывод изображения
Напомню, что Turbo Vision создает программы, рассчитанные на текстовый режим работы видеоадаптера. В этом разделе рассматриваются особенности вывода текстовых сообщений.
Заполнение области
Видимый элемент может быть частично или полностью перекрыт другими видимыми элементами. Turbo Vision позволяет располагать окна на экране в произвольном порядке, в том числе и накладывая их друг на друга. С помощью метода
Procedure GetClipRect(var R: TRect)
видимый элемент может получить координаты минимальной площади, которую он должен заполнить в данный момент. Обращение к GetClipRect обычно используется в методе Draw и позволяет до минимума сократить время обновления информации на экране.
Следует помнить, что в правильно построенной программе никакой другой видимый элемент не должен вторгаться в область владения данного элемента. Поэтому, если видимый элемент не заполнит всю выделенную ему область, этого за него не сделает никто другой, и незаполненная область окажется "замусоренной" предыдущим выводом в эту часть экрана.
Вывод на экран не рекомендуется выполнять с помощью стандартной процедуры write (WriteLn), т. к. она заполняет только ту часть экрана, которая необходима для вывода, в то время как длина строки в видимом элементе может быть больше строки вывода. С другой стороны, эта процедура игнорирует границы видимого элемента и может "залезть" в чужую область.
Вывод в Turbo Vision основан на применении методов MoveChar, MoveStr и WriteLine. Все три метода используют переменную типа TDrawBuffer в качестве буфера видеопамяти. Метод MoveChar заполняет буфер нужным символом, например, пробелом или символом char (#176) — этим символом заполняется фон панели экрана. Метод MoveStr переносит в буфер строку (подстроку), а метод writeLine переносит буфер в видеопамять и таким образом осуществляет собственно вывод на экран.
Тип TDrawBuffer представляет собой массив слов:
type
TDrawBuffer = array [0..MaxViewWidth-1] of Word;
Константа MaxViewWidth определена в интерфейсной части модуля views и устанавливает максимально возможную длину вывода (132 символа). Элементы массива задают двухбайтные последовательности, необходимые для размещения кода выводимого символа (младший байт) и его атрибутов (старший байт) в видеопамяти ПК. Байт атрибутов определяет цвет выводимого символа и цвет фона, а также содержит признак мерцания (рис. 17.1).
Рис. 17.1. Байт атрибутов видеопамяти
При обращении к методам MoveChar и MoveStr байт атрибутов задается в качестве одного из параметров обращения. Его можно получить с помощью функции Getcolor, параметр обращения к которой определяет нужный номер элемента палитры.
Цвет изображения
Все цвета в Turbo Vision определяются системой палитр. За каждым стандартным видимым элементом закреплен свой набор условных номеров цветов, называемый палитрой. Размер палитры (количество определенных для видимого элемента цветов) зависит от функциональности элемента. Чем сложнее элемент, чем больше функций он выполняет, тем богаче его палитра (каждому элементу палитры приписывается некоторая функциональность: один элемент ответственен за фон изображения, другой — за текст, третий выделяет специальные символы и т. д.). Например, для скроллера палитра состоит всего из двух элементов: первый элемент определяет цвет основного текста, второй — цвет выделенного текста. Обычно скроллер входит в качестве терминального видимого объекта в группу, палитра которой будет больше. Например, часто скроллер помещается в окно TWindow, палитра которого насчитывает уже 8 элементов (см. рис. 17.2).
Палитры образуют систему связанных друг с другом ссылок. Каждый элемент палитры содержит не какой-то конкретный цвет или его код, а целое число, указывающее на номер элемента в палитре своего владельца. Если владелец входит в группу, содержимое его палитры устанавливает связь с нужными элементами палитры этой группы и т. д. Ссылки завершаются на
владельце всех владельцев , т. е. на программе, однако палитра т Program и его потомков содержит не ссылки, а сами байты цветовых атрибутов.
Рамка пассивна
Рамка активна
Кнопка рамки
Страница TScroller
Полоса управления TScroller
Нормальный текст TScroller
Iтекст TScroller
рЗарезервировано
£ с)11 121315 Палитра
I 111,11 I
!345678
I I
£' Палитра TScroller
12
IПодсвеченный текст
текст
РИС. 17.2. Связь палитры TScroller с палитрой
Пусть, например, при формировании изображения в методе Draw выбран первый элемент палитры (нормальный текст). Этот элемент содержит число 6, указывающее номер шестого элемента палитры владельца TScroller. Если владельцем скроллера является объект Twindow, это число означает ссылку на шестой элемент палитры Twindow, который содержит число 13 как ссылку на тринадцатый элемент владельца окна (см. рис. 17.2). Если, наконец, владельцем окна является программа, то число 13 — это ссылка на тринадцатый элемент палитрыкоторый содержит байт
атрибутовт. е. символы будут выводиться желтым цветом на синем фо
не (рис. 17.3).
CColorCColor$17$1F$1А$31$31$1Е$71$00$17$1F$1А$31$31$1Е$71$00:2:28Рамка пассивнаРамка активнаКнопка рамкиСтраница8Рамка пассивнаРамка активнаКнопка рамкиСтраница:1:115151ЗарезервированоВыбранный текст скроляера Нормальный текст скроллера Зарезервировано1ЗарезервированоВыбранный текст скроляера Нормальный текст скроллера Зарезервировано
CColor
CColor
$17
$1F
$1А
$31
$31
$1Е
$71
$00
$17
$1F
$1А
$31
$31
$1Е
$71
$00
:2
:2
8
Рамка пассивна
Рамка активна
Кнопка рамки
Страница
8
Рамка пассивна
Рамка активна
Кнопка рамки
Страница
:1
:1
15
15
1Зарезервировано
Выбранный текст скроляера Нормальный текст скроллера Зарезервировано
1Зарезервировано
Выбранный текст скроляера Нормальный текст скроллера Зарезервировано
Рис. 17.3. Фрагмент палитры TProgram
Чтобы получить цвет элемента, используется обращение к функции
Getcolor. Эта функция просматривает всю цепочку ссылок от текущего ви-
1694!
димого элемента до программы и найденный таким образом байт атрибутов из палитры TProgram возвращает в качестве результата. Параметром обращения к функции является номер элемента палитры видимого объекта. Если указан номер несуществующего в данной палитре элемента, функция вернет атрибуты $CF и изображение будет выводиться мигающими белыми символами на красном фоне. Такого сочетания цветов нет ни в одной стандартной палитре, поэтому появление мигающих бело-красных символов на экране сигнализирует о непредусмотренном разработчиками Turbo Vision функциональном использовании элемента. Если, например, вставить кнопку TButton в текстовое окно TWindow, то окажется, что первый элемент палитры TButton (нормальный текст кнопки) ссылается на 10-й элемент палитры владельца, в то время как в палитре TWindow только 8 элементов.
Чтобы изменить цвет изображения, нужно либо изменить ссылку в палитре элемента или его владельца, либо сменить атрибут цвета в палитре TProgram. На практике обычно меняют палитру TProgram, т. к. она определяет цвет всех родственных элементов. Например, если вы измените элемент палитры, ответственный за цвет основного текста в окне, одновременно все окна изменят свой цвет нужным образом, что, по всей видимости, будет логически правильным.
Палитры Turbo Vision задаются в виде обычных текстовых строк. Это дает возможность применять к палитрам все операции и преобразования, которые используются при работе со строковыми данными. Для изменения k-ro элемента палитры TProgram следует изменить к-й символ в строке, указатель на которую возвращает функция GetPalette. Пусть, например, нам нужно, чтобы во всех окнах скроллера стандартный цвет текста (желтый на голубом фоне) был заменен на белый на черном фоне. Тогда можно использовать такой прием:
Uses Арр, .'. . ;
type
TMyProgram = object (TApplication)
Constructor Init;
end;
Constructor TMyProgram.Init;
begin
GetPaletteA [13]:= #$0F; (Задаем белый цвет на черном фоне}
TApplication.Init(Инициализируем программу}
end;
В этом фрагменте в конструкторе TMyProgram. Init осуществляется замена 13го элемента палитры (этот элемент отвечает за цвет основного текста скролле-
I
ра, см. рис. 17.3). После такого изменения во всех скроллерах программы основной текст будет выводиться белыми символами на черном фоне.
Для изменения палитры видимого элемента только одного типа нужно перекрыть его метод GetPalette. Допустим, нам необходимо, чтобы скроллер рисовал основной текст таким же цветом, как полосы скроллера. В этом случае мы должны посмотреть, каким по счету элементом в палитре окна- владельца скроллера определяется цвет полос (в нашем примере это элемент с номером 5). Таким образом, палитра скроллера должна содержать значения 5 и 7 вместо прежних 6 и 7 (см. рис. 17-2). Создадим новый объект:
type
TMyScroller = object (TScroller)
Function GetPalette: PPalette; Virtual;
end;
Function TMyScroller.GetPalette: PPalette;
const
NewColors = #5#7;
NewPalette: String [2] = NewColors;
begin
GetPalette := @NewPalette
end;
Существует и менее универсальный, но более простой путь изменения цвета только в одном видимом элементе. Как мы знаем, изображение этого элемента в конечном счете формируется в его методе Draw; если этот метод перекрывается в вашей программе, а в некоторых случаях, например в скрол- лере, он должен обязательно перекрываться, то можно воздействовать на цвет непосредственно при обращениях к процедурам MoveChar и Movestr. Например:
type
MyScroller = object (TScroller)
Procedure Draw; Virtual;
end;
• Procedure MyScroller.Draw;
var
Color: Byte;
begin
(* Color := GetColor (1) ; {Стандартныйцвет скроллера}*)
Color := $OF; (Задаем белые символы на черном фоне}
MoveChar(...,...,Color
MoveStr(...,...,Color)
end;
В этом примере в строке комментария с* *) указывается обычный способ получения стандартного цвета основного текста скроллера. Вместо этого желаемый цвет задается нужными атрибутами в переменной color, которая затем используется при обращениях к процедурам MoveChar и MoveStr.
Палитра TProgram насчитывает 63 элемента и учитывает все возможные функциональные действия, осуществляемые видимыми элементами. Более того, этот объект на самом деле имеет три 63-элементных палитры: CColor (цветная палитра), CBlackWhite (черно-белая) и CMonoChrome (монохромная). В установочной секции модуля views на основе тестирования аппаратных средств ПК из этих палитр выбирается рабочая палитра, с помощью которой затем и будут формироваться изображения. При необходимости вы можете переустановить палитру TProgram с помощью глобальной процедуры SetVideoMode, например:
Program MyProgram;
UsesViews, .... ;
var
Main: TApplication;
beginSetVideoMode(smBWSO);Main.Init;beginSetVideoMode(smBWSO);Main.Init;{Начало основной программы} {Выбрать черно-белую палитру} {Инициализация программы)
begin
SetVideoMode(smBWSO);
Main.Init;
begin
SetVideoMode(smBWSO);
Main.Init;
end;
Обращение к SetVideoMode должно предшествовать инициализации основной программы, работающей в среде Turbo Vision. Параметром обращения к этой процедуре может быть одна из следующих констант:
const
smBWSO = $002;{Черно -белыйрежим работы цветного адаптера}
smCO80 = $003; (Цветной режим работы}
smMono = $007; {Монохрома тический адаптер}
Эти константы можно дополнять константой
const
smFont8x8 = $100; (Задает 43/50 строк для экрана EGA/VGA}
для задания режима вывода 43-й или 50-й строк на экране дисплея, оснащенного адаптером EGA или VGA. Например:
SetVideoMode(smCO80+smFont8x8);
Группы
Замечательным свойством видимых элементов Turbo Vision является их способность образовывать группы. Все группы являются потомками абстрактного объекта TGroup. Любая программа в конечном счете наследуется от TProgram или TApplication и, следовательно, является потомком TGroup, т. е. Представляет собой группу.
Группа — это просто пустое окно. Главная особенность группы заключается в том, что она может управлять входящими в нее элементами. Как и любой другой видимый элемент, группа должна уметь заполнять нужным изображением выделенную ей часть экрана И обрабатывать все связанные с этой областью события. Однако в большинстве случаев группа обращается за выполнением требуемых действий к своим элементам. Например, визуализация группы происходит исключительно с помощью методов Draw тех элементов, которые образуют группу. С другой стороны, некоторые команды могут относиться к группе в целом. Например, группе может адресоваться команда cmClose (закрыть), в результате которой будет уничтожено изображение группы, т. е. очищен выделенный ей участок экрана. Для реализации этой команды группа будет последовательно передавать ее всем входящим в нее элементам.
Важно помнить, что группа обладает способностью включать в себя видимые элементы динамически, в ходе исполнения программы. Как правило, состав группы определяется действиями пользователя: если, например, OH нажал командную клавишу, связанную с раскрытием опции главного меню, группа TDesktop, ответственная за рабочую зону экрана, обогащается дополнительными видимыми элементами "выпавшего" меню. После нажатия клавиши <Esc> эти элементы будут удалены из состава группы.
Создание группы и изменение ее состава
Создание группы осуществляется путем включения в программу экземпляра объекта-наследника TGroup и присоединения к нему всех видимых элементов группы. Любой видимый объект, т. е. наследник Tview, имеет два поля: owner и Next. Поле owner указывает на владельца этого элемента, a Next — на следующий равный ему элемент группы. При включении видимого элемента в группу его поля изменяются так, что owner содержит ссылку на экземпляр группы-владельца, a Next имеет значение NIL. После добавления к группеещеодногоэлементаполеNextранеевключенногоэлементаизме-
няет свое значение и содержит ссылку на этот новый элемент. Таким образом создается связанный список элементов фуппы (рис. 17.4).
Рис. 17.4. Связанный список элементов группы
Для присоединения элемента предусмотрены методы insert или ExecView. Метод insert присоединяет очередной видимый элемент к списку элементов фуппы. В зависимости от набора связанных с элементом признаков он может размещаться в центре ofCenterx и/или стать активным и, наконец, появиться на экране (sfvisible). После создания элемента управление передается следующему за обращением к insert оператору профаммы.
Метод ExecView осуществляет те же действия, что и метод insert, однако после создания видимого элемента управление будет передано в него и оператор, следующий за обращением к ExecView, получит управление только после уничтожения этого элемента. Более точно процесс взаимодействия с профаммой элемента, присоединенного к методом ExecView, можно описать следующим образом. Любой видимый элемент наследует виртуальный метод Valid, с помощью которого он может сигнализировать своему владельцу о том, насколько успешно он выполнил возложенные на него обязанности. Обычно valid возвращает True, если успешно создан и размещен в динамической памяти экземпляр объекта, и False — в противном случае. Объект может перекрыть метод valid и возвратить False, если он хочет оставить управление у себя, именно например, поступает объект TDialog. Метод ExecView приостанавливает исполнение следующего оператора профаммы до тех пор, пока все подэлементы фуппы не вернут valid = = True. Таким образом, метод ExecView используется в том случае, когда
среди подэлементов группы есть хотя бы один элемент, реализующий диалог с пользователем.
Метод Delete удаляет элемент группы из связанного списка.
Z-упорядочение
и дерево видимых элементов
Последовательное присоединение элементов к группе приводит к так называемому ^■упорядочению видимых элементов. Z-упорядочение — это трехмерная модель видимых элементов, в которой координаты X и У определяют положение элементов на экране, а координата Z — порядковый номер элемента в группе. Например, на рис. 17.5 показана Z-модель элементов окна с рамкой и скроллером.
Рис. 17.5. Z-модель видимых элементов окна
В этой модели каждый элемент можно представить в виде стеклянной пластины, накладывающейся на уже существующее изображение. То изображение, которое мы видим на экране, есть проекция трехмерной модели на плоскость XY. Трехмерный образ позволяет "взглянуть" на видимые элементы со стороны и увидеть порядок, в котором они присоединяются к группе.
Важно помнить, что любая группа визуализирует свои элементы в порядке, определяемом их Z-упорядочением. Для рис. 17.5 это означает, что сначала создается изображение рамки, очерчивающей все окно, затем на рамку накладываются полосы скроллера, потом — сам скроллер с текстом, а накрывает сверху все изображение и скрепляет его в единое целое "прозрачная пластина" TWindow.
Для того чтобы группа нашла связанный с ней список элементов, используется поле First, входящее в любой видимый объект. Это поле содержит NIL, если данный элемент — терминальный видимый объект; если этот элемент — группа, поле First содержит указатель на первый вставленный в группу элемент, т. е. на самый "нижний" элемент в смысле Z-упорядочения.
Таким образом, цепочка ссылок First — Next образует дерево видимых элементов, т. к. каждый элемент Next может быть новой группой и в этом случае его поле First <> NIL.
Программа Turbo Vision всегда владеет строкой меню, строкой статуса и ра- , бочей зоной экрана, а следовательно, имеет дерево видимых элементов, показанное на рис. 17.6.
Application
MenuBar
DeskTopI
Рис. 17.6. Основное дерево видимых элементов TApplication
Отметим, что деревья видимых элементов показывают принадлежность элементов, а не их иерархию в генеалогии объектов Turbo Vision, т. е. связи на рис. 17.6 определяют функциональную подчиненность экземпляров объектов Application, MenuBar, Desktop И StatusLine.
Деревья видимых элементов динамически изменяются в ходе работы программы. Они могут расти, если к программе присоединяются новые группы, или уменьшаться, если эти группы уничтожаются. В отличие от этого, генеалогическое дерево объектов может только расти за счет создания потомков.
Все ветви дерева видимых элементов всегда заканчиваются терминальными видимыми объектами.
Активные элементы
Внутри любой группы видимых элементов в каждый момент времени может быть выбран (активизирован) один и только один элемент. Даже если в программе открыто несколько окон с текстом, активным считается только то окно, с которым вы работаете в данный момент. Более того, поскольку окно представляет собой группу, в нем будет активным лишь один элемент. Если, например, вы воздействуете мышью на полосу скроллера, будет активна именно эта полоса. Рис. 17.7 иллюстрирует сказанное: на нем показано дерево видимых элементов для двух открытых окон, причем активные элементы выделены двойной рамкой.
Цепочка активности видимых элементов используется при обработке событий (см. главу 18).
Активный элемент обычно выделяется на экране тем или иным способом. Например, выбранное окно очерчивается двойной рамкой, а остальные —
одинарной; внутри диалогового окна активный элемент выделяется яркостью (цветом). С помощью метода select видимый элемент можно сделать активным по умолчанию в момент его создания. При активизаций группы активизируется ее элемент, указанный как активный по умолчанию. Пользователю может потребоваться изменить текущий активный видимый элемент. Он может это сделать, манипулируя мышью, или нажав командную клавишу (если элемент связан с командной клавишей), или, наконец, с помощью клавиши <Tab>.
Рис. 17.7. Цепочка активности видимых элементов просмотра текста
Заметим, что существуют видимые элементы, которые нельзя сделать активными. Например, не может быть активным видимый элемент TBackground (фон рабочей зоны экрана). В момент создания элемента с помощью признака ofSelectable вы можете указать, будет ли этот элемент выбираемым, т. е. можно ли его сделать активным в ходе работы программы. Однако, если вы объявите выбираемым тот же элемент TBackground, он все равно не сможет активизироваться, т. к. "знает", что на самом деле активизация ему недоступна. Точно так же не сможет активизироваться рамка окна (заметим, что указать на рамку мышью можно, и программа способна, например, перемещать окно с рамкой, однако это еще не означает активизации рамки: рамка не может быть объектом диалога с пользователем). Обычно сброс признака ofSelectable используется для того, чтобы запретить элементу стать активным: он, в принципе, может активизироваться, но его активизация в программе не нужна. Таким способом можно, например, сделать неактивной метку в диалоговом окне и, следовательно, превратить ее в статический поясняющий текст.
Модальные видимые элементы
Все программы в среде Turbo Vision рассчитаны на диалоговый способ взаимодействия с пользователем, а следовательно, в них активно используются точки ветвления, управляемые командами пользователя (точки диалога). В точке диалога создается активный видимый элемент, называемый модальным элементом.
Примером модального элемента является диалоговое окно. Когда в программе создается и активизируется модальный элемент, только этот элемент и входящие в его состав элементы могут взаимодействовать с пользователем. Любая часть дерева видимых элементов, не являющаяся модальным элементом или не принадлежащая ему, не может быть активна в этот момент. Если, например, на экране развернуто диалоговое окно, любые отметки мышью вне его пределов или нажатие на не предусмотренные в этом окне командные клавиши будут игнорироваться.
Единственным исключением из этого правила являются командные клавиши и соответствующие поля для мыши, определенные в строке статуса. Эти клавиши (поля) всегда доступны пользователю и нажатие на них (отметка их мышью) обрабатывается модальным элементом точно так же, как если бы они были определены в нем, хотя этот элемент может и не владеть строкой статуса.
Для временного исключения определенной в строке статуса команды (или группы команд) из списка активных предусмотрен метод DisableCommands. После завершения работы модального элемента можно восстановить активность команд методом EnableCommands. Параметром обращения к этим методам служит произвольное множество типа TCommandSet, содержащее до 256 кодов команд. В Turbo Vision тип TCommandSet определен следующим образом:
type
TCommandSet = set of Byte;
Таким образом запрещать (и разрешать) можно только те команды, коды которых принадлежат диапазону 0-255.
Временно запрещенные команды выделяются в строке статуса пониженной яркостью (оттенком).
Изменение свойств элемента
Каждый видимый элемент Turbo Vision имеет 5 полей, которые определяют его поведение в диалоговой среде и служат для изменения свойств элемента. К ЭТИМ ПОЛЯМ ОТНОСЯТСЯ Options, GrowMode, DragMode, State И EventMask. Поле EventMask активно используется в обработчиках событий и описано в
главе 18. Поля Options, GrowMode и DragMode доступны как для чтения, так и для записи. Поле state доступно только для чтения и изменить его состояние можно с помощью обращения к методу setstate.
Поле Options
|Младший разряд ofCentered|Младший разряд ofCenteredГ— TU i гш .Options Старшийразряд,Г— TU i гш .Options Старшийразряд,Не определеныНе определеныПоле Options представляет собой шестнадцатиразрядное слово, биты (разряды) которого показаны на рис. 17.8.
|
Младший разряд ofCentered
|
Младший разряд ofCentered
Г— TU i гш .Options Старшийразряд,
Г— TU i гш .Options Старшийразряд,
Не определены
Не определены
1ofSelectable of TopSelect ofFirstClick ofFraned ofPreProcess ofPostProcess ofBuffered ofTileable ofCenterX ofCenterY
Рис. 17.8. Разряды поля Options
Рассмотримназначение всехэтихбитов.
ofSelectable
Если этот флаг установлен (имеет значение 1), пользователь может выбрать видимый элемент мышью или клавишей <Tab>. Если вы поместили на экран информационный элемент, то вам может потребоваться, чтобы пользователь не мог выбрать его. Например, статические текстовые объекты и раМКИ ОКОН обычно Не МОГУТ выбираться И ИМеЮТ ofSelectable 0.
ofTopSelect
Видимый элемент будет передвигаться наверх в смысле Z-упорядочения, пока не станет самым верхним среди других равных ему видимых элементов. Этот флаг в основном используется для окон в рабочей зоне экрана. Вы не • должны изменять его для видимых элементов в группе.
ofFirstClick
Если флаг сброшен, отметка элемента мышью игнорируется, и наоборот — установленный флаг разрешает выбор элемента мышью. Если в диалоговое окно помещена кнопка, вы наверняка захотите выбрать ее мышью, поэтому
кнопка имеет флаг ofFirstClick установленным по умолчанию. Но если вы создаете окно, то можете сбросить этот флаг, если хотите, чтобы оно не "откликалось" на выбор мышью.
ofFramed
Если флаг установлен, видимый элемент имеет рамку.
ofPreProcess
Если флаг установлен, видимый элемент будет получать и, возможно, обрабатывать активные события до того, как их получит и обработает активный элемент (см. разд. 18.4).
ofPostProcess
Установленный в 1 флаг ofPostProcess позволяет видимому элементу обрабатывать события после того, как они были получены активным элементом, и при условии, что активный элемент не очистил событие (см. разд. 18.4).
ofBuffered
Когда этот бит установлен, образ группы при первой ее визуализации автоматически сохраняется в буфере. В следующий раз, когда группе нужно будет визуализироваться, она копирует образ из буфера на экран вместо прорисовки всех своих подэлементов, что ускоряет процесс создания изображений. Если программа испытывает недостаток в динамической памяти, монитор памяти Turbo Vision освобождает буферы групп до тех пор, пока запрос к динамической памяти не сможет быть выполнен.
Если группа имеет буфер, вызов метода Lock будет блокировать вывод изображения на экран до тех пор, пока не будет вызван метод unLock. Сразу после вызова unLock буфер группы целиком выводится на экран. Блокирование уменьшает мерцание во время сложных корректировок экрана. Например, TDesktop блокирует себя, когда выводит свои элементы каскадом или черепицей.
ofTileable
Объект TDesktop может располагать связанные с ним окна каскадом или черепицей. Если окна располагаются каскадом, каждое следующее окно накладывается на предыдущее и почти полностью перекрывает его; при расположении окон черепицей каждое окно располагается так, чтобы не перекрывать другие окна. Если вы хотите, чтобы окна располагались каскадом, вам следует очистить флаг ofTileable, если черепицей — установить его в 1. Если для окна установлен режим вывода каскадом, оно будет занимать одно и то же
место на экране, в то время как расположение окон черепицей приводит к изменению их размеров и положения при открытии каждого нового окна.
Расположение видимых элементов черепицей или каскадом выполняется в
TApplication.HandleEvent очень Просто:
cmTile:
begin
DeskTopA.GetExtent(R) ;
DeskTopA.Tile (R) ;
end;
cmCascade:
begin
DeskTopA.GetExtent(R);
DeskTopA.Cascade(R);.
• end;
ofCenterX
Этот флаг следует установить в 1, если вы хотите центрировать видимый элемент по горизонтали, т. е. вставить в группу так, чтобы слева и справа от него было приблизительно одинаковое расстояние до границ группы.
ofCenterY
Флаг обеспечивает центрирование элемента по вертикали. Если вы хотите, чтобы ваши окна выглядели одинаково хорошо в режимах 25 и 43/50 строк на экране, следует установить ofCenterY = 1.
ofCentered
Установка этих разрядов обеспечивает центровку видимого элемента относительно границ группы одновременно по горизонтальной и вертикальной осям.
Поле GrowMode
Восьмиразрядное поле GrowMode определяет, как должен изменяться видимый элемент, когда его владелец (группа) изменяет свои размеры. Разряды этого поля показаны на рис. 17.9.
Среда Turbo Vision позволяет изменять размеры окон с помощью мыши: для этого надо "схватить" мышью правый нижний угол окна. Флаги GrowMode определяют, как будут вести себя в этой ситуации вставленные в окно элементы. Напомню, что изображение встроенного элемента всегда отсекается
I
границами группы. Стандартное состояние элементов среды Турбо Паскаль соответствует установленным флагам gfGrowHiX и gfGrowHiY.
।GrouMode— ।Старший разряд Нладший разряд
gfGrovAl 1
Не определеныНе определены-gfGrowLoX gfGrowLoV gf&TQMHiX gfGrouHSV gfGrowRel
Не определены
Не определены
Рис. 17.9. Разряды поля GrowMode
gfGrowLoX
Если флаг установлен, левая граница видимого элемента всегда располагается на одинаковом расстоянии от правой границы группы-владельца. Таким образом, при уменьшении горизонтального размера окна вставленный в него видимый элемент смещается влево и, возможно, отсекается левой границей окна.
gfGrowLoY
Если этот флаг установлен, верхняя граница видимого элемента всегда располагается на одинаковом расстоянии от нижней границы группы, т. е. уменьшение вертикального размера окна приводит к смещению элемента вверх.
gfGrowHiX
Если флаг установлен, правая граница видимого элемента всегда располагается на одинаковом расстоянии от левой границы группы, т. е. при уменьшении горизонтального размера окна видимое на экране положение его внутреннего элемента остается неизменным и, возможно, отсекается правой границей окна.
gfGrowHiY
Если этот флаг установлен, нижняя граница видимого элемента всегда располагается на одинаковом расстоянии от верхней границы группы, т. е. При уменьшении вертикального размера окна видимое на экране положение его внутреннего элемента останется неизменным. Стандартное состояние элементов среды Турбо Паскаль соответствует установленным флагам gfGrowHiX И gfGrowHiY.
gfGrowAII
Если разряды gfGrowAII установлены в 1, видимый элемент передвигается в процессе изменения размеров своего владельца, отсекаясь его левой и верхней границами.
gfGrowRel
Если флаг установлен, видимый элемент пропорционально изменяет свои размеры при изменении размеров владельца. Вы должны использовать эту опцию только с TWindow или с наследниками от TWindow, которые присоединяются к панели экрана. В этом случае окна сохраняют свои относительные размеры и положения на экране при переходе от 25 строк к 43/50 строкам и обратно. Этот флаг не предназначен для видимых элементов внутри окна.
Поле DragMode
Поле DragMode размером в один байт определяет, как ведет себя видимый элемент при перемещении владельца. Напомню, что Turbo Vision позволяет перемещать окно на экране с помощью мыши, если "схватить" мышью верхнюю рамку окна.
Должен заметить, что мне не удалось добиться сколько-нибудь заметных результатов при различных установках поля DragMode. По умолчанию Turbo Vision устанавливает DragMode = 32 = dmLimitLoY.
На рис. 17.10 указаны разряды поля DragMode. Рассмотрим назначение каждого из них.
।-DragMode —।
Старший разряд Младший разряд
। |_j [- - -—-—dnLinitAII
|| ГТ 1 угтпl|
।dnDragMoue dnDragGrow
, dnLinitLoY
dnLinitHiX dnLinitHiV
Рис. 17.10. Разряды поля DragMode
dmDragMove
Установленный флаг dmDragMode разрешает перемещать окно с помощью мыши.
dmDragGrow
Если флаг установлен, окно может изменять свои размеры.
dmUmitLoX
Если флаг установлен, левая сторона видимого элемента не может выходить за границы своего владельца.
dmLimitLoY
Если флаг установлен, верхняя часть видимого элемента не может выходить за границы своего владельца.
dmUmitHiX
Если флаг установлен, правая сторона видимого элемента не может выходить за границы своего владельца.
dmLimitHiY
Если флаг установлен, нижняя часть видимого элемента не может выходить за границы своего владельца.
dmLimitAII
Если флаг установлен, ни одна часть видимого элемента не может выходить за границы своего владельца.
Поле State
Шестнадцатиразрядное поле state хранит различные признаки, определяющие поведение или состояние видимого элемента.
На рис. 17.11 показаны разряды поля state. Рассмотрим их назначение.
sfVisible
Разрешает визуализировать видимый элемент, если визуализируется его владелец. По умолчанию этот флаг установлен.
sfCursorVis
Разрешает показать курсор внутри видимого элемента. По умолчанию этот флаг очищен.
sfCursorlns
Если этот флаг установлен, курсор будет занимать всю высоту строки, если сброшен, курсор имеет стандартную высоту (две нижних строки развертки).
sfShadow
Если флаг установлен, видимый элемент имеет "тень".
sfActive
Указывает, является ли данный элемент активным окном или элементом активного окна.
sfSelected
Указывает, является ли данный элемент текущим (выбранным) видимым элементом. Каждый объект TGroup имеет поле Current, содержащее указатель на текущий выбранный элемент или NIL, если не выбран ни один элемент. В каждый момент времени в группе может быть выбран только один элемент.
sfFocused
Указывает, принадлежит ли данный элемент цепочке активности видимых элементов, т. е. выбран ли он и все его владельцы в данный момент времени.
sfDragging
Разрешает изменять размеры элемента.
sfDisable
Флаг запрещает выбирать данный видимый элемент. Если флаг сброшен, элемент можно выбрать мышью или клавишей <Tab>.
sfModal
Если флаг установлен, то данный элемент будет модальным. В программе Turbo Vision всегда имеется один и только один модальный элемент. Обычно это экземпляр TApplication или TDialog. Модальный видимый элемент активизируется вызовом метода Execview и образует корень активного дерева событий: он перехватывает события и управляет ими до тех пор, пока не
будет вызван его метод EndModal (см. главу 18). Модальный элемент может передавать события своим элементам и получать события от них, но он никогда не передает события своему владельцу (события локализуются в модальном элементе).
sfExposed
Указывает, виден ли элемент или любая его часть в данный момент времени (элемент может закрываться другими видимыми элементами). Если флаг установлен, метод Exposed данного элемента возвращает значение True.
Воздействие на состояние поля State
В отличие от других полей, поле state доступно только для чтения (поля options, DragMode и GrowMode доступны также и для записи). Это означает, что в программе не может использоваться оператор вида
State := NewState;
Установить новое состояние этого поля можно только с помощью метода Setstate, доступного любому видимому элементу. Метод Setstate объявлен в Turbo Vision следующим образом:
type
TView = object (TObject)
Procedure Setstate(AState: Word; Enable: Boolean); Virtual;
end;
При обращении к методу параметр AState должен содержать маску разрядов поля state, а признак Enable указывает, должны ли устанавливаться эти разряды (Enable = True) ИЛИ очищаться (Enable = False).
Отметим, что Turbo Vision вызывает метод Setstate всякий раз, когда элемент активизируется или выбирается. Это дает возможность программе устанавливать новое состояние элемента или реагировать на действия пользователя (например, на активизацию видимого элемента). В отличие от этого, ПОЛЯ Options, DragMode И GrowMode активизируются ТОЛЬКО При ИНИЦИЭЛИ- зации элемента и далее обычно не меняются.
Видимые элементы часто перекрывают метод Setstate, чтобы гибко реагировать на действия пользователя. Например, кнопка (объект TButton) просматривает флаги поля state и изменяет свой цвет на бирюзовый, когда становится активной. В следующем примере объект TButton вначале проверяет, принадлежит ли он активному окну, чтобы решить, рисовать себя или нет. Затем он проверяет, выбрана ли кнопка в окне и, если это так, вызыва-
ет свой метод MakeDefauit, чтобы установить или сбросить (в зависимости от значения Enable) свою активность по умолчанию:
Procedure TButton.SetState(AState: Word; Enable: Boolean);
begin
TView .'setstate (AState, Enable) ;
if (AState and (sfSelected + sfActive) ) <> 0then
DrawView;
if (AState and sfFocused) <> 0 then
MakeDefault(Enable);
end;
Другой пример: допустим, что ваша программа включает в себя текстовый редактор и вы хотите разрешить или запретить все команды редактирования в полосе меню в зависимости от того, открыт редактор или нет. В этом случае вы можете создать такой метод setstate для текстового редактора:
Procedure TEditor. SetState (AState: Word; Enable: Boolean);
const
EditorCommands =
cmGotoLine, cmFindProc, cmFindError];
begin
TView.SetState(AState, Enable);
if (AState and sfActive) <> 0 then
if Enable then EnableCommands(Editorcommands)
else DisableCommands(EditorCommands);
end;
evNothing: () ;evNothing: () ;{Пустое событие){Пустое событие)
evNothing: () ;
evNothing: () ;
{Пустое событие)
{Пустое событие)
Глава 18
События
Как уже неоднократно подчеркивалось, программы, работающие в среде Turbo Vision, — это программы, управляемые событиями. В этой главе подробно рассматривается механизм событий и способы их использования.
Природа событий
События представляют собой небольшие пакеты информации, которыми обмениваются видимые элементы и которые создаются средой Turbo Vision в ответ на те или иные действия пользователя. Нажатие на любую клавишу или манипуляция мышью порождает событие, которое передается по цепочке активности видимых элементов до тех пор, пока не найдется элемент, знающий как обработать это событие. Может оказаться, что в программе нет ни одного элемента, способного обработать событие. В этом случае обычно ничего не происходит, по умолчанию Turbo Vision просто удаляет ненужное событие, однако программист может предусмотреть свою реакцию в этой ситуации.
Важно помнить, что события сами по себе не производят никаких действий в программе, но в ответ на событие могут создаваться новые видимые элементы, модифицироваться или уничтожаться существующие элементы, что и приводит к изменению содержимого экрана. Иными словами, все действия по созданию и изменению изображения реализуются видимыми объектами, а события лишь управляют их работой — именно это мы имеем в виду, говоря о программах, управляемых событиями.
Технически событие представляет собой обычную для Турбо Паскаля запись, имеющую следующую структуру:
type
TEvent = record
What: Word;{ Тип события}
case Word of
evMouse: ({Событие от мыши:}
Buttons: Byte; ,{Состояние кнопок}
Double : Boolean;{Признакдвойного нажатия}
Where : TPoint);{Координаты мыши}
evKeyDown: ({Событие от клавиатуры:}
case Integer of
(KeyCode: Word) ;{Код клавиши}
(CharCode: Char;
ScanCode: Byte));
evMessage: ({Событие-сообщение}
Command: Word;{Код команды}
case Word of 0: (InfoPtr : Pointer);
(InfoLong: Longlnt);
(InfoWord: Word);
(Infolnt :
: (InfoByte: Byte);
(InfoChar: Char));
end;
Ядром события является поле what, которое описывает тип события. Оставшаяся часть записи содержит дополнительную информацию, например, код нажатой клавиши или координаты точки, в которой была нажата кнопка мыши.
Виды событий
Существуют 4 основных класса событий:
О события от мыши;
О события от клавиатуры;
сообщения;
пустые события.
Внутри класса события могут подразделяться на виды. Например, класс событий от мыши состоит из таких видов, как перемещейие указателя мыши, нажатие на кнопку мыши и т. п. Каждый класс имеет определенную маску, с помощью которой можно легко идентифицировать класс события, не анализируя его вид. Например, вместо того, чтобы проверять 4 различных вида событий от мыши, можно просто проверить, пришло ли
это событие от мыши или оно имеет другой источник. Два следующих оператора идентичны:
if Event.What and ((svMouseDown or evMouseUp or
evMouseMove or evMouseAuto) <> 0) then
if Event.What and (evMouse <> 0) then /
Для анализа класса событий используются следующие маски:
О evNothing{ 'Пустое ' событие}‘,
ПevMouse(Событие от мыши};
ПevKeyboard{Событие от клавиатуры);
ПevMessage(Сообщение).
На рис. 18.1 показаны разряды поля what и соответствующие маски.
।— — Euent.UhatСтарший разряд।— — Euent.UhatСтарший разрядМладший разряд■BuMessage виКвуboard buMousbМладший разряд■BuMessage виКвуboard buMousbeuHouseDoun euHouseUp euMouseMoueeuHouseDoun euHouseUp euMouseMoue$FF00= $0010= $000F$FF00= $0010= $000F= $0001 = $0002 = $0004= $0001 = $0002 = $0004bvMousbAuIo = $0008bvMousbAuIo = $0008euKeyDown BuConnandeuKeyDown BuConnand$0010$0100$0010$0100euBroadcast = $8200euBroadcast = $8200
।— — Euent.Uhat
Старший разряд
।— — Euent.Uhat
Старший разряд
Младший разряд
■BuMessage виКвуboard buMousb
Младший разряд
■BuMessage виКвуboard buMousb
euHouseDoun euHouseUp euMouseMoue
euHouseDoun euHouseUp euMouseMoue
$FF00
= $0010
= $000F
$FF00
= $0010
= $000F
= $0001 = $0002 = $0004
= $0001 = $0002 = $0004
bvMousbAuIo = $0008
bvMousbAuIo = $0008
euKeyDown BuConnand
euKeyDown BuConnand
$0010
$0100
$0010
$0100
euBroadcast = $8200
euBroadcast = $8200
Рис. 18.1. Разряды поля What
События от мыши
Существуют 4 вида событий от мыши: событие evMouseDown возникает как отклик на нажатие кнопки мыши; когда кнопка отпускается, возникает событие evMouseUp; перемещение указателя мыши порождает событие evMouseMove; наконец, если кнопка мыши удерживается в нажатом состоянии, Turbo Vision периодически генерирует событие evMouseAuto. С каждым событием от мыши в записи TEvent передаются также координаты, которые имеет указатель мыши в данный момент. Отметим, что в отличие от других координат Turbo Vision, координаты мыши задаются относительно границ экрана, а не относительно границ какого-либо видимого элемента. Горизонтальная координата мыши меняется в диапазоне от О до 79, вертикальная — от 0 до 24 (режим 25 строк на экране) или от 0 до 42/49 (режим 43/50 строк).
События от клавиатуры
В этом классе событий имеется единственное событие evKeyDown, связанное с нажатием на клавишу. В поле TEvent.KeyCode в этом случае содержится так называемый расширенный код клавиши, который вы можете интерпретировать как совокупность двух байтов CharCode и ScanCode. При нажатии на алфавитно-цифровую клавишу поле charCode содержит соответствующий ASCII-символ, при нажатии на функциональную клавишу поле CharCode содержит символ #о, а поле ScanCode — код сканирования нажатой клавиши. Для облегчения идентификации нажатой клавиши можно использовать константы kbxxxx, определенные в интерфейсной части модуля Drivers.
Сообщения
Сообщения бывают трех видов: команды, общие сообщения и пользовательские сообщения. Команды помечаются в поле what флагом evConunand, общие сообщения — флагом evBroadcast и пользовательские сообщения — константой, определенной пользователем. Большинство событий преобразуется в команды. Например, если пользователь отметит мышью какое-то поле строки статуса, сообщение от мыши поступит в конечном счете в обработчик событий строки статуса (любой видимый элемент имеет метод HandleEvent, называемый обработчиком событий, см. разд. 16.3), который определит, какое именно поле было отмечено. С каждым полем строки статуса обычно связана какая-то команда, поэтому обработчик очистит пришедшее к нему сообщение от мыши и создаст новое сообщение, содержащее выбранную команду. Общие и пользовательские сообщения не являются исключением и обычно также преобразуются в команды.
/
Пустые события
Пустым событие становится после его обработки каким-либо видимым элементом. Технически пустое событие соответствует значению TEvent.What = evNothing = о. Когда событие обработано, видимый элемент вызывает метод ClearEvent, с помощью которого в поле what устанавливается значение evNothing. Объекты должны игнорировать событие evNothing, поскольку оно уже обработано каким-то видимым элементом. .
Маршрутизация событий
Как уже говорилось, любая программа, работающая в среде Turbo Vision, является прямым или косвенным (через TApplication) потомком TProgram и основана на обращении к трем главным методам этого объекта Init, Run И Done.
Например:
Uses Арр, . . .;
type
TMyProgram = object (TApplication)
end;
var
' MyProgram = TMyProgram;
begin{Исполняемая часть программы:)
MyProgram. Init;{Инициализировать программу)
MyProgram. Run;{Выполнить программу}
MyProgram. Done(Завершить pa боту}
end.
Процесс получения и обработки событий инициализируется методом TProgram.Run, который ДЛЯ ЭТОГО обращается К методу TGroup.Execute (любая программа является потомком TGroup). Метод TGroup.Execute реализует следующий цикл:
var
Event: TEvent;
beginEvent.What :=beginEvent.What :=repeatif Event.What <> evNothingEventError(Event);GetEvent(Event);HandleEvent(Event);until EndState <> Continue;repeatif Event.What <> evNothingEventError(Event);GetEvent(Event);HandleEvent(Event);until EndState <> Continue;{Инициализироватьпустое событие){Основной цикл программы}then{Инициализироватьпустое событие){Основной цикл программы}then{Событие не очищено — ошибка} (Получить событие}{Передать событие обработчику){Событие не очищено — ошибка} (Получить событие}{Передать событие обработчику)
begin
Event.What :=
begin
Event.What :=
repeat
if Event.What <> evNothing
EventError(Event);
GetEvent(Event);
HandleEvent(Event);
until EndState <> Continue;
repeat
if Event.What <> evNothing
EventError(Event);
GetEvent(Event);
HandleEvent(Event);
until EndState <> Continue;
{Инициализироватьпустое событие)
{Основной цикл программы}
then
{Инициализироватьпустое событие)
{Основной цикл программы}
then
{Событие не очищено — ошибка} (Получить событие}
{Передать событие обработчику)
{Событие не очищено — ошибка} (Получить событие}
{Передать событие обработчику)
end;
Метод GetEvent наследуется всеми видимыми элементами от TView и является основным источником событий. Этот метод вначале проверяет, не подготовил ли событие метод putEvent и, если это так, возвращает его. Затем GetEvent проверяет клавиатуру и мышь. Если какое-то из этих устройств изменило свое состояние, метод формирует и возвращает соответствующее событие. Если изменения не произошло, GetEvent обращается к
методу TProgram.Idle, чтобы запустить "фоновую" задачу (подробнее об этом см. разд. 18.6).
С помощью вызова метода TGroup.Execute события всегда начинают свой путь с модального видимого элемента. Напомню, что модальный элемент определяет точку диалога; в программе в каждый момент времени есть один и только один модальный элемент (см. разд. 17.4). В самом начале программы таким элементом является обычно экземпляр объекта TProgram или его потомка. Если в программе открыто модальное диалоговое окно, оно обратится к TGroup. Execute и, следовательно, путь событий начнется c этого окна. В любом случае начинает обработку события метод HandleEvent модального видимого элемента. Дальнейший маршрут события зависит от того, является ли событие позиционированным, активным или общим.
Позиционированные события
Позиционированные события — это всегда события от мыши (evMouse). Модальный видимый элемент получает позиционированное событие первым и начинает просматривать входящие в него элементы в порядке, обратном их Z-упорядочению (см. разд. 17.3.2), до тех пор, пока не найдет элемент, координаты которого включают в себя координаты указателя мыши. Затем модальный элемент передает событие найденному элементу. Поскольку видимые элементы часто перекрываются на экране, может оказаться, что координата указателя мыши принадлежит более чем одному видимому элементу. Следование обратному Z-упорядочению гарантирует, что событие получит самый верхний видимый элемент. Заметим, что вам не нужно прослеживать Z-упорядочение элементов в вашем обработчике событий, достаточно вызвать унаследованный метод HandieEvent, который автоматически направит событие нужным образом.
Процесс передачи события продолжается до тех пор, пока не обнаружится терминальный видимый элемент (например, полоса прокрутки) или не будет найден ни один элемент с нужными координатами. Если элемент не знает, как обработать событие, он передает его вверх по активной цепочке своему владельцу. Технически это реализуется с помощью выхода из обработчика HandieEvent вызовом процедуры Exit; событие не следует очищать обращением к ClearEvent. Процесс повторяется до тех пор, пока событие не будет обработано или не достигнет модального элемента. Если модальный элемент не знает, как обработать вернувшееся к нему событие, он вызывает метод EventError.
В Turbo Vision существуют средства, позволяющие видимым элементам, не принадлежащим цепочке активности, получать и обрабатывать активные события (см. разд. 18.4).
Общие события
Общие события — это общие или пользовательские сообщения. Общие события не имеют конкретного адресата и посылаются всем элементам текущего модального элемента.
Модальный элемент получает событие и начинает передавать его своим элементам в Z-порядке. Если какой-то из элементов — группа, он также передает событие своим элементам и также в Z-порядке. Процесс продолжается до тех пор, пока не будет найден элемент, который обработает событие, или пока событие не получат все видимые элементы, принадлежащие (прямо или косвенно) модальному элементу.
Общие события обычно используются для организации межэлементных связей. Подробнее об этом см. разд. 18.7..
Фаза событий
Обычно активные события (evKeyDown и evCommand) получают и обрабатывают видимые элементы, принадлежащие цепочке активности. Однако часто возникают ситуации, когда необходимо, чтобы активное событие обработал неактивный элемент. Например, если на экране активно окно скроллера с полосами прокрутки, то события от клавиатуры будут передаваться окну. Как заставить в этом случае полосы реагировать на нажатие клавиш <PgUp> или <PgDn>? Для этого в Turbo Vision предусмотрен специальный механизм, основанный на так называемой фазе события. Когда модальный элемент получает событие, его передача выполняется в следующей последовательности.
Событие посылается в Z-порядке всем видимым элементам, которые принадлежат модальному элементу и у которых поле Options имеет установленный флаг ofPreProcess.
Если событие не очищено ни одним из них, оно посылается активным элементам (по цепочке активности).
Если событие все еще не очищено, оно посылается в Z-порядке всем видимым элементам, у которых установлен флаг ofPostProcess.
Таким образом, вы должны установить флаги ofPreProcess или ofPostProcess (или оба вместе) при инициализации видимого элемента, если хотите, чтобы он мог получить активное событие до или после (или и до и после) того, как его получат активные элементы.
Для предыдущего примера необходимо инициализировать полосы прокрутки с установленными флагами ofPostProcess, если требуется, чтобы полосы "увидели" и обработали нажатие на клавиши смещения курсора, <PgUp>, <PgDn>и т.п.Разумеется,вэтомслучаеполосыполучатсобытие
evKeyDown только при условии, что скроллер не обработает его сам.
В некоторых ситуациях элемент, перехватывающий событие и до, и после активных элементов, должен модифицировать свое поведение в зависимости от фазы события. Рассмотрим такой типичный пример. Пусть в программе создано диалоговое окно, имеющее строку ввода и три кнопки, для которых определены командные клавиши <Q>, <W>, <E>. Как добиться того, чтобы эти клавиши использовались в качестве командных клавиш, т. е. приводили к "нажатию" соответствующих кнопок при условии, что активна любая кнопка, а в сочетании с клавишей <Alt>, — если активна строка ввода (именно так действуют командные клавиши в диалоговом окне среды Турбо Паскаля)? Если инициализировать кнопки с флагом ofPreProcess, они смогут без труда определить факт нажатия на командную клавишу, однако в строке ввода пользователь не сможет ввести буквы Q, W и Е, т. к. они будут перехвачены кнопками до того, как событие от клавиши получит строка ввода. Если инициализировать кнопки с флагом ofPostProcess, пользователю будут недоступны сочетания <АИ>+<клавиша> для нажатия кнопки, если активна строка ввода, поскольку все события evKeyDown будут в этом случае направляться в строку. Решение очевидно: нужно определить оба флага, но на препроцессорной фазе следует проверять ввод <Alt>+ +<клавиша>, а на постпроцессорной — < клавишах
Для реализации этих проверок обработчик событий объекта TButton должен каким-то образом определить текущую фазу события. С этой целью в любой группе предусмотрено поле Phase. Это поле доступно только для чтения и содержит ОДНО ИЗ значений phPreProcess, phFocused ИЛИ phPostProcess В зависимости от фазы события.
Следующий фрагмент иллюстрирует ту часть обработчика событий кнопок, которая проверяет командную клавишу:
evKeyDown: {Это часть оператора case)
begin
С := HotKey(TitleA); (Получаем в С букву клавиши} {Проверяем А1Л-Склавиша>:}
if (Event.KeyCode =or
{Проверяем <клавиша>:}
(OwnerА.Phase =and (С <> #0) . and
(UpCase(Event.CharCode) = C) or
{Проверяем активность и нажатие пробела:}
(State and sfFocused <> 0) and(Event.CharCode =
Press {Да, кнопка выбрана: выдаем нужную команду) .
end;
В этом фрагменте не показанная здесь функция HotKey выделяет из надписи на кнопке символ командной клавиши (он обрамляется знаком ~), а стандартная
для Turbo Vision функцияпреобразует этот символ в расширенный
код клавишМетодPress реализует "нажатие" на
кнопку и выдает сообщение evBroadcast с командой TButton. command.
Отметим, что рассмотренный пример приведен только в качестве иллюстрации: стандартный объект TButton реализует свой обработчик событий именно таким образом и вам нет нужды переопределять его (по умолчанию экземпляр TButton инициализируется с установленными флагами ofpreProcess и ofPostProcess).
Команды
Поскольку события обычно связаны с какими-то действиями пользователя, программа должна, как правило, реагировать на эти действия изменением видимого изображения. С точки зрения Turbo Vision это означает, что обработчики событий должны преобразовывать события в действия, связанные е реакцией на произошедшее событие. Для реализации этих действий в Turbo Vision предусмотрены команды.
Команда — это просто целое число без знака, кодирующее определенную последовательность действий. В Turbo Vision существует ряд стандартных команд для реализации многих часто встречающихся действий. Например, командареализует завершение работы программы и выход в ДОС,
закрывает активное окно,"распахивает" окно на весь экран
или возвращает ему прежние размеры и т. д. Идентификаторыявля
ются идентификаторами предопределенных констант, которые кодируют стандартные действия (например, cmQuit =cmzoom = 5 и т. д.).
В своей программе вы можете наряду со стандартными командами определить и использовать собственные команды для реализации специфических действий. Для этого необходимо создать свою константу-команду и в нужный момент сообщить видимым элементам о необходимости выполнить ее. Разумеется, вы можете выбрать произвольный идентификатор для вновь определяемой команды, однако ваша программа станет намного понятнее, если при образовании новых идентификаторов следовать каким-то определенным правилам. В этом смысле употребление префикса cm в идентификаторах новых команд кажется вполне логичным.
Преобразование
активных событий в команды
Как указать на необходимость выполнения команды? Для этого в Turbo Vision вы просто создаете событие-командув поле Command ко
торой помещаете код нужной команды.
Например:
const
cmMyCommand =
(Ниже показан фрагмент обработчика событий:)
Event. What := evCommand;(Определяем событие-команду)
Event.Command := cmMyCommand;(Указываем код команды)
Event.InfoPtr := NIL;(Признак активного события}
PutEvent(Event);{Создаем событие)t
,-
В этом фрагменте событие-команда создается обращением к методу PutEvent. Заметим, что поле Event. InfoPtr должно содержать NIL, если событие активно; если событие уже обработано, для его очистки используется стандартный метод ClearEvent, который помещает в поле what признак evNothing, а В'Поле InfoPtr — указатель @Self (указатель на таблицу виртуальных методов объекта). Подробнее об использовании поля Event.InfoPtr см. в разд. 18.7.
Вновь созданное событие вернется модальному элементу, который должен знать, как его обрабатывать. Например, часто событие-команда создается обработчиком событий строки статуса как реакция на нажатие предусмотренной в этой строке командной клавиши или отметку мышью соответствующего поля. В этом случае обработчик событий программы может иметь такой вид:
Procedure MyProgram.HandleEvent(var Event);
begin
Inherited Handl eEvent (Event);
case Event.What of
evCommand:
begin{Обработать команды:}
case Event.Command of
cmMyCommand: MyProcedure;{Выполнить действия,
elseexitend;(case)ClearEvent(Event) end;elseexitend;(case)ClearEvent(Event) end;связанные с командой cmMyCommand)
elseexitend;(case)
ClearEvent(Event) end;
elseexitend;(case)
ClearEvent(Event) end;
(He обрабатывать непредусмотренные команды)
(Очистить событие)
end;
Часто модальным элементом является диалоговое окно с несколькими кнопками. Если вам необходимо связать с кнопками свои команды и получить выбранную в диалоге команду, то можно закрыть окно с помощью вызова EndModal, передав этому методу в качестве параметра код команды.
В следующем примере создается диалоговое окно с двумя кнопками. При нажатии кнопки Команда cmPrint окно закроется и на экран будет выведена строка
Действие команды cmPrint
Если нажать кнопку Выход или закрыть окно клавишей <Esc>, эта строка не появится.
Uses CRT,Арр,Dialogs, Objects,Drivers,Views;
type
PProg = ATProg;
TProg = object (TApplication)
Constructor Init;
end;
PDial =
TDial = object (TDialog)
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
const
cmPrint = 100;
Constructor TProg.Init;
var
R:-
Dia: PDial;
begin
Inherited Init;
R.Assign(20,9,60,17);
Dia := New(PDial, Init(R,''));
R. Assign(3,4,22,6);
DiaA.Insert(New(PButton,
Init(R,'Команда cm~P~rint',cmPrint,bfDefault)));
R.Assign(23,4,35,6);
DiaA.Insert(New(PButton, Init(R,'Выход',cmCancel,bfNormal)));
if ExecView(Dia) = cmPrint then
begin.*
{Вывод сообщения "в лоб", с помощью стандартных средств Турбо Паскаля.
В TurboVision есть более удобные способы вывода сообщений}
GotoXY(30,12);
TextColor(Black);
TextBackground(White);
Write ( 1 Действие команды cmPrint ')
end
end;{TProg.Init}
Procedure TDial.HandleEvent(var Event: TEvent);
begin
Inherited HandleEvent(Event);
if (Event.What =and
(Event.Command = cmPrint) then EndModal(cmPrint)
end;{TDial. HandleEvent}
var
Prog: TProg;
begin
Prog.Init;
Prog.Run;
Prog.Done
end.
В обработчике событий диалогового окна TDial. HandleEvent вначале вызывается стандартный обработчик TDialog.HandleEvent. Это дает возможность кнопке Команда cmPrint преобразовать событие, связанное с ее выбором, в команду cmPrint. Вновь созданное событие возвращается обработчику TDial.HandleEvent, T. K. именно он является обработчиком событий модального элемента. Возвращаемая модальным элементом команда служит значением стандартной функции ExecView. Для упрощения программы вывод сообщения реализуется стандартными средствами Турбо Паскаля. Заметим, что в Turbo Vision имеется процедура MessageBox, обеспечивающая более удобный вывод сообщений.
•
Запрещение и разрешение команд
В качестве значения константы-команды можно использовать любое число в диапазоне от 0 до 65 535, однако следует учесть, что диапазоны 0-99 и 256-999 Turbo Vision резервирует для стандартных команд и их не следует использовать для определения команд пользователя. Два диапазона зарезервированных команд выбраны потому, что команды с кодами 0-255 можно временно запретить, в то время как остальные команды запретить невозможно. Для запрещения или разрешения команд служит глобальный тип TCommandSet, представляющий собой множество чисел в диапазоне 0-256
(мощность множеств в Турбо Паскале не может превышать 256, вот почему запретить можно только первые 256 команд). Команды запрещаются обращением к методу DisableCommands, а разрешаются обращением к методу EnableCommands. Диалоговые элементы, связанные с запрещенными командами, отличаются по цвету и их нельзя выбрать мышью или командными клавишами. Например, если в конструкторе TProg.Init (см. предыдущий пример) перед оператором
if ExecView(Dia) =then
вставить оператор
DisableCommand([cmPrint]);
кнопка Команда cmPrint будет выведена цветом фона окна и станет недоступной для диалога.
Модификация и обработка событий
События, определенные пользователем
Старшие разряды поля Event. what содержат информацию о TOM, что событие относится к классу сообщений. Первые шесть разрядов в этом поле программист может использовать для определения собственных классов событий.
Необходимость в новых классах событий может возникнуть в том случае, когда ваша программа работает с нестандартными источниками информации. Если, например, вы собираетесь работать с последовательным портом, то, возможно, захотите определить класс событий evserial, используя для его идентификации один или несколько старших разрядов поля Event.What. Технически получить информацию из нового источника и сделать ее событием можно путем перекрытия метода TProgram.GetEvent (см. разд. 18.6.4) или "фонового" метода TProgram.Idle (см. разд. 18.6.5).
По умолчанию все новые классы событий маскируются маской evMessage, т. е. считаются сообщениями. Такие события модальный элемент рассылает всем СВОИМ элементам в Z-порядке (см. разд. 17.3.2). Если вы хотите, чтобы новый класс событий передавался как позиционированные или активные события, то можете изменить маски этих событий. В Turbo Vision определены маски PositionalEvents и FocusedEvents. Первая позволяет всем видимым элементам рассматривать событие как позиционированное, вторая — как активное. По умолчанию маска PositionaiEvents выделяет все биты evMouse, a FocusedEvents содержит evKeyboard. Если вам понадобится, ЧТОбы новый класс событий обрабатывался так же как активные или позиционированные события, то следует добавить к нужной маске биты, соответствующие этому классу, в поле what.
Пусть, например, создан новый класс событий evSerial с маской $8000, т. е. этот класс связан со старшим разрядом поля what. Если потребуется обработать этот класс так же, как активные события, нужно задать новую, маску:
const
evSerial = $8000;
FocusedEvents := FocusedEvents or evSerial;
Обратите внимание, что при добавлении к любой маске новых разрядов применяются поразрядные операции над целыми числами (операции or, and и not). He следует использовать здесь операцию арифметического сложения +, т. к. в этом случае разрешен поразрядный перенос и новая маска может оказаться не той, какую вы хотели получить. Если, например, к маске evMouse прибавить единицу, получится маска evKeyboard, т. е.
evKeyboard = evMouse + 1
В то же время операция
evMouse or 1
не вызовет поразрядного переноса и маска evMouse останется прежней, т. к. ее младший бит уже установлен.
'
Маскирование и очистка событий
Каждый видимый элемент имеет поле EventMask. По умолчанию значение этого поля устанавливается таким образом, чтобы видимый элемент обрабатывал все необходимые ему события И не откликался на другие. Например, TProgam и TDialog имеют EventMask = $FFFF, что позволяет им откликаться на любые возможные события, в том числе и определенные программистом. Кнопка TButton имеет EventMask = $0311, т. е. откликается на события evBroadcast, evCommand, evKeyDown, evMouseAuto, evMouseUp И evMouseDown. Как ВИДИМ, ей доступны все стандартные события, кроме evMouseMove — кнопка не может реагировать на перемещение мыши.
Соответствующей установкой поля EventMask вы можете запретить или разрешить любому видимому элементу реагировать на те или иные события. Например, можно создать кнопку, доступную только с помощью мыши:
var
MouseButton: PButton;
MouseButtonNew (PButton, Init (...');
MouseButtonA.EventMask := evMouse; Insert(MouseButton);
17 Зак 941
Для очистки события следует обратиться к методу ClearEvent, который устанавливает в поле Event.What значение evNothing, а в поле Event.InfoPtr — адрес таблицы виртуальных методов объекта. Таким образом, в поле Event.InfoPtr остается "подпись" видимого объекта, который очистил событие. Эта информация необходима для организации межэлементного взаимодействия (см. разд. 18. 7).
Перекрытие HandleEvent
После того как вы определили команду и установили элемент управления, который генерирует ее (например, элемент меню или кнопка диалогового окна), нужно научить ваш видимый элемент реагировать на возникновение этой команды.
Каждый видимый элемент наследует обработчик событий — метод HandleEvent, который уже знает, как реагировать на большую часть ввода пользователя. Если вы хотите, чтобы видимый элемент делал что-то специфическое для вашей программы, то необходимо перекрыть HandleEvent и научить новый обработчик событий двум вещам: как откликаться на определенные вами команды и как реагировать на события от мыши и клавиатуры нужным вам образом.
Метод HandleEvent определяет поведение видимого элемента. Два видимых элемента с идентичными методами HandleEvent будут одинаково откликаться на события. Когда вы порождаете новый тип видимого элемента, вы обычно хотите, чтобы его поведение более или менее соответствовало поведению его предка с некоторыми изменениями. Наиболее простой способ ДОСТИЧЬ ЭТОГО — вызвать HandleEvent Предка В методе HandleEvent НОВОГО объекта.
Общий вид HandleEvent наследника:
procedure NewDescendant.HandleEvent(var Event: TEvent);
begin
{Код, изменяющий или ограничивающий унаследованное поведение) Inherited HandleEvent(Event);
(Код, выполняющий дополнительные функции)1
end;
Другими словами, если вы хотите, чтобы новый объект обрабатывал события не так, как это делал его предок, то перехватить определенные события нужно до передачи события в метод HandleEvent предка. Если вы хотите, чтобы ваш новый объект вел себя так же, как его предок, но с дополнительными функциями, то добавить код следует после вызова процедуры HandleEvent предка.
Перекрытие GetEvent
Единственным источником событий является метод Tview. GetEvent. Только этот метод обращается к внешним источникам информации — клавиатуре и мыши. Если в вашей программе используются другие устройства ввода информации (например, джойстик или коммуникационный канал), вы должны перекрыть этот метод и научить его работе с нестандартными устройствами.
Проще всего перекрыть метод можно при объявлении нового типа вашей программы, например:
Uses Арр,...;
type
MyProgram = object (TApplication)
Procedure GetEvent(var Event: TEvent); Virtual;
end;
Procedure MyProgram.GetEvent(var Event: TEvent);
begin
TApplication.GetEvent(Event);
if Event.What = evNothing then
begin
(Обращение к нестандартным источникам информации) end
end;
Поскольку MyProgram в конечном счете наследует GetEvent от TView, все видимые элементы вашей программы будут пользоваться новым источником информации.
Преимущества централизованного сбора событий очевидны. Перекрывая единственный метод GetEvent, вы можете заставить программу реагировать на внешнюю информацию любым удобным для вас способом. Например, можно перехватывать заранее обусловленные командные клавиши и развертывать их в целую серию событий. Таким способом легко создавать различного рода макрокоманды.
Неиспользованное время
Поскольку программа, работающая в среде Turbo Vision, рассчитана на диалог с пользователем, в ней всегда найдутся такие промежутки времени, в течение которых она просто пассивно ожидает его действий. Turbo Vision предоставляет вам удобное средство, позволяющее в этот момент загрузить процессор не слишком долгой, но нужной для вас работой.
Стандартный метод TView.GetEvent построен таким образом, что если нет никаких событий, он обращается к псевдоабстрактному методу TView.Idle. По умолчанию TView.Idle ничего не делает, он просто возвращает управление методу GetEvent, заставляя его непрерывно сканировать клавиатуру и мышь. Вы можете перекрыть TView.Idle, чтобы выполнить нужные операции.
В следующем примере каждые 5 с в правый верхний угол экрана выводится системное время. Для упрощения программы вывод осуществляется стандартными средствами Турбо Паскаля.
Uses DOS,CRT,App;
type
TProg = object (TApplication)
Procedure Idle; Virtual;
end;
Procedure TProg.Idle;
const
old: Byte = 0; {Старое значение секунд)
dt = 5;(Шаг вывода)
var
ho,mi,se,slOO: Word;
Function TimeStr(k: Word): String;
var
String [2];
begin
str(k,s);
if k < 10 then
s := ' 0 * +s;
TimeStr := s
end;•
begin(TProg.Idle}
GetTime(ho,mi,se,slOO);
if (se mod dt0) and (old <> se) then
begin
OldSe;
Textcolor(Black);
TextBackGround(White);'
GotoXY(72,l);
Write(TimeStr(ho)+':'+TimeStr(mi)+':'+TimeStr(se))
end
end; (TProg.Idle)
var
Prog.-TProg;.
begin
Prog.Init;
Prog.Run;
Prog.Done
end.
Разумеется, не следует поручать методу TView.Idle слишком сложную работу, иначе пользователь программы будет безуспешно нажимать на клавиши, пытаясь вернуть ее к жизни. Предполагается, что рабочий цикл метода не будет превышать нескольких сотен миллисекунд. Если все-таки вы хотите выполнить достаточно длинную фоновую задачу, попытайтесь разбить ее на серию мелких шагов,
Ненужные события
Некоторые события могут оказаться ненужными в данном контексте программы. Например, пользователь может нажать временно запрещенную командную клавишу или отметить мышью поле вне текущего диалогового окна. Ненужные события — это события, обработка которых не предусмотрена в данном модальном элементе или в любом из его подэлементов. Такие co- бытия возвращаются модальному элементу, который в этом случае вызывает свой виртуальный метод EventError. Этот метод в свою очередь вызывает метод EventError своего владельца и так происходит до тех пор, пока не будет вызван метод TApplication. EventError. По умолчанию метод TApplication.EventError просто ничего не делает.
Вы можете перекрыть метод EventError любого видимого элемента (или программы), чтобы, например, сообщить пользователю о его ошибке и/или дать справку о возможностях программы в данный момент. Кроме того, контроль за ненужными событиями может быть полезен на этапе отладки программы.
Взаимодействие видимых элементов
Иерархия объектов Turbo Vision построена так, чтобы любой объект имел все необходимые для его работы поля и методы. Взаимодействие видимых элементов обычно осуществляется путем создания и использования групп. Если у вас возникнет необходимость организовать взаимодействие не связанных друг с другом объектов, следует прежде всего тщательно проанализировать программу: возможно вы не использовали всех средств Turbo Vision или некорректно разделили задачи между двумя видимыми объектами.
В большинстве случаев задача может быть решена путем создания нового объекта, объединяющего в себе свойства двух других.
Если программа спроектирована правильно и видимые элементы требуют взаимодействия между собой, можно создать объект-посредник. Типичным примером такого объекта является внутренний буфер clipboard диалоговой среды Турбо Паскаля (опция Edit). Для передачи фрагмента текста из одного окна редактора в другое фрагмент помещается в буфер командными клавишами <Shift>+<Del> или <Ctrl>+<Ins>, затем вызывается другое окно и фрагмент вставляется в нужное место командой <Shift>+<Ins>. Вы можете организовать такого рода буфер и в вашей программе, т. к. с помощью Turbo Vision доступны все средства диалоговой среды Турбо Паскаля. Преимуществом создания объекта-посредника является то, что с его помощью легко решается проблема взаимодействия сразу нескольких объектов друг с другом. Например, если вы создаете сложную интегрированную систему, включающую текстовый редактор, систему управления базами данных, электронную таблицу и сервисные средства, промежуточный буфер позволит передать данные из текстового редактора в базу данных, или из базы данных в электронную таблицу и т. д.
Другим способом организации взаимодействия элементов является использование событий-сообщений. Эти события создаются с помощью глобальной функции Message. Эта функция описана в интерфейсной части модуля views следующим образом:
Function Message (Receiver: Pview; What, Command: Word;
InfoPtr: Pointer): Pointer;
Первым параметром указывается ссылка на экземпляр объекта, которому адресуется сообщение. Остальные параметры служат для создания записи TEvent. Функция создает запись события и, если это возможно, вызывает метод ReceiverA.HandleEvent для обработки этого события. Если адресат не существует или при обращении к функции указан параметр Receiver = NIL, функция возвращает NIL — это означает, что событие не было обработано. Если событие успешно обработано (ReceiverA. HandieEvent возвращает событие с полем what = evNothing), функция вернет указатель Event. InfoPtr.
Как уже говорилось в разд. 18.6.2, стандартный метод ClearEvent очищает событие, устанавливая Event.What = evNothing И Event. InfoPtr = @Self. Таким образом, объект, обработавший и очистивший событие, оставляет в Event.InfoPtr указатель на свою таблицу виртуальных методов. Этот указатель позволяет полностью идентифицировать объект-получатель сообщения и организовать связь с ним.
Рассмотрим пример. В диалоговой среде Турбо Паскаля имеется окно отладки, с которым вы, очевидно, хорошо знакомы. Если программист потре-
открыть это окно, среда должна проверить, открывалось ли оно ранее: если да, то окно просто переносится наверх (в смыслеес
ли нет, — создается вновь. Для реализации проверки среда дает сообщение
evBroadcast,
В методеокна отладки есть проверка на команду
cmFindWatchWindow:
ifevBroadcast) and
= cmFindWatchWindow) then
ClearEvent(Event);
Если окно существует, оно очистит событие и оставит в нем свою "подпись", поэтому сразу за передачей сообщения р'еализуется такая проверка: if= NIL then
новое
else
существующее окно
Поскольку окно отладки — это единственный объект, который знает, как реагировать на команду cmFindWatchWindow, в диалоговой среде Турбо Паскаля может использоваться только одно окно этого типа.
Таким же способом компилятор Турбо Паскачя определяет окно редактора, из которого следует брать текст компилируемой программы. Это окно — всегда верхнее на экране, поэтому компилятор посылает сообщение с командой, на которую откликаются только окна редактора. Так как событие-сообщение передается в Z-порядке, первое откликнувшееся окно, т. е. окно, очистившее событие, и будет самым верхним.
Видимый элемент может послать сообщение, установив в поле указатель на собственную таблицу виртуальных методов. Это даст возможность получателю сообщения при необходимости обратиться к методам объекта-отправителя.
Организовать взаимодействие можно также с помощью прямого вызова обработчика событий нужного объекта. Для этого экземпляр объекта-адресата (переменная типа объект или указатель на него) необходимо инициализировать предыдущим обращением к конструктору и программа должна "видеть" его, т. е. он должен быть глобальным по отношению к инициатору взаимодействия.
Контекстная помощь
В Turbo Vision предусмотрены средства, облегчающие создание контекстнозависимой справочной службы. С этой целью каждый видимый элемент имеет специальное шестнадцатиразрядное полесодержимым
которого вы можете распоряжаться по своему усмотрению. Обычно каждому видимому элементу присваивается свой код (целое число в диапазоне от О до 65 535), устанавливаемый в поле HelpCtx. В этом случае при нажатии на заранее обусловленную командную клавишу, открывающую доступ к справочной службе (обычно это клавиша <F1>), программа может получить текущий контекст (прямым чтением поля Heipctx или с помощью метода GetHelpCtx) и передать его в качестве параметра вызова справочной службе.
Где обрабатывать событие, связанное с нажатием клавиши вызова справочной службы? Идеальным местом для этого является источник всех событий — метод GetEvent. Этот метод связан с любым видимым элементом, в том числе и с терминальным видимым объектом, и поэтому без труда может получить текущий контекст.
В следующем примере (пример 18.1) на экране создается диалоговое окно с двумя кнопками. Клавиша <F1> используется для доступа к справочной службе. Если активна (выбрана) левая кнопка, нажатие на <F1> даст сообщение "Левая кнопка", если правая — "Правая кнопка". Если на экране нет диалогового окна (оно вызывается клавишей <F2>), появится сообщение "Нет окна".
Uses CRT,Арр,Dialpgs,Objects,Drivers, Views, Menus;
type
PProg = лТРгод;
TProg = object (TApplication)
Procedure HandleEvent (varEvent: Теvent) ; Virtual;
Procedure GetEvent (var Event: Tevent) ; Virtual;
Procedure InitStatusLine; Virtual;
end;
Procedure TProg . HandleEvent (varEvent: TEvent) ;
Procedure Dialoglnit;
var
R: TRect;
Dia: PDialog;
B1,B2: PButton;
c: Word;
begin
ClearEvent(Event);
R.Assign(20,9,60,17) ;
Dia:=New(PDialog,Init(R, 11 ) ) ;
; Bl := BlA.HelpCtx := 1; DiaA.Insert(Bl); R.Assign(20,4,35, 6); B2 := New(PButton,Init(R,'Правая',0,0)); B2A.HelpCtx := 2; DiaA.Insert(B2); с := ExecView(Dia)
end; {Dialoglnit} begin{TProg.HandleEvent/ TApplication.HandleEvent(Event); if (Event.What = (Event.Command =then
Dialoglnit end;{TProg.Handl eEvent}
Procedure TProg.InitStatusLine; var R: TRect; begin
GetExtent(R);
R.A.Y := pred(R.B.Y) ; StatusLine := New(PStatusLine, Init(R, NewStatusDef(0, 0, {Начальная строка статуса} NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit, NewStatusKey('~F1~ Справка',kbFl,cmHelp, NewStatusKey('~F2~ Окно диалога',kbF2,cmMenu, NIL))), NewStatusDef(1,2,{Строка статуса с диалоговым окном)
NewStatusKey('-Esc- Выход',kbEsc,cmCancel, NewStatusKey('~F1~ Справка', kbFl,cmHelp, NIL)), NIL)) )) ; end; (TProg. InitStatusLine} Procedure TProg.GetEvent(var Event: TEvent); const txt: array [0..2] of String = ('Нет окна','Левая кнопка','Правая кнопка'); begin
TApplication.GetEvent(Event);
if Event.Command =then
begin
GotoXY(60,l) ;
TextColor(Black+Blink) ;
TextBackGround(White);
Write(Txt[GetHelpCtx]);
Delay(2000);
GotoXY(60,l);
Write ('')
end
end; {TProg.GetEvent)
var
Prog: TProg;
begin
Prog.Init;
Prog.Run;
Prog.Done
end.
Для упрощения программы справочное сообщение выводится стандартными средствами Турбо Паскаля. Сообщение появляется в верхнем правом углу экрана и через 2 с стирается, в течение этого промежутка времени доступ к клавиатуре и мыши блокируется.
Контекст видимого элемента может управлять содержимым строки статуса. С этой целью в методе InitStatusLine используются два или более вызова NewStatusDef со своим диапазоном контекста (см. предыдущий пример).
В состав демонстрационных модулей Turbo Vision включен файл HelpFile.PAS и компилятор TVHC.PAS, существенно упрощающие процесс создания контекстно-чувствительной помощи и работы с ней.
Средства модуля HelpFil^ позволяют по заданному контексту найти в особом файле справочной службы нужную справку и вывести ее на экран. Текст справки помещается в окно со скроллером, границы которого могут изменяться. Справочная служба контролирует размер окна и форматирует текст так, чтобы строки не выходили за границы окна. В тексте справки могут быть созданы перекрестные ссылки, представляющие собой выделенные цветом фрагменты текста. Справочная служба обеспечивает доступ к этим ссылкам так, как это реализовано в среде Турбо Паскаля: ссылку можно выбрать клавишей <Tab> или отметкой мышью, после чего нажатие на <Enter> приведет к раскрытию нового справочного окна с соответствующей справкой.
Для использования этих средств необходимо предварительно создать файл справочной службы, содержащий текстовые данные и средства управления ими. Этот файл создается программой TVHC.PAS из обычного текстового файла, с учетом следующих соглашений:
□ если очередная строка начинается символами . topic, эта строка определяет заголовок темы;
П если в тексте встречается фрагмент, обрамленный фигурными скобками, этот фрагмент определяет перекрестную ссылку.
Заголовок темы содержит идентифицирующее справку уникальное кодовое слово и связанный с ним контекст. Например:
.topic Viewer = 2
Здесь:
П viewer — кодовое слово;
О 2 — контекст справочной службы.
Все остальные строки до очередного заголовка темы составляют справочное сообщение и будут выводиться в окне справочной службы. Если очередная строка справки начинается пробелом, эта строка не форматируется и может отсекаться границами окна, в противном случае строка выводится с учетом текущих границ окна (если очередное слово не умещается в окне, остаток текстовой строки переносится на следующую строку окна). Например:
.topic FileOpen = 3
File Open
Эта опция меню используется для загрузки файла
Здесь строки
File Open
начинаются пробелом и поэтому не форматируются, т. е. сохраняют свой вид независимо от границ окна, в то время как при выводе строки
Эта опция меню используется для загрузки файла
будут контролироваться границы окна (строка не начинается пробелом) и, если очередное слово выйдет за его правую границу, остаток строки вместе с не уместившимся словом будет выведен в следующей строке окна.
Любой фрагмент строки справочного сообщения, обрамленный фигурными скобками, рассматривается как перекрестная ссылка. Перекрестная ссылка может содержать кодовое слово заголовка нужной справки или произвольный текст, за которым указывается двоеточие и кодовое слово. Например:
С помощью опции (FileOpen} можно загрузить файл.
или
Окно обеспечиваетфайла:в режиме
В первом случае будет выведена строка
С помощью опцииможно загрузить файл.
причем слово FileOpen будет выделено цветом и может использоваться как перекрестная ссылка на справку, заголовок темы которой имеет вид
FileOpen =
Во втором случае в окне выводится строка
Окно обеспечивает просмотр файла в режиме скроллера.
Слова просмотр выделяются цветом и являются перекрестной ссылкой на справку с заголовком
Viewer =
В заголовке темы можно указывать несколько кодовых слов, например:
FileOpen=3, OpenFile=103, FFileOpen
Доступ к такой справке возможен для контекстов
const
= 3;
chOpenFile = 103;
chFFileOpen= 104;
Обратите внимание, что за кодовым словом FFileOpen не указывается контекст, в этом случае считается, что связанный с ним контекст есть преды- контекст, увеличенный на 1, т. е.
chFFileOpen = 103 + i = 104
Файл DEMOHELP.TXT содержит пример исходного текстового файла, подготовленного с учетом описанных требований для преобразования программойв файл справочной службы.
Подготовка справочного файла и его использование осуществляется в следующей последовательности.
Подготавливается текстовый файл NAMETEXT, содержащий заголовки тем, справки и перекрестные ссылки.
Вызывается программа TVHC.EXE (исходный текст этой программы содержится в файле \BP\EXAMPLES\DOS\TVDEMOS\TVHC.PAS) для создания файла справочной службыи вспомогательного моду
ля NAMEPAS. Обращение к программе описано ниже.
В программе, работающей с модулем HelpFile выполняется следующее:
вызывается процедура RegisterHelpFile для регистрации объектов модуля HeipFile в потоке;
открывается поток, связанный с созданным файлом NAMEHELP;
создается экземпляр объекта THelpFile и ему передается поток и нужный контекст;
инициализируется работа созданного экземпляра;
ликвидируется экземпляр объектаTHelpFile.
Действия п. 3 осуществляются каждый раз, когда необходимо выдать ту или иную справку.
Для преобразования текстового файла во внутренний формат, используемый средствами модуля HeipFile, предназначена программа, исходный текст которой поставляется в файле TVHC.PAS. Для работы с этой программой ее необходимо оттранслировать в ЕХЕ-файл. Формат вызова программы TVHC.EXE:
TVHC NAMETEXT [NAMEHELP [NAMEPAS]]
(в квадратных скобках указаны необязательные параметры).
Здесь:
NAMETEXT — имя исходного текстового файла;
NAMEHELP — имя выходного файла справочной службы;
NAMEPAS — имя выходного файла, содержащего текст модуля с контекстами в виде констант chXXXX. Если имена выходных файлов опущены, то будут созданы файлы с именем исходного файла и расширением HLP для файла справочной службы и PAS для текста модуля.
Текст файла NAMEPAS имеет следующий вид:
unit namepas;
interface
const
chTopicl =
chTopic2 =
chTopicN = nN;
implementation end.
Поскольку этот файл создается программой TVHC.EXE автоматически, будьте внимательны при обращении к ней. Не рекомендуется опускать имена NAMEHELP и NAMEPAS. Если имя исходного текстового файла совпадает с име-
нем программы или любого другого PAS-файла, старый файл будет стерт и на его месте будет создан файл с текстом модуля!
Для иллюстрации техники использования средств модуля HelpFile рассмотрим следующий пример. Пусть текст справочной службы помещен в файл HelpTest.TXT и имеет следующий вид (пример 18.2).
| . topic NoContext=0
Добро пожаловать
|в справочную службу системы Turbo Vision
В текстовом файле для справочной службы вы должны подготовить {заголовки тем: topic} и (перекрестныессылки: CrossRef}. Весь текст от одного заголовка до другого представляет собой текст справки и выводится в окне справочной службы. При подготовке текста учитываются следующие соглашения: если очередная строка начинается символом пробел, эта строка не будет форматироваться с учетом границ окна;
во всех остальных случаях выводимый в окне текст форматируется с учетом границ окна: слово, не умещающееся на строке, переносится на следующую строку экрана.
Для преобразования текстового файла в файл справочной службы вызывается программа TVHC.EXE:
|TVHC NAMETXT [NAMEHELP [NAMEPAS]]
Здесь NAMETXT — имя исходного текстового файла; NAMEHLP — имя выходного файла справочной службы; NAMEPAS — имя выходного файла, содержащего текст PAS-модуля с определением всех контекстов в виде констант chXXXX.
Имя файла NAMEHELPможноне указывать— вэтом случае выходнойфайл будет
иметь имя входногофайлаи расширениеHLP.Если не указан файлHELPPAS,
будет создан файл с именем исходного и расширением PAS.
По умолчанию программа использует расширение ТХТ для входного файла, HLP для выходного файла справочной службы и PAS — для файла констант.
. topic Topical
Заголовок темы имеет следующий формат:
. topic Name [=N] [, Namel[=N2] [...]]
ЗдесьName—имятемы(можетсостоять толькоизлатинскихбукв
и цифр; разница в высоте букв игнорируется); N — контекст справочной службы, при котором выдается данная справка.
В квадратных скобках показаны необязательные параметры. Если опущен контекст N, программа присваивает соответствующей перекрестной ссылке контекст NPrev + 1, где NPrev — предыдущий определенный в программе контекст.
. topic CrossRef=2
В произвольное место текста справки можно вставить так называемую перекрестную ссылку:
|(text [:title])|
Здесь () — фигурные скобки; text — произвольный текст или заголовок темы; title — заголовок темы; этот параметр вместе с двоеточием опускается, если text — заголовок темы.
Если вы подготовите такой файл, то после вызова
tvhc helptest
будут созданы два файла: файл справочной службы HELPTEST.HLP и файл модуля с определениями констант HELPTEST.PAS. Содержимое этого последнего файла будет таким:
unit helptest;
interface
const
hcCrossRef = 2;
hcNoContext = 0;
hctopic = 1;
implementation
end.
Следующая программа (пример 18.3) будет выдавать контекстно-зависимые справочные сообщения из файла HELPTEST.HLP при каждом нажатии на клавишу <F1>.
Uses App, Menus, Drivers, Views, Objects, HelpFile, Dialogs;
const
cmChangeCtx =
type
PCtxView = ATCtxView;
TCtxView = object (TView)
Constructor I nit;
Procedure Draw; Virtual;
end;
MyApp = object (TApplication) Ctx: PCtxView;
Constructor Init;
Procedure InitStatusLine; Virtual;
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
PMyHeIpWindow =
MyHelpWindow = object (THelpWindow)
Function GetPalette: PPalette; Virtual;
end;
Procedure MyApp.InitStatusLine;
var
R: TRect;
begin
GetExtent(R);
R.A.Y :=
StatusLine :=
NewStatusDef(0, $FFFF,
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,
NewStatusKey(*~F1~ Помощь', kbFl, cmHelp,
NewStatusKey('~F2~ Изменить контекст *, kbF2, cmChangeCtx,
NewStatusKey('~F5- Распахнуть окно', kbF5, cmZoom,
NIL)))),
NIL)))
end;
Constructor MyApp. Init";
begin
TApplication.Init;
Ctx : = New(PCtxView, Init);
Insert(Ctx);
RegisterHelpFile
end;
Procedure MyApp.HandleEvent;
var
HF: PHelpFile;
HS: PDosStream;
HW: PMyHelpWindow;
const
HelpOpen: Boolean = False;
Procedure DoHelp;
(Обеспечивает доступ к контекстно-зависимой справочной службе)
var
С: Word;
begin
ClearEvent(Event);
{ Открываем DOS-поток: }
HS := New(PDosStream, Init('HELPTEST.HLP', stOpenRead));
{ Создаем и инициализируем экземпляр объекта THelpFile: )
HF := New(PHelpFile, Init(HS));
HelpOpen := HSA.Status =
if HelpOpen then
begin
(Создаем окно справочной службы и связываем его с потоком HS и текущим контекстом:}
HW ;= New(PMyHelpWindow, Init(HF, GetHelpCtx));
if ValidView(HW) о NIL then
beginС := ExecView(HW);Dispose(HW)beginС := ExecView(HW);Dispose(HW)(Выдаем справку){Ликвидируем окно)(Выдаем справку){Ликвидируем окно)
begin
С := ExecView(HW);
Dispose(HW)
begin
С := ExecView(HW);
Dispose(HW)
(Выдаем справку)
{Ликвидируем окно)
(Выдаем справку)
{Ликвидируем окно)
end;
HelpOpen := False end
else
Dispose(HF, Done)
end;
begin
TApplication.HandleEvent(Event); case Event.Command of
cmHelp: if not HelpOpen then
DoHelp;cmChangeCtx:DoHelp;cmChangeCtx:(Выдаем справку)
DoHelp;
cmChangeCtx:
DoHelp;
cmChangeCtx:
(Изменяем контекст по клавише F2)
begin
if HelpCtx = 3 then
HelpCtx := 0
else
Inc(HelpCtx);
CtxA. Draw;
ClearEvent(Event)
end
end
end;
Function MyHelpWindow.GetPalette;
const
P = #16#17#18#19#20#47#21#13;
String [8] = P;
begin
GetPalette := @C
end;
Constructor TCtxView.Init;
var
TRect;
.
begin
R.Assign(67,0, 80,1) ;
TView.Init(R);
end;
Procedure TCtxView.Draw;
var
String;
TDrawBuffer;
Byte;
begin
Str (ApplicationA.HelpCtx,3);
S := 'Контекст =
С := GetColor(2);
MoveChar(B, ' ', C, Size.X);
MoveStr(B, S, C) ;
WriteLine(0,0,Size.X,1,B)
end;
var
P: MyApp;
begin
P.Init;
P.Run;
P.Done
end.
События513
В программе предусмотрена смена текущего контекста с помощью клавиши <F2>. Разумеется, в реальной программе смена контекста будет, судя по всему, происходить иначе: обычно в текст конструкторавидимого
элемента вставляется оператор
HelpCtx
гдеNNN —нужныйконтекст.
Для визуализации текущего контекста в программе имеется простой объект TCtxview, с помощью которого в верхнем правом углу экрана выводится строка
Контекст = N
где N — текущий контекст.
Глава 19
Коллекции
Одним из недостатков языка Паскаль (и Турбо Паскаль) является невозможность создания и использования в программе массивов с переменной размерностью — так называемых динамических массивов. Этот недостаток особенно ощутимо сказывается в диалоговых программах, работа которых в существенной своей части определяется действиями пользователя. Если программист заранее не знает, какие именно требования к имеющимся в программе массивам предъявит пользователь, он обычно резервирует для них максимально возможные объемы памяти или размещает массивы в куче. И тот и другой способы нельзя считать вполне удовлетворительными: в первом случае возникают неестественные ограничения на предельно возможные размеры массивов или выделенная под их размещение память расходуется нерационально, во втором случае приходится прибегать к дополнительным ухищрениям, чтобы организовать индексированный доступ к динамической памяти.
Разработчики Turbo Vision решили проблему кардинально, создав механизм коллекций. Хотя экземпляры объектов можно объединять в массивы, как и "обычные" переменные Паскаля, вы вряд ли захотите хранить их в виде массивов, поскольку коллекции не только снимают проблемы фиксированных границ, но имеют еще и целый ряд новых возможностей, которыми массивы не обладают.
Изучению коллекций посвящается эта глава.
Элементы коллекций
Коллекции предназначены, строго говоря, для тех же целей, что и массивы языка Турбо Паскаль: они позволяют создать набор из произвольного количества элементов и организуют Индексный способ доступа к этим элементам. В отличие от массивов коллекции обладают двумя новыми свойствами. Во-первых, их размер может динамически меняться в ходе работы программы, фактически ограничиваясь лишь доступной памятью. Во-вторых, в кол-
лекции могут храниться элементы разных типов. Последнее свойство называется полиморфизмом коллекций.
Технически коллекции представляют собой массивы нетипизированных указателей на размещенные в динамической памяти элементы коллекций. Эти массивы размещаются в куче — отсюда возможность динамического изменения размеров коллекций; с другой стороны, входящие в эти списки указатели позволяют ссылаться на произвольные элементы, отсюда полиморфизм.
Полиморфизм коллекций — это очень мощное средство, которым следует пользоваться с осторожностью, поскольку фактически коллекция хранит лишь указатели на элементы. Компилятор не может проверить правильность доступа к элементам. Вы можете поместить в коллекцию один объект, а взять его из коллекции как объект другого типа, и компилятор не предупредит вас об этом.
Создание коллекций
Для создания коллекции следует прежде всего определить тип тех данных, которые будут в ней храниться. Например, вам необходимо создать электронный каталог вашей личной библиотеки. В этом случае для каждой указанной в каталоге книги можно выделить пять полей данных: автор, название, издательство, год издания и количество страниц. Создадим новый объект ТВоок следующего вида:
Uses Objects;
type
PBook = АТВоок;
ТВоок' =(TObject)
Autor: PString;
{Ав тор}
Title: PString;
{Название}
PubHouse: PString;
{Издательство)
Year: Word;
(Год издания}
Pages: Word;
{Количество страниц}
Constructor Init(A,T,PH:
String; Y,P: Word);
Destructor Done; Virtual;
end;
Мы включили в объект поля и два основных метода работы с ними: конструктор Init используется для размещения в динамической памяти очередной записи каталога, деструктор Done удаляет записи из кучи. Заметим, что в объекте задаются не сами текстовые строки, а указатели на них (тип PString в Turbo Vision описан как указатель на тип string). Такое разме-
щение данных, как правило, дает значительную экономию памяти. Например:
Constructor TBook.Init(А,Т,РН: String; Y,P: Word);
begin
Autor := NewStr(A);
Title := NewStr(T);
PubHouse :=
YearY;
• \
Pages := P
end; (TBook.Init}
Используемые в конструкторе функции Newstr размещают в динамической памяти текстовую строку, выделяя под нее минимально необходимую память, что значительно выгоднее по сравнению с типичным описанием текстовых полей вида
type
TBookobject (TObject)
Autor, Title, PubHouse: String;
end;
Для освобождения динамической памяти в деструкторе Done предусмотрена процедура DisposeStr:
Destructor TBook.Done;
begin
DisposeStr(Autor);
DisposeStr(Title);
DisposeStr(PubHouse)
end; {TBook.Init}
После того как тем или иным способом определены типы данных, создание коллекции не вызывает проблем, что очевидно из листинга примера 19.1.
var
BookList: PCollection;
begin
BookList := New(PCollection, Init(50,10)); with BookList'' do
begin
Insert(New(PBook, Init('Джордейн P.',
'Справочник программиста персональных компьютеров’+ типа IBM PC, XT и AT', 'Финансы и статистика', 1991,544)));
Insert(New(PBook, Init('Шелдон', 'Язык Си для профессионалов','И.В.К.-СОФТ',1991,383))); Insert(New (PBook, Init('Скэнлон Л. ',
'Персональные ЭВМ IBM PC и XT. '+
'Программирование на языке ассемблера', 'Радио и связь',1991,336)));
Insert(New(PBook,
Init('Йенсен К., Вирт Н. ',
'Паскаль. Руководство для пользователя ’ +
•и описание языка', 'Финансы и статистика',1982,151)));
end;
Dispose(Booklist, Done);
end;
Для создания коллекции мы обратились к методу TCollection.Init, указав ему начальную длину коллекции (50 элементов) и шаг наращивания (10 элементов). Руководствуясь этими указаниями, Turbo Vision зарезервирует в динамической памяти место для размещения 50 указателей. Если в ходе наполнения коллекции ее длина превысит начальную, Turbo Vision будет наращивать коллекцию порциями, каждая из которых достаточна для размещения 10 указателей.
Смысл параметров, передаваемых методу TCollection.Init, станет понятнее, если рассмотреть механизм создания и обновления коллекции. Вначале в куче резервируется участок памяти, достаточный для размещения массива из M) указателей (N0 — начальный размер коллекции). Если в ходе наполнения коллекции ее длина превысит M) элементов, резервируется новый участок памяти, достаточный для размещения массива из NO + DN указателей (DN — шаг наращивания коллекции), затем старый массив переносится на новое место, а память, выделенная под его размещение, возвращается в кучу. Таким образом, чем больше начальная длина коллекции и шаг ее наращивания, тем меньше суммарные потери времени на расширение коллекции, но и тем больше могут стать потери памяти, если реальная длина коллекции окажется значительно меньше М) + k x DN (k = 0, 1, 2, ...).
Операторы insert размещают в динамической памяти элементы коллекции. В реальной программе наполнение коллекции будет, судя по всему, осуще-
каким-то иным способом, чем простое программирование обращений к методу insert (см., например, программу Notebook из главы 15). Для нас сейчас важно другое, мы нигде не говорили о том, какого типа объекты будет хранить коллекция; для обеспечениядействий по раз
мещению в памяти очередного элемента мы просто обращаемся к соответствующему методуаужонделаетостальное— ведьон"знает"какэто
следует сделать.
Отметим, что обращение
Dispose(BookList, Done);
вызывает автоматическое обращение к методуперед уничтоже
нием каждого элемента коллекции, после чего уничтожается экземпляр Tcollection. Это стало возможным потому, что объект TBook объявлен как потомок от TObject. Если бы мы его объявили независимым объектом type
TBook = object
end;
мы должны были бы сами позаботиться об освобождении кучи, а обращение Dispose(BookList, Done);
привело бы к "зависанию" программы.
Доступ к элементам коллекций
Итак, оператором
BookList
мы объявили о создании коллекции, а операторами
Insert(New(PBook, Init(...)))
наполнили эту коллекцию нужными элементами. Как осуществить доступ к элементам коллекции? Для этого имеется несколько способов.
Во-первых, к любому элементу коллекции можно обратиться по его порядковому номеру (индексу). В отличие от массивов Турбо Паскаля, индексы которых могут иметь произвольные границы, коллекции индексируются целыми числами в диапазоне от 0 до(count — общее количество
элементов в коллекции). Любая коллекция имеет поле Count, которое указывает текущую длину коллекции. Чтобы по индексу получить доступ к нужному элементу, используется метод At, который возвращает указатель на
элемент.
Например, нам требуется вывести на экран содержимое тех записей нашего каталога, которые соответствуют 1991 году издания. Тогда вместо точек в конце программы (пример 19.1) следует вставить оператор
PrintYear(BookList, 1991);
Кроме того, в раздел описаний программы добавим две новых процедуры:
Procedure Printitem(A,T,PB: String; Y,P: Word);
{Выводит на экран элемент коллекции}
begin
WriteLn(A);
WriteLn('', T) :
WriteLn('',PB,', ',Y,', ', P)
end;{Printitem}
Procedure PrintYear(BookList: PCdllection; Y: Word);
{Выводит на экран издания нужного года выпуска)
var
Book: РВоок;
'
k: Integer;
begin
WriteLn;
for k := 0 to pred(BookListA.Count) do
begin
Book := BookListA.At(k);
with BookA do if Year = Y then
PrintItem(AutorA,TitleA,PubHouseA,Year,Pages)
end
end; (PrintYear)
В процедуре PrintYear организуется счетный цикл от 0 до pred (TCollection.Count). С помощью оператора
Book := BookListA.At(k) ;
в переменную Book помещается указатель на элемент коллекции с индексом k. Именно здесь обеспечивается полиморфизм коллекций: метод At возвращает нетипизированный указатель, который вы можете интерпретировать нужным вам образом. Однако здесь же таится источник трудно обнаруживаемых ошибок: в левую часть оператора присваивания можно поместить указатель любого типа и тип этого указателя может не соответствовать типу /г-ГО элемента коллекции.
Отметим, что обращение к методу At с индексом, выходящим за границы коллекции, активизирует вызов метода TCollection.Error, который
по умолчанию аварийно Завершает исполнение программы (подробнее см. разд. 17.6).
Помимо использования метода At, коллекции обеспечивают доступ к трем итерационным методам, которые могут оказаться весьма удобными. Метод ForEach осуществляет некоторую заранее заданную операцию сразу над всеми элементами коллекции, а методы FirstThat и LastThat отыскивают в коллекции первый элемент, удовлетворяющий некоторому опять же заранее заданному критерию поиска: FirstThat ищет от начала коллекции к ее концу, a LastThat — в обратном направлении.
Чтобы воспользоваться методом ForEach, мы должны сначала создать процедуру без параметров, которая осуществляет нужные действия над всеми элементами коллекции, и передать адрес этой процедуры как параметр обращения к ForEach. Например, для того чтобы вывести на экран содержимое всего нашего каталога, мы должны внести в программу следующие изменения. Поместите в раздел описаний следующий текст процедуры PrintAli:
Procedure PrintAli(Book: PCollection);
(Вывод всех элементов коллекции)
Procedure PrintBook(P: РВоок); far;
begin
\:
with рА do
PrintItem(AutorA,TitleA,PubHouseA Year, Pages)
end; {PrintBook}
begin (PtintAll) ,
BookA.ForEach(@PrintBook);
end; (PrintAli)
Как видите, ^эта процедура содержит внутреннюю процедуру printBook, в которой осуществляется нужное нам действие — вывод очередного элемента каталога на экран. Этот вывод достигается с помощью вызова уже использованной нами ранее процедуры Printitem. Таким образом, описание процедуры PrintAli должно следовать после описания Printitem, чтобы этот вызов был синтаксически правильным. Далее, вывод всех элементов коллекции в процедуре PrintAli осуществляется оператором
BookA.ForEach(@PrintBook);
который обращается к методу TCollection. ForEach, передавая ему в качестве параметра адрес процедуры PrintBook. Чтобы программа успешно выполнила нужные действия, процедура, адрес которой передается методу ForEach, должна удовлетворять двум условиям:
О она должна быть рассчитана на дальнюю модель вызова (для этих целей мы указали директиву far сразу за заголовком PrintBook);
О она должна быть локальной для процедуры, в которой реализуется вызов ForEach, именно поэтому мы разместили ее в теле процедуры PrintAll.
Осталось в тело главной программы поместить оператор PrintAll(BookList);
перед оператором уничтожения коллекции. Если вы запустите таким образом подготовленную программу на счет, на экран будет выведено:
Джордейн P.
Справочник программиста персональных компьютеров типа IBM PC, XT и AT
Финансы и статистика, 1991, 544
Шелдон
Язык Си для профессионалов
И.В.К.-СОФТ, 1991, 383
Скэнлон Л.
Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера
Радио и связь, 1991, 336
Йенсен К., Вирт H. .
Паскаль. Руководство для пользователя и описание языка
Финансы и статистика, 1982, 151
Все действия по выводу содержимого коллекции реализуются методом ForEach, который для собственно вывода каждого очередного элемента обращается к процедуре printBook.
Точно таким же образом реализуется обращение к методам FirstThat и LastThat. Например, если нам потребуется отыскать в каталоге запись, содержащую слово "Вирт" в поле Autor, можно добавить в программу следующие строки
Procedure SearchAutor(BookList: Pcollection; A: String);
Function FindAutor(P: PBook) : Boolean; far;
begin
FindAutor :=pos(A, PA.AutorA) о О
end; (FindAutor/
var
Book: PBook;
begin {SearchAutor)
Book := BookListA.FirstThat(@FindAutor) ; .
if Book = NIL then
WriteLn('Нет автора ',A)
else with BookA do
begin
WriteLn;
Printitem(AutorA,Title",PubHouse",Year, Pages)
end
end; {SearchAutor)
В тело главной программы следует добавить оператор
SearchAutor(BookList,'Вирт');
Собственно поиск элемента коллекции реализуется оператором
Book := BookList".FirstThat(@FindAutor);
который для этих целей обращается к методу TCollection.FirstThat. В этом методе реализуется последовательный анализ всех элементов коллекции, начиная с самого первого (с индексом 0), причем для анализа используется вызов функции FindAutor. Как видим, эта функция нужным образом анализирует очередную запись и возвращает True, если условие поиска удовлетворено. Метод FirstThat возвращает указатель на элемент коллекции, для которого удовлетворено условие поиска, или NIL, если этому условию не отвечает ни один элемент. Таким образом, оператор
if Book = NIL then
else
проверяет результат поиска и выводит на печать найденный элемент коллекции или сообщение нет автора если условие поиска не удовлетворено.
Как и в случае метода ForEach, функция, передаваемая методу FirstThat, должна транслироваться в расчете на дальнюю модель памяти и локализоваться в теле процедуры, в которой осуществляется вызов метода.
Любой элемент коллекции можно удалить или заменить новым. Для удаления используется метод AtFree, которому в качестве параметра передается индекс удаляемого элемента. При удалении элемента предполагается, что коллекция содержит указатели на объекты, порожденные от TObect и размещенные в куче, поэтому автоматически вызывается метод TObject.Done. Индексы всех элементов, размещенных в коллекции после удаляемого элемента, уменьшаются на 1.
С помощью метода DeleteAll удаляются все элементы из коллекции, но сама коллекция при этом сохраняется, т. е. очищенная коллекция будет иметь count = о. Для очистки коллекции вызывается AtFree для каждого элемента.
Чтобы заменить существующий элемент новым, используется метод At Put (Index, Item), где:
Index — индекс заменяемого элемента;
Item — указатель на новый элемент.
Метод Atlnsert (index, Item) вставляет новый элемент в коллекцию в позицию index и увеличивает индексы всех ранее существовавших в коллекции элементов от элемента index до конца коллекции на единицу, т. е. "раздвигает" коллекцию.
Отсортированные коллекции
Часто бывает необходимо каким-либо образом упорядочить коллекцию, т. е. расставить ее элементы в определенном порядке. Для этих целей в Turbo Vision предусмотрен специальный объект TSortedCollection. Этот объект порожден от TCollection и, следовательно, уже умеет создавать коллекцию, вставлять в нее элементы и удалять их. Единственное, чего он не умеет — это сортировать коллекцию. В TSortedCollectiori есть абстрактный метод Compare, который используется для упорядочения элементов и который вы должны перекрыть, чтобы обеспечить нужную вам сортировку. Таким образом, чтобы создать отсортированную коллекцию, вы должны создать объект- потомок от TSortedCoiiection и перекрыть его метод Compare. ,
По умолчанию этот метод получает в качестве параметров указатели на два элемента коллекции и должен вернуть 1, 0 или “1 в зависимости от того, больше, равно или меньше какое-то поле первого элемента по сравнению с этим же полем второго элемента. Поле, по которому сравниваются элементы, называется ключевым.
Например, нам требуется создать отсортированную коллекцию, содержащую каталог библиотеки (см. пример разд. 19.3), причем ключевым является поле AutorA. Тогда создадим новый объект
type
PSort = ATsort;
TSort = object (TSortedCoiiection)
Function Compare(Keyl, Key2: Pointer): Integer; Virtual;
end;
чтобы перекрыть метод Compare. Если теперь объявить новый метод TSort. compare следующим образом:
Function TSort.Compare(Keyl, Key2: Pointer): Integer;
var
PSort absolute Keyl;
PSort absolute Key2;
begin
if Ал.AutorA < BA.AutorA then
Compare-1
else if AA.AutorA =then
Compare := 0
else
Compare1
end;{TSort.Compare}
то после объявления
var
BookList: PSort;
вместо
var
BookList: PCollection;
программа выведет каталог, отсортированный по фамилиям авторов:
Джордейн P.
Справочник программиста персональных компьютеров типа IBM PC, XT и AT
Финансы и статистика, 1991> 544
Йенсен K., Вирт H.
Паскаль. Руководство для пользователя и описание языка
Финансы и статистика, 1982, 151
Скэнлон JI.
Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера
Радио и связь, 1991, 336
Шелдон
Язык Си для профессионалов
И.В.К.- СОФТ, 1991, 383
Ключевое поле определяется методом TSortedCollection.KeyOf. Этот метод по заданному в качестве параметра обращения указателю на элемент коллекции возвращает указатель на ключевое поле. По умолчанию метод KeyOf возвращает указатель на весь элемент, однако вы можете перекрыть его новым методом, возвращающим указатель на нужное ключевое поле. Пусть, например, нам требуется отсортировать каталог по году издания книг (поле Year). Добавим в описание объекта TSort перекрытие метода KeyOf:
type
TSort = object (TSortedCollection)
Function KeyOf (Item: Pointer) : Pointer; Virtual;
end;
Опишем новый метод следующим образом:
Functon TSort.KeyOf(Item: Pointer):'Pointer;
begin
KeyOf :=
end;
и изменим описание метода compare:
Function TSort.Compare(Keyl, Key2: Pointer): Integer; var
AIntegerabsolute Keyl;
AInteger absolute-
begin
if AA < BA then
Compare-1
else if AA =then
Compare := 0
else
Compare 1
end; (TSort.Compare)
Теперь после запуска программы на экран будет выведено:
Йенсен K., Вирт Н.
Паскаль. Руководство для пользователя и описание языка
Финансы и статистика, 1982, 151
Джордейн P.
Справочник программиста персональных компьютеров типа IBM PC, XT и AT
Финансы и статистика, 1991, 544
Обратите внимание: в отсортированной коллекции теперь хранятся только два элемента! Произошло это потому, что по умолчанию Tsortedcollection игнорирует новую запись, если в коллекции уже существует элемент, ключевое поле которого имеет такое же значение. Таким образом, обычно в отсортированной коллекции содержатся записи с уникальными ключевыми полями.
Можно ли поместить в коллекцию два или больше элементов с одинаковыми полями? Turbo Vision позволяет сделать это: поле Tsortedcollection.
Duplicates по умолчанию содержит False, что указывает на уникальность ключевого поля; если перед наполнением коллекции вы поместите в это
поле значение True, коллекция не будет контролировать уникальность ключевых полей.
Изменим начало раздела исполняемых операторов главной программы следующим образом:
begin
BookList :=
with BookListA do
begin
•
Duplicates := True;{Отменяемуникальность ключей)
end;
end.
Теперь на экран будет выведено:
Йенсен К., Вирт H.
Паскаль. Руководство для пользователя и описание языка
Финансы и статистика, 1982, 151
Скэнлон Л.
Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера
Радио и связь, 1991, 336
Шелдон
Язык Си для профессионалов
И.В.К.-СОФТ, 1991, 383
Джордейн Р.
Справочник программиста персональных компьютеров типа IBM PC, XT и AT
Финансы и статистика, 1991, 544
Заметим, что, очередной элемент вставляется перед первым элементом с равным значением ключевого поля. Точно так же поиск First.That вернет указатель на первый из нескольких элементов с одинаковыми ключевыми полями, а метод LastThat — на последний из них.
Коллекции строк
Для создания и использования коллекции отсортированных строк в Turbo Vision предусмотрен объект TSrtingCollection. Этот объект является прямым потомком от TSortedcollection и отличается от него тем, что его метод compare не является абстрактным, по умолчанию он осуществляет обычное для Турбо Паскаля лексикографическое сравнение двух строк. Та-
КИМ образом, если вам необходимо отсортировать коллекцию строк по алфавиту (точнее, в соответствии с внутренней кодировкой символов), то это можно сделать с помощью экземпляра объекта TSortedCollection без какого-либо перекрытия его методов.
В следующем примере (листинг 19.2) создается словарь слов, входящих в некоторый текстовый файл. По умолчанию используется файл с текстом программы, но вы можете указать имя любого текстового файла в качестве параметра вызова программы.
6чччччч«ччч Чч'ччч'чч^Ь'чч’ч^ччк^УччччччЧч Л . ч ч« Л. Л чччччччччч■ ТЧ ч ч V^t. ч ^U/ч^ч «?ч^гл1I •tit^fifl^Rtt к
Uses Objects;
var
f: file of Char;
Function OpenFile(var Name: String): Boolean; {Возвращает FALSE, если нельзя открыть файл} begin
if ParamCount = 1 then
Name := ParamStr(1) {Первыйпараметр в строке вызова программы должен содержать имя файла,) else{если это не так, анализируется файл,
содержащий текст программы}
Name := сору(ParamStr(0) , 1, роз ('.',ParamStr(0))) +' РАЗ' ; Assign(f, Name);
{$!-}
Reset(f);
{$!+}
OpenFile := IOResult=0 end;{Open File}
Function GetWord: String;
{Получает из файла очередное слово}
var
с: Char;
w: String;
Function Letter(var c: Char): Boolean;
{Возвращает TRUE, если символ — буква}
begin
с := UpCase(c);
•I
1X Зак 94!
/Проверяем на строчную русскую букву: }
if с in ['а*..'п'] then{а — русская буква}
с := chr (ord(с)-ord( 'а')+ord( 'А') )(А — русская буква)
else if с in ['р'..'я'] then{р — русская буква}
с := chr(ord(с)-ord(’р')+ord('Р'));(Р - русская буква}
{Проверяем на заглавную букву:)
Letter := с in ['А*..'Z','А'..'Я']
end; (Letter}
begin {GetWord}
w : =* * *
с := #0; ,
while not EOF(f) and not Letter(с) do Read(f,с);
if not EOF(f) then while not EOF(f) and Letter(с) do begin
w := w+c; Read(f,c) end;
GetWord := w
end;{GetWord}
Procedure PrintList(List: PStringCollection);
{Выводит на экран список слов)
Procedure PrintWord(p: PString); far;
begin
Write(pA,' ':20-Length(pA))
end; {Printword}
begin (PrintList)
WriteLn;
WriteLn;
ListA.ForEach(@PrintWord);
WriteLn
end; (PrintList}
var
WordList: PStringCollection;
w: String;
begin {Основная программа} if not OpenFile(w) then
WriteLn('Нельзя открыть файл '+w)
else
begin
WordList :=
repeat
w := GetWord;
if (w <> * 1) and (MaxAvail > 255) then
' WordListA.Insert(NewStr(w))
until w=' ';
PrintList(WordList)
end
end.
Отметим, что в операторе
if (w О ' ') and (MaxAvail > 255) then
осуществляется контроль за доступной динамической памятью. В Turbo Vision есть и встроенные способы контроля кучи (см. разд. 19.6).
Как и в любой другой отсортированной коллекции, в коллекции строк по умолчанию хранятся элементы с уникальными ключевыми полями. Чтобы подавить контроль за уникальностью строк, добавьте оператор
WordListA.Duplicates := True;
сразу за оператором создания коллекции
WordList :=
и сделайте еще один прогон программы, — вы увидите, как много раз встречается в файле одно и то же слово.
Метод TStringCoiiection. Compare следует перекрыть, если вы хотите осуществить свой способ сортировки строк. Например, используя объект
type
PStrSor =ATStrSor;
TStrSor = object (TStringCoiiection)
Function Compare(kl, k2: Pointer): Integer; Virtual;
end;
Function TStrSor.Compare(kl, k2: Pointer): Integer;
var
PString absolute kl;
PString absolute k2;
begin
if slA < s2A then
Compare1
else if slA =then
Compare0
else
Compare-1
end;
вместо PStringCollection, вы сможете вывести на экран список слов, отсортированных в обратном порядке.
Полиморфные коллекции
Как уже говорилось, коллекции Turbo Vision обладают свойством полиморфизма — они позволяют хранить различные объекты. Поскольку каждый объект имеет все необходимые для него поля и методы, работа с полиморфными коллекциями не создает дополнительных проблем. Действительно, в полиморфной коллекции вам обычно нет нужды следить за тем, какого типа объект хранится в том или ином элементе — достаточно вызвать нужный виртуальный метод, чтобы осуществить над элементом требуемые действия.
Рассмотрим следующий пример. Пусть необходимо создать и использовать библиотеку графических примитивов (точки, окружности, прямоугольники и т. п.). Каждый из этих элементов может отличаться своим набором полей и методов. Однако некоторые методы выполняют над объектами однотипные действия, такие, например, как создание нового объекта или его вычерчивание на экране. Если эти методы сделать виртуальными и инкапсулировать в объект-предок, каждый из его потомков сможет осуществить нужные действия одинаковым способом.
Для нашего примера можно создать следующий объект-родитель:
type
PGraphObject = ATGraphObject;
TGraphObject = object (TObject)
X, Y: Integer;{Координаты характерной точки}
Constructor Init;{Создание объекта}
Procedure Draw; Virtual;{Вычерчивание}
end;
Объект TGraphObject содержит общие для всех потомков поля и методы. Заметим, что методы Init и Draw должны перекрываться в объектах- потомках, поэтому их содержимое не имеет значения. Однако полезно вынести в них некоторые общие для всех потомков части программы. Например, конструктор Init может помещать в поля х и Y заданные начальные
значения; если этот метод наполнить конкретным содержанием, он может использоваться во всех объектах иерархии:
Constructor TGraphObject.Init ;
(Присваивает случайные значения координатам X и Y) begin
X := Random(GetMaxX);
Y := Random(GetMaxY)
end;
Здесь GetMaxx, GetMaxY — максимальные координаты графического экрана. Виртуальный метод Draw весьма специфичен: его конкретная программная реализация будет существенно зависеть от типа объекта. Поэтому объявим этот метод абстрактным:
Procedure TGraphObject.Draw;
{Абстрактный метод для вычерчивания графического примитива) begin
Abstract
end;
Как видим, тело этого метода содержит обращение к глобальной процедуре Abstract, которая аварийно завершает выполнение программы и выдает соответствующую диагностику, если в программе встречается вызов метода TGraphOb j ect. Draw. Таким стандартным способом Turbo Vision сообщает пользователю о некорректности программы. Вы можете сделать тело этого метода другим, если вас не устраивают стандартные действия, однако во всех случаях имеет смысл предусмотреть возможность некорректного вызова абстрактного метода, даже если вновь создаваемая библиотека будет использоваться только вами — это значительно облегчит отладку программы.
Создадим три потомка от TGraphObj ect:
type
PPoint = ATPoint;■
TPoint = object (TGraphObject){Точка}
Procedure Draw; Virtual;
end;
PCircle = ATCircle;(Окружность}
TCircle = object (TGraphObject)
R: Integer;
Constructor Init;
Procedure Draw; Virtual;
end;
PRectangle = ATRectangle;(Прямоугольник)
TRectangle = object (TGraphObject)
W, H: Integer;
Constructor Init;
Procedure Draw; Virtual;
end;
i
Объект TPoint (точка) не имеет новых полей и поэтому лишь перекрывает абстрактный метод TGraphObject.Draw:
Procedure TPoint.Draw;
(Выводит точку на экран)
begin
PutPixel(X, Y, White)
end;
В объектах TCircle (окружность) и TRectangle (прямоугольник) инкапсулированы новые поля, поэтому перекрываются также и методы Init:
Constructor TCircle.Init;
(Создает окружность случайного радиуса в случайном месте)
begin
TGraphObject.Init;(Получаемкоординаты центра)
RRandom (GetMaxY div 2)(Получаемрадиус)
end;
Procedure TCircle.Draw;
(Вычерчивает окружность)
begin
Circle(X, Y, R)
end;
Constructor TRectangle.Init;
(Создает случайный прямоугольник}
begin
TGraphObject.Init;{Верхний левый угол)
W := Random (GetMaxX div 2);{Ширина)
H Random (GetMaxY div 2){Высота}
end;
Procedure TRectangle.Draw;
(Вычерчивает прямоугольник)
begin
Rectangle(X, Y, X+W, Y+H)
end;
После того как определены нужные объекты, не составляет особого труда поместить эти объекты в коллекцию и вывести их на экран. Например, для вывода всех элементов коллекции можно использовать такую процедуру:
Procedure DrawAll(C: PCollection);
{Выводит все элементы полиморфной коллекции}
Procedure Drawitem (p: PGraphObject); far;
begin
рЛ.Draw{Это и есть полиморфизм в действии 1}
end;
begin
CA.ForEach(@DrawItem)
end;
Как видим, в процедуре Drawltem с полиморфизмом мы встречаемся дважды. Во-первых, метод ForEach обращается к ней, передавая в качестве параметра обращения нетипизированный указатель на элемент коллекции; это позволяет трактовать параметр как указатель на любой объект, в том числе и на TGraphObject. Во-вторых, в процедуре реализовано обращение к виртуальному методу объекта-родителя Draw: поскольку этот метод перекрывается во всех потомках, каждый из них будет использовать свой метод Draw для вывода на экран.
Сформируем программу, поместив в нее вместо точек уже рассмотренные фрагменты:
Uses Objects,Graph,CRT;
type
Constructor TGraphObject.Init;
Procedure TGraphObject.Draw;
Constructor TPoint.Init;
1
Procedure TPoint.Draw;
Constructor TCircle.Init;
Procedure TCircle.Draw;
Constructor TRectangle.Init;
Procedure TRectangle.Draw;
Procedure DrawAll(C: PCollection);
var
a, r, k: Integer;
List: PCollection;
p: Pointer;
begin
a 0;
{Инициализируем графический режим работы экрана:} InitGraph(a, r, *\TP\BGI');
r := GraphResult;
if r о 0 then
WriteLn(GraphErrorMsg(r)){Ошибка инициализации)
else
begin
{Создаем коллекцию:}
List := New(PCollection, Init (20,5)) ;
(Наполняем ее 20 элементами:)
for k := 1 to 20 do
begin
case k mod 3 of
0: p New(PPoint, Init);
p := New(PCircle, Init);
p := New(PRectangle, Init)
end;
if p <> NIL then
ListA.Insert(p)
end;
DrawAll(List);{Выводим на экран все элементы)
While not KeyPressed do;{Ждем нажатия на любую клавишу)
CloseGraph{Возвраща емся в текстовый режим)
end
end.
В этой программе предполагается, что драйвер графического экрана расположен в каталоге \TP\BGI на текущем диске. Если это не так, следует указать маршрут поиска этого драйвера в качестве параметра обращения к процедуре InltGraph. Кроме того, каталог, содержащий стандартную графическую библиотеку Graph, должен быть указан опцией Options | Directories | Unit directories, если, разумеется, библиотека не содержится в текущем каталоге.
Коллекции и управление памятью
Поскольку элементы коллекций располагаются в динамической памяти, при их использовании особенно важными становятся вопросы контроля за состоянием кучи.
Любая коллекция не может расти до бесконечности: с одной стороны, ее размеры определяются доступной памятью и размером элементов, с другой стороны — общее количество элементов коллекции не может превышать
65520 div SizeOf(Pointer) = 16380
Иными словами, все указатели на элементы коллекции должны располагаться в пределах одного сегмента. Величина 16 380 задается значением глобальной константы MaxCollectionSize, объявленной в интерфейсной части МОДУЛЯ Objects.
Таким образом, при наполнении коллекции необходимо следить за общим количеством элементов (переменная TCollection.Count), которое не может превысить значение, задаваемое константой Maxcoiiectionsize. Кстати, если при обращении к методу TCollection.Init начальное значение NO коллекции указано слишком большим, оно заменяется на MaxCollectionsize.
Элементы коллекции обычно размещаются в куче, поэтому перед размещением очередного элемента следует проверить доступную память (возвращается стандартной функцией MaxAvail).
В ходе расширения коллекции может оказаться, что динамической памяти не хватает для размещения нового массива указателей (напомню, что расширение коллекции заключается в создании нового массива из М) + к х ND указателей, где М) — начальная длина, ND — шаг наращивания коллекции, k = 1, 2, ...; после этого в новый массив копируется старый массив указателей, а место, выделенное под размещение старого массива, возвращается в кучу). Если обнаружена нехватка памяти для расширения коллекции или если при обращении к методу TCollection.At указан индекс, превышающий размер коллекции, вызывается метод TCollection.Error. По умолчанию этот метод завершает выполнение программы с кодом 212
(ошибки периода исполнения). Вы можете перекрыть TCollection. Error, чтобы нужным образом отреагировать на возникшую ситуацию. В этом случае следует учесть, что заголовок TCollection.Error в Turbo Vision имеет следующий вид:
Procedure TCollection.Error(Code, Info: Integer);
Параметр Code в стандартных ситуациях соответствует следующим константам:
□ coIndexError = -1 — индекс метода At превысил значение count - 1; параметр Info содержит неверный индекс;
О coOverflow-2 — нет памяти для расширения коллекции; параметр
Info содержит требуемый размер коллекции.
Константы сохххх определены в интерфейсной части модуля objects.
Глава 20
Потоки
Основным способом хранения данных в Turbo Vision является их размещение в полях объектов. Это, разумеется, не означает, что в вашей программе будут исключительно поля объектов и/или методы доступа к ним — программа в среде Turbo Vision — это прежде всего программа на Турбо Паскале, а следовательно, вы можете реализовать в ней и все другие средства этой системы программирования. Однако в ходе изучения Turbo Vision вы уже, очевидно, смогли по достоинству оценить мощные возможности, предоставляемые техникой объектно-ориентированного программирования и средой Turbo Vision, и, надеюсь, вряд ли захотите широко использовать в своей программе неинкапсулированные данные. у
Для сохранения данных на диске с целью последующего их считывания в программу можно тем или иным способом вьщелить поля из объектов и затем применить обычные средства доступа к файлам. Согласитесь, что такой прием нельзя считать естественным для объектно-ориентированной программы. Во всяком случае, следует учесть, что потоки — а их изучению посвящена эта глава — разработаны, прежде всего, для сохранения в файлах инкапсулированных данных, т. е. полей объектов.
Сущность потоков
Поток Turbo Vision — это коллекция объектов со специфичным способом хранения элементов: для этих целей коллекция-поток использует дисковый файл, отображаемую память (EMS-память) или файловое устройство (устройство ввода/вывода данных).
Весьма удобной моделью потока может служить обычный дисковый файл с прямым или последовательным доступом. В дальнейшем, говоря о потоках, я буду в основном иметь в виду эту модель, а там, где это необходимо,, уточнять детали доступа к EMS-памяти и к файловым устройствам.
Как известно, в Турбо Паскале существуют типизированные и нетипизиро- ванные файлы. При работе с типизированными файлами Турбо Паскаль осуществляет необходимый контроль за типом данных, предупреждая вас о
возможных ошибках; типизированные файлы, как правило, не обеспечивают высокой скорости доступа к данным. При обращении к нетипизирован- ным файлам контроль возлагается целиком на программиста, но в этом случае обычно существенно увеличивается скорость работы с файлами. Потоки Turbo Vision обеспечивают в известном смысле компромисс между обоими способами хранения информации в файле: они позволяют компилятору выполнить проверку типов на уровне проверки объектов, но не контролируют. сами данные, помещаемые в файл, т. к. в правильно сконструированной программе объект сам осуществляет необходимые операции с диском и делает это безошибочно и очень быстро.
Основным отличием потоков от файлов является их полиморфизм. Как и коллекции, потоки Turbo Vision могут быть полиморфными, что означает, что в одном потоке могут сохраняться разные объекты (точнее, их поля). Как программист, знакомый с техникой объектно-ориентированного программирования, вы должны понимать, что записать в один файл несколько разных объектов не составляет особой проблемы — для этого нужно лишь вызвать соответствующий виртуальный метод, который знает, какие поля и в какой последовательности помещаются в файл. Но вот каким образом поддерживается полиморфизм при чтении данных? Как из общего потока выделяются данные, относящиеся к конкретному объекту? Эту проблему еще можно было бы решить, если бы потоки обеспечивали только последовательный доступ к данным — тогда мы могли бы просто вызывать виртуальные методы чтения данных в том же порядке, в каком объекты записывались в файл. Но потоки Turbo Vision предоставляют также и произвольный способ доступа к данным!
Решение проблемы разработчиками Turbo Vision найдено в особом механизме, называемом регистрацией объектов. Зарегистрировать объект — означает приписать ему некоторый уникальный регистрационный номер. Регистрационный номер записывается в файл (а следовательно и считывается из него) первым. Прочитав регистрационный номер, Turbo Vision однозначно определяет объект, которому принадлежат данные, и вызывает соответствующий виртуальный метод доступа к файлу.
Таким образом, для работы с потоками необходимо:
Зарегистрировать объекты, которые будут помещаться в поток или считываться из него; отметим, что все стандартные объекты Turbo Vision уже имеют уникальные регистрационные номера и процедура их регистрации предельно проста (см. ниже).
Инициализировать (создать) поток; в ходе инициализации указывается имя файла и некоторая другая информация, используемая для обеспечения доступа к файлу.
Поместить данные в поток и/или прочитать их из него.
Удалить поток.
Важной особенностью доступа к потокам является возможность записи в них групп и соответственно чтения групп из потока. При этом группа сама будет автоматически вызывать нужную процедуру доступа для каждого из своих элементов, что значительно упрощает работу с потоком.
Регистрация объектов
Для регистрации любого объекта используется обращение к глобальной процедуре RegisterType, определенной в интерфейсной части модуля Objects. Единственным параметром обращения к этой процедуре является запись типа TStreamRec, в которой группируются некоторые важные для Turbo Vision характеристики объекта:
type
PStreamRec
=
TStreamRec
ObjType:
= record
Word;
(Регистрационныйномер объекта)
VMTLink:
Word;
{Смещение таблицы виртуальных методов}
•Load:
Pointer;
(Адрес метода чтения)
Store :
Pointer;
(Адресметода записи)
Next:
Word;
{Связь в списке}
end;
Для каждого
объекта
вашей программы (как стандартного, так и нестан-
дартного) должна быть создана своя запись типа TStreamRec, если только вы
собираетесь помещать соответствующий объект в поток или получать его из потока. Однако для стандартных объектов такие записи уже существуют, и вам нет нужды создавать их заново: по принятому в Turbo Vision соглашению запись TStreamRec для стандартного объекта имеет такое же имя, как имя объекта, с заменой начальной буквы т на R. Например, для TWindow регистрационная запись называется RWindow, для TDialog — RDialog и т. д. Имеет смысл следовать этому соглашению, определяя идентификаторы регистрационных записей для нестандартных объектов.
Итак, для каждого нестандартного объекта вы должны подготовить запись TStreamRec, причем фактически определяются только первые четыре поля этой записи, т. к. поле Next используется для организации связанного списка И заполняется внутри Процедуры RegisterType.
В поле ObjType вы должны поместить константу-идентификатор объекта. В Turbo Vision константы 0-99 уже задействованы в стандартных регистрационных записях RXXXX, таким образом, если вам понадобятся стандартные регистрационные записи, в вашем распоряжении остаются константы в диапазоне от 100 до 65 535. Выбор констант из этого диапазона может быть
произвольным, однако Turbo Vision требует, чтобы каждый объект характеризовался уникальной константой. Заметим, что Turbo Vision следит за уникальностью регистрационных констант и аварийно завершает исполнение программы (код ошибки 212), если в программе делается попытка зарегистрировать два разных объекта с одинаковыми регистрационными номерами.
Поле VMTLink должно содержать смещение адреса таблицы виртуальных методов (ТВМ) объекта. Турбо Паскаль имеет стандартную функцию TypeOf
(Type: object), которая возвращает адрес ТВМ для указанного типа туре объекта, поэтому поле VMTLink обычно заполняется значением Ofs (Typeof (TName)A), где TName — имя нестандартного объекта.
Поля Load и Store должны содержать адреса соответствующих виртуальных методов (см. разд. 20.3), обеспечивающих чтение объектов из потока и их запись в поток.
, Например, если в программе определен объект
type
TMyWindow = object (TWindow)
Constructor Load(var S: TStream);
Procedure Store(var S: TStream); end;
то для его регистрации создается запись const
RMyWindow: TStreamRec = ( ObjType: 100;
VMTLink: Ofs(TypeOf(TMyWindow)л);
Load : @TMyWindow.Load;
Store : @TMyWindow.Store);
Обычно регистрация осуществляется в конструкторе Init вновь созданного объекта, например:
Constructor TMyWindow.Init;
begin
RegisterType(RMyWindow);
end;
Однако если в программе предполагается помещать в поток (или считывать из него) объекты разного типа, регистрацию объектов, как правило, реализуют отдельной процедурой, вызываемой в конструкторе Init программы
или в любом другом удобном месте, но перед фактическим обращением к потоку. Например:
Procedure RegisterMyProgram;
begin
RegisterType (RWindow) ;{Регистрация стандартного объекта}
RegisterType(RMyWindow); {Регистрациянового объекта)
end;
Для упрощения регистрации стандартных объектов в модулях Turbo Vision предусмотрены процедуры Registerxxxx, где хххх — имя соответствующего модуля. Например, процедура RegisterDialogs осуществляет регистрацию всех неабстрактных объектов модуля Dialogs, т. е. TButton, TCluster, TInputLine и т. д.; процедура Registerviews регистрирует объекты модуля Views И Т. Д.
\
Создание и удаление потока
Для работы с потоками в Turbo Vision предусмотрен абстрактный тип TStream И Три его ПОТОМка — TDOSStream, TBufStream И TEMSStream.
Объект TDOSStream реализует небуферизованный доступ к файлу или файловому устройству. Буферизация потока означает создание некоторого промежуточного буфера, в который помещаются данные перед физической записью их на диск или сразу после физического чтения с диска. Буферизация позволяет согласовать формат данных с размерами дискового сектора и обычно значительно ускоряет доступ к потоку, поэтому в большинстве случаев в программах используется буферизованная версия потока, реализуемая объектом TBufstream.
Объект TEMSStream обеспечивает доступ к отображаемой памяти компьютера, оборудованного специальной EMS-платой (для ПК с процессорами 80386 и более поздними EMS-память может эмулироваться). Запись объектов в EMS-память и чтение их из нее осуществляется с предельно возможной скоростью, однако содержимое этой памяти разрушается после выключения компьютера. Таким образом, TEMSStream служит для временного хранения данных с целью минимизации времени доступа к ним. Если в программе предполагается частое обращение к потоку, имеет смысл скопировать его в EMS-память в начале работы программы и перенести хранящиеся в нем данные на диск перед завершением работы.
В каждом из объектов TXXXStream предусмотрен свой конструктор Init, с помощью которого создается экземпляр нужного потока. Ниже описывается формат вызова каждого конструктора.
Constructor TDOSStream.Init(FileName: FNameStr; Mode: Word);
Здесь:
О FileName — имя дискового файла или файлового устройства;
О Mode — способ доступа к данным.
Параметр FiieName может содержать полное имя файла с указанием диска и маршрута поиска. Параметр Mode определяет способ доступа к данным. Для задания этого параметра можно использовать следующие константы, определенные в модуле objects:
const
stCreat= $3C00; (Создать файл}
stOpenRead =файл только длячтения}
stOpenWrite =файл только длязаписи)
stOpen =файл для чтенияи записи)
Constructor TBufStream (FileName: FNameStr;
Mode: Word; Size: Word);
Здесь:
FiieName — имя дискового файла или файлового устройства;
П Mode — способ доступа к данным;
size — размер буфера в байтах.
Размер буфера выбирается равным размеру дискового сектора (512 байт) или размеру кластера (n х 512, п = 1, 2, 4, 8, ...). Минимальные потери времени обеспечивает размер буфера, равный размеру кластера диска (кластер — минимальная порция дискового пространства, выделяемая каждому файлу). При работе с гибкими дисками размер кластера обычно равен одному или двум секторам, для жесткого диска этот размер зависит от общей емкости диска и чаще всего равен 4 или 8 секторам. Если вы не знаете размер кластера диска, с которым будет связан поток, установите size = 512.
Constructor TEMSStream.Init(MinSize, MaxSize: LongInt);
Здесь MinSize, MaxSize определяют соответственно минимальный и максимальный размеры блока, который будет передаваться в EMS-память. Параметр MaxSize имеет смысл только при использовании драйвера EMS- памяти, версии меньше 4.0: в этом случае попытка разместить в расширенной памяти блок, больше MaxSize, вызовет ошибку; для версии драйвера 4.0 и выше в памяти можно разместить блок любого размера, в этом случае параметр MaxSize МОЖНО Опускать.
После завершения работы с потоком следует удалить его экземпляр — это аналогично тому, как вы закрываете дисковый файл по окончании работы с ним. Для удаления потока нужно обратиться к его методу Done, например:
Dispose(PMyStream, Done);
Здесь PMyStream — указатель на экземпляр потока, размещенный в куче. Если вы не использовали указатель на поток (т. е. если экземпляр потока размещен в обычной переменной), удаление потока осуществляется вызовом деструктора
MyStream.Dope;
(MyStrem — экземпляр потока).
В ходе реализации процедуры TStream.Done очищается внутренний буфер (если поток буферизованный), закрывается файл и уничтожается экземпляр потока.
Работа с потоком
Базовый объект TStream реализует три метода, используемых для непосредственной работы с потоком. Метод TStream.Put предназначен для передачи объектов в поток и выполняет приблизительно такие же функции, как стандартная файловая процедура write. Метод TStream.Get служит для чтения объектов из потока, его аналогом является процедура Read. Наконец, с помощью метода TStream.Error анализируется состояние потока после завершения некоторой операции: если обнаружена ошибка при обмене данными с потоком, вызывается этот метод, который по умолчанию просто устанавливает признаки ошибки В Информационных ПОЛЯХ TStream. Status И TStream.ErrorInfo. Приблизительным аналогом метода TStream.Error является стандартная файловая функция lOResult.
Сразу же замечу, что в случае возникновения ошибки все последующие операции с потоком блокируются до тех пор, пока не будет вызван метод TStream.Reset.
Методы Put и Get практически никогда не перекрываются: для реализации операций с потоком они обращаются к виртуальным методам store и Load, которые должны быть определены в каждом объекте, если только этот объект помещается в поток или считывается из него. Главное назначение методов Put и Get состоит в обеспечении полиморфизма потока за счет контроля регистрационных номеров объектов. Методы Load и store никогда не вызываются прямо, но только из методов Put и Get, т. к. они ничего не знают о регистрационных номерах и не могут работать в полиморфных потоках.
Методы Put и Get
Чтобы поместить объект в поток, нужно обратиться к методу Put, передав ему в качестве параметра инициализированный экземпляр объекта. Например:
var
MyStream: TBufStream;MyStream: TBufStream;{Экземпляр потока){Экземпляр потока)
MyStream: TBufStream;
MyStream: TBufStream;
{Экземпляр потока)
{Экземпляр потока)
MyWindow: TMyWindow;MyWindow: TMyWindow;{Экземпляр объекта /{Экземпляр объекта /
MyWindow: TMyWindow;
MyWindow: TMyWindow;
{Экземпляр объекта /
{Экземпляр объекта /
MyStream.Put(MyWindow);(Помещаем объект в поток}
Предварительно объект должен быть зарегистрирован обращением к RegisterType, а ПОТОК — инициализирован С ПОМОЩЬЮ TXXXStream.Init.
Метод Put вначале отыскивает объект в регистрационном списке, создаваемом процедурой RegisterType, и получает из этого списка регистрационный номер объекта и адрес его метода store. Затем в поток записывается регистрационный номер и вызывается метод store, который делает остальное, т. e. копирует в поток все поля объекта.
По такой же схеме работает и метод Get: вначале он считывает из потока регистрационный номер объекта, затем отыскивает его в регистрационном списке и вызывает соответствующий конструктор Load. Конструктор размещает в динамической памяти экземпляр считываемого объекта, а затем считывает из потока все его поля. Результатом работы Get является нетипизи- рованный указатель на вновь созданный и инициализированный объект, Например:
type
MyStream: TBufStream;{Экземпляр потока}
PWindow :=PWindow :=PWindow: PMyWindow;(Указатель на экземпляр объекта}
PWindow :=
PWindow :=
(Получаем объект из потока)
Заметим, что количество считываемых из потока данных и тип ТВМ, который назначен вновь созданному объекту, определяется не типом PWindow (см. выше), а регистрационным номером, полученным из потока. Вы можете ошибочно поместить в левой части оператора присваивания указатель на объект другого типа и Turbo Vision не сможет предупредить вас об этом!
Методы Put и Get позволяют автоматически сохранять в потоке и получать из него сложные объекты (группы). Эта возможность реализуется внутри методов Store И Load.
Методы Store и Load
Метод store осуществляет запись данных в поток. Для этого он использует метод низкого уровня write, передавая ему в качестве параметров имя записываемого поля и длину поля в байтах. Заметим, что вам нет нужды записывать все поля объекта: для записи наследуемых полей достаточно просто обратиться к методу store объекта-родителя. Ваш метод store должен записывать только те поля, которые добавляются к полям родителя.
Если, например, создан объект
type
TMyDialog = object (TDialog)
St: String;{Новое поле)
Procedure Store(var S: TStream); Virtual;
end;
то метод TMyDialog. store может иметь такую реализацию:
Procedure TMyDialog.Store(var S: TStream) ;
begin
TDialog.Store(S);{Сохраняемнаследуемые поля)
SA.Write(St, SizeOf (St));{Сохраняемновое поле} .
end;
Аналогичным образом реализуется и конструктор Load: с помощью обращения к низкоуровневому методу TStream.Read он получает из потока только дополнительные поля и только в том порядке, как они были записаны в поток методом store:
Constructor TMyDialog.Load(var S: TStream)
begin
TDialog.Load(S);{Получаемнаследуемые поля}
новое
end;
Вам следует тщательно следить за соответствием методов store и Load: метод Load должен прочитать ровно столько байтов и строго в той последовательности, сколько байтов и в какой последовательности поместил в поток метод store. Помните, что в Turbo Vision нет средств контроля за правильностью считываемых данных!
Если ваш объект — группа, следует включить в него поля-указатели на каждый из элементов и использовать методы PutSubViewptr и GetSubViewPtr соответственно для записи в поток и чтения из него. Например:
type
TMyDialog object (TDialog)
St: String;{Текстовое поле}
PB: PButton;{Указатель на кнопку},
Procedure Store(var S: TStream); Virtual;
Constructor Load(var S: TStream);
end;
II
I
I
TStream);{Сохраняемнаследуемые поля){Сохраняем текстовое поле}{Сохраняем кнопку)S: TStream);{Получа ем на следуемые поля){Получаем тестовое поле){ Получа ем кнопку}TStream);{Сохраняемнаследуемые поля){Сохраняем текстовое поле}{Сохраняем кнопку)S: TStream);{Получа ем на следуемые поля){Получаем тестовое поле){ Получа ем кнопку}Procedure TMyDialog.Store(var S: begin
TStream);
{Сохраняемнаследуемые поля)
{Сохраняем текстовое поле}
{Сохраняем кнопку)
S: TStream);
{Получа ем на следуемые поля)
{Получаем тестовое поле)
{ Получа ем кнопку}
TStream);
{Сохраняемнаследуемые поля)
{Сохраняем текстовое поле}
{Сохраняем кнопку)
S: TStream);
{Получа ем на следуемые поля)
{Получаем тестовое поле)
{ Получа ем кнопку}
TDialog.Store(S);
SA.Write(ST, SizeOf(St)); PutSubViewPtr(S, PB);
end;
Constructor TMyDialog.Load(var
begin
TDialog. Load (S)
SA.Read(St, SizeOf(St));
GetSubViewPtr(S, PB);
end;
Обработка ошибок
При обнаружении ошибки поток вызывает свой метод TStream.Error, который определяет необходимую реакцию программы. По умолчанию этот метод просто записывает информацию об ошибке в поля TStream. Status и TStream.Errorinfo.
Поле status определяет ТИП ошибки, в соответствии со следующими константами модуля objects:
const
stOk= 0;
{Нет ошибки)
stError=-l;
/ Ошибка доступа)
stlnitError =-2;
[Ошибка инициализации потока}
stReadError =-3;
[Чтение за концом потока)
stWriteError =-4;'
{Нельзя расширить поток)
stGetError=-5;
stPutError =-6;
[Get для незарегистрированного объекта) (Put для незарегистрированного объекта)
Поле Errorinfo определено только для status = -5 или status = -6. В первом случае оно содержит регистрационный номер, полученный из потока и не обнаруженный в регистрационном списке; во втором — смещение ТВМ незарегистрированного объекта, который программа пытается поместить в поток.
Сразу после обнаружения ошибки Turbo Vision блокирует все операции с потоком до тех пор, пока аварийная ситуация не будет сброшена обращением К методу TStream. Reset.
Прямой доступ к потокам
Поток имеет методы, имитирующие файловые процедуры прямого доступа к дисковому файлу.
С помощью функции GetPos программа может получить текущую позицию в потоке, т. е. номер байта, начиная с которого будет осуществляться очередная операция с потоком (первый байт потока имеет номер 0).
Метод seek (Роз: Longlnt) перемещает текущую позицию в потоке в байт Роз от начало потока.
Метод Getsize возвращает общий размер потока в байтах.
С помощью метода Truncate можно удалить из потока все данные, начиная с текущей позиции до конца потока.
Как видим, эти процедуры можно использовать только в том случае, если вне потока существует индексная коллекция, содержащая начальные позиции в потоке для каждого из сохраняемых в нем объектов. Такая коллекция используется в ресурсах, поэтому для обеспечения прямого доступа к потоку лучше создать файл ресурсов (см. главу 21).
Использованиепотоков
с произвольными данными
Хотя потоки спроектированы в основном для работы с объектами, вы можете хранить в них не только поля объектов, но и любые другие данные. При этом не следует обращаться к методам Put и Get, т. к. они предполагают доступ к объектам. Вместо этого вы должны обратиться к низкоуровневым процедурам Write И Read.
Следующая простая программа (пример 20.1) использует поток для сохранения десяти случайных целых чисел.
...................................... ................. м^ у...,цу ^oj. ■^м^^^ц*^MAЦ!M|цц,.цi|MX»ш&uмцMjD^^^!
: Пример 20.1. Иллюстрацияиспользования потока
Uses Objects;
var
S: TBuf Stream;(Экземпляр потока)
k,j: Integer;
begin
WriteLn ('Запись в поток:') ;
S.Init ('Test.dat', stCreate, 512);{Создаем поток)
for k := 1 to 10 do
{Получаем случайное целое) {Выводим на экран) {Помещаем в поток){Получаем случайное целое) {Выводим на экран) {Помещаем в поток)begin
{Получаем случайное целое) {Выводим на экран) {Помещаем в поток)
{Получаем случайное целое) {Выводим на экран) {Помещаем в поток)
j := Random (100);
;
S.Write(j,2)
end;
S.Done;S.Done;(Удаляем поток)
S.Done;
S.Done;
S. In.it (’Test.dat', stOpenRead, 512);
WriteLn;
WriteLn('Чтение из потока:');
for k1 to 10 do
begin
S.Read(j,2);{Получаемцелое из потока)
Write (j : 8){Выводим на экран)
end;
S.Done;
WriteLn
end.
Для простоты в профамму не включены средства контроля ошибок. В ходе прогона программы в текущем каталоге диска будет создан файл TEST.DAT размером в 20 байт, а на экран будут выведены две строки с одинаковыми числами.
Глава 21
Ресурсы Turbo Vision
Ресурсы — это индексированные потоки. Главное отличие ресурсов от потоков заключается в том, что к объектам, хранящимся в ресурсе, можно обращаться по ключам — уникальным строкам, идентифицирующим объекты. Таким образом, ресурсы спроектированы специально для облегчения произвольного доступа к потокам. . • Использование ресурсов открывает перед вами ряд интересных возможностей. Вы можете загружать любые видимые элементы из предварительно созданного файла ресурсов вместо того, чтобы создавать их в теле основной программы. Так как реализация методов Load обычно значительно проще, чем реализация конструкторов Init, это дает возможность несколько уменьшить размер вашей программы, поскольку в основной программе вы уже не описываете методы Init, а просто загружаете нужные объекты из ресурса. Разумеется, вы должны предварительно разработать вспомогательную программу, создающую нужный файл ресурсов.
Разные ресурсы могут хранить одинаковое количество текстовых строк. Поскольку к каждой строке ресурса можно обращаться по индексу, вы можете простой заменой файла ресурсов изменить, например, язык, на котором написаны сообщения встроенной справочной службы. Причем такая замена никак не отражается на содержательной стороне справок и не влечет за собой никаких изменений в тексте программы. Все, что вам необходимо в этом случае, — это подготовить несколько идентичных по смыслу, но написанных на разных языках файлов текстовых ресурсов. Для отечественных программистов, ориентирующихся на западный рынок, такая возможность, согласитесь, может показаться весьма заманчивой.
Наконец, смена файла ресурсов позволит вам легко создать демонстрационную версию вашей программы без какой-либо ее перенастройки. Для этого вы готовите два файла ресурсов: один рассчитан на полные возможности программы, другой позволяет использовать только часть ее возможностей и предоставляет пользователю сокращенные меню и строки статуса. Поставка программы с тем или иным файлом ресурсов определяет ее возможности.
В Turbo Vision ресурс реализуется с помощью объекта TResourceFiie, который содержит поток и связанную с ним отсортированную коллекцию строк. С помощью метода Init вы создаете ресурс, методом Put помещаете в поток нужные вам объекты, а с помощью метода Get получаете из него объект по его имени или индексу.
Создание ресурса
Поскольку ресурс — это поток, для создания ресурса необходимо открыть (создать) поток. Это делается с помощью метода Init для потока нужного типа (обычно используется буферизованный поток TBufStream или его потомок). После того как поток успешно открыт, в него помещают нужные объекты с их уникальными ключами и затем поток закрывается — файл ресурсов готов.
Пример 21.1 иллюстрирует, как можно создать ресурс, содержащий строку статуса с именем (ключом) "Статус".
Пример 21.1. Создание ресурса
Uses Drivers, Objects, Views, App, Menus; var
StatusRes: TResourceFiie;{Файл ресурсов}
PStatusStrm: PBufStream;{Буферизованныйпоток}
Procedure CreateStatusLine;
{Создает строку статуса и помещает ее в файл ресурсов} var
R: TRect;
PStatus: PStatusLine;
begin
R.Assign(0,24,80,25) ;
PStatus := New(PStatusLine, Init(R,
NewStatusDef (0, $FFFF,
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit,
NewStatusKey('Демонстрация строки статуса',0,0, NIL)),
NIL)))
' StatusRes.Put(PStatus, 'Статус');
Dispose(PStatus, Done) end;
begin
PStatusStrm := New(PBufStream,Init( 'status.res',stCreate,1024));
StatusRes.Init(PStatusStrm); {Создаемпоток)
RegisterType(RStatusLine); {Регистрируем строку статуса} CreateStatusLine;{Помещаем строку статуса в поток}
StatusRes.Done end.
В программе создается поток, связанный с указателем PStatusStrm, и в него с помощью процедуры CreateStatusLine помещается строка статуса. Этой строке присваивается ключ статус.
Использование ресурса
Получить объект из ресурса не представляет проблемы: после инициализации файла ресурсов вы просто вызываете его метод Get, которому передаете ключ (имя) нужного ресурса. Например, в следующей программе (пример 21.1) используется файл STATUS.RES, созданный в предыдущем примере.
.........,.l|.....H...#f.....y^,»l.|MIM^.,..v,..J.„»^|...,/H.....».,...................1..r.........v...................„.........Ml Ц............ v/<>*
Пример 21.2. Использование ресурса:
A«R••• .^^^R^V?. • • ■ » • ^o^n^^Sr^^A eeeeeee^«9»j .... .7. г...7... Ло. .‘Л'Я.. । fl«.. Z. 1
Uses Objects, Drivers, Views, Menus, Dialogs, App;
var
StatusRes: TResourceFile; {Файл ресурсов)
type
PMyApp =лТМуАрр;{Программа в Turbo Vision}
ТМуАрр = object (TApplication)
Constructor Init;
Procedure InitStatusLine; Virtual;
end;
Constructor ТМуАрр.Init;
{Открывает поток, связанный с файлом ресурсов}
begin
StatusRes.Init(New(PBufStream, Init('status.res',stOpen,1024))); if StatusRes.StreamA.Status <> 0 then
Halt;
RegisterType(RStatusLine);
Inherited Init
end;
Procedure TMyApp.InitStatusLine;
{Получает строку статуса из файла ресурсов)
begin
StatusLine :=
end;
var
Prog: TMyApp;
begin
Prog.Init;
Prog.Run;
Prog.Done
end.
Заметим, что если в файле ресурсов нет ресурса с указанным именем, метод Get возвращает значение NIL.
Любой ресурс можно получить из файла ресурсов сколько угодно раз и в любом порядке, т. е. файл ресурсов — это поток с произвольным доступом. При необходимости можно дополнять ранее созданный файл ресурсов новыми объектами или заменять существующие в нем объекты другими.
Для ускорения доступа к файлу ресурсов его можно связать с EMS-памятью или разместить на виртуальном диске.
Строковые ресурсы
Списки строк встречаются в программах Turbo Vision очень часто, поэтому специально для этого вида ресурсов разработаны два особых объекта — TStrListMaker И TStringList. С ПОМОЩЬЮ TSrtListMaker создается СТрОКОВЫЙ ресурс, а с помощью TStringList ранее созданный строковый ресурс становится доступен программе. Предполагается, что создание строкового ресурса и его использование осуществляется в разных программах, поэтому обоим объектам в Turbo Vision присвоен один и тот же регистрационный номер. По той же причине TStrListMaker имеет метод Put, но не имеет метода Get, a TStringList, наоборот, имеет Get и не имеет Put.
В отличие от остальных ресурсов доступ к строковым ресурсам осуществляется по индексам. Таким образом, ваша программа становится независимой от языка, используемого для вывода сообщений. Чтобы поместить на экран
ту или иную информацию, достаточно указать индекс нужного сообщения и в зависимости от выбранного файла строкового ресурса сообщение будет выведено на нужном языке.
В двух следующих программах (примеры 21.3 и 21.4) иллюстрируется создание и использование строкового ресурса. В программе CreateStringRes создается строковый ресурс, содержащий все строки текста программы. Константы NStr и SizeStrRes определяют количество строк и общую длину строкового ресурса в байтах. В нашем примере эти величины заранее не известны и выбираются приблизительно, но так, чтобы в ресурсе гарантированно разместился бы весь текст программы. В реальной программе они должны выбираться, исходя из конкретной задачи.
; Пример 21.3. Создание строкового ресурса ■ ■-- '...,-•:
Program CreateStingRes;
(Эта программа создает строковый ресурс}
Uses Objects;
const
SizeStrRes = 2000;{Общая длина всех строк в ресурсе}
NStr = 40;{Общее количество строк)
var
f: text;(Файл с текстом программы)
ProgText: TResourceFile;
Strings : TStrListMaker;{ Строковый ресурс}
k: Integer;
String;
begin
s := copy(ParamStr(0),l,pos(1.',ParamStr(0)));
Assign(f,s+'PAS');
Reset(f);{Открываем файл с текстом программы}
RegisterType (RStrListMaker) ;(Регистрируем объект}
ProgText.Init(New(PBufStream, Init('prog.res',stCreate,1024)));
Strings.Init(SizeStrRes,Nstr);
k := 0;
while not EOF(f) do
begin
ReadLn(f,s);ReadLn(f,s);(Читаем строку программы)
ReadLn(f,s);
ReadLn(f,s);
inc(k)£
Strings. Put (k, s) end;
(k — порядковый номер строки} {Помещаем строку в ресурс)
Close(f);
{Закрываем файл с текстом программы}
{Помещаем ресурс в поток)
ProgText.Put(@Strings,1Программа’);
Strings.Done;
{Закрываемресурс)
ProgText.Done end.
(Закрываем поток)
В профамме usestringRes текст, полученный из созданного строкового ресурса, читается и выводится на экран "задом наперед", начиная с последней строкитекста.
: Пример 21.4. Использование строкового ресурса
:.. "
Program UseStingRes;
{Эта программа использует ранее созданный строковый ресурс}
Uses Objects;
var
ProgText: TResourceFile;(Файл ресурсов)
PStrings : PStringList;{Строковый ресурс)
k,N: Integer;
begin
RegisterType(RStringList);{Регистрируем объект}
ProgText.Init(New(PBufStream,(Создаем поток)
Init('prog.res', stOpenRead, 1024)));
PStrings :=(Получаем из потока ресурс)
RStringList(ProgText.Get('Программа'));
N := 1;
while (PStringsA. Get (N)) o'' do inc(N);{N-l = общее количество строк/
for k := N-l downto 1 do
WriteLn(PStringsA.Get(k) ) ; {Получаем и выводим строки)
PStringsA.Done;{Закрываем ресурс)
ProgText. Done{Закрываем поток}
end.
Заметим, что индексы, по которым осуществляется доступ к строковому ресурсу, могут быть произвольными целыми числами в диапазоне от 0 до 65 535. Чтобы определить общее количество строк в строковом ресурсе, учитывается то обстоятельство, что при обращении к TStringList.Get с недействительным индексом метод выдает пустую строку. На практике этот способ нельзя считать надежным (в строковом ресурсе могут храниться и пустые строки), однако других способов определения этого параметра в TStringList не существует. -
Перед обращением к TSrtListMaker.Put и TSrtingList.Get соответствующие Объекты ДОЛЖНЫ быть зарегистрированы С ПОМОЩЬЮ RegisterType.
Глава 22
Объекты-контролеры
При разработке диалоговых программ часто бывает необходимо не только предоставить пользователю возможность ввода данных, но и проверить их допустимость в данном контексте программы. Если, например, пользователь должен вводить целочисленные величины, то в набранной им строке могут содержаться только цифры, а если от него ожидается ввод имени файла, строка должна представлять собой правильный маршрут поиска дискового файла.
В этой небольшой главе рассматривается имеющаяся в Turbo Vision группа объектов-контролеров, упрощающих решение подобного рода задач.
Типы объектов-контролеров
Объект-контролер обычно связывается со строкой ввода TInputLine и активизируется при выполнении метода TInputLine.Valid. Получив управление, контролер осуществляет необходимую проверку данных и блокирует завершение ввода, если обнаружена ошибка. В Turbo Vision входят пять объектов- контролеров, являющихся наследниками абстрактного объекта TValidator и отличающихся алгоритмом проверки данных.
TPXPicture Validator
Объект TPXPictureValidator проверяет введенную пользователем строку на соответствие некоторому шаблону ввода. Шаблон ввода определяется с помощью специальных символов (табл. 22.1), подобно тому, как задается допустимый ввод в базе данных Paradox корпорации Borland.
Таблица 22.1. Управляющие символы шаблона
СимволНазначение
#Любая цифра
?Любая буква
@Любой символ (буква, цифра или специальный символ)
Таблица22.1 (окончание)
СимволНазначение
&Любая буква с автоматическим преобразованием в верхний регистр
! Любой символ с автоматическим преобразованием букв в верхний регистр
* Повторение следующего символа произвольное количество раз
Следующий символ — не управляющий
[ ]Заключенная в скобки последовательность символов может отсутст
вовать
,Разделение вариантов
{}Создание группы
Любой другой символ шаблона представляет сам себя, т. е. не является управляющим. Символ предшествующий специальному символу, отменяет его специальное назначение. Чтобы в шаблон вставить собственно символ его нужно удвоить.
Перечисленные символы позволяют формировать шаблоны любой сложности. Например:
### — трехзначное число; примеры правильного ввода: 123, 566, 080; неправильный ввод: 12 (нет одной цифры), -12 (первый символ — не цифра);
*# — число с произвольным количеством цифр; допустимый вёод: 1, 12, 12345678987654321; неправильный ввод: -12, 0334а;
[+,-]*# — произвольное целое число; допустимый ввод: +123, -56, 7890; Недопустимый ВВОД: +-123, 1.23, 12345Е+02;
а {#[#])-{#[#]}-{##[##]} - дата в формате дд-мм-гг; допустимый ввод: 1-2-33, 01-02-1933; недопустимый ввод: 1/2/33, 1 фев зз;
#;**# — число в диапазоне 0—9 умножается (символ *) на произвольное положительное целое число; правильный ввод: 2*2, 3*12345; неправильный ВВОД: 2/2, 123*12345.
TFilterValidator
Этот объект проверяет все введенные пользователем символы на их принадлежность к набору допустимых символов. Если очередной вводимый символ не принадлежит эталонному множеству, он игнорируется и при наборе просто не появляется в строке ввода. Таким образом, объект TFilterValidator
действительно фильтрует вводимую строку, удаляя из нее неправильные символы. Например, экземпляр объекта
:=
выбирает из ввода только цифры.
Объект TRangeValidator порожден ОТОн преобразует
символьный ввод в целое число и проверяет, находится ли оно в диапазоне указанных значенийЕго методвернет значение True
только в том случае, когда, во-первых, пользователь введет правильное число, и, во-вторых, это число будет принадлежать нужному диапазону. Например, объект
MyRange :=
позволяет вводить числа в диапазоне от 0 до 100
Абстрактный объект TLookupValidator предназначен для контроля соответствия введенной пользователем строки набору эталонных строк. Фактически он модифицирует поведение базового объектана случай, когда
проверяется произвольная текстовая строка. В его потомках должен перекрываться метод Lookup, осуществляющий нужный контроль и возвращающий True только в том случае, когда подготовленная пользователем строка соответствует одному из желаемых значений. Например, с помощью объекта type
TMyLookupValidator =
FunctionBoolean; Virtual;
end;
Function const
=
Text2
begin
Lookupor (S=Text2) or•
end;
можно потребовать от пользователя ввода только одной из трех возможных строк: Turbo, Pascal или Turbo Pascal.
TStringLookupValidator
Этот объект порожден от TLookupValidator и сравнивает введенную пользователем строку с набором допустимых строк, хранящихся в отсортированной коллекции. Чтобы объект осуществлял сравнение с нужным набором строк, он инициализируется следующим образом:
var
MyCollection: PStringCollection;
MyStrVal: PStringLookupValidator;
begin
{Создаем коллекцию строк)
MyCollection := New(PStringCollection, Init(1,1));
{Наполняем коллекцию — размещаем строки в куче}
MyCollection^.Insert(NewStr('Турбо'));
MyCollectionA.Insert(NewStr(’Паскаль *));
{Создаем объект-контролер)
MyStrCol := New(PStringLookupValidator,Init(MyCollection))
end;
22.2. Использование объектов-контролеров
Контроль текстового ввода
Как уже говорилось, основное назначение объектов-контролеров состоит в контроле текстовой информации, вводимой пользователем с помощью объектов TinputLine или их специализированных потомков. Для упрощения подключения контролера в объекте TinputLine предусмотрен специальный метод SetValidator, единственным параметром обращения к которому является ссылка на экземпляр объекта-контролера. Таким образом, подключение контролера осуществляется в два этапа: вначале инициируется экземпляр объекта-контролера, а затем вызывается метод TInputLine.SetValidator для объекта, осуществляющего ввод данных. Например:
var
InpLine: PInputLine;
Validator: PRangeValidator;
begin
{Создаем строку ввода}
InpLine := New(PInputLine,Init(...));
{Создаем объект-контролер)
Validator := New(PRangeValidator, Init(0,10));
{Связываем контролер с редактором}
InpLineA.SetValidator(Validator);
end;
Можно объединить оба действия в одном операторе:
InpLineA.SetValidator(New(PRangeValidator,Init(0,10))
При работе совместно с объектом TInputLine контролер может активно воздействовать на ввод пользователя. Это относится к объектам TFilterValidator И TPXPictureValidator: ОНИ контролируют ВВОД каждого символа и игнорируют нажатие пользователем клавиши, если очередной символ не соответствует требуемому. Объект TPXPictureValidator, кроме того, способен автоматически дополнять ввод пользователя неспециальными символами шаблона. Если, например, шаблон задан следующим образом:
InpLineA.SetValidator(New(PPXPictureValidator,Init('#/#/#',True)))
то нажатие пользователем любых клавиш, кроме цифровых, будет игнорироваться, а сразу после нажатия первой (второй) цифровой клавиши в строке ввода появится символ /. Вставкой неспециальных символов шаблона управляет второй параметр обращения к методу TPXPictureValidator.Init (True — разрешить вставку, False — запретить).
Проверка других объектов
Хотя чаще всего для реализации текстового ввода вы будете использовать объект TinputLine или его специализированного потомка, не исключена возможность проверки данных, подготовленных другими видимыми объектами. Для этого в поле options любого видимого элемента предусмотрен бит ofValidate, единичное состояние которого приведет к тому, что при каждой потере видимым элементом фокуса ввода будет вызываться его метод valid, который он наследует от TView. Перекрыв этот метод, вы сможете обратиться к любому объекту-контролеру для проверки данных.
Реализация нестандартного контроля
Возможно, вам понадобится сконструировать собственный объект- контролер. В этом случае вы должны помнить, что за контроль отвечают четыре метода базового объекта TValidator: Valid, IsValidlnput, IsValid И Error, причем абстрактные методы IsValid и Error перекрываются в потомках, вызываются другими методами объекта-контролера и не предназначены для вызова извне.
Метод valid получает строку, введенную пользователем, осуществляет ее контроль и возвращает True или False в зависимости от результатов контроля (True означает правильный ввод). Метод Isvalidlnput также получает строку символов ввода, однако, в отличие от Valid, он получает ее после каждого изменения пользователем входной строки. Метод Isvalidlnput имеет два параметра обращения:
Function TValidator.IsValidlnput(var S: String;
SuppressFill: Boolean): Boolean
Параметр SuppressFill указывает, должен ли метод автоматически форматировать строку перед ее проверкой. Если этот параметр имеет значение True, метод должен дополнить подготовленный пользователем ввод s нужными в этом месте символами и только после этого производить контроль. Поскольку параметр s определен как параметр-переменная, метод может вставлять дополнительные символы непосредственно в строку ввода, но ни в коем случае не должен удалять ошибочно введенные символы из нее. Из всех стандартных контролеров только TPXPictureValidator использует SuppressFill = True.
Глава23
Практика использования
Программирование использованием библиотеки Turbo Vision весьма специфично по сравнению с обычным программированием в среде Турбо Паскаля. Эта специфика прежде всего связана с широко применяемым в Turbo Vision механизмом ООП: как правило, нельзя чисто механически перенести старую программу в новую объектно-ориентированную среду, обычно для этого требуется переосмысление проекта в целом. Другая особенность Turbo Vision — интенсивное использование динамической памяти. Если вы не имеете достаточного опыта работы с кучей, у вас могут быть проблемы, связанные с динамически размещаемыми объектами. И, наконец, программы, управляемые событиями, — это еще одна весьма специфичная особенность Turbo Vision, которая вначале может создавать определенные трудности в отладке.
В этой главе обсуждаются дополнительные средства, имеющиеся в Turbo Vision и увеличивающие ее эффективность. Кроме того, приводятся практические рекомендации по разработке всей программы в целом и программированию отдельных ее частей.
Контроль за динамической памятью
Как правило, объекты в Turbo Vision размещаются в куче. Это отвечает специфике диалоговых программ: на этапе разработки программист обычно не может учесть все возможные действия пользователя программы. Чтобы не накладывать неестественные ограничения на те или иные ее возможности, не следует злоупотреблять статическими определениями объектов, т. к. в этом случае программа не сможет гибко учитывать специфические требования пользователя.
В правильно разработанной программе контроль за доступной динамической памятью должен осуществляться перед каждым обращением к New, в противном случае нехватка памяти может вызвать аварийный останов программы и все подготовленные пользователем данные и промежуточные результаты будут безвозвратно потеряны.
В Turbo Vision имеются средства, упрощающие этот контроль: глобальная функция LowMemory будет возвращать True, если размер свободного участка кучи стал слишком мал (по умолчанию меньше 4 Кбайт). Таким образом, вместо того, чтобы контролировать кучу перед каждым обращением к New, можно обратиться к функции LowMemory перед началом размещения динамического объекта или сразу после того, как объект размещен в куче. Если LowMemory возвращает True, дальнейшая работа с кучей возможна только после ее очистки. Резервный участок кучи длиной в 4 Кбайт называется пулом надежности. Предполагается, что его размеры достаточны для размещения любого объекта Turbo Vision, поэтому обычно контроль с помощью LowMemory осуществляется сразу после процедуры динамического размещения нового видимого элемента.
В следующем примере создается простое диалоговое окно.
Uses Memory,...;{Функция LowMemory1 определена в модуле Memory}
R.Assign(20,3,60,10);
D := New(Dialog, Init (R, 'Диалоговое окно'));
with DA do
begin
. R.Assign(2,2,32,3);
Insert(New(PStaticText, Init(R, 'Сообщение-вопрос')));
R.Assign(5,5,14,7);
Insert(New(PButton, Init(R, '~Y~es (Да)', cmYes)));
RAssign(16,5,25,7);
Insert (New (PButton, Init(R, '~N~o (Нет)', cinNO) ))
end;
if LowMemory then
begin
Dispose(D,Done);
{Нет памяти: удаляем paопределение}
OutOfMemory;
(Сообщаем об этом}
Dolt := False
{Признак ошибки}
end
else
Dolt :=
Если вызов LowMemory у вас следует сразу после динамического размещения объекта, то в ходе самого размещения не должен произойти аварийный останов, связанный с нехваткой памяти. Таким образом, размер пула надежности должен быть достаточным для размещения всего объекта. Переменная LowMemSize задает размер пула надежности в параграфах (участках, длиной
по 16 байт). По умолчанию она имеет значение 4096 div 16 = 256, т. е. размер пула надежности составляет 4 Кбайт.
На практике вместо прямого обращения к LowMemory чаще используется вызов метода
TProgram.ValidView (P: Pointer): Pointer
Этот метод получает в качестве параметра обращения указатель р на динамический объект и осуществляет следующие действия:
если Р = NIL, метод возвращает NIL;
если LowMemory = True, метод освобождает память, связанную с р, вызывает метод TProgram. OutOfMetaory и возвращает NIL;
О если обращение к методу TView.Valid (cmValid) дает False (см. ниже), объект р удаляется из кучи и метод ValidView возвращает NIL;
в противном случае считается, что размещение осуществлено успешно, и метод возвращает значение указателя р.
Метод TProgram. ValidView осуществляет стандартные действия по контролю надежности использования кучи. Обычно к нему обращаются перед тем, как поместить новый видимый элемент в группу, например:
DeskTopA.Insert(ValidView(New(TMyWindow, Init(...))));
Заметим, что нехватка памяти вызывает обращение к виртуальному методу OutOfMemory, предназначенному для выдачи сообщения о ненормальной ситуации. По умолчанию этот метод ничего не делает и просто возвращает управление вызывающей программе. Вы должны перекрыть его, если хотите сообщить пользователю о возникшей проблеме.
В ряде случаев может оказаться полезной глобальная функция
Function MemAlloc (Size: Word): Pointer
которая осуществляет те же действия, что и New или GetMem, но в отличие от них не распределяет пул надежности. Функция возвращает указатель на выделенную область кучи или NIL, если в куче нет свободного блока нужного размера. Аналогичные действия осуществляет функция MemAllocSeg, отличающаяся от MemAlloc только тем, что выделяет память, выровненную на границу параграфа (на границу сегмента).
Обработка ошибок инициализации и модальных состояний
Каждый видимый элемент наследует виртуальный метод
TView.Valid (Command: Word): Boolean
С помощью этого метода решаются две задачи: если параметр обращения
Command = cmValid = 0
метод должен проверить правильность инициализации объекта и выдать True, если инициализация прошла успешно; при обращении с параметром Command о cmValid метод возвращает True только тогда, когда модальное состояние диалогового элемента можно завершить командой command. По умолчанию метод valid возвращает True. Вы должны перекрыть этот метод, если хотите автоматизировать контроль за инициализацией объекта и/или за завершением работы модального элемента.
Поскольку метод Valid автоматически вызывается из метода ValidView, в нем нет необходимости контролировать правильность использования кучи — это делает vaiidview. С другой стороны, в методе valid можно проверить другие условия правильного функционирования объекта. Например, если в объекте есть обращение к дисковому файлу, можно проверить существование этого файла.
Типичный метод valid имеет такой вид:
Function TMyView.Valid(Command: Word): Boolean;
begin
ValidTrue;
(Проверяем корректность инициализации:}
if Command = cmValid then
if not Correctlylnit then
begin
ReportErrorlnit; (Сообщяем о некорректной инициализации)
Valid := False
end
else
{Проверяем корректность завершения:)
else if Command <> EnableCommand then
begin
ReportErrorEnd(Сообщяем о некорректном выходе)
Valid := False
endI
end;
В этом фрагменте предполагается, что результат проверки правильности создания элемента возвращается в логической переменной Correctlylnit, проверка корректности завершения работы модального элемента осуществляется сравнением команды завершения с ожидаемой командой EnableCommand,
а сообщения об обнаруженных отклонениях от нормы выдаются процедурами ReportErrorlnit и ReportErrorEnd. Заметим, что сообщения об ошибках инициализации, не связанных с динамическим распределением объекта в куче, реализуются в методе valid, в то время как сообщения об ошибках кучи — В методе ValidView.
Если видимый элемент — модальный, метод valid перекрывается также для того, чтобы сообщить вызывающей программе о том, будет ли корректным в данном контексте завершение модального состояния командой command или нет. Таким способом можно, например, перехватить выход из окна редактора в случае, если в нем остался несохраненный в файле текст. Разумеется, в подобной ситуации программа может не только выдать сообщение пользователю, но и предпринять необходимые действия ДЛЯ корректного завершения работы модального элемента. Метод valid автоматически вызывается методом Execute модального элемента перед завершением работы.
Перекрытие методов valid особенно полезно на этапе создания сложных программ: автоматический вызов проверки состояния используемого видимого элемента и выдача исчерпывающей диагностики могут дать неоценимую помощь программисту. Если вы перекрываете методы valid, то можете программировать, не заботясь о многочисленных проверках — методы сделают это за вас.
Отладка программ
Если вы пытались отлаживать какую-либо программу в Turbo Vision, то наверняка убедились, что трассировка (пошаговое прослеживание логики работы) таких программ весьма не эффективна. Вызвано это двумя обстоятельствами. Во-первых, значительная часть библиотеки Turbo Vision скрыта от вас: библиотека поставляется в TPU-файлах, прослеживание работы которых невозможно. Во-вторых, в Turbo Vision используется принцип отделения логики создания видимых элементов от логики обработки связанных с ними событий: как только видимый элемент активизируется вызовом Execute, начинает работать его метод HandleEvent, который может породить целую цепочку непрослеживаемых трассировкой действий программы.
Ключом к решению проблемы отладки программ в Turbo Vision является расстановка точек контроля в наследуемых методах HandleEvent. Если программа не хочет открывать диалоговое окно или не реагирует на нажимаемую кнопку, следует, прежде всего, убедиться в том, что ваши действия действительно порождают нужное событие.
Может случиться, что установленная контрольная точка не будет реагировать вообще или, наоборот, будет активизироваться слишком часто. Если точка не активизируется, это означает, что ваш обработчик событий просто "не видит" событие. В этом случае необходимо убедиться в том, что поле
EventMask видимого объекта содержит маску, позволяющую ему реагировать на событие нужного вида. Другой причиной "исчезновения" события может быть его перехват (и обработка) другим видимым элементом. Это бывает вызвано различными обстоятельствами. Например, вы могли ошибочно связать две разные команды с одной константой или применить команду, которую использует также другой видимый элемент. Кроме того, обычно в наследуемых методах HandleEvent вызывается обработчик событий объекта- родителя, который может "украсть" событие у вашего обработчика. В таких ситуациях бывает достаточно сделать вызов родительского метода после того, как событие будет обработано вами.
Если контрольная точка активизируется слишком часто, значит, вы установили ее неправильно. Например, если вы установили эту точку внутри метода TGroup. Execute, точка будет непрерывно активизироваться, т. к. значительная часть времени работы программы тратится на ожидание события. Если вам все-таки требуется установить контрольную точку именно в этом месте, сделайте ее условной, чтобы она не реагировала на пустые или ненужные события.
Иногда запущенная программа "зависает", т. е. перестает реагировать на любые действия пользователя. Такие ошибки отлаживать труднее всего. Если программа "зависла", попытайтесь, прежде всего, локализовать то место, в котором это происходит. Это обычно осуществляется расстановкой контрольных точек В подозрительных местах программы. Следует помнить, что в Turbo Vision "зависания" связаны в основном с тремя видами ошибок:
освобождается динамический объект, который входил в состав ранее освобожденной динамической группы;
читаются данные из потока в ошибочно зарегистрированный объект (объект имеет неуникальный регистрационный номер);
элемент коллекции ошибочно трактуется как элемент другого типа.
Ошибки первого вида встречаются наиболее часто. Например, прогон следующего невинного на первый взгляд варианта программы приводит к зависанию:
Uses Objects,Views;
var
Gl, G2: PGroup;
TRect;
begin
R. Assign (10,5, 70,20) ;
Gl := New(PGroup, Init(R));
R.Grow(-10,-3);
G2 :=New(PGroup, Init(R));
G1A.Insert(G2);
Dispose (Gl, Done);
Dispose(G2, Done) {Здесь программа "зависнет"!} end.
Заметим, что перестановка операторов Dispose местами приводит к корректному варианту, т. к. метод Gl . Done умеет контролировать освобождение своего элемента G2 и не освобождает его вторично. Во всех случаях оператор Dispose (G2, Done) излишен: освобождение группы вызывает автоматическое освобождение всех ее элементов.
Поскольку динамическая память используется в Turbo Vision очень интенсивно, полезно предусмотреть в отладочном варианте программы визуализацию ее размера. Это можно выполнить с помощью объекта THeapview:
Unit HeapView;
Interface
Uses Dialogs,Objects;
type
PHeapView =ATHeapView;
THeapView =
Constructor Init(var R: TRect);
Procedure Update;
end;
Implementation
Constructor THeapView.Init;
var
String;
begin
Str(MemAvail,S);
Inherited Init(R,#3+S)
end;
Procedure THeapView.Update;
var
String;
begin
Str(MemAvail,S);'
DisposeStr(Text);
Text := NewStr(#3+S);
Draw
end;
end.
Например, в следующей программе (пример 23.1) показан способ включения контрольного окна, создаваемого в этом объекте, в верхний правый угол экрана.
Пример 23.1. Вывод контрольного окна в верхнем правом углу экрана
Uses Objects, Views, Арр, HeapView;
var
H: PHeapView;
(Окно для MemAvail}
W: PWindow;
G: PGroup;
R: TRect;
P: TApplication;
begin
P.Init;
(Стандартная программа)
R.Assign(70,0,80,1) ;
(Верхний правый угол)I
New(H, Init(R));
(Создаем окно контроля)
P. Insert ,(H);
(Помещаем его на экран)
ReadLn;
СПауза — показываем начальный размер кучи)
R. Assign(10,5,70,20);
W := New (PWindow, Init (R, * ^0)); {Создаем окно) R.Assign(5,3,55,12) ;
G := New(PGroup, Init(R));
WA.Insert(G);
(Вставляем в окно группу)
DeskTopA. Insert (W) ;- HA.Update;
{Выводим на экран)
(Обновляем окно контроля)
ReadLn;
(Пауза — размер кучи перед освобождением)
Dispose(W, Done);
(Освобождаем окно и труппу)
HA.Update;
(Обновляем окно контроля)
ReadLn;
P.Done
{Пауза — размер после освобождения)
end.
Для получения текущего значения общего размера кучи используется вызов метода THeapView. Update в нужных местах программы. Вы можете автоматизировать обновление окна контроля, если включите вызов Update в перекрываемый метод TProgram.Idle. В следующем варианте (пример 23.2) показан способ отображения MemAvail в фоновом режиме. Кроме того, в программе иллюстрируются возможности функции MessageBox.
■ г-,утжч-г'к-:
: Пример 23.2. Работа с MamAvail
{$Х+) (Используется расширенный синтаксис вызова функции MessageBox} Uses Obj ects,Views,Арр,HeapView,MsgBox;
type
MyApp = object (TApplication) Procedure Idle; Virtual;
end;
var
H: PHeapView;
Procedure MyApp. Idle;
begin
HA.Update
end;
var
W: PWindow;
PGroup;
TRect;
MyApp;
begin
P.Init;
R.Assign(70,0,80,1);
New(H,Init(R));
P.Insert(H);
MessageBox(#3'Размер кучи до размещения',NIL,0);
R.Assign(10,5,70,20);*
W :=New(PWindow, Init(R,'',0));
R.Assign(5,3,55,12);
G :=New(PGroup, Init(R));
WA.Insert(G);
DeskTopA.Insert(W);
MessageBox(#3'Размер кучи после размещения', NIL,0);
Dispose(W, Done);
MessageBox(#3'Размер кучи после освобождения', NIL,0);
P.Done/
end.
Константа #3 вставляется в начало строки сообщения в том случае, когда требуется центрировать эту строку (расположить ее симметрично относительно границ окна
Использование оверлея
Модули Turbo Vision разработаны с учетом возможного применения их в оверлейных программах. Все они могут быть оверлейными за исключением модуля Drivers, который содержит обработки прерываний и другой системный интерфейс низкого уровня.
При разработке оверлейных программ старайтесь спроектировать логические последовательности вызовов тех или иных модулей так, чтобы по возможности уменьшить свопинг (динамический обмен оверлеев). Поскольку программы Turbo Vision рассчитаны на интенсивный диалог с пользователем, размещайте (если это возможно) все процедуры, связанные с некоторой точкой диалога, в том же модуле, в котором создается и исполняется соответствующий модальный элемент. Например, используемые в программе диалоговые окна, как правило, порождаются от TDialog, а диалоговые элементы этих окон — от и Если вы поместите все три порожденных типа в модуль, ваша программа будет исполняться быстрее, т. к. взаимосвязанные вызовы объектов не будут приводить к свопингу оверлеев.
Заметим, что размеры всех основных оверлейных модулей — Арр, objects, views, Menus приблизительно одинаковы и составляют около 50 Кбайт. С учетом ваших собственных объектов, обеспечивающих интенсивное взаимодействие с пользователем и порожденных от или TDialog, типичный размер оверлейного буфера составит не менее 64 Кбайт. Если вы хотите минимизировать потери времени на свопинг и то же время создать достаточно компактную программу, вам придется поэкспериментировать с размером оверлейного буфера и/или испытательной зоны. Вы можете также возложить на пользователя вашей программы ответственность за выбор размера оверлейного буфера, предусмотрев соответствующий параметр в строке вызова программы.
В следующем примере (пример 23.3) показан возможный способ инициализации оверлея.
23.3. Инициализация ОЕ23.3. Инициализация ОЕ
23.3. Инициализация ОЕ
23.3. Инициализация ОЕ
{$F+,0+,S-}
($M 8192,65536,655360}•
Uses Overlay, Drivers, Memory, Objects, Views, Menus, StdDlg, App;
{Объявляем оверлейные модули:}
($0 Арр }
{$0 Dialogs }
(SOHistList }
{$0Memory j
{$0 Menus}
{SO Objects}
($0 StdDlg}
($O Views}
const
OvrBufDisk = 96 * 1024; {Размер буфера без EMS-памяти}
OvrBufEMS= 72 * 1024; {Размер буфера при использовании EMS-памяти}
type
ТМуАрр = object (TApplication)
Constructor Init;
Destructor Done; Virtual;
end; (ТМуАрр)
Procedure InitOverlays;
var
FileName: String [79];
begin
FileName := ParamStr(0);■
Ovrlnit(FileName);
if OvrResult <> 0 then
begin
PrintStr('Невозможно открыть оверлейный файл *, FileName);
Halt;
end;
OvrlnitEMS;
if OvrResult = 0 then
OvrSetBuf(OvrBufEMS)
else
begin
OvrSetBuf(OvrBufDisk);
OvrSetRetry(OvrBufDisk div 2); end
end;(InitOverlays)
Constructor TMyApp.Init;
begin.
InitOverlays;
TApplication.Init;
end; (TMyApp.Init) Destructor TMyApp.Done;
begin
TApplication.Done;
end; {TMyApp.Done}
var
MyApp: TMyApp;
begin
MyApp.Init;
MyApp.Run;
MyApp. Done ; end.
В этой программе реализован механизм размещения оверлеев в исполняемом ЕХЕ-файле. Для этого после компиляции программы выполните команду ДОС
copy/b MyProg.exe+MyProg.ovr MyProg.exe
Чтобы определить маршрут поиска ЕХЕ-файла, в процедуре InitOverlays проверяется параметр вызова с номером 0. Заметим, что в этот параметр автоматически помещается маршрут доступа к загруженной программе только в том случае, когда ваш ПК работает под управлением MS-DOS версии 3.0 и выше.
Внимание
Размер оверлейного буфера можно сделать меньшим, если программа обнаружит доступную EMS-память, ведь в этом случае потери времени на свопинг будут минимальными. Разумеется, инициализация оверлея осуществляется до обращения кTApplication. Init, т. к. модуль Арр, в котором находится этот метод, сделан в программе оверлейным.
Порядок вызова наследуемого метода
Большая часть объектов Turbo Vision спроектирована в расчете на их дальнейшее перекрытие в прикладных программах. Типичным примером такого рода объектов является Tview, метод Draw которого создает на экране пустой прямоугольник и, следовательно, не может отображать никакой полезной информации. Поскольку все видимые элементы порождены от Tview, вам необходимо перекрыть метод Draw в собственном объекте-потомке. Более того, поскольку TView.Draw не делает никакой полезной работы, его не нужно вызывать в перекрытом методе. Однако полностью перекрываемые методы, подобные TView.Draw, скорее исключение из общего правила. Обычно в перекрытом методе вызывается соответствующий метод, наследуемый от родителя, т. к. в нем реализуются некоторые необходимые для потомка действия. В такого рода ситуациях важна последовательность вызова наследуемого метода: вызывать ли его до реализации специфичных действий или после? Ниже приводятся практические рекомендации на этот счет.
Конструктор
Вызывайте наследуемый метод до реализации дополнительных действий:
Procedure MyObject. Init () ;
begin
{Вызов наследуемого конструктора Init} , {Реализация дополнительных действий}
end;
Такая последовательность необходима по той простой причине, что вызов наследуемого конструктора приводит к обнулению всех дополнительных полей объекта MyObject. Если, например, у вас имеется следующий фрагмент программы:
type
MyObject = object (TWindow)
Value: Word;
Ok : Boolean;
Constructor Init(var Bounds: TRect; ATitle: TTitleStr;
AValue: Word; AOk: Boolean);
end;
Constructor MyObject.Init;
begin
Inherited Init(Bounds, ATitle, wnNoNumber);
Value :=
Ok
end;
то дополнительные поля Value и ok получат нужные значения 16 и True. Однако, если обращение
(Bounds, ATitle,
поставить после оператора ok := True, в них будут помещены значения о и False. Из этого правила существует одно исключение, связанное с загрузкой коллекции из потока конструктором Load. Дело в том, что в наследуемом методереализуется следующий цикл:
Constructor(var S:
begin
for I := 0 to Count - 1 do
AtPut(I, Getltem(S));
end;
Если элементами коллекции являются произвольные наборы двоичных данных (нето вам потребуется перед чтением очередного элемента
сначала получить из потока его длину. Следующий пример (пример 23.4) иллюстрирует сказанное.
type
PDataCollection =
TDataCollection = object (TStringCollection)
Word;
ConstructorS:
FunctionS:Pointer; Virtual;
end;
ConstructorS:
begin
S.Read(ItemSize, SizeOf(ItemSize));
Inherited
end;
FunctionS: TStream) : Pointer;
var
Item: Pointer;
begin
GetMem(Item, ItemSize);
S.Read (ItemA, ItemSize);
Getltem := Item;
end;
В этом примере конструктор Load сначала загружает из потока поле ItemSize, содержащее длину читаемого элемента. Затем вызывается конструктор TCollection.Load, В котором осуществляется ВЫЗОВ Getltem. Новый Getltem использует поле ItemSize, чтобы определить размер читаемых данных, и резервирует нужный буфер в динамической памяти. Разумеется, запись полиморфных коллекций в поток должна происходить в том же порядке, т. е. сначала записывается длина очередного элемента, а уже потом — его данные.Ч '
Деструктор
Вызывайте наследуемый метод после реализации дополнительных действий:
Procedure MyObject.Done;
begin
{Реализация дополнительных действий}
{Вызов наследуемого деструктора Done) end;
Работа деструктора проходит в обратном порядке по отношению к конструктору. Вначале вы должны освободить всю дополнительно распределенную динамическую память, а уже затем вызвать наследуемый деструктор, чтобы уничтожить весь объект.
Другие методы
Порядок вызова наследуемого метода зависит от конкретного алгоритма. В большинстве случаев наследуемый метод вызывается первым, но могут использоваться и другие последовательности. Особое значение имеет вызов наследуемого обработчика событий HandleEvent. В самом общем виде структура нового обработчика будет такой:
Procedure MyOb ject.HandleEvent(var Event: TEvent);
begin
{Изменение наследуемых свойств)
{Вызов наследуемого обработчика/
{Добавление новых свойств}
end;
Таким образом, вначале вы должны запрограммировать те действия, которые изменяют стандартное поведение перекрытого обработчика, затем вызвать его и, наконец, осуществить новую обработку событий. Разумеется, любая из этих трех частей может отсутствовать. Например, стандартный обработчик TDialog.HandleEvent лишь расширяет свойства наследуемого метода TWindow.HandleEvent, добавляя в него обработку событий от клавиатуры и событий-команд:
Procedure TDialog.HandleEvent(var Event: TEvent);
begin
Inherited HandleEvent(Event);
case Event.What of
evKeyDown:
evCommand:
end;
end;
Этот обработчик перехватывает все события от клавиатуры и мыши, в том числе и нажатие на клавишу <Tab>. Если вы хотите обработать событие от клавиши <Tab> особым способом, то должны перехватить это событие до вызова стандартного обработчика. Например:
Procedure TNoTabsDialog.HandleEvent(var Event: TEvent);
begin
if (Event.What = evKeyDown) then
if=or
(Event.KeyCode =then
ClearEvent(Eventу;
Inherited HandleEvent(Event);
end;
Примеры программных реализаций
В этом разделе приводятся примеры программных реализаций некоторых типичных задач, решаемых с помощью Turbo Vision. Эти примеры не имеют сколько-нибудь серьезного прикладного назначения, они просто иллюстри-
руют возможные приемы программирования. Поскольку большинство видимых объектов используется в тесной взаимосвязи, примеры иллюстрируют программирование сразу нескольких объектов.
Строка статуса
В следующей программе (пример 23.5) создается строка статуса, содержание которой зависит от установленного контекста подсказки (определяется значением поляВ зависимости от действий пользователя
эта строка будет содержать текст Esc Выход F1 Сменить контекст на 1 Однажды в студеную, зимнюю пору либо
ESC Выход F2 Сменить контекст на О Я из лесуБыл сильный
Переключение строки осуществляется клавишами <F1> и <F2>, для выхода из программы служит клавиша <Esc>. ; ";■.■■
: Пример 23.5. Создание строки стату Uses type PMyStatusLine = ATMyStatusLine;
= object (TStatusLine) FunctionString;
end; MyAppobject
StatLine: PMyStatusLine; Constructor Init; ProcedureVirtual;
ProcedureVirtual;
end; • const = = {-) Constructor begin
Inherited Init;
Insert(StatLine) {Использовать нестандартную строку статуса) end{MyАрр.Init}; f1
Procedure MyApp.InitStatusLine;
(Инициализация нестандартного поля MyApp.StatLine) var
R: Trect;
begin
GetExtent(R);
R.A.Y :=
StatLine := New(PMyStatusLine, Init(R,
NewStatusDef(0,0,{Первыйвариант строки)
NewStatusKey(’~Esc~ Выход',kbEsc,cmQuit,
NewStatusKey('~F1~ Сменить контекст на 1', kbFl,cmCntxl, NIL)),
NewStatusDef(1,1,{Второйвариант строки)
NewStatusKey('~Esc~ Выход',kbEsc,cmQuit,
NewStatusKey('~F2~ Сменить контекст на 0', kbF2,cmCntx2, NIL)), NIL) ))) end{MyApp.InitStatusLine};
Procedure MyApp.HandleEvent;
{Переключение контекста и обновление строки статуса} begin
Inherited HandleEvent(Event);
case Event.Command of
cmCntxl: HelpCtx 1;
cmCntx2: HelpCtx := 0;
else
ClearEvent(Event);
end;
if Event.What <> evNothing then
begin
StatLineA. Update
ClearEvent(Event)
end
end{MyApp.Handl eEvent};
Function TMyStatusLine.Hint(Cntx: Word): String;
{Переключение поля подсказки}
const
Prompt: array [0. .1] of String = (
1Однажды в студеную, зимнюю пору1,
'Яиз лесу вышел. Был сильный мороз. . . 1 ); begin
Hint:
end{TMyStatusLine.Hint};
(/
var
P: MyApp;
begin
P.Init;
P. Run;
P.Done
end.
Меню
Стандартная программа содержит поле MenuBar типа TMenuBar. По умолчанию метод TApplication.InitMenuBar устанавливает это поле в NIL, что означает отказ от меню. Если вы хотите использовать меню в вашей программе, необходимо перекрыть этот метод. В следующем примере (пример 23.6) создается двухуровневое меню, показанное на рис. 23.1.
Рис. 23.1. Двухуровневое меню
Опцию Подменю можно выбрать следующими способами:
нажатием клавиш <F10>, <смещение указателя>, <Enter>;
О нажатием клавиш <Alt>+<S>;
отметкой мышью.
t
Опции Первый выбор и Второй выбор можно выбрать клавишами <F1> и <F2> без развертывания подменю. После развертывания подменю можно использовать те же клавиши, а также клавиши с цифрами 1 и 2, отметку мышью или смещение указателя к нужной опции и Опция Третий выбор доступна только после развертывания подменю. Выбор каждой из этих трех опций приводит к появлению на экране окна с сообщением. Кроме того, опция Третий выбор попеременно запрещает или разрешает действие команд И cmQuit.
{$Х+}
Uses
type
= object (TApplication)
ProcedureVirtual;
ProcedureEvent:
end;
const
cml = 201;
= 202;
=
/;
Procedure
var
R: TRect;
begin
GetExtent(R);.
R.B.Y :=
:= New(PMenuBar,
(Главная полоса меню)
элемент главного
'~5'Подменю.. . 1 ,hcNoContext,
всплывающее подменю)
Первый
Второй
разделяющую
Третий
NIL))))),
Newltem({Второй элемент главного меню)
'~ESC~ Выход','~ESC~',kbEsc,cmQuit,О,
NIL))))) ;
end{MyApp InitMenuBar};
()
Procedure MyApp . HandleEvent; .
const
Flag: Boolean = True;
cms =;
begin
Inherited HandleEvent (Event);
case Event . Command of
cml: MessageBox ( #3 * Первыйвыбор ' , NIL, 0 ) ;
cm2: MessageBox (#3'Второй выбор', NIL,0); cm3:
begin
MessageBox (#3'Третий выбор', NIL, 0); if Flag then
DisableCoinmands (cms)
else
EnableCommands(cms); Flag := not Flag end end end {MyApp. HandleEvent}; { )
var
P: MyApp;
begin
P.lnit;
P.Run; •
end.
Диалоговое окно
На рис. 23.2 показан вид диалогового окна, которое создается и используется в рассматриваемом ниже примере (пример 23.7).
Рис. 23.2. Диалоговое окно со строкой ввода и кнопками
Если диалог завершен командой Установить, на экране разворачивается окно, в котором сообщаются полученные из диалогового окна параметры — текст из строки ввода и настройка кнопок. Если диалог завершен командой Не изменять, окно сообщений содержит строку
Команда ' Не изменять 1
если диалог завершен нажатием клавиши <Esc>, на экран не выводится ни- какихсообщений. ,.
Пример иллюстрирует использование строки ввода, зависимых и независимых кнопок и нестандартных команд.
Для задания начальных параметров и чтения параметров, установленных в результате диалога, служит поле TDialog.Data. Это поле содержит данные, используемые в ходе диалога, в виде записи, поля и типы которой строго соответствуют порядку и типу вставляемых в окно терминальных элементов. В нашем примере (см. текст программы) первым в окно вставляется статический текст, однако этот терминальный элемент не имеет параметров, которые можно было бы изменить в ходе диалога, поэтому в записи Data ему не отводится место. Второй по счету в окно вставляется строка ввода TInputLine. Этот объект характеризуется длиной L строки, указываемой вторым параметром обращения к TinputLine. Init, поэтому для него в Data выделяется поле, длиной L+l байт. Каждому кластеру с кнопками выделяется поле WORD, что дает возможность задать в кластере до 16 независимых кнопок и до 65 536 зависимых: каждая независимая кнопка связана с соответствующим разрядом 16-битного поля (первая кнопка — с младшим разрядом),
а каждой зависимой кнопке соответствует свое число (первой кнопке соответствует число 0, второй — 1 и т. Д-). Установка данных в поле TDialog.Data осуществляется Методом TDialog.SetData, ПОЛУЧИТЬ данные после диалога можно с помощью метода TDialog.GetData.
Г'п ^---^.^Мй^^
Пример 23.7. Создание диалогового окна $ S •
milllllllllieilllWW^RvlIIIWWIIIIIIIIIIIIIIWIIIIIWIIIIIIllllllllllll^lllRIW^^WWIWllWWIII^^WIillWIIIIIIIIWIIIIIIWIWeillllllllllR^^^W^IIIIIIIIIIII^IIItVr
($X+)
Uses Objects,App,Drivers,Dialogs,Menus,Views,MsgBox;
type
MyApp = object (TApplication)
Procedure InitStatusLine; Virtual;
Procedure HandleEvent(var Event: Tevent); Virtual;
Procedure GetDialog;
end;
PMyDialog = ATMyDialog;
TMyDialog = object (TDialog)
Procedure HandleEvent(var Event: Tevent); Virtual;
end;
const
cmO = 200;
cml = 201;
cm2 = 202;
/)
Procedure MyApp.InitStatusLine;
{Создает строку статуса)
var
R: TRect;
begin
GetExtent(R) ;
R.A.Y :=
StatusLine : = New(PStatusLine, Init(R,
NewStatusDef(0,$FFFF,
NewStatusKey('~Alt-X~ Выход',kbAltX,cmQuit,
NewStatusKey('~F1~ Вызов окна',kbFl,cmO, NIL)),
NIL)))
end{MyApp.initStatusLine};
Procedure MyApp.HandleEvent;
{Обрабатывает нестандартную команду cmO}
begin
Inherited HandleEvent(Event);
case Event.Command of
cmO: GetDialog
else
ClearEvent(Event)
end
end {My/фр.HandleEvent);
Procedure MyApp.GetDialog;
/Создает и использует диалоговое окно)
var
R: TRect;
{Координаты элементов)
D: PMyDialog;
I: PInputLine;
RB: PRadioButtons;
{Диалоговое окно) {Строка ввода } {Зависимые кнопки)
CB: PCheckBoxes;
{Независимые кнопки}
s : String;
{Для вывода сообщения)
const)
L 120;
{Длина строки ввода)
type
TDialogData = record
{Параметрыдиалогового окна)
I_Data: String [В];
CB_Data: Word;
{Текст в строке ввода) (Независимые кнопки)
RB_Data: Word
(Зависимые кнопки)
end;
const
st: array [0..2] of String = ('Первое','Второе','Третье');
Data: TDialogData = (
(Начальные параметры диалога)
I_Data : ' Начальный текст';
CB_Data: 3;
(1-я и 2-я кнопки)
RB_Data: 2) ;
(3-е продолжение)
begin
R. As s ign (5,3, '5,18) ;
(Координатыдиалогового окна)
D := New(PMyDialog,Init(R,'Пример диалогового окна')); with DA do
begin
R. Assign(1,1,69,3) ;
Insert (New (PStaticText, (Вставляем статический текст)
Init(R,#3'3to статический текст')));
R.Assign(20,3,60,4);
I := New(PInputLine, Init(R, L)) ;
Insert(I);{Вставляем строку ввода}
R.Assign(1,3,20,4) ; Insert(New(PLabel,{Вставляемметку строки ввода}
Init(R,'~1~ Строка ввода:*, I))); R. Assign (60, 3,62,4) ;• Insert(New(PHistory, {Вставляем список ввода} Init(R,1,0))); R.Assign(10,6,30,9); CB := New(PCheckBoxes, Init(R, NewSItem('Первая кнопка’, NewSItem ('Вторая кнопка', NewSItem('Третья кнопка', NIL))))); Insert(СВ);{Вставляемнезависимые кнопки}
R.Assign(6,5,30,6); Insert(New(PLabel,{Вставляемметку кнопок}
Init(R,'~2~ Независимые кнопки',СВ))); R.Assign(40,6,63,9); RB := New(PRadioButtons, Init(R, NewSItem('Первое продолжение', NewSItem('Второе продолжение', NewSItem('Третье продолжение', NIL)))) ); Insert(RB);(Вставляем зависимые кнопки}
R.Assign(36,5,63,6) ; Insert(New(PLabel,(Вставляемметку кнопок}
Init(R,'~3~ Зависимые кнопки',RB))); R.Assign(14,11,32,13); Insert(New(PButton, {Вставляемкнопку "Установить") Init (R, '~4~Установить ', cml,bfNormal) )) ; R.Assign(40,ll,58,13); Insert(New(PButton,{Вставляемкнопку "Не изменять"}
Init(R,'~5~ He изменять',cm2,bfNormal))) ; SetData(Data){Устанавливаемначальные значения}
end;
{Помещаем окно на экран и получаем команду завершения) case DeskTopA.ExecView(D) of cml:
begin .{Была команда "Установить":}
DA.GetData(Data) ;{Получаемновые значения)
with Data do
begin{Готовим сообщение}
s := #31 Параметры диалогового окна:'+ #13'Текст : '+I_Data+#13'Кнопки: ';
if CB—Data and 1 о 0 then — s := s+' Первая';
if CB_Data and 2 <> 0 then s : = з+ ' Вторая ' ;
if CB_Data and 4 <> 0 then s :=s+* Третья* ;
s :=s+#13'Продолжение: '+st[RB Data] end
end;
cm2: s := #3'Команда "Не изменять"'; else s :=
end; ' (case) if s <>then
MessageBox (s , NIL, 0) end{MyApp.GetDialog) ;
/;
Procedure TMyDialog.HandleEvent;
{Обрабатывает нестандартные команды cml и cm2) begin Inherited HandleEvent (Event); if Event.What =then
case Event . Command of cml: EndModal (cml) ; {Завершить с командой cml) cm2: EndModal (cm2){Завершить с командой cm2)
end;
ClearEvent (Event){Очистить другие события)
end{TMyDialog.HandleEvent};
{)
var
P: MyApp;
begin
P.Init;
P.Run;
P.Done
end.
Для использования нестандартных команд сто, cml и cm2 перекрываются обработчики событий строки статуса и диалогового окна. Чтобы завершить диалог с выдачей нестандартной команды, в обработчике событий окна вызывается метод EndModal, в результате чего метод ExecView возвращает значение соответствующей команды. Заметим, что стандартная для Turbo Vision команда cmCancel (закрыть окно по клавише <Esc>) обрабатывается стандартным методомTDialog.HandleEvent.
Окно с текстом
В следующей программе (пример 23.8) на экране создается несколько окон, содержащих один и тот же текст — текст программы (см. рис.23.3).
Каждое новое окно открывается с помощью клавиши <Ins>. Активное окно
можно удалить клавишей <Del> или распахнуть на весь экран клавишей
<F5>. С помощью мыши вы можете перемещать активное окно по экрану и/или изменять его размеры.
ЙММЙМ : ПримерЙММЙМ : Примерт
ЙММЙМ : Пример
ЙММЙМ : Пример
23.8. Создание текстовых окон
Uses Objects,Арр,Views,Drivers,Menus;
const
crnNewWin = 200;
cmDelWin = 201;
MaxLine = 22;(Количество текстовых строк}
var
Lines: array [0..MaxLine] of String [80];
••
MyApp = object (TApplication)
WinNo : Word;
Constructor Init;
Procedure InitStatusLine; Virtual;
Procedure HandleEvent(var Event: Tevent); Virtual;
Procedure NewWindow;
end;
PInterior = ATInterior;
TInterior = object (TView)
Constructor Init(R: TRect);
Procedure Draw; Virtual;
end;
{)
Constructor MyApp.Init;'
(Открывает и читает файл с текстом 11ротраммы}
var
f: text;
s: String;
k: Integer;
begin
Inherited Init;
WinNo0;(Готовим номер окна}
for К := 0 to MaxLine do
Lines [k] :=массив строк)
s := copy (ParamStr (0), l,pos ('. ’, ParamStr, (0))) +' PAS' ;
($1-1
Assign(f,s);
Reset(f);
ifО О
exit;нельзя
for k := 0 todo
if not EOF(f)
Close (f)
($1+1 end (MyApp. Init};
Procedure var
R:
begin
GetExtent(R); :=
StatusLine :=
NewStatusDef (0,$FFFF,
NewStatusKey('~Alt-X~Выход',kbAltX,cmQuit,
Открыть
Удалить
NewStatusKey ( '~Г5~Распахнуть' , kbF5,cmZoom, NIL) ) ) ) ,
NIL))) end /;
Procedure
нестандартных команд cmNewWin,
begin
Inherited caseof
cniNewWin: begin
ClearEvent(Event);
NewWindow;
end;
cmClose;
end;
ClearEvent(Event) end 1
Procedure MyApp.NewWindow;
{Открывает новое окно)
var
R: TRect;
W: PWindow;
begin
Inc (WinNo) ;{Номер окна)
случайные размеры и положение );
R.Move(Random(80-R.B.X),Random(24-R.B.Y) ) ;
W:=New(PWindow,Init(R,' ' ,WinNo)) ;
(R)в R границы
внутренней части окна) просмотр
WA. Insert (New(PInterior, Init (R) ) ) ;
Insert (W) ;окно на
end
(}
Constructor просмотра текста во внутренней части begin
Inherited
gfGrowHiX+gfGrowHiY
end
(
Procedure
текста в
var
k: Integer;
В:
begin
for k := 0 todo
begin
MoveChar(B, ' 1,GetColor(1), Size.X);
MoveStr(B,copy(Lines[k], 1,Size.X) , GetColor(1)) ;
20 Зак 941
■WriteLine(O,k,Size.X,l,B)
end
end (TInterior.Draw);
(-1
var
P: MyApp;
begin
IIP.Init;
I
I
P. Run;
P.Done
end.
В программе объявляется тип TInterior, предназначенный для создания изображения во внутренней части окон. Его метод Init определяет способ связи объекта Tinterior со стандартным объектом TWindow: оператор
GrowMode := gfGrowHiX+gfGrowHiY
задает автоматическое изменение размеров объекта Tinterior при изменении размеров окна так, чтобы вся внутренняя часть окна была всегда заполнена текстом. Метод TInterior.Draw заполняет внутреннюю часть окон текстовыми строками, которые в ходе выполнения конструктора TMyApp.Init предварительно считываются из файла с исходным текстом программы в глобальный массив Lines. Для вывода текста сначала с помощью метода Movechar буферная переменная в типа TDrawBuffer заполняется пробелами, затем методом Movestr в нее копируется нужный текст, а с помощью WriteLine содержимое переменной в помещается в видеопамять. Такая последовательность действий стандартна для вывода текстовых сообщений в Turbo Vision. Заметим, что функция GetColor(1) возвращает номер элемента палитры, связанный с обычным текстом; для выделения тестовых СТРОК МОЖНО ИСПОЛЬЗОВаТЬ ВЫЗОВ GetColor (2) .
23.6.5. Окно со скроллером
Скроллером называется специальное окно, обеспечивающее просмотр (скроллинг) текста. Типичный скроллер — это окно редактора интегрированной среды системы Турбо Паскаля; его поведение вам, очевидно, хорошо знакомо. Средства Turbo Vision обеспечивают стандартные функции скрол- лера для окна, создаваемого в приводимой ниже программе (пример 23.9). В частности, это окно (см. рис.23.4) управляется мышью, реагирует на клавиши смещения курсора, оно может изменять размеры и свое положение на экране, его можно "распахнуть" на весь экран.
E
-[ I ]-— -™Просмотр файла
Uses Objects,Арр,Drivers,Menus,Uiews,lnt5;*
var
Lines: PCollection; {Коллекция для хранения текстовых строк}
type
TMyApp =
Procedure Run; virtual;
end;
PInterior =ATInterior;
TInterior = objectdScrol ler)
Constructor Init(R: TRect; sx.sv: PSCrollBar);
Procedure Draw; иirtual;
end;
{}
PROCEDURE TMyApp .Run;
{Читает строки из текстового Файла и обеспечивает их просмотр} маг
TRect;
U: PUindou;
string;j
Alt-X Exit
Рис. 23.4. Окно со скроллером
Uses Objects,App,Drivers,Menus,Views;
var•
Lines: PCollection;{Коллекция для хранения текстовых строк)
type
ТМуАрр = object (TApplication)
Procedure Run; Virtual;
end;
PInterior =ATInterior;
TInterior = object (TScroller)
Constructor Init(R: TRect; SX,SY: PScrollBar);
Procedure Draw; Virtual;
end;
Procedure TMyApp.Run;
(Читает строки из текстового файла и обеспечивает их просмотр) var
R: TRect;
W: PWindow;
s,name: String;
f: text;
begin
[Получаем в NAME имя файла с текстом программы:}
name:=copy (ParamStr(O), l,pos ( 1 . ' , ParamStr (0)) ) +' PAS' ;
[Создаем коллекцию текстовых строк:)
Lines := New(PCollection, Init (10,5) ) ;
assign(f, name);
1ST-)
reset(f) ;
{$!+)
if lOResult = 0 then
begin[Файл успешно открыт)
with LinesAdo while not EOF(f) do
begin
ReadLn(f,s) ;
Insert(NewStr(s))
end;
Close (f)
end
else(Файл не был открыт)
LinesA.Insert(NewStr('Нетдоступа к файлу ' +name) ) ;
{Создаем окно со скроллером: )
DeskTopA.GetExtent (R);
W := New ( PWindow, Init (R, 'Просмотрфайла '+name, 0)) ;
with WA do
begin
GetClipRect(R);
R.Grow(-1,-1) ;
Insert (Newt PInterior, Init (R,StandardScrollBar ( sbHorizontal+ sbHandleKeyboard) ,
StandardScrollBar(sbVertical+sbHandleKeybbard)) )) end;
DeskTop^.Insert(W) ;
[Ждемдействий пользователя: )
Inherited Run
end{ TMyApp. Run);
(;
Constructor TInterior.Init;
(Создает окно скроллера}
begin
Inherited Init (R,SX, SY) ;
GrowMode := gfGrowHiX+gfGrowHiY;
SetLimit(128, Lines'4 .Count-1)
end (TInterior.Init};
'
Procedure TInterior.Draw;
(Выводит на экран содержимое окна скроллера} var
Y: Integer;
TDrawBuffer;
S: String;
begin
for Y0 to pred(Size.Y) do
begin
MoveChar (B, ' 1 ,GetColor(l), Size.X);
if (Y+Delta.Y< Lines'4. Count) and
(Lines'4 .At (Y+Delta.Y) о NIL) then
begin
S :=-
MoveStr (B,copy(s, Delta.X+l,
Length (s) - Delta. X), GetColor(l)) end;
WriteLine(0,Y,Size.X,1,B)
end
end (TInterior. Draw};
(;
var
P: TMyApp;
begin
P.Init;
P. Run;
P.Done
end.
В программе перекрывается метод TApplication.Run. В потомке тмуАрр этот метод вначале считывает текстовые строки из файла с текстом программы в коллекцию Lines и создает на экране окно со скроллером. После этого вызывается стандартный метод TApplication.Run.
МетодDraw обеспечивает вывод нужных строк в окно
Для определения порядкового номера выводимых строк и их положения относительно границ скроллера служит полеDelta.
Внимание
Если в коллекцию помещается "пустая" строка, т. е. строка нулевой длины, глобальная функция NewStr возвращает значение NIL. В методеDraw
оператор
if (Y+Delta.Y< LinesA.Count) and (LinesA.At(Y+Delta.Y) <> NIL) then...
осуществляет проверку значенияиз коллекции указателя, на NIL;
если бы мы не предусмотрели эту проверку, прогон программы (использование на некоторых ПК мог бы привести к аварийному останову.
Просмотр списка файлов
Ниже приводится программа (пример 23.10), в которой показано, как можно создать и использовать диалоговое окно для выбора файлов из любого каталога. В пример включены лишь минимальные средства, с помощью которых на экране формируется окно выбора файлов и окно с сообщением о конкретном выборе (см. рис. 23.5).
e-IHe-IHIIInformationInformationFORMS PASFORMS PAS. : , LTT ! i L : ! Ti П iit : : ITT ’ П ; ———т- Текущий. : , LTT ! i L : ! Ti П iit : : ITT ’ П ; ———т- ТекущийРис. 23.5. Окно выбора файловРис. 23.5. Окно выбора файловASCIITAB.PAS ASCIITAB.TPU CALC.PAS CALC.TPU CALENDAR.PAS CALENDAR.TPU DATACOLL.PAS DEMOCMDS.PASASCIITAB.PAS ASCIITAB.TPU CALC.PAS CALC.TPU CALENDAR.PAS CALENDAR.TPU DATACOLL.PAS DEMOCMDS.PASDEMOCMDS.TPU DEMDHELP.HLP DEMOHELP.PAS DEMOHELP.TPU DEMOHELP.TXT FIELDS.PAS FILEUIEU.EXE FILEUIEU.PASDEMOCMDS.TPU DEMDHELP.HLP DEMOHELP.PAS DEMOHELP.TPU DEMOHELP.TXT FIELDS.PAS FILEUIEU.EXE FILEUIEU.PASВыбран таилВыбран таилFORHCMDS.PAS*FORHCMDS.PAS*FUIEUER.PAS FUI EWER.TPU GADGETS.PAS GADGETS.TPU GENFORM.PAS GENFORMS.BAT»FUIEUER.PAS FUI EWER.TPU GADGETS.PAS GADGETS.TPU GENFORM.PAS GENFORMS.BAT»
e-IH
e-IH
I
I
Information
Information
FORMS PAS
FORMS PAS
. : , LTT ! i L : ! Ti П iit : : ITT ’ П ; ———т- Текущий
. : , LTT ! i L : ! Ti П iit : : ITT ’ П ; ———т- Текущий
Рис. 23.5. Окно выбора файлов
Рис. 23.5. Окно выбора файлов
ASCIITAB.PAS ASCIITAB.TPU CALC.PAS CALC.TPU CALENDAR.PAS CALENDAR.TPU DATACOLL.PAS DEMOCMDS.PAS
ASCIITAB.PAS ASCIITAB.TPU CALC.PAS CALC.TPU CALENDAR.PAS CALENDAR.TPU DATACOLL.PAS DEMOCMDS.PAS
DEMOCMDS.TPU DEMDHELP.HLP DEMOHELP.PAS DEMOHELP.TPU DEMOHELP.TXT FIELDS.PAS FILEUIEU.EXE FILEUIEU.PAS
DEMOCMDS.TPU DEMDHELP.HLP DEMOHELP.PAS DEMOHELP.TPU DEMOHELP.TXT FIELDS.PAS FILEUIEU.EXE FILEUIEU.PAS
Выбран таил
Выбран таил
FORHCMDS.PAS*
FORHCMDS.PAS*
FUIEUER.PAS FUI EWER.TPU GADGETS.PAS GADGETS.TPU GENFORM.PAS GENFORMS.BAT»
FUIEUER.PAS FUI EWER.TPU GADGETS.PAS GADGETS.TPU GENFORM.PAS GENFORMS.BAT»
Для реализации просмотра списка файлов и выбора из этого списка нужного файла в Turbo Vision предусмотрен объект Этот объект создает специальное окно скроллера, содержащее одну вертикальную полосу и указатель на текущий элемент. Имена файлов помещаются в коллекцию
строк, указатель на которую передается объекту с помощью метода TListBox.NewList.
В программе предусмотрены две глобальные переменные, содержащие указатель на коллекцию L и номер выбранного элемента FOC. В объекте TApplication перекрываются методы Run и Done. Новый метод ТМуАрр.Run создает коллекцию и окно просмотра. Метод ТМуАрр. Done перед завершением работы программы формирует окно, в котором сообщается имя выбранного из списка файла. Заметим, что это имя помещается в переменную FOC в момент выхода из программы с помощью перекрываемого метода TListBox.Valid.
SbSbПример 23.10. Создание окна выбо|
Sb
Sb
($х+)
Uses DOS, Obj ects, Арр, Views , Dialogs , Drivers , MsgBox;
var
L: PStringCollection;{Коллекция имен файлов}
Foe: String;(Выбранный файл}
type
TMyApp = object (TAppiication)
Procedure Run; Virtual;
Destructor Done; Virtual;
end;
PMyListBox =ATMyListBox;
TMyListBox = object (TListBox)
Function. Valid (Command: Word) : Boolean; Virtual;
end;
f)
Procedure ТМуАрр. Run;
{Создает диалоговое окно с TListBox)
var
R,RR: TRect;
W: PDialog;
SearchRec;
PScrollBar;I
P: PListBox;
begin
{Создаем коллекцию имен файлов:)
L := New (PStringCollection, Init (50,10));
FindFirst( '\games\fl9\*. *’,Archive, S) ;
While DosError = 0 do withdo
begin
Insert (NewStr(Name)) ;
FindNext(S)(
end;
{Создаем окно:)
R. Assign (17,4,63,14) ;
:=
в окно
withdo
begin
RR.Assign(44,1,45,9) ;
В :=
Insert (B);
R. Assign (1,1, 44,9) ;
P :=
PA.NewList(L);
Insert (P)
end;
окно на
Inherited Runкоманду
end; ( TMyApp. Run}
{}
Function TMyListBox. Valid;
в имя выбранного
begin
Foe :=PString(LA.At (Focused)) A;
Valid := True
end;Valid)
/;
Destructor
имя выбранного
var
R:
begin
60,22)
файл
Done
end
{--•
var
P: TMyApp;
begin
P.Init;
P.Run;
P.Done
end.
Окноуправляется мышью и клавишами. В частности, клавишами
смещения курсора можно выбрать нужный файл, клавишами <PgUp>, листать окно со списком. Работают также клавиши
<Ctrl>+<PgUp>, <Ctrl>+<PgDn>.
В момент обращения к методувызывается функция
Valid, которая определяет номер выделенного файла (этот номер хранится в полеи переписывает имя этого файла из
коллекции в глобальную переменную FOC.
1
.м ■
ЧАСТЬ III
ПРАКТИКАПРОГРАММИРОВАНИЯ
1.
Глава 24
Текстовый ввод/вывод в графическом режиме
При разработке многих практически важных программ, таких как САПР, электронные таблицы, базы данных, бывает необходимо вывести на экран графики с поясняющим текстовым материалом. К сожалению, в текстовом режиме работы экрана практически невозможно построить более или менее сложные графики, а в графическом режиме Турбо Паскаль не обладает столь же простыми и мощными возможностями текстового вывода, которые предоставляют процедуры Write/WriteLn и Read/ReadLn для текстового режима. Точнее говоря, использование этих процедур в графическом режиме вызывает определенные трудности: выводимый на экран текст имеет строго определенное начертание (шрифт) и на цветных экранах всегда выводится серым цветом, а отсутствие привычного мигающего курсора затрудняет ввод данных.
Специальные процедуры вывода текстовых сообщений outText и OutTextXY могут создавать великолепные по качеству текстовые вставки, но разработанные фирмой Borland стандартные векторные шрифты не содержат символов кириллицы, поэтому в подавляющем большинстве случаев приходится пользоваться матричным шрифтом DefaultFont с гораздо более скромными возможностями. ,
В этой главе описываются подпрограммы модуля F_GrText, в котором в существенной мере устранены указанные недостатки. В главе анализируется структура векторных шрифтов, удовлетворяющих стандарту BGI (от Borland Graphics Interface — графический интерфейс фирмы Borland). На основе этого анализа вы можете создать и использовать свои собственные векторные шрифты. Для облегчения этой задачи на дискете, прилагаемой к книге, имеется графический редактор шрифтов (файл FONTEDIT.PAS), а в состав модуля включена процедура Outstring, позволяющая выводить сообщения любым векторным шрифтом с любыми пропорциями и под любым углом наклона к координатным осям экрана.
Остальные подпрограммы модуля с помощью графических средств обеспечивают достаточно полную имитацию текстового режима для ввода/вывода данных. Все они рассчитаны на работу с наиболее популярными графиче-
скими адаптерами CGA, EGA и VGA. Для увеличения скорости работы процедур ввода/вывода в них применяется прямой доступ к графической видеопамяти, поэтому я начну главу с обсуждения организации видеопамяти и доступа к
Организация видеопамяти в графическом режиме
Адаптер
Адаптер CGA в графическом режиме использует те же 16 Кбайт видеопамяти (и по тому же адресучто и в текстовом режиме. Это дает
возможность реализовать на экране лишь 320x200 пикселов с четырьмя возможными цветами в режиме низкого разрешения и 640x200 пикселов с двумя цветами в режиме высокого разрешения. Связано это с особенностями работы видеопамяти: врежиме низкого разрешения каждый
пиксел отображается в видеопамяти двумя битами (разрядами), а в режиме высокого разрешения — одним битом. Два бита дают возможность закодировать один из четырех возможных цветов, в то время как один бит — лишь два цвета. Так как нулевое значение бита (или пары битов для низкого разрешения) отождествляется с отсутствием светимости пиксела, т. е. с черным цветом, фактически длядоступны лишь 3 цвета в режиме
низкого и один — в режиме высокого разрешения.
Видеопамять в адаптере CGA трактуется как одна длинная строка битов, причем первый бит (пара битов) кодирует пиксел, расположенный в левом верхнем углу экрана, второй бит (пара битов) — пиксел, расположенный справа от него и т. д.; 640-й бит (или 320-я пара битов для низкого разрешения) кодирует самый правый пикселстроки развертки. Следующая
строка развертки определяется битами, расположенными начиная с середины видеопамяти, т. е. начиная с адресаЭта схема сохраняется
для всех остальных строк — четные строки (с номерами 0, 2 и т. д.) определяются содержимым первой половины видеопамяти, в то время как нечетные берутся со смещением $2000 = 8192 байта от ее начала.
Таким образом, чтобы в режиме высокого разрешения изменить светимость пиксела с координатами X, У (0<=х<=639, 0<=Y<=199), нужно в байте по адресу
Mem[$B800: ( (Ydiv2)*80 + ord(odd(Y) ) *$2000 X div
изменить бит
mod 8
(бит 7 соответствует старшему разряду байта), а для режима низкого разрешения (о<=х<=319,— пару битов
(7 - 2*(Х mod и (6 - mod
по адресу
Mem[$B800:((Y div 2)*80 + ord(odd(Y))*$2000 + X div 4)]
При непосредственном обращении к видеопамятинеобходи
мо синхронизировать доступ к памяти (чтение или запись байта) с периодом строчной и/или кадровой развертки, в противном случае на экране могут появиться интерференционные помехи в виде "снега". Синхронизация осуществляется с помощью проверки байта состояния адаптера, получаемого из его регистра статуса (портПеред чтением/записью байта следует
прерывно читать содержимое этого порта до тех пор, пока нулевой или третий биты получаемого из него байта статуса не станут равны единице: нулевой бит равен 1 в момент перехода луча развертки к началу следующей строки (в это время луч погашен в течение примерно 7 а бит приобретает значение 1 в момент перехода луча к началу кадра (луч погашен в течение 4 мс). Для ПК минимальной производительности (процессор 8088 с частотой 4,77 МГц) 7 мкс достаточны для пересылки одного, а 4 мс — около 900 байт. Во всех случаях соответствующая часть программы реализуется с помощью встроенного ассемблера, а пересылка должна идти при закрытых прерываниях, чтобы исключить влияние таймера.
Адаптеры EGA и VGA
Совершенно по-другому осуществляется доступ к видеопамяти адаптеров EGA и VGA. В графическом режиме эта память (она начинается по адресу разбивается на четыре так называемые битовые плоскости.
Запись в один байт видеопамяти приводит к возможному изменению сразу четырех байтов (по байту в каждой плоскости) инесколькими
регистрами (рис. 24.1).
Для целей поддержки текстового ввода/вывода в графическом режиме нам понадобится использовать непосредственную запись в видеопамять адаптеровпоэтому рассмотрим эту проблему более подробно. Замечу,
что программирование адаптера VGA в стандартном режиме (640x480, 16 цветов), устанавливаемом процедуройс помощью автодетекти
рования, полностью аналогично программированию адаптера EGA.
Каждая битовая плоскость связана с аппаратным устройством, формирующим определенную цветовую составляющую RGB-монитора: первая плоскость — синюю составляющую, вторая — зеленую, третья — красную (четвертая плоскость определяет интенсивность цвета). Поэтому цвет любого пиксела определяется совокупным значением битов в одном и том же разряде всех четырех битовых плоскостей:бит первого байта опреде
ляет цвет самого левого верхнего пиксела, следующий разряд первого байта — цвет второго пиксела в первой строке и т. д. Например, чтобы первый
слева-сверху пиксел светился ярким белым цветом (точнее, цветом с номером 15; фактическое значение цвета может изменяться процедуры переустановки палитры), все 4 битовые плоскости должны иметь единицу в старшем разряде первого байта, а чтобы этот пиксел не светился (светился цветом 0) — ноль; другие комбинации нулей и единиц в том же разряде обеспечивают остальные цвета пиксела.
НЕ ИЗМЕНЯЮТСЯНЕ ИЗМЕНЯЮТСЯОт ЦПОт ЦП11НЕ ИЗМЕНЯЮТСЯНЕ ИЗМЕНЯЮТСЯРеРеРЕГИСТР ЦВЕТЯРЕГИСТР ЦВЕТЯБУФЕРНЫЕРЕГИСТРЫБУФЕРНЫЕРЕГИСТРЫБИТОВЫЕ плоскостиБИТОВЫЕ плоскости**0000:00010000:0001•Н~ А000:0000•Н~ А000:0000МАСКИБИТОВМАСКИБИТОВ
НЕ ИЗМЕНЯЮТСЯ
НЕ ИЗМЕНЯЮТСЯ
От ЦП
От ЦП
1
1
НЕ ИЗМЕНЯЮТСЯ
НЕ ИЗМЕНЯЮТСЯ
Ре
Ре
РЕГИСТР ЦВЕТЯ
РЕГИСТР ЦВЕТЯ
БУФЕРНЫЕ
РЕГИСТРЫ
БУФЕРНЫЕ
РЕГИСТРЫ
БИТОВЫЕ плоскости
БИТОВЫЕ плоскости
*
*
0000:0001
0000:0001
•Н~ А000:0000
•Н~ А000:0000
МАСКИ
БИТОВ
МАСКИ
БИТОВ
Рис.Схема записи в видеопамять адаптера
Между данными, посылаемыми (получаемыми) центральным процессором (ЦП), и видеопамятью расположены 4 буферных регистра — по одному на каждую битовую плоскость. Если ЦП записывает данные в видеопамять; он фактически записывает их в буферные регистры, точно так же при чтении — получает данные из них. Допустим, что показанные на рисунке управляющие регистры (регистры маски и цвета) не влияют на обмен данными. Тогда при записи байта со значением 128 (или в двоичном представлении по адресу во все четыре буферных регистра будет помещена одна и та же цепочка битов юоооооо, в результате чего самый левый пиксел в верхней строке будет светиться белым цветом, а семь расположенных справа от пикселов будут погашены.
Чтобы изменить цвет первого пиксела, мы должны перед записью байта отключить тот или иной буферный регистр. Для этого используется четырехразрядный регистр цвета: если в одном разрядов этого регистра имеется ноль, соответствующий буферный регистр защищен от изменения, а значит,
не будет меняться и связанная с ним битовая плоскость. Чтобы, например, зажечь первый пиксел синим цветом, мы должны предварительно поместить в регистр цвета число 1, тогда лишь одна первая плоскость при записи байта изменит свое значение, в то время как три других останутся без изменения. Во многих случаях не требуется одновременно изменять все восемь пикселов, связанных с одним и тем же байтом данных. Регистр маски битов позволяет управлять содержимым нужного разряда всех четырех буферных регистров одновременно: если регистр маски имеет ноль в каком-либо разряде, соответствующий разряд буферных регистров будет защищен от изменения. Таким образом, чтобы изменить только один первый пиксел, мы должны поместить в регистр маски двоичное число 10000000 (128), в регистр цвета — код нужного цвета, а затем записать по адресу $А000:0000 любое число с единицей в старшем разряде. Замечу, что регистр маски лишь запрещает изменение нужного разряда (разрядов) буферных регистров; для того, чтобы остальные пикселы на самом деле не изменили свой цвет, нам нужно предварительно прочитать значение байта по адресу $А000:О000 — в результате буферные регистры будут заполнены правильными значениями, а если этого не сделать, они сохранят значения, оставшиеся от предыдущего обращения к видеопамяти, которое, возможно, было связано с другим адресом.
Фактически мы только что рассмотрели лишь простейшие возможности одного (нулевого) режима записи. В адаптерах EGA/VGA существуют три различных режима записи: О, 1 и 2. Нужный режим устанавливается с помощью предварительного выбора регистра режима. Для этого в порт по адресу $ЗСЕ необходимо переслать число 5 — номер регистра режима записи. Затем в порт по адресу $3CF следует послать номер режима записи (О, 1 или 2). Разумеется, к этому моменту адаптер должен находиться в графическом режиме работы. Вот как, например, можно установить режим записи 2:
Uses Graph;
var
D,R: Integer;
begin
D Detect;
InitGraph(D,R,''); {Устанавливаемграфический режим) if GraphResultogrOk then
halt;
Port($3CE) := 5;{Номер регистра)
Port($3CF) := 2; [Посылаемв него номер режима)
end.
Таким образом, для записи одного пиксела по адресу X, У (отсчет от нуля) в режиме 0 необходимо:
Определить адрес видеобайта, содержащего нужный пиксел; для координат X, 7 ЭТО будет байт ПО адресу Mem[$АООО: (Y*80+X div 8j ] .
Установить единицу в бит (7-Х mod 8) в регистре маски битов и поместить нули во все остальные разряды, т. е. запретить изменение любых пикселов, кроме нужного; для этого в порт $ЗСЕ нужно поместить номер регистра маски битов 8, а в порт $3CF — сформированную маску битов.
Установить нужный цвет в регистр цвета; для этого в порт $ЗС4 нужно послать 2 — номер регистра цвета, а затем в порт $ЗС5 — код нужного цвета.
Заполнить буферные регистры; для этого нужно прочитать видеобайт из ПамЯТИ ПО адресу Mem[$A000:(Y*80+X div 8) ].
Установить единицу в бит (7-х mod 8) в прочитанном видеобайте и послать его в видеопамять (если запрещены изменения во всех разрядках, кроме нужного, можно послать любой байт, содержащий единицу в этом разряде).
/
Следующая программа (пример 24.1) иллюстрирует описанный алгоритм. В ней выводится вертикальная белая линия в центре экрана, после чего программа ожидает нажатия на клавишу <Enter>.
Пример 24.1. Вывод графической информации i
Uses Graph; var d, г,e, x: Integer; у: Word; b,bb: Byte;
begin
d := Detect;
InitGraph(d, r, 1 *) ;(Инициализируем графику)
е : = GraphResult; if eOgrOk then WriteLn(GraphErrorMsg(e)). (Сшибка инициализации}
else begin x := succ(GetMaxX) div 2;{Середина экрана}
b := x mod 8;(Смещение в байте)
Ь : = 128 shl b;
(Маска для битов)
Port[$3CE] :—•
{Выбираем регистр режима)
Port[$3CF] : = 0;
{Режим записи 0}
Port[$3CE] := 8;
{Выбираем регистр маски}
Port[$3CF] := b;
(Маскируем биты}
Port[$3C4] := 2;
(Выбираем регистр цвета}
Port[$3C5] := White;
(Устанавливаем цвет}
for у := 0 to GetMaxY do
(Цикл по строкам)
begin
bb := Mem[$A000:Y*80+X div 8] ;{Заполняем регистры}
Mem[$A000:Y*80+X div 8] := $FF{Выводим точку)
end;
ReadLn;
CloseGraph
end
end.
До сих пор были рассмотрены лишь простейшие возможности режима записи 0. С помощью предварительных настроек трех других управляющих регистров можно получать самые разнообразные эффекты.
Регистр специальных эффектов управляет способом взаимодействия данных, передаваемых ЦП, с содержимым буферных регистров. Назначение разрядов этого регистра показано на рис. 24.2.
• Количество
циклических сдвигов
- Код операции
Не нплг| "'-едется
Рис. 24.2. Назначение разрядов регистра спецэффектов
Количество циклических сдвигов определяет, на сколько разрядов вправо будут циклически сдвинуты данные перед тем, как они поступят в буферные регистры. При циклическом сдвиге данных "выталкиваемые" справа младшие разряды переносятся в старшие (левые) разряды байта. Код операции
задает способ взаимодействия данных, передаваемых ЦП, с содержимым буферных регистров:
О 00 — данные из ЦП переносятся в видеопамять без изменения (операцией MOV);
01 — используется операция AND (логическое И);
10 — OR (логическое ИЛИ);
— XOR (исключающее ИЛИ).
Регистр специальных эффектов выбирается посылкой значения 3 в порт $ЗСЕ, после чего его значение устанавливается посылкой нужного байта в порт
Регистры фонового цвета и разрешения установки совместно определяют те битовые плоскости, в которые будут помещены единицы в случае, если программа разрешила установку пиксела в регистре маски, но не зажигает пиксел (передает в этом разряде ноль). Если, например, оба регистра содержат единицымладшем разряде, т. е. кодируют цвет 1 (синий), то при
писи байта в видеопамять во все разрешенные маской и установленные в единицу разряда байта будет помещен код цвета из регистра цвета, в то время как в разрешенные разряды с нулями — код фонового цвета 1. В результате зажигаемые пикселы будут выведены нужным цветом на синем фоне. Регистр фонового цвета выбирается посылкой 0 в порт $ЗСЕ, а регистр разрешения посылкой 1 в этот порт; в обоих случаях данные передаются в выбранный регистр посылкой байта в порт $3CF (в контроллере используются лишь младшие 4 разряда этого байта).
В режиме записи 1 реализуется специальный эффект, связанный с копированием произвольной части экрана на другое место. В этом режиме нужно просто читать байты видеопамяти по одному адресу и записывать их по другому адресу. Содержимое регистров маски битов и цвета в режиме 1 игнорируется.
В следующей программе (пример 24.2) сначала создается окно в центре экрана, которое постепенно заполняется случайными линиями. После нажатияналюбуюклавишуокносмещаетсявверхи вправо.Послеещеодного
нажатия любой клавиши программа завершает свою работу.
;'•
I Пример 24.2. Реализация смещения изображения в режиме 1
^l..1i-.‘.'Yi^'i1'.i'.’r?.S^:?..fK?r*iMk%#?.<fififfmr^TI№f?S!1flt1W^№№/lM.'/ftt«:.‘..i«iCXi№:.1^M?f?'rr,,r^1iiV)t№#iWA.71Wi*A^7«^A<iJin./.1.*Kf^7f«..A....i
Uses
var
r, Integer;
Word;
b: Byte;
Char;
begin
d := Detect;
{Инициализируемграфику){Инициализируемграфику)(Ошибка инициализации}(Ошибка инициализации}InitGraph (d, г, 1 ') ; e := GraphResult; if eOgrOk then
{Инициализируемграфику)
{Инициализируемграфику)
(Ошибка инициализации}
(Ошибка инициализации}
WriteLn(GraphErrorMsg(e))
else
begin
(Создаем окно и обводим его рамкой)
Rectangle(20*8,GetMaxY div 3,60*8,2*GetMaxY div 3);
SetViewPort (20+8+1, Ge'tMaxY div 3+1,
60+8-1,2*GetMaxY div 3-l,ClipOn) ;
{Заполняем окно случайными прямыми}
while not KeyPressed do(Ждем нажатия клавиши)
beginbeginNNSetColor(Random(16)); Line(Random(GetMaxX), Random(GetMaxY),SetColor(Random(16)); Line(Random(GetMaxX), Random(GetMaxY),(Случайный цвет)(и координаты)(Случайный цвет)(и координаты)(линии)(линии)
begin
begin
N
N
SetColor(Random(16)); Line(Random(GetMaxX), Random(GetMaxY),
SetColor(Random(16)); Line(Random(GetMaxX), Random(GetMaxY),
(Случайный цвет)
(и координаты)
(Случайный цвет)
(и координаты)
(линии)
(линии)
Random(GetMaxX),Random(GetMaxY))
end;
(Сбрасываем буфер клавиатуры и устанавливаем режим записи 1)
while KeyPressed do
с := Readkey;
Port[$3CE] := 5;(Выбираемрегистр режима)
Port[$3CF] := 1;{ Режим записи 1}
{Переносим окно вверх и вправо}
for Y := GetMaxY div 3 to 2*GetMaxY div 3 do (По строкам)
for X := 20 to 60 do(По байтам)
begin
b := Mem[$A000:Y*80+X] ;
Mem[$A000:(Y-40)*80+Х+15] := b
end;
(Ждем нажатия любой клавиши)
if ReadKey=tO then с := ReadKey;
CloseGraph
end
end.
В режиме записи 2 данные от ЦП поступают в буферные регистры через регистр цвета. Для записи необходимо сначала нужным образом сформировать регистр маски битов, разрешив изменение нужного пиксела (пикселов), -а затем по соответствующему адресу видеопамяти передать код необходимого цвета (предварительно прочитав из видеопамяти в буферные регистры). В этом режиме задействованы лишь четыре младших разряда байта данных.
Следующая программа (пример 24.3) вычерчивает горизонтальную линию в середине экрана, затем ожидает ввода любого целого числа в диапазоне О— 15. Это число трактуется как код требуемого цвета, и линия вычерчивается этим цветом еще раз. Ввод и вычерчивание продолжается до тех пор, пока не будет введено любое число больше 15.
•■•у -и-wi ™у у •■ у •■•■■,. •.••,. ..у.£уу^«уи|уш|ш||и||уу||и|иуу||^ц^^
Пример 24.3. Иллюстрация режима записи 2
Uses Graph;
var
Integer;
у,х: Word;
b: Byte;
const
Col: Byte = White;
begin
d := Detect;
)графику)
e
ifthen
WriteLn(GraphErrorMsg(е))(Ошибка инициализации)
else
begin
Y :=2;
:= 5;
:= 2;записи 2)
:= 8;
:=все
repeatвывода
for X:=0 todiv 8 doбайтам)
:=
код
until Col>15;(Повторяемдля разрешенных цветов}
CloseGraph
end
end.
Хотя в описываемом ниже модуле F_GrText и не используется чтение графического экрана, наш обзор режимов работы адаптеров EGA/VGA был бы не полным, если бы мы не рассмотрели эту возможность.
Существуют два режима чтения. В режиме 0 читается байт из выбранной битовой плоскости. Чтобы с помощью этого режима получить цвет восьми пикселов, нужно последовательно прочитать все четыре битовые плоскости, а затем поразрядно объединить прочитанные данные. Замечу, что четырехкратное чтение необходимо и для получения цвета единственного пиксела, поэтому в целях ускорения работы программы следует по возможности обращаться к группе смежных пикселов. Для использования режима чтения О нужно выбрать регистр режимов, послав 5 в порт $ЗСЕ, а затем послать код режима 0 в порт $3CF. Как вы, очевидно, заметили, числом 5 выбирается и регистр режимов записи. На самом деле этим кодом указывается регистр режимов, младшие 2 бита которого определяют режим записи, а третий разряд — режим чтения. Для выбора битовой плоскости нужно указать регистр карты, послав 4 в порт $ЗСЕ, а затем послать номер плоскости — число от о до з — в порт $3CF.
В следующей программе (пример 24.4) иллюстрируется техника использования режима чтения 0 и попутно — реализация спецэффектов в режиме записи 0. Вначале программа в режиме записи 0 выводит байт со значением 128, указав на необходимость его сдвига на 7 разрядов вправо, В результате вместо самого левого пиксела будет зажжен белым цветом восьмой по счету пиксел верхней строки. Затем устанавливается красный фоновый цвет и выводится байт со значением 1. В результате седьмой по счету пиксел (сдвиг на 7 разрядов сохраняется!) станет белым, в то время как остальные пикселы — красными. В заключение программа четырежды читает видеопамять по одному и тому же адресу, каждый раз указывая очередной код битовой плоскости, и преобразует прочитанное в коды цветов первых восьми пикселов самой верхней строки. В результате на экран будет выведено: 444444 15 4
£1^*У^У-'вЧ/ ’ */Л1Л” V.V *• * "1 *W V i’^W'jf*^** WtJ'StAMMM МО* LVAlMMf ^til#54Mj^U|M4|M^^^MWie^Wf^^WtiSX^^^tiV*^^My V * t
i Пример 24.4. Режим чтения О
яЖМК?к.‘А?т< .Л ,'Лл*«. .1. .,Tj.'»4^tW^^WlW3wWtSwi3flRRiC^7lCTAV>^.e.i^) л ^WfirAjvttTH.Vi.t; r^kwj'.iTW^^W^wwrRvHifWmKSew^iwWKS. А1.; а:
Uses Graph;
var
d,r,e: Integer;
k, j,c: Byte;
a: array [0..3] of Byte;
begin
d := Detect;
InitGraph(d,r, ' *);
e := GraphResult;
if e<>grOk then
WriteLn(GraphErrorMsg(e))
else
begin
Port[$3CE] := 5;
Port[$3CF] : = 0;
Port[$3CE] : = 8;
Port[$3CF] =
Port[$3C4] = 2;
Port[$3C5] = White;
Port[$3CE] = 3;
Port[$3CF] = Mem[$A000:0] := 128;
ReadLn;
Port[$3CE] := 0;
Port[$3CF] := Red;
Port[$ЗОЕ] := 1;
Port[$3CF] := $4;
Mem[$A000:0] := 1;
ReadLn;
for k := 0 to 3 do
begin
Port[$3CE] :=
Port[$3CF] := k;
a[k] :=
end;
CloseGraph;
{Преобразуем 4 прочитанных байта for j := 0 to 7 do
begin
с 0;
{Регистр режима)
{Режим записи 0}
(Регистр маски}
(Разрешаем все биты)
{Регистр цвета}
(Устанавливаем цвет}
(Регистр спецэффектов}
{Сместить на 7 пикселов вправо)
{Выводим пиксел со сдвигом}
{Ждем нажатия <Enter>)
{Регистр фонового цвета}
{Красный фон}
(Регистр разрешения установки}
{Разрешить красный)
(Выводим пиксел со сдвигом)
(Читаем 4 плоскости}
{Регистр карты)
(Номер плоскости)
{Читаем байт)
в коды цветов 8 пикселов}
{Цикл по пикселам)
{Начальный цвет}
for k := 0 to 3 do{Цикл по плоскостям}
с := c+(a[k] shr (7-j) and 1) shl k;
Write(с:3){Выводим код цвета}
end;
WriteLn
end
end.
Режим чтения 1 задается посылкой кода 8 в регистр режимов (сначала 5 — в порт $ЗСЕ, затем 8 — в порт $3CF). В этом режиме осуществляется сканирование 8 пикселов экрана и выбор тех из них, которые имеют нужный цвет. Перед чтением байта нужно в регистр сравнения послать код цвета (2 в порт $ЗСЕ и код цвета в порт $3CF). Получаемый байт будет содержать единицы в тех разрядах, для которых битовые плоскости определяют тестируемый цвет, и нули — во всех остальных разрядах. Этот результат можно изменить, если настроить регистр игнорирования цвета: по умолчанию этот регистр (он выбирается посылкой 7 в порт $ЗСЕ и устанавливается посылкой байта в порт $3CF) содержит цепочку битов 1111, что блокирует его вмешательство в процедуру сравнения; если установить другое значение регистра, битовые плоскости, для которых разряды регистра имеют значение о, будут игнорироваться. Пусть, например, самый первый байт верхней строки имеет цвет 3 (бирюзовый), что соответствует такой цепочке битов для первого разряда битовых плоскостей: 0011; пусть также мы установили цвет 2 (зеленый) в регистр сравнения (код 0010); поскольку при сравнении будет выявлено различие в младшем разряде обоих кодов, чтение по адресу $АООО:ОООО в режиме 1 вернет байт со значением о в старшем разряде, если регистр безразличия содержит 1111, и 1 — если то.
Структура CHR-файлов для векторных шрифтов
В этом разделе мы рассмотрим структуру CHR-файлов, т. е. файлов с расширением CHR, в которых поставляются векторные BGI-шрифты. Такие шрифты широко используются в различных программных продуктах фирмы Borland, в том числе — в компиляторах Турбо Паскаль, Турбо С, Borland C++. Необходимость анализа очевидна — ведь стандартные шрифты фирмы Borland не содержат символов кириллицы, и поэтому отечественные программисты либо вообще отказываются от работы с этими шрифтами, либо переводят сообщения на английский язык, что, согласитесь, не всегда удобно. Познакомившись с несложными соглашениями, по которым конструируются CHR- файлы, вы затем сможете создать собственную версию генератора (редактора)
векторных шрифтов или воспользоваться программой FONTEDIT.PAS, находящейся на прилагаемой к книге дискете, чтобы вставить в стандартные шрифты кириллицу. Существует профессиональный пакет Borland BGI Toolkit, в состав которого включен более мощный редактор FE.EXE.
Любой CHR-файл состоит из пяти частей: заголовка, блока параметров, таблицы смещений, таблицы ширин, блока векторных команд. Структура файла показана в табл. 24.1.
Таблица 24.1. Структура CHR-файла
Название поля
Смещение
Размер поля
Содержимое
FontFilelD
0
4
'PK'#8#8
Copyright
2
0-253
Любое (кроме $1A)
CopyrightEnd
?
1
$1A
HeaderOffset
CopyrightEnd+1
2
HeaderSize
FontName
CopyrightEnd+3
4
Название шрифта
FontSize
CopyrightEnd+7
2
Длина загружаемой части шрифта
FontVersion
CopyrightEnd+9
3
1
ParPrefix
(Обычно 128)
1
+
CharsCount
ParPrefix+1
2
Количество символов в шрифте
Reservl
ParPrefix+3
1
Неиспользуется
FirstChar
ParPrefix+4
1
Код первого символа
DataOffset
ParPrefix+5
2
FontStart-ParPrefix
FillFlag
ParPrefix+7
1
Флаг заполнения
UpperMargin
ParPrefix+8
1
Верхняя граница символа
Reserv2
ParPrefix+9
1
Неиспользуется
LowerMargin
ParPrefix+10
1
Нижняя граница символа
Reserv3
ParPrefix+11
5
Неиспользуется
Offsets
ParPrefix+16
2* [CharsCount]
Таблица смещений
Widths
ParPrefix+16+
2* [CharsCount]
[CharsCount]
Таблица ширин
FirstData
ParPrefix+16+
Образы символов
3* [CharsCount]
FontEnd
Поле FontFilelD открывает любой CHR-файл и всегда содержит символы 'РК'#8#8, которые служат для определения корректности файла. Символы ' РК' — это инициалы Филиппа Кана (Philip Kahn) — основателя фирмы Borland. Два следующих за ними символа #8 ("Забой") стирают эти инициалы при выводе содержимого файла на экран командой туре, поэтому на экране будет видна только последовательность символов из массива Copyright, в котором содержатся название шрифта и объявление авторских прав фирмы Borland. Длина массива Copyright в принципе может быть произвольной (во всех четырех стандартных шрифтах этот массив имеет одинаковую длину — 83 байта), т. к. он всегда заканчивается полем CopyrightEnd, содержащим символ #26 (#$1А) — конец файла.
Сразу за полем CopyrightEnd следует двухбайтное поле HeaderSize, содержащее длину заголовка в байтах плюс 1 (в стандартных шрифтах это поле имеет значение 128).
Поле FontName содержит четырехбуквенное название шрифта; это название передается процедуре InstallUserFont и должно совпадать с именем CHR-файла.
Двухбайтное поле FileSize содержит длину CHR-файла за вычетом длины заголовка, т. е. оно равно
FileSize(File)- HeaderSize
Трехбайтное поле Fontversion в стандартных шрифтах содержит значения 1, О, 1 и, по всей видимости, означает номер версии шрифта. При разработке собственных шрифтов лучше сохранить это поле таким же, как в стандартных шрифтах.
Остальная часть заголовка не имеет значения и может быть произвольной.
Поле ParPrefix отстоит на HeaderSize байтов от начала файла (в стандартных шрифтах — на 128 байт). Оно открывает блок параметров шрифта и всегда содержит символ +. В поле CharsCount указывается общее количество символов, начертания которых определены в CHR-файле, а в Firstchar — код первого определенного символа. Поля CharsCount и Firstchar в совокупности задают диапазон кодов символов; в стандартных шрифтах они обычно содержат значения 32 и 223, что означает, что эти шрифты определяют начертания ASCII-символов, начиная с #32 (пробел) и до символа #254 включительно.
Поле DataOffset содержит расстояние в байтах от поля parPrefix до поля FirstData. Его значение определяется по формуле
DataOffset = 16+3*CharsCount
в которой слагаемое 16 учитывает длину блока параметров, а 3*CharsCount — длину двух таблиц, следующих сразу за блоком параметров. Для стандартных шрифтов из файлов Trip, Goth и Litt это поле содержит
16 + 3*223 = 685
(ДЛЯ ЭТИХ Шрифтов CharsCount = 254-32 + 1 = 223).
Поле FillFlag содержит так называемый флаг заливки символов: если он сброшен (имеет нулевое значение), процедуры OutText и OutTextXY выводят только векторный "скелет" каждого символа, а при ненулевом значении заливают его текущим рисунком заполнения (устанавливается процедурой SetFillstyle). Поскольку контуры многих символов незамкнуты, заливка может разлиться по всему экрану, поэтому в стандартных шрифтах это поле всегда имеет значение о.
Поле UpperMargin содержит максимальное, a LowerMargin — минимальное значение локальных координат для оси Y. Локальные вертикальные координаты векторных команд могут иметь значения в диапазоне от “64 до +63 (см. ниже). Таким образом, эти поля определяют максимальный вертикальный размер любого символа: поле upperMargin задает высоту от базовой линии до верхнего края самого высокого символа шрифта, а поле LowerMargin обычно содержит отрицательное значение типа Shortlnt, показывающее, насколько ниже базовой линии может располагаться нижний край любого символа.
Остальные поля блока параметров не используются и содержат нули.
Сразу за блоком параметров начинается таблица смещений Offsets. Эта таблица содержит CharsCount двухбайтных слов — по одному слову на каждый символ. В ней указывается, на каком расстоянии от поля FirstData содержится начало векторных команд каждого символа. Самый первый элемент таблицы всегда содержит о. Разница в смещениях между соседними элементами определяет длину векторных команд для очередного символа, например, разница между вторым и первым элементами — длину командной последовательности (в байтах) для первого символа, между третьим и вторым — для второго элемента и т. д. Для последнего символа эта длина определяется разницей между полями FileSize и последним элементом таблицы. Если вы не загружаете весь файл в память, то вам понадобится построить свою таблицу значений длин командных последовательностей, чтобы, no мере надобности, динамически подкачивать эти данные с диска, как это сделано в процедуре OutString МОДУЛЯ F_GrText (см. разд. 24.3.4).
Поле widths длиной CharsCount байтов содержит таблицу ширин, в которой указывается ширина (длина по оси X) каждого символа. Обычно каждый символ выравнивается влево относительно локальных координат, поэтому его ширина в таблице должна задавать также небольшое межсимвольное расстояние справа от него, чтобы символы не сливались друг c другом.
Поле FirstData определяет начало векторных команд, используемых для вычерчивания первого символа. Каждая векторная команда занимает одно двухбайтное слово и содержит локальные координаты и собственно команду (рис. 24.3).
Поскольку локальные координаты определены в диапазоне от “64 до +63, для их представления используются 7 младших разрядов каждого байта, в то время как старшие разряды А И В определяют код команды (табл. 24.2).
Рис. 24.3. К определению векторных команд: а — структура команды; б — вертикальные координаты символов
Таблица 24.2. Векторные команды
А
в
Команда
0
0
Конец образа символа
1
0
Не используется
0
1
Поднять "перо" и перевести его в точку X, Y
1
1
Опустить "перо" и вести его до точки X, У
Как правило, последовательность векторов начинается командой 01, по которой воображаемое "перо" в поднятом состоянии переводится к началу самого первого вычерчиваемого вектора. Заканчивается последовательность командой оо, в которой координаты X и У игнорируются, поэтому обычно это — команда $оооо. Непосредственно перед ней часто указывается команда widths [N]+$80, что означает установку "пера" на горизонтальной оси на расстоянии ширины символа от начала координат. То положение, которое "перо" заняло в конце вычерчивания предыдущего символа, автоматически становится началом локальных координат для векторных команд следующего символа. Если не перевести перо в самый правый край предыдущего символа, следующий за ним символ может частично или полностью перекрыть предыдущий символ, а если при этом вертикальная координата не равна нулю, символы будут вычерчиваться "лесенкой".
Координата X определяет обычную декартовую координату по оси абсцисс и всегда имеет значение в диапазоне от о до widths [N] (widths [N] — ширина символа в таблице ширин). Координата К может задавать как положительные, так и отрицательные значения, поскольку нижняя кромка символа (поле LowerMargin) может располагаться ниже координаты Y=0 ("хвостики" у букв р, ?,?Ит. п.). Поэтому при определении координаты Y используется следующее соглашение: если Y<=63, значение координаты, заданное в команде, остается неизменным, в противном случае оно определяется разницей Y-128.
В приложении 6 приводится описание относительно несложной программы, с помощью которой вы сможете разработать собственные векторные символы и включить их в стандартные CHR-файлы.
Модуль поддержки текстового ввода/вывода F_GrText
В описываемый ниже модуль F_GrText включено около 20 подпрограмм, большая часть которых имитирует обычный текстовый режим ввода/вывода для программ, работающих с графическим экраном. С помощью этих подпрограмм вы сможете без особого труда перенести работу ранее созданных диалоговых программ, рассчитанных на стандартные процедуры вво- да/вывода Read, ReadLn, Write и WriteLn, в графическую среду. После такого переноса программа сможет инициализировать графический режим и работать внешне точно так же, как если бы она работала в текстовом режиме, однако теперь ей станут доступны все процедуры и функции стандартного модуля Graph. Иными словами, модуль позволяет объединить в рамках одной программы два режима одновременно — текстовый и графический.
£ Примечание .
Текст модуля находится в файле F_GrText. pas на сопровождающей дискете. Модуль ссылается на не описанный в книге модуль F_Text, обеспечивающий многостраничный вывод в текстовом режиме (см. файл F_Text.pas).
Основой модуля являются нестандартные драйверы текстовых устройств. Их действие связано с перехватом обращения процедур write, WriteLn, Read и ReadLn к физическим устройствам — соответственно к дисплею и клавиатуре. После такого перехвата программа пользователя получает в свое распоряжение текстовую строку, подготовленную для вывода на экран процедурами write/writeLn, или может самостоятельно готовить текстовую строку, которая затем будет интерпретироваться стандартными процедурами Read/ReadLn.
С этой целью в исполняемую часть модуля включены функции Graphwrite и GraphRead. Первая выводит на экран графическими средствами матричное
изображение любого символа с учетом текущих атрибутов (цвета символов и цвета фона), а вторая реализует на графическом экране мигающий курсор и эхо-повтор вводимых символов точно так, как это делается в текстовом режиме. Более того, в модуле предусмотрена возможность динамического изменения матричных шрифтов, позволяющая программе менять фактическое разрешение текстового вывода и начертание надписей. Например, при работе с VGA-адаптером программа может использовать 25, 30, 34 или 60 строк по 80 символов в каждой в зависимости от загруженного матричного шрифта.
Поскольку в модуле программными средствами имитируются аппаратные возможности текстового вывода, этот вывод осуществляется значительно медленнее, чем в текстовом режиме, что, впрочем, не является существенным для многих приложений. Во всяком случае, возможность сочетать в рамках одного изображения текстовые и графические данные, на мой взгляд, с лихвой окупает этот недостаток.
Для качественных надписей векторными шрифтами в состав модуля включены средства, с помощью которых вы сможете выводить текст под любым углом к координатным осям экрана.
Шрифты и координаты
Прежде чем описывать подпрограммы модуля, следует подробнее рассмотреть используемый в них способ создания изображений символов. Подпрограммы получают адрес загруженного в память массива, содержащего поточечные образы каждого из 256 возможных символов. При необходимости вывода на экран соответствующая процедура отыскивает образ нужного символа и, руководствуясь им, последовательно, точка за точкой, рисует его на экране. Таким образом, непременным условием реализации текстовых возможностей модуля является предварительная загрузка в память нужного шрифта. Техника разработки матричных шрифтов здесь не рассматривается, однако не пугайтесь: если вас устраивает вывод, реализуемый в текстовом режиме на вашем ПК, вам не придется самостоятельно разрабатывать шрифт. Его можно создать с помощью следующей простой программы (пример 24.5).
у™™™™™™™™*™™,!™^™.....^...^^^^^^
: Пример 24.5. Программа формирования шрифта' . " f ;
(++
\ Эта программа предназначена для создания \
\ поточечного шрифта, который может затем I
Iиспользоваться в модуле F_GrTextI
1*t
Uses Graph,DOS,CRT;
type
ab = array [0..Maxlnt] of Byte;
var
D,R,E: Integer;
P: Aab;
k,X,Y,B,H: Byte;
XO,YO: Integer;
F: File;
S: String;
Reg: registers;
begin
{Инициализируем графический режим)
D := Detect;
InitGraph(D,R, 1 ’ )r
E := GraphResult;
if EogrOk then
begin
WriteLn(GraphErrorMsg(E));
Halt
end;
{Проверяем драйвер и назначаем высоту шрифта}
case D of
CGA: H := 8;
EGA: H := 14;
VGA: H 16;
else
H := 8;
end;
{Резервируем нужную динамическую память и
открываем файл с именем "SxH.fnt", где н — высота шрифта)
GetMem(P,256*Н);
Str(H,S);
s := *8x'+s+'.fnt';
Assign(F,S);-■■
ReWrite(F,1);■
{Определяем начальные координаты для эхо-повтора}
ХО := 0;
YO20;
{Направляем текстовый вывод в верхний левый угол}
GotoXY(l, 1);
(Основной цикл создания побитовых образов символов}
for k := 0 to 255 do
begin
{Выводим очередной символ средствами прерывания
$10, функция $А (вывод символа без атрибутов)}
with Reg do
begin
ah := $А;
al := k;
bh := 0;
сх : = 1;
Intr($10,Reg)
end;
(Сканируем созданное изображение слева-направо-сверху-вниз}
for Y := 0 to Н-1 do
begin
В := 0; (Начальное значение очередного байта) for X := 0 to 7 do
if GetPixel (X, Y) OGetBkColor then begin
{Формируем байт-
В := В or (128 shr X) ;
(Гасим символ в левом верхнем углу экрана)
PutPixel(X,Y,GetBkColor);
(И повторяем его ниже }
•
end;
PAtk*H+Y] := В (Запоминаем байт)
end;
{Сдвигаем место эхо-повтора символов)
inc (Х0,10);
if XO>GetMaxX then
begin
XO := 0;
inc(YO,20)
end
end;
21 Зак 941
шрифт в файл и завершаем
BlockWrite(F,PA, 256*Н);
Close(F)
CloseGraph
end.
В этой программе один из ключевых параметров — высота шрифта (переменная н) определяется на основе анализа типа загруженного драйвера. В адаптерах CGA, EGA и VGA реализуется одинаковоепо гори
зонтали в графическом режиме — 640 пикселов. Это определяет ширину поточечного образа каждого символа в 640/80 = 8 пикселов (для максимальной длины текстовой строки в 80 символов). Высота графического экрана, а следовательно, и высота образа символа различна: 200 строк развертки для CGA дают высоту символа в 200/25 = 8 пикселов для 25 текстовых строк, в товремякакдляEGAимеем350/25=14пикселов, адляVGA —
480/25 = 19 пикселов. Адаптер VGA в текстовом режиме обеспечивает разрешение 720x400 пикселов, поэтому в нем используется шрифт размером 9x16. Однако поточечный образ каждого символа определяется матрицей 8x16 бит, т. к. в этом случае дополнительный 9-й пиксел по горизонтали автоматически выводится справа от символа цветом фона и служит для разделения символов друг от друга в строке.
После прогона приведенной выше программы в текущем каталоге будет сформирован двоичный файл, содержащий поточечные образы всех 256 символов. С помощью этих образов осуществляется вывод сообщений в текстовом режиме, а значит, вы можете загрузить этот файл в память, чтобы рисовать символы графическими средствами.
Замечу, что для VGA-адаптера стандартный шрифт 8x16 обеспечивает 480/16 = 30 текстовых строк, т. е. вывод на экран этим шрифтом будет чуть мельче, чем в текстовом режиме. Если вы хотите получить более точную имитацию текстового режима на VGA-адаптере, можно преобразовать создаваемый программой файлв файлУвеличение высоты каждого
образа до 19 пикселов обеспечит почти полную имитацию текстового вывода. Такое преобразование реализует следующая программа (пример 24.6). .......... • . I ^p ............. . . ......... .......... ............. ......... ... . .... . ......... . — . ' ......... .... ........... ........ .......... ............... T
| Пример 24.6. Программа формирования шрифта высотой 19 пикселов
:.-.-..;:. .<.;....;..;;...;?.. д.Х'ъх.. -.;..?..# i.:.■:....A-..£.;./;?.; A: •:;...*;.....,/ ...*..;.......*:...,.:.r..-A.......;.x;.....'..т^:....;..'.\7л«.:
(++
I Эта программа служитдляпреобразования \
Iфайлавфайл|
+ ----+
Uses Graph;
type
tl6 = array (0..15] of Byte;
tl9 = array [0..18] of Byte;
tl6_l = array [0. .7,0..15,0..18] of Byte;
tl9_l = array [0..7,0..18,0..15] of Byte;
var
F16: File of tl6;
F19: File of tl9;
C16: tl6;
C19: tl9;
A16: tl6_l;
A19: tl9_l absolute al6;
X,Y,k,B,S: Byte;
. D,R,E,X0,Y0: Integer;
begin
(Инициализируем графику)
D := Detect;
InitGraph(D,R,'');
E := GraphResult;
if EogrOk then
begin
WriteLn(GraphErrorMsg(E));
Halt
end;
(Задаем начальные координаты эхо-повтора}
ХО0;
YO := 0;
(Открываем файлы)
Assign(F16,'8x16.fnt');
Reset(Fl6);
Assign(F19,'8x19.fnt');
Rewrite(F19);
(Основной цикл преобразования}
while not EOF(F16) do
begin
Read(F16,C16);(Читаем очередной образ 8x16}
(Сканируем его слева-направо-сверху-вниз в виде матрицы 8x16)
for Y := 0 to 15 do
for X := 0 to 7 doi
begin
( Выделяем нужный бит}
B:=ord((С16[Y] and (128 shr X))<>0);
if BOO then
PutPixel(XO+X,YO+Y,White);
(Формируем 19 единиц или нулей}
for k := 0 to 18 do
A16[X,Y,k] :=В end;
{Создаем изображение 8x19)
for Y := 0 to 18 do
begin
В 0;
for X := 0 to 7 do begin
S := 0;(Начальное значение суммы)
for k := 0 to 15 do
S:=S+A19[X,Y,k];{Суммируемвеса}
(Проверяем сумму)
if (S>9) then
begin{Большинетво клеток закрашено -
помещаем единицу и выводим пиксел}
В := В or (128 shr X) ;
PutPixel(Х0+9+Х,YO+Y,White)
end
end;
C19[Y] := В
end;
(Записываем сформированный образ в файл 8x19)
Write(F19,C19);
{Сдвигаем эхо-повтор}
inc(X0,20) ;
if Х0>640 then
begin
ХО := 0;
inc(Y0,20)
end
end;
(Завершаем программу) CloseGraph;
Close(Fl6);
Close(F19)
end.
Программа считывает каждый образ шрифта 8x16 в переменную C16, затем анализирует этот образ и формирует шрифт 8x19 в переменной C19. Преобразование шрифта осуществляется методом весовых коэффициентов. Суть метода состоит в том, что каждый пиксел шрифта 8x16 разбивается в вертикальном направлении на 19 частей (переменная Al 6). Эти части заполняются единицами, если пиксел светится, или нулями — если не светится. После этого переменная Al б рассматривается как матрица 8x19, каждый элемент которой в вертикальном направлении разбит на 16 частей (переменная А19). Для этой переменной находится сумма всех элементов каждого пиксела (переменная s). Если эта сумма больше 9, значит большая часть пиксела в шрифте 8x19 должна быть засвечена, и этот пиксел объявляется светящимся, если меньше — не светящимся. Рис. 24.4 иллюстрирует сказанное.
Рис. 24.4. Пример преобразования символа шрифта 8x16 в шрифт 8x19
Как показывает практика, программа обеспечивает вполне приемлемое качество генерируемого шрифта 8x19.
Подпрограммы модуля F_GrText, имитирующие средства модуля Crt, имеют одинаковый с ними интерфейс. Важным отличием является то, что используемая в них координатная сетка текстового вывода определяется загруженным шрифтом. В связи с этим процедуры проверяют факт загрузки шрифта и игнорируют вызов в том случае, если шрифт не загружен. Для загрузки
шрифта программа пользователя должна разместить в памяти поточечные образы символов и вызвать процедуру регистрации шрифта setFont. После выполнения этой процедуры координаты текстового ввода/вывода могут изменяться в диапазоне от 1 до Maxchar для горизонтальной оси и от 1 до MaxLine — для вертикальной. Параметры Maxchar и MaxLine определяются следующим образом:
MaxChar =X
MaxLine = (GetMaxY+1) div Y
где x — ширина, Y — высота поточечных образов символов. Например, для VGA-адаптера при загрузке шрифта 8x8 параметр MaxLine получит значение 60, а для шрифта 8x19 — 25; в обоих случаях параметр MaxChar имеет значение 80. Это означает, что в первом случае программа работает с текстовым экраном 80x60 символов, а во втором — 80x25 символов. Левый верхний угол экрана имеет координаты (1, 1).
С графическим экраном отождествляется текстовый курсор подобно тому, как такой курсор определяет место вывода данных в текстовом режиме. Текстовый курсор становится виден на графическом экране только при обращении к процедурам Read/ReadLn и сразу исчезает после окончания ввода. Текстовый курсор никак не связан с графическим курсором. Он изменяет свое положение после вывода очередного символа процедурами Write/WriteLn ИЛИ При исполнении Процедуры GotoXY.
Драйверы текстовых устройств
Для упрощения текстового ввода/вывода в модуль F_GrText включен собственный драйвер текстовых устройств, позволяющий работать со стандартными процедурами Write/WriteLn и Read/RedaLn в графическом режиме. В этом разделе рассматриваются вопросы создания драйверов текстовых устройств.
С каждым текстовым файлом Турбо Паскаль связывает переменную типа Text, которая определена как запись следующего типа:
type
TextBuf = array [0..127] of Char; .
TextRec = record
Handle
Word;
{Дескриптор файла}
Mode
Word;
{Режим использования файла}
BufSize
Word;
{Длина буфера)
Private
Word;
{Не используется}
BufPos
Word;
{Индекс следующего символа в буфере)
BufEnd
Word;
(Количество необработанных символов)
BufPtr
: ''TextBuf;
(Адрес буфера}
OpenFunc :. Pointer;
InOutFunc: Pointer;
Flush'Func: Pointer;
CloseFunc: Pointer;
UserDataarray [1..16] of Byte;
Namearray [0. .79] of Char;
Buffer; TextBuf
end;
Поле Handle содержит дескриптор файла и используется при работе с дисковым файлом средствами ДОС.
Поле Mode может принимать одно из следующих значений:
const
fmClosed = $D7B0; {Файл закрыт}
fmlnput =— в режиме чтения данных)'
=— в режиме
fmlnOut = $D7B3; {Файл — в режиме чтения/записи}
Любые другие значения этого поля означают, что файл не был открыт.
Поле UserData в Турбо Паскале зарезервировано для внутренних нужд вызывающей программы.
В поле Name содержится полный путь к файлу в формате ASCIIZ. В этом формате строка определяется как цепочка символов, заканчивающаяся символом #0.
Четыре поля типа Pointer в записи TextRec имеют следующий смысл:
О OpenFunc — адрес функции, получающей управление при открытии текстового файла;
О CloseFunc — адрес функции, вызываемой при закрытии текстового файла;
G InOutFunc — адрес функции, активизируемой в момент физического обращения к устройству для записи или чтения информации;
□ FlushFunc — адрес функции, осуществляющей "выталкивание" текстового буфера при буферизованном выводе информации.
Каждая из этих четырех функций должна транслироваться в расчете на дальнюю модель памяти и соответствовать следующему заголовку:
type
FuncType = Function (var F: TextRec): Integer;
Значение, возвращаемое функцией, зависит от успешности выполнения соответствующей операции и равно нулю, если операция прошла успешно.
Совокупность всех четырех функций определяет драйвер текстового устройства.
Назначение стандартным файлам input и Output пользовательского драйвера заключается в установке соответствующих адресов в поля-указатели записей TextRec. Если вы захотите назначить свой драйвер произвольному текстовому файлу, вам понадобится разработать свою процедуру Assign. В ходе ее выполнения помимо установки адресов функций в поля-указатели, вам нужно будет также обнулить имя файла, присвоив Name[ 0 ] : = #0. Кроме того, в поле Mode необходимо поместить значение fmClosed, в Bufsize — размер буфера, а в BufPtr — его адрес.
Функция/
Функция OpenFunc вызывается стандартными процедурами Reset, Rewrite и Append и предназначена для осуществления всех необходимых подготовительных действий для работы с конкретным файлом. Эта функция всегда вызывается первой, до вызова любой другой функции драйвера. Анализируя поле Mode, она может определить режим работы открытого файла и соответствующим образом установить значение поля InOutFunc, связав его с процедурой чтения или записи информации. Если функция вызывается из процедуры Append, т. е. файл открывается одновременно для чтения и записи данных, поле Mode содержит значение fmlnOut. В этом случае процедура должна изменить значение этого поля на fmOutput перед передачей управления в вызывающую программу.
Функция InOutFunc
Функция InOutFunc вызывается стандартными процедурами Read, ReadLn, Write, WriteLn, EOF, EOLn, SeekEOF, SeekEOLn И Close.
Если Mode = fmlnput, функция должна прочитать не более Bufsize символов в буфер BufPtrA и возвратить количество фактически прочитанных символов в поле BufEnd. Кроме того, в поле BufPOS функция должна поместить о. Если функция возвращает о в поле BufEnd, Турбо Паскаль рассматривает это как признак конца файла.
Если Mode = fmOutput, функция InOut должна вывести на устройство BufPos символов из буфера BufPtrA и поместить о в поле BufPOS.
/
Функция FlushFunc
ФУНКЦИЯ FlushFunc вызывается В КОНЦе работы процедур Read, ReadLn, write, WriteLn. Если Mode = fminput, функция должна поместить в поля BufPos и BufEnd значения о. Если Mode = fmOutput, функция может "вытолкнуть" буфер, т. е. записать остаток буфера в файл. Если Flush ничего не делает, текст будет передаваться на устройство до тех пор, пока не заполнится буфер или пока не закроется файл.
Функция CloseFunc
Функция CloseFunc вызывается стандартной функцией close для физического завершения работы с файлом.
Подпрограммы модуля F_GrText
Все подпрограммы модуля F_GrText можно разделить на три части: подпрограммы, имитирующие стандартные средства модуля CRT для графического режима (процедуры ClrScr, GotoXY, TextBackground, TextColor, window, функции WhereX, WhereY), средства вывода текстовых сообщений векторными шрифтами под любым углом к координатным осям экрана и с любыми ПРОПОРЦИЯМИ (процедура OutString И фуНКЦИЯ RegisterCHRFile) И вспомогательные подпрограммы. Чтобы подпрограммы модуля F_GrText не были перекрыты одноименными подпрограммами модуля CRT, необходимо объявлять модуль F_GrText в предложении uses после модуля CRT.
Процедура ClrScr — очищает текстовое окно или экран, т. е. заполняет его цветом текстового фона. Заголовок:
Procedure ClrScr;
После выполнения процедуры курсор помещается в левый верхний угол окна.
П Процедура Colors — устанавливает атрибуты текстового вывода. Заголовок:
Procedure Colors(Text, Back: Byte);
П Функция GetMaxChar — возвращает максимальное количество символов в строке экрана. Заголовок:
Function GetMaxChar: Byte;
П Функция GetMaxLine — возвращает максимальное количество строк на экране. Заголовок:
Function GetMaxLine: Byte;
П Процедура GetWindGraphCoo — возвращает графические координаты текстового окна. Заголовок:
Procedure GetWindGraphCoo(var XI, Y1, X2, Y2: Integer);
При выходе из процедуры переменные XI—Y2 содержат графические координаты установленного в данный момент текстового окна (или экрана в целом, если окно не установлено). Координаты соответствуют внешнему периметру окна: XI — самый левый пиксел первого символа, Х2 — самый правый пиксел последнего символа, Y1 — самый верхний пиксел первой строки, Y2 — самый нижний пиксел последней строки.
О Процедура Gotoxv — переводит текстовый курсор в нужное место экрана. Заголовок:
Procedure GotoXY(X, Y: Byte);
Здесь x, Y — новые координаты курсора.
Текстовые координаты X, Y должны быть в пределах от 1 до MaxChar (MaxLine). Если новое положение курсора находится за границами активного в данный момент окна, вызов процедуры игнорируется.
Примечание —j
Текстовый курсор может перемещаться только в позицию, кратную ширине шрифта по горизонтали и высоте шрифта по вертикали. Таким образом, нельзя поместить текстовый курсор в произвольное место графического экрана (например, в точку с графическими координатами (1, 1)). Обращение GotoXY(1,1) означает, как и в обычном текстовом выводе, позиционирование курсора в левый верхний угол окна (или экрана, если окно не установлено).
О Функция GraphCoox — возвращает горизонтальную графическую координату по заданной текстовой координате. Заголовок
Function GraphCooX(X: Byte): Integer;
Здесь х — заданная текстовая координата.
Функция возвращает графическую координату левого края символа, который будет выводиться в позицию х, или (—1), если к моменту обращения не загружен шрифт или х выходит за границы активного текстового окна.
Функция GraphCooY — возвращает вертикальную графическую координату по заданной текстовой координате (см. функцию GraphCoox).
Процедура GraphWriteOn — назначает стандартным файлам input и Output новые драйверы (см. разд. 24.4.2). Заголовок:
Procedure GraphWriteOn;
После вызова GraphWriteOn стандартные Процедуры Read/ReadLn И Write/WriteLn будут использовать средства модуля F_GrText.
При вводе это означает эхо-повтор вводимых символов в текстовом окне с учетом установленного цвета символов и фона. В этот момент на экране появляется текстовый курсор, а пользователю становятся доступны следующие средства редактирования ввода:
забой символа слева от курсора клавишей <Backspace>;
перевод курсора к началу строки ввода — клавиша <Ноте>;
перевод в конец строки ввода — клавиша <End>;
перевод на символ влево или вправо — клавишами смещения курсора;
забой символа, на который показывает курсор — клавишей <Delete> (<Del>);
переход к режиму замены или возврат к режиму вставки — клавишей признаком работы в режиме замены являются увеличенные размеры курсора.
Любой ввод должен завершаться нажатием на <Enter> или <Esc>. При нажатии навведенная строка копируется в буфердлина
строки помешается ви на экране осуществляется перевод курсо
ра в начало следующей строки. Замечу, что "пустой" ввод соответствует BufEnd=o, что интерпретируется процедурами Read и ReadLn как нажатие <Ctrl>+<Z> (конец файла input). Чтобы исключить аварийное завершение работы программы (а именно так реагирует Турбо Паскаль на этот в случае "пустого" ввода в буфер помещается символ #32 ("Пробел"). При завершении ввода по клавише <Esc> втакже
помещается единственный символ "пробел" и дополнительно глобальная переменнаяв интерфейсной части модуля F_GrText получает
значение True (при выходе поона имеет значение False). Чтобы
определить факт нажатия клавиши <Esc>, программа пользователя может проанализировать состояние переменной EscFiag.
При выводе символов процедуратестирует аппаратуру дис
плея и осуществляет прямой доступ к видеопамяти, если используются адаптеры EGA или VGA. Как показывает практика, прямой доступ к памятиувеличивает скорость формирования символов
на экране враз, в то время как дляв случае стан
дартного шрифта 8x8 он не дает существенного выигрыша в скорости (и не используется в модуле). Вы можете запретить прямой доступ к видеопамяти EGA/VGA, если поместите в глобальную переменную значение False (по умолчанию она имеет значение True).
О Процедура GraphWriteOff — восстанавливает стандартное назначение файлов input и Output, т. е. ликвидирует действие процедуры Заголовок:
Procedure
Процедура setFont — регистрирует загруженный матричный шрифт для последующего использования его в процедурах ввода/вывода. Заголовок:
ProcedureFont; X, Y:
Здесь:
Font — любая переменная, содержащая загруженный шрифт;
х — ширина символа в пикселах;
— высота символа.
Например, шрифт из файла 8xl6.fnt можно загрузить, выполнив следующий фрагмент программы:
Uses Graph,F_GrText;
var
D,R: Integer;
F: File;
Font: Pointer;
begin
{Инициализируем графический режим}
D Detect;
InitGraph(D,R,'');
{Открываем файл}
Assign(F, '8x16.fnt');
Reset (F, 1) ;
{Резервируем динамическую память
для размещения шрифта}■
GetMem(Font,FileSize(F));
{Считываем файл в память)
BlockRead(F,FontA,FileSize(F) )
Close (F) ;
{Регистрируем шрифт)
SetFont(Font,8,16);
{Назначаем нестандартные драйверы ввода/вывода}
GraphWriteOn;
В ходе выполнения процедуры осуществляется пересчет разрешения экрана для текстового ввода/вывода. С этой целью рассчитываются внутренние переменные MaxChar (максимальная длина текстовой строки на графическом экране) и MaxLine (максимальное количество текстовых строк). Для расчета используется графическое разрешение экрана, возвращаемое стандартными функциями GetMaxX и GetMaxY, поэтому перед обращением к процедуре должен быть инициализирован графический режим работы адаптера. После выполнения процедуры отменяется ранее установленное текстовое окно (если, разумеется, оно было установлено с помощью процедуры window), а курсор переводится в левый верхний угол экрана. Текущие цвета символов и фона не меняются.
Следует заметить, что параметр х, задающий горизонтальную ширину символов, используется только для определения MaxChar. При фактическом выводе на экран ширина любого символа считается равной 8 пикселам
(т. е. предполагается, что поточечные образы символов описываются матрицами размером 8х//бит). Кроме того, если вы установите'х не равным 8, это скажется на границах текстового окна и вводе символов, в то время как при выводе на EGA/VGA прямым доступом к памяти символы будут располагаться так, как если бы была объявлена ширина х 8. Если вы хотите поэкспериментировать с х, установите AppHandle = False, чтобы отключить прямой доступ к видеопамяти адаптеров EGA/VGA.
□ Процедура SetWindow — устанавливает окно, очищает его и обводит рамкой. Заголовок:
Procedure Setwindow (XI, Y1, X2, Y2, Border: Byte;
Header: String; Clip, Build, Play: Boolean);
Здесь:
XI, ..., Y2 — текстовые координаты окна;
Border — тип рамки (соответствует константам xxxxBorder модуля F_Text);
Header —заголовок окна;
clip — признак отсечки;
параметр Build управляет "разворачиванием" окна; л
параметр Play задает звуковое сопровождение.
В верхней части окна (на строке Yl) выводится заголовок Header; если длина заголовка больше Х2-Х1-2, его "лишние" символы отбрасываются; если Header='', заголовок не выводится. Для Clip=True границы вновь созданного окна будут соответствовать внутренней части прямоугольника X1-Y2 без учета рамки (т. е. X1=X1+1, Y1=Y1+1, X2=X2-1, Y2=Y2-1), чтобы возможный вывод текста в окно не испортил его рамку. Если Build = = True, окно как бы "разворачивает" свои размеры, постепенно заполняя все его пространство. При этом, если Play = True, процесс "развертки" окна сопровождается звуковым сигналом.
П Процедура scrolling — осуществляет прокрутку окна на заданное число строк вверх или вниз. Заголовок:
Procedure Scrolling(N: Integer);
Здесь N — количество строк, на которые сдвигается окно (при N<O окно сдвигается вниз).
Если абсолютное значение N превосходит высоту окна или если N = о, окно полностью очищается. После прокрутки положение курсора не меняется.
П Процедура TextBackground — устанавливает цвет фона для символов. Заголовок:
Procedure TextBackground(Color: Byte);
В качестве параметра color могут быть заданы соответствующие константы цвета, определенные в модуле Graph. Замечу, что установка цвета фона с номером 0 означает, что символы будут накладываться на существующее изображение, т. е. выводиться так, чтобы не менялись пикселы фона. Вы можете использовать эту возможность для наложения поясняющей надписи на график. Установка любого другого цвета приведет к заполнению пикселов фона нужным цветом, т. е. вывод будет стирать существующее графическое изображение. Для цвета фона могут использоваться как темные, так и яркие тона.
Процедура SetColor — устанавливает цвет символов. Заголовок:
Procedure SetColor (Color: Byte);
О Функция Textcoox — возвращает ближайшую текстовую горизонтальную координату по заданной графической координате. Заголовок:1
Function TextCooX (X: Integer): Byte;
Если заданная графическая координата выходит за горизонтальные границы окна, функция возвращает ноль.
Функция TextcooY — возвращает ближайшую текстовую вертикальную координату по заданной графической координате. Заголовок:
Function TextCooY(Y: Integer): Byte;
Если заданная графическая координата выходит за вертикальные границы окна, функция возвращает ноль.
О Функция WhereX — возвращает текущее положение текстового курсора по горизонтали. Заголовок:
Function WhereX: Byte;
Возвращаемая координата определяется относительно границ окна (или экрана, если окно не установлено).
Функция WhereY — возвращает текущее положение текстового курсора по вертикали. Заголовок:
Function WhereY: Byte;
Возвращаемая координата определяется относительно границ окна (или экрана, если окно не установлено).
О Процедура window — устанавливает текстовое окно. Заголовок:
Procedure Window(XI, Yl, X2, Y2: Byte);
Здесь XI, Yl — текстовые координаты левого верхнего, а Х2, Y2 — правого нижнего углов окна. Координаты всегда определяются относительно границ всего окна.
Установленное на экране окно ведет себя точно так же, как и в текстовом режиме, за исключением автоматической прокрутки при выходе курсора за нижнюю границу окна. Вместо прокрутки вывод будет просто подавлен, если очередной символ должен выводиться за правый нижний угол окна.
Функция RegisterCHRFile — регистрирует векторный шрифт и возвра- шает его идентификационный номер для последующего использования в процедуре outstring. Заголовок:
Function RegisterCHRFile(FileName: String): Integer;
Здесь FileName — имя файла формата CHR, содержащего регистрируемый векторный шрифт.
Особенности структуры CHR-файлов подробно рассмотрены выше (см. разд. 24.2). Регистрация шрифта заключается в проверке заголовка файла и считывании в память таблицы смещений и таблицы ширин. При успешном завершении регистрации функция возвращает положительное число — идентификационный номер шрифта, который используется в качестве одного из параметров обращения к процедуре outstring. При обнаружении ненормальных ситуаций функция возвращает отрицательное значение, имеющее следующий смысл:
-1 — ошибка доступа к файлу (файл нельзя открыть или обнаружена ошибка при чтении из него данных);
-2 — содержимое файла не соответствует формату BG 1-шрифта (ошибка в заголовке файла или несоответствие длины файла параметрам шрифта);
-з — нет памяти для размещения информации о шрифте (для регистрации одного шрифта требуется чуть больше 2 Кбайт динамической памяти).
□ Функция RegisterCHRFont — регистрирует векторный шрифт и возвращает его идентификационный номер для последующего использования в процедуре outstring. Заголовок:
FunctionRegisterCHRFont(Font: Pointer): Integer;
Здесь Font — адрес участка оперативной памяти, в который ранее был прочитан CHR-файл.
Эта функция аналогична функции RegisterCHRFile за исключением того, что перед обращением к ней шрифтовой файл необходимо прочитать в оперативную память ПК. Использование этой функции вместо RegisterCHRFile дает два преимущества. Во-первых, заметно быстрее становится вывод процедурой outstring, т. к. исключаются обращения к диску для чтения векторных команд. Выигрыш в скорости особенно ощутим, если вы используете CHR-файлы, расположенные на дискете. Во-
. вторых, ваша программа может компилироваться со встроенными шрифтами подобно тому, как это реализовано в модуле Graph. Например, после преобразования файла TRIP.CHR командой
binobj trip.chr trip.obj trip
можно использовать такую программу:
Uses Graph, F_GrText;
Procedure Trip; External;
($L Trip.obj}
var
Integer;
begin
d := Detect;
InitGraph(d,r, ' ') ;
MoveTo(0,100);
if RegisterCHRFont(@Trip)>0 then
{Шрифт зарегистрирован нормально}
OutString (' Нормальная регистрация', 1, 0,1,1)
else
begin {Ошибка регистрации}
SetTextStyle(TriplexFont,HorizDir, 4);
OutText(’Ошибка при регистрации шрифта')
end;
ReadLn;
CloseGraph
end.
О Процедура outstring — выводит текстовую строку в текущую позицию графического экрана. Заголовок:
Procedure OutString(S: String; Font, Angle: Integer;
ScaleX, ScaleY: Real);
Здесь:
s — текстовая строка, которую требуется вывести на экран;
Font — номер векторного шрифта (шрифт должен быть предварительно зарегистрирован С ПОМОЩЬЮ фуНКЦИИ RegisterCHRFile ИЛИ RegisterCHRFont);
Angle — угол по отношению к горизонтальной координате экрана, на которую должен быть повернут выводимый текст (задается целым числом угловых градусов; положительные углы отсчитываются против часовой стрелки);
Scalex, scaleY — масштабные коэффициенты соответственно по ширине шрифта и по его высоте.
Перед выводом процедура вначале определяет текущее положение графического указателя с помощью вызова Getx, GetY и считает это положение точкой отсчета локальных координат первого выводимого символа. После вывода очередного символа эта точка смещается в соответствии с векторными командами, а после завершения вывода всей строки графический указатель перемещается в конец последнего выведенного символа. Локальные координаты каждой векторной команды умножаются на масштабные коэффициенты scalex, ScaleY и затем пересчитываются с учетом требуемого угла поворота. Поскольку в процедуре интенсивно используются тригонометрические преобразования координат, вывод с ее помощью происходит заметно медленнее, чем в случае использования стандартных процедур OutText, OutTextxY. Для ускорения счета все тригонометрические преобразования осуществляются над данными типа single, что примерно в 5—8 раз сокращает время вывода на ПК с арифметическими сопроцессорами.
24.3.4. Тестовая программа
В следующей программе (пример 24.7) иллюстрируется использование средств модуля F_GrText. Программа вначале выводит несколько раз одно и то же сообщение с помощью процедуры OutString. Для каждого символа сообщения устанавливается свой угол наклона, плавно меняющийся от 0 до 30 градусов и обратно (см. рис. 24.5).
Нажмите любую клавишу
! Пример использования модуля F_GrText !
«,}
Uses CRT,Graph,F_GrText;
const
DX = 5В; {Ширина текстового окна}
DY =
war
D,R,E: integer;
Г: file;{Файл со шрифтом}ы окон}
^ С^Й^ ^пС&
Турво Паскаль
Рис. 24.5. Экран, создаваемый тестовой программой
Затем с помощью вызова Setwindow в верхней части экрана создается текстовое окно, в которое выводятся начальные строки PAS-файла с текстом программы. После нажатия на любую клавишу текстовое окно очищается и программа реализует ввод произвольной текстовой строки. После завершения ввода содержимое окна сдвигается вниз и программа ожидает нажатия на любую клавишу.
?"W‘^/-?^yZ?4V^*.^4*£VftK^jHj|ffii|flftgqKy!||!$iflliaj^^Пример 24.7. Тестовая программа• •о $™МА?СУ#Л Л. ЛИ;.. .ЛТ.К I .......it;.';;;.'.". ...7..^.... 1Л п iЛЛ. А* • «л.V.v . л-.". .а лл&&& W?J м! ЛЯВЗ?"W‘^/-?^yZ?4V^*.^4*£VftK^jHj|ffii|flftgqKy!||!$iflliaj^^Пример 24.7. Тестовая программа• •о $™МА?СУ#Л Л. ЛИ;.. .ЛТ.К I .......it;.';;;.'.". ...7..^.... 1Л п iЛЛ. А* • «л.V.v . л-.". .а лл&&& W?J м! ЛЯВЗ1 -х W*jWSW*)H№41**i! * <1 -х W*jWSW*)H№41**i! * <,..;...:,..;...:•r. L л^л »t «"Л'..т. • 4Vл Г. л... ;. .. 4. л. л. .< 1 л л л •. /•r. L л^л »t «"Л'..т. • 4Vл Г. л... ;. .. 4. л. л. .< 1 л л л •. /
?"W‘^/-?^yZ?4V^*.^4*£VftK^jHj|ffii|flftgqKy!||!$iflliaj^^
Пример 24.7. Тестовая программа
• •о $™МА?СУ#Л Л. ЛИ;.. .ЛТ.К I .......it;.';;;.'.". ...7..^.... 1Л п iЛЛ. А* • «л.V.v . л-.". .а лл&&& W?J м! ЛЯВЗ
?"W‘^/-?^yZ?4V^*.^4*£VftK^jHj|ffii|flftgqKy!||!$iflliaj^^
Пример 24.7. Тестовая программа
• •о $™МА?СУ#Л Л. ЛИ;.. .ЛТ.К I .......it;.';;;.'.". ...7..^.... 1Л п iЛЛ. А* • «л.V.v . л-.". .а лл&&& W?J м! ЛЯВЗ
1 -х W*jWSW*)H№41**i! * <
1 -х W*jWSW*)H№41**i! * <
,..;...:
,..;...:
•r. L л^л »t «"Л'..т. • 4Vл Г. л... ;. .. 4. л. л. .< 1 л л л •. /
•r. L л^л »t «"Л'..т. • 4Vл Г. л... ;. .. 4. л. л. .< 1 л л л •. /
i+ -
I Пример использования модуля F_GrText \
**;
Uses CRT, Graph, F_GrText;
const
DX = 50,(Ширина текстового окна)
DY = 11;{Высота текстового окна}
tl: Strings1 Лучшая в мире система программирования *;
t2 : String= 1 ТурбоПаскаль 1
Grad = Pi/180;Grad = Pi/180;varD,R,E: Integer;Angle: Integer;xl,yl,x2,y2: Integer;F: File;FT: Text;N: Integer;P: Pointer;S: String;k,H: Byte ;C: Char;begin{Инициализируем графику)D :- Detect;• InitGraph (D,R, ’ ');E := GraphResult;if EOgrOk thenbeginvarD,R,E: Integer;Angle: Integer;xl,yl,x2,y2: Integer;F: File;FT: Text;N: Integer;P: Pointer;S: String;k,H: Byte ;C: Char;begin{Инициализируем графику)D :- Detect;• InitGraph (D,R, ’ ');E := GraphResult;if EOgrOk thenbegin{Перевод градус/радиан){Угол поворота букв)(Координаты окон){Файл со шрифтом)(Файл с текстом программы}(Указатель на шрифт){Перевод градус/радиан){Угол поворота букв)(Координаты окон){Файл со шрифтом)(Файл с текстом программы}(Указатель на шрифт)
Grad = Pi/180;
Grad = Pi/180;
var
D,R,E: Integer;
Angle: Integer;
xl,yl,x2,y2: Integer;
F: File;
FT: Text;
N: Integer;
P: Pointer;
S: String;
k,H: Byte ;
C: Char;
begin
{Инициализируем графику)
D :- Detect;
• InitGraph (D,R, ’ ');
E := GraphResult;
if EOgrOk then
begin
var
D,R,E: Integer;
Angle: Integer;
xl,yl,x2,y2: Integer;
F: File;
FT: Text;
N: Integer;
P: Pointer;
S: String;
k,H: Byte ;
C: Char;
begin
{Инициализируем графику)
D :- Detect;
• InitGraph (D,R, ’ ');
E := GraphResult;
if EOgrOk then
begin
{Перевод градус/радиан)
{Угол поворота букв)
(Координаты окон)
{Файл со шрифтом)
(Файл с текстом программы}
(Указатель на шрифт)
{Перевод градус/радиан)
{Угол поворота букв)
(Координаты окон)
{Файл со шрифтом)
(Файл с текстом программы}
(Указатель на шрифт)
WriteLn(GraphErrorMsg(Е));
Halt
end;
(Выводим текст процедурой OutString)
if RegisterChrFile('trip.chr') >0 then
begin
yi :=div 20;
xl :=
x2 := Length(tl);
y2 := GetMaxY-2*yl;
while xl<y2 do
begin
MoveTo (0,xl); for k := 1 to x2 do begin Angle := Round(30*sin(grad*(k*360/x2))); OutString(tl[k],1,Angle,0.9,1); end; inc(xl,yl) end; MoveTo(2*GetMaxX div 7,GetMaxY-3*yl); for k 1 to Length(t2) do OutString(t2[k],1,0,1,1)
end;
(Выбираем нужный шрифт)
case D of
CGA: H := 8;
EGA: H := 14;
VGA: H := 16;
else
H := 8;
end;
{Готовим имя файла, содержащего шрифт}
Str(Н,S);
S := ' 8x'+S+'. fnt';{Имя файла}
{Открываем файл со шрифтом}
Assign(F, S);
Reset(F,1) ;
{Резервируем память и грузим шрифт)
GetMem(P,FileSize(F)) ;
BlockRead(F,PA,FileSize(F) );
Close(F);
{Регистрируем шрифт и перехватываем ввод/вывод)
SetFont(р,8,Н) ;
GraphWriteOn;
{Создаем текстовое окно}
TextColor(Black);
TextBackGround(White);
Setwindow ((GetMaxChar-DX) div 2,2,(GetMaxChar+DX) div 2, (GetMaxLine+DY) div 3,DoubleBorder, ' Нажмите любую клавишу ',True,False,False);
{Открываем файл с текстом программы}
S := copy(ParamStr(0),l,pos('.',ParamStr(0)))+'pas';
Assign(FT,S);
Reset(FT) ;
{Читаем файл и выводим его в окно} for N := 1 to DY do
begin
ReadLn(FT,S);
WriteLn(S)
end;
Close(FT);
{Ждем действий•
while not KeyPressed do;
while KeyPressed do
С := ReadKey;
{Очищаем текстовое окно и вводим строку}
ClrScr;
WriteLn('Введите произвольный текст');
ReadLn(S) ;
(Сдвигаем ввод вниз окна и ждем}
Scrolling(-DY div 2) ;
WriteLn('Текст смещен на ',-DY div 2, ' строк');
WriteLn('Нажмите любую клавишу...');
С := ReadKey;
{Завершаем работу программы}
GraphWriteOff;
CloseGraph
end.
Глава 25
Обработка ошибок периода исполнения программы
В стандартном языке Паскаль нет средств обработки так называемых исключительных ситуаций, т. е. ситуаций, связанных с возникновением той или иной ошибки в момент работы профаммы. В Турбо Паскале такие средства есть, но они не реализованы в виде стандартных подпрофамм. Отсутствие средств обработки исключительных ситуаций затрудняет создание различного рода диалоговых систем, в которых данные вводятся пользователем по запросам профаммы. В результате ошибки в подготовке данных в профам- мах могут возникнуть некорректные ситуации типа деления на ноль или извлечения квадратного корня из отрицательного числа. Такие ситуации в профаммах на Турбо Паскале обрабатываются особой процедурой, которая дает краткое сообщение о характере и месте возникновения ошибки и аварийно завершает работу профаммы. В ряде случаев стандартное поведение профаммы требуется изменить, например, сообщить пользователю о характере ошибки и вернуться в точку диалога, чтобы он мог ввести новые или отредактировать ранее введенные данные.
В этой главе обсуждаются средства, с помощью которых можно обрабатывать исключительные ситуации, и приводится текст модуля F_Jump, предназначенного для этих целей.
Механизм обработки исключительных ситуаций
Для обработки исключительных ситуаций в Турбо Паскале предусмотрены соответствующие средства: в стандартный модуль system включена переменная ExitProc, с помощью которой можно перехватить управление профам- мой в момент возникновения исключительной ситуации. По умолчанию эта переменная (она имеет тип Pointer) хранит значение NIL; если поместить в нее адрес некоторой процедуры пользователя (назовем ее процедурой выхода), то при возникновении исключительной ситуации управление передается в эту
процедуру. Процедура выхода должна быть процедурой без параметров. Ее следует транслировать в расчете на дальнюю модель памяти.
Проиллюстрируем сказанное на примере простейшей процедуры выхода. Пусть ее задачей является только расшифровка стандартного сообщения Турбо Паскаля. Это сообщение имеет вид
Runtime error nnn at хххх:уууу,
где:
nnn — код (номер) ошибки;
хххх: уууу — адрес той инструкции в коде программы, при выполнении которой возникла исключительная ситуация.
Код ошибки и адрес ее возникновения хранятся в переменных ExitCode и ErrorAddr модуля system. Если в момент завершения программы эти переменные имеют значения соответственно о и NIL, никакого сообщения не выдается.
Создадим новый модуль с именем UsrExit, в интерфейсной части которого будет объявлена процедура UserExit. Эту процедуру (пример 25.1) может использовать любая программа для выдачи развернутой диагностики в момент аварийного завершения.
^■^бйаайет^юй^хг^^^зчхv^^v^r;:^--^-^^
Пример 25.1. Процедура диагностики аварийного завершения
-LГ.Г.Г
{=-==-====) UNIT UsrExit;{"«————}
INTERFACE
var
OldExit: Pointer;{Старое значение ExitProc}
Procedure UserExit;
{Процедура выхода. Осуществляет печать диагностического сообщения и завершает работу программы.}
Function HexB(b: Byte): String;
(Перевод байта в 16-ричное представление}
Function HexW(n: Word): String;
(Перевод слова в 16-ричное представление}
IMPLEMENTATION
Procedure UserExit;
(Процедура выхода. Осуществляет печать диагностического сообщения и завершает работу программы.}
var
txt: String;
begin {UserExit}
ExitProc :=01dExit; {Восстанавливаемработу стандартной процедуры выхода}
WriteLn('Ошибка при выполнении программы ’); case ExitCode of
1:
txt =
'Неверный номер функции';
2:
txt =
'Не найден файл';
3:
txt =
'Не найден путь’;
4:
txt =
* Слишком много открытых файлов';
5:
txt =
'Отказано в доступе к файлу';
6:
txt =
'Недопустимый файловый дескриптор *;
12:
txt :
=код доступа к файлу';
15:
txt :
=номер дисковода1;
16:
txt :
=удалить текущий каталог*;
17:
txt :
=указывать разные дисководы*
100
: txt
=чтения с диска *;
101
: txt
=записи на диск*;
102
: txt
=не присвоено имя *;
103
: txt
=не открыт *;
104
: txt
=не открыт для ввода’;
105
: txt
=не открыт для вывода’;
106
: txt
=числовой формат';
150
: txt
=защищен от записи';
151
: txt
=
152
: txt
:= 'Не готов диск';
153
: txt
=
154
: txt
=в исходных данных *;
155
: txt
=длина структуры*;
156
: txt
=установки головок на диске
157
: txt
=тип носителя *;
158
: txt
=найден сектор *;
159
: txt
=бумага на принтере *;
160
txt
=при записи на устройство';
161
: txt
=при чтении с устройства' ;
162
: txt
=
200
: txt
=на ноль’;
201
: txt
=при проверке границ *;
202
: txt
=
txt := ' Переполнение кучи' ;
txt := 'Недействительная операция с указателем*;
txt := 'Переполнение при операции с плавающей '+ 'запятой' ;
txt := 'Исчезновение порядка при операции '+
’ с плавающей запятой ’ ;
txt := 'Недопустимая операция с плавающей запятой';
txt := 'Не инициализирован оверлей';
txt := 'Ошибка чтения оверлейного файла'
else txt := 'ОшибкаN'+Str (ExitCode);
end;{case)
WriteLn(txt, ' по адресу
HexW (seg(ErrorAddr^)) , ' : ' , HexW(ofs(ErrorAddrA)) ) ;
ExitCode :.= 0;
ErrorAddr := NIL
end;{UserExit)
{>
Function HexB(b: Byte) : String;
/Перев од байта в 16-ричное представление}
const
HD: array [0..15]ofChar= ( 'О' , '1' , '2', '3', '4 ' , '5', '6' , ' 7 ', ' 8', ' 9',' А', 'В', 'С ,' О', 'Е' ,'F') ;
begin {НехВ}
HexB := HD[b shr 4]+НО[Ь and $F]
end; (НехВ)
{;
Function HexW(n: Word) : String;
(Перевод слова в 16-ричное представление)
begin {HexW}
HexW := НехВ (Hi (n)) +НехВ (Lo (п) )
end;{HexW}
end. (Unit UsrExit)
Внимание
Процедуру UserExit вовсе не обязательно объявлять дальней (с помощью зарезервированного слова Far), т. к. любая подпрограмма, объявленная в интерфейсной части модуля, автоматически транслируется в расчете на дальнюю модель памяти.
В процедуре userExit есть глобальная переменнаяВ нее програм
ма пользователя должна поместить первоначальное значение переменной ExitProc, чтобы осуществились все необходимые стандартные действия, связанные с завершением работы программы. Для вывода адреса ошибочного оператора содержимое переменнойпреобразуется в шестна-
формат с помощью функцииТакое преобразование в
программе пользователя может служить и для других целей, поэтому функция Hexw и вспомогательная функция нехв также объявлены в интерфейсной части модуля.
Использование процедуры UserExit проиллюстрируем следующей простой программой (пример 25.2).
Пример 25.2. Пример использования процедуры ,
Uses
var
x,y, z: real;
begin
OidExit := ExitProc;старое значение ExitProc)
ExitProc :=адрес процедуры
• repeat
ввести
ReadLn (x, у);(Вводим операнды}
zих
WriteLn (z: 15:7){Выводимрезультат}
until z = 0;
ExitProc := OidExit•стандартное
end.
В этой программе вводятся два произвольных вещественных числа и находится частное от деления первого на второе. Профамма повторяет эти действия до тех пор, пока частное не станет равным нулю, или пока'не возникнет исключительная ситуация. Последнее возможно в операторе
ReadLn(x,y)
если будет нарушен требуемый формат задания вещественных чисел (например, в записи числа будет указан недопустимый символ), или в операторе
z := х/у.
если делитель равен нулю.
Если вы решите использовать подобную процедуру в вашей программе, не забудьте восстановить стандартное значение NIL в переменной ExitProc перед завершением ее работы, в противном случае возможно "зависание" программы со всеми вытекающими отсюда последствиями.
Модуль F_Jump
Процедура userExit не делает главного: она не предохраняет программу пользователя от аварийного завершения. Чтобы продолжить исполнение программы после обработки исключительной ситуации, нужно уметь передавать управление из процедуры выхода в нужную точку программы. На первый взгляд такие действия нетрудно реализовать с помощью оператора GOTO, однако вспомним, что все метки локализуются в той подпрограмме, где они описаны, причем никакая метка не может быть глобальной. Иными словами, в Турбо Паскале невозможно осуществить выход из процедуры оператором
GOTO <ВНЕШЧЯ МЕТКА>
Для наших целей придется воспользоваться возможностями языка ассемблера. Учтем, что работа любой программы на уровне машинных команд управляется содержимым нескольких регистров центрального процессора. Для вычисления адреса очередной команды микропроцессор извлекает номер сегмента из регистра cs, а смещение — из регистра IP; для доступа к данным номер сегмента берется из регистра DS, а смещение — из вх или индексного регистра (si или DI); наконец, для доступа к программному стеку используется содержимое ss как сегмент стека, а содержимое одного из регистров-указателей (SP или bp) — как смещение. Таким образом, для организации правильного выхода из процедуры завершения в нужную точку программы необходимо где-то запомнить содержимое этих регистров, которое соответствует корректному исполнению программы с нового места. На практике достаточно запомнить содержимое лишь четырех регистров — SP, ВР, cs и IP. Остальные регистры в ходе работы программы, написанной на Турбо Паскале, или не меняются, или автоматически обновляются.
typeJumpRec = recordSPsave,BPsave: Word;JmpPtr: PointertypeJumpRec = recordSPsave,BPsave: Word;JmpPtr: PointerДля запоминания указанных регистров введем переменные следующего типа:
type
JumpRec = record
SPsave,
BPsave: Word;
JmpPtr: Pointer
type
JumpRec = record
SPsave,
BPsave: Word;
JmpPtr: Pointer
(Здесь сохраняется SP)
(Здесь — регистр BP}
(Здесь - CS и IP)
end;
В программе можно объявить сколько угодно переменных этого типа и связать их содержимое с различными точками выхода.
Общая схема управления программой, в которой осуществляется блокировка аварийного завершения, должна быть такой. Перед выполнением некоторого критического фрагмента программы, т. е. фрагмента, в котором может возникнуть исключительная ситуация, необходимо запомнить в переменной типа JumpRec нужную информацию (как это делается, мы обсудим ниже) и указать в переменной ExitProc адрес процедуры выхода. Если в ходе исполнения фрагмента возникнет исключительная ситуация, управление будет передано процедуре выхода, в конце которой следует восстановить содержимое сохраненных регистров. Сразу после того как в cs и IP будет помещена необходимая информация, работа программы продолжится с нужного места.
Сохранение регистров SP и cs не вызывает особых сложностей — в Турбо Паскале для доступа к ним предусмотрены стандартные функции SPtr и cseg. Однако в стандартных библиотеках нет аналогичных функций для чтения регистров ВР и IP. Во всех случаях восстановление сохраненных значений регистров возможно только с помощью ассемблера (в дальнейшем для краткости я буду писать "ассемблер" вместо "язык ассемблера").
Особые сложности связаны с сохранением текущего значения регистра IP, в котором содержится адрес (точнее, смещение) очередной машинной команды. Дело в том, что с адресом любого исполняемого оператора Турбо Паскаля может быть связана только метка, однако в Турбо Паскале не предусмотрено никаких операций над метками. В частности, невозможно получить связанный с меткой адрес. Таким образом, для определения адреса нужного оператора придется использовать единственно возможный путь: разместить ассемблерный фрагмент непосредственно перед этим оператором и вычислить расстояние в байтах от некоторого места внутри ассемблерной части до начала этого оператора.
Размещение ассемблерного фрагмента в теле программы не вызывает особых проблем — для этого в вашем распоряжении имеются средства встроенного ассемблера и InLine-ОПераторов. Отмечу, что в силу тривиальности ассемблерных частей их можно реализовать в виде макросов. Макрос представляет собой процедуру, тело которой заменяет один InLine-ОПератор. Такая конструкция рассматривается компилятором как готовая программная заготовка, снабженная собственным именем. Всюду, где в тексте программы встречается вызов макроса, компилятор автоматически вставляет нужные программные коды. Макросы могут иметь параметры, которые перед передачей управления помещаются в стек в соответствии с соглашениями Турбо Паскаля. Однако для вызова макроса не используется машинная инструкция CALL, как это обычно бывает при вызове подпрограммы, ведь тело макроса располагается сразу за предыдущим оператором Турбо Паскаля. По этой
причине в конце макроса не следует указывать инструкцию RET. Во всем остальном макросы ничем не отличаются от других InLine-Процедур.
Текст модуля F_Jump
Ниже приводится текст модуля F_Jump (пример 25.3), в котором объявляется тип JumpRec и два макроса SetJump и Longjump. Макрос SetJump осуществи ляет необходимые действия по сохранению регистров, поэтому его необходимо размещать непосредственно перед тем оператором, на который требуется передать управление в случае возникновения исключительной ситуации. Макрос Longjump осуществляет обратные действия — восстанавливает сохраненные ранее регистры. В результате его работы управление будет передано в нужную точку программы, поэтому этот макрос следует размещать внутри процедуры выхода, непосредственно перед ее завершением.
: •2 v у j::
i Пример 25.3. Сохранение и восстановление регистров
:..„......?."».*.....тат«?Х»1811Ж11№Ле7иЛ/1«1|11..'.?№№й?,».ЪтЛ
f======j UNIT F_ Jump;{==============}
(
(Сохраняет регистр SP}{Сохраняет регистр DP){EAR-адрес продолжения)(Сохраняет регистр SP}{Сохраняет регистр DP){EAR-адрес продолжения)I Модуль содержит ма кроопределения, позволяющие запомнить \ \ любую точку программы с тем, чтобы после обработки ошибки| I периода исполнения передать управление в эту точку. \
(Сохраняет регистр SP}
{Сохраняет регистр DP)
{EAR-адрес продолжения)
(Сохраняет регистр SP}
{Сохраняет регистр DP)
{EAR-адрес продолжения)
InLine (
$5F/
{pop di
$07/
;DI=Ofs (JumpDest))
{pop es
$26/$89/$25/
;ES=Seg(JumpDest)}
{moves:[di],sp
$26/$89/$6D/$02/
; помещаем SP в SPsave }
{moves:[di+2],bp ;помещаем ВР в BPsave }
$Е8/$00/$00/
{call null
/помещаем в стек текущий}
.
{null:
$58/
/адрес командой CALL)
{popах
$05/$0C/$00/
;АХ=текущий адрес )
'
/addax, 12
$2б/$89/$45/$04/
;смещение команды NEXT }
(mov es:[di+4],ax ;сохраняем FAR-адрес } $26/$8C/$4D/$06);
{mov es:[di+ 6],cs ;команды NEXT в JmpPtr }
{NEXT: ; эта инструкция идет сразу за макросом и связана с началом очередного оператора Турбо Па скаля} (;
Procedure LongJump (varJumpDest: JumpRec) ;
{Этот макрос восстанавливает содержимое регистров SP и ВР из полей SPsave, BPsave записи JumpDest и делает FAR-переход по адресу JmpPtr. }
inline (
$5F/
/popdi
$07/
;DI=Ofs(JumpDest)}
/popes
$26/$8B/$25/
;ES=Seg (JumpDest) }
{mov sp,es:[di] $26/$8B/$6D/$02/
;восстанавливаем SP ;
/восстанавливаем $26/$FF/$6D/$04);
far;}
IMPLEMENTATION
(============} end. (F_Jump} {—===========}
Макрос располагается непосредственно перед оператором, на который передается управление при возникновении исключительной ситуации. Так как для выполнения всех предусмотренных в макросе действий он должен получить управление, следующий за ним оператор Турбо Паскаля может выполняться дважды: первый раз при инициализации процедуры выхода, т. е. до возникновения исключительной ситуации (и сразу после выполнения макроса SetJump), второй раз после ее возникновения (и сразу после выполнения макроса Разумеется, второй проход оператора вовсе не обязателен — он будет осуществлен только при возникновении исключительной ситуации. Однако программист обязан помнить о своеобразной реентерабельности этой точки программы и предусмотреть в ней альтернативные действия. Таким образом, оператором, на который передается управление в исключительной ситуации, как правило, является условный оператор. В качестве условия, управляющего его работой, следует использовать какой-то признак, свидетельствующий о возникновении исключительной ситуации или о ее отсутствии. Этим признаком может быть логический флаг, устанавливаемый процедурой выхода, или содержимое стандартных переменных и ErrorAddr (если, разумеется, процедура выхода не сбрасывает эти значения).
Тестовая программа
В следующей программе (пример 25.4) осуществляется ввод произвольного вещественного числа и находится частное от деления единицы на корень квадратный из этого числа. В программе возможно возникновение исключительной ситуации при вводе числа (ошибочный символ в записи вещественного числа), извлечении корня (число меньше нуля) и делении (делитель равен нулю). Все три критические точки запоминаются в переменной Jump, которая используется в исключительной ситуации. Программа повторяет описанные действия до тех пор, пока не будет введена единица.
Тестовая программа
(++
Программа иллюстрирует механизм выхода из процедуры \ завершения в нужную точку программы. В программе I
I осуществляется ввод произвольного целого числа, затем \
I вычисляется корень квадратный из него и находится \
I обратная ему величина. Для завершения работы программы \
Iследует ввести единицу.|
у +)
Uses F_Jump;
var
OldExit: Pointer;{Старое значение переменной ExitProc)
Jump: JumpReс;{Сохранение точки выхода)
Г: Real;{Вспомога тельная переменная)
label
Loop;
Procedure MyExit; Far;
{Эта процедура обрабатывает исключительную ситуацию)
begin
LongJump(Jump)
end;{MyExit}
begin
OldExitExitProc;
repeat
Loop:{Точка повторения ввода}
if ErrorAddr <> NIL then
begin
Write('Повторите ввод числа: 1); ErrorAddr := NIL;
ExitCode := 0
end
else
Write('Введите число: ') ;
ExitProc := @MyExit;{Адрес процедуры выхода}
SetJump(Jump);{Контролируем ввод данных}
if ErrorAddr <> NIL then
begin
WriteLn('Ошибка при вводе вещественного числа');
Goto Loop
end
else
ReadLn(г);
извлечение
if ErrorAddr <> NIL then
begin
при извлечении
Goto Loop
end
else
r :=
Set Jump (Jump);{Контролируем деление )
if ErrorAddr <> NIL then begin
при делении
Goto Loop
end
else
r :=
WriteLn(r:20:10)
•
until r = 1;
ExitProc := OldExit
end.
Как нетрудно заметить, процедура обработки исключительной ситуации MyExit в рассмотренном примере просто возвращает управление в нужную точку программы. Разумеется, в реальной программе ее можно сделать более сложной. Однако для большей "прозрачности" программы я рекомендую все . дополнительные действия, связанные с обработкой исключительной ситуации, размещать в теле основной программы — ведь в критических точках обычно используются условные операторы вида
if ErrorAddr <> NIL
Отмечу одну особенность: непосредственно перед передачей управления в пользовательскую процедуру выхода Турбо Паскаль автоматически помещает в переменную ExitProc значение NIL. По этой причине оператор
ExitProc :=
помещен в тело цикла Repeat. .Если разместить его перед операто
ром Repeat, то после первой же ошибки профамма перестанет обрабатывать исключительные ситуации, поэтому повторная ошибка приведет к аварийному завершению
При использовании описанного средства обработки исключительных ситуаций в сложных программах с несколькими модулями следует учесть, что при передаче управления в подпрограмму другого модуля обычно изменяется значение сегмента кода. Если процедура обработки ошибки находится у вас в одном модуле, а ошибка возникла в процедуре из другого модуля, управление передается в процедуру обработки ошибки со значением сегмента кода, соответствующим модулю, в котором произошла ошибка. В результате процедура LongJump не сможет установить правильный NEAR-адрес переменной Jump и программа "зависнет". Чтобы этого не произошло, Longjump должна содержать FAR-адрес переменной Jump. Проще всего этого можно достичь, если поместить переменную Jump в интерфейсную часть любого модуля и с помощью этой переменной организовать обработку исключительных ситуаций.
221 Зак. 941
//
/
/
Глава 26
Работа с диском на физическом уровне
В Турбо Паскале есть довольно богатый набор подпрограмм, рассчитанных на работу с файлами и каталогами. Однако в некоторых нестандартных приложениях, таких как защита программ от компьютерных вирусов или нелегального копирования (см. главы 29 и 30), требуется обратиться не к файлу, а к диску как таковому, т. е. работать с диском на физическом уровне (чтение/запись дисковых секторов безотносительно их логической привязки к тому или иному файлу). В этой главе обсуждаются общие проблемы работы с дисками и описываются средства модуля F_Disk, содержащего набор соответствующих подпрограмм.
Работа с диском на физическом уровне требует от программиста предельного внимания, т. к. ошибочное обращение может безвозвратно нарушить файловую структуру. Я настоятельно рекомендую использовать описываемые в этом разделе средства только в случае крайней необходимости, когда стандартные подпрофаммы не могут обеспечить требуемого результата. Думаю, что вы будете достаточно благоразумны и пожертвуете отдельную дискету для ваших экспериментов с диском.
1
Структура дисков ДОС
Чтобы работать с диском на физическом уровне, необходимо ясно представлять себе детали устройства и особенности использования диска. В этом разделе излагаются минимальные сведения о физическом устройстве и логической организации дисковых накопителей ПК.
Принцип работы дисковых накопителей
Любой дисковый накопитель информации основан на принципе независимого перемещения диска и головок считывания/записи. Диск имеет двустороннее ферромагнитное покрытие и вращается специальным двигателем со
строго постоянной скоростью. С каждой стороны диска к ферромагнитному слою тесно прилегают по одной головке считывания/записи (см. рис. 26.1).
Головки могут перемещаться в радиальном направлении отдельным шаговым приводом и фиксироваться в любом новом положении. Собственно говоря, только возможностью перемещения головок относительно диска дисковые накопители радикально отличаются от обычных бытовых магнитофонов, во всем остальном принципы их действия очень схожи: головки взаимодействуют с вращающимся ферромагнитным слоем диска и записывают или считывают информацию подобно тому, как записывается или считывается звук на магнитной ленте. Однако в отличие от магнитофонов для поиска нужной информации на диске не требуется просматривать всю записанную информацию от самого начала, а достаточно просто сместить головки к нужной дорожке. Такой способ доступа к информации называется произвольным, в то время как используемый в ленточных накопителях просмотр ленты для поиска информации называется последовательным способом доступа.
Рис. 26.1. Головки и дорожки дискового накопителя
Головки взаимодействуют с покрытием только после прекращения радиального перемещения и фиксации в новом положении, поэтому каждое новое положение головки оставляет на поверхности вращающегося диска невидимые следы в виде концентрических окружностей, которые называются дорожками. Каждая дорожка по окружности делится на одинаковое количество секторов, в пределах которых и осуществляется запись/считывание информации. Программы дисковой операционной системы MS-DOS записывают в любой сектор информацию объемом не более 512 байт. Таким образом, общая емкость D любого гибкого диска, используемого ДОС, составляет
D = 512 х Ntx Nsx 2 байт,
где Nt — количество дорожек, N$ — количество секторов на дорожке, а множитель 2 учитывает две поверхности дискеты. Например, для дискет диаметром 133 мм (5 V4 дюйма) для формата DSDD (Double Side, Double Density — две стороны, двойная плотность) N, = 40, Ns = 9, поэтому емкость равна
D = 512 х 40 х 9 х 2 = 360 х 1024 = 360 Кбайт,
а для формата DSHD (Double Side, High Density — две стороны, высокая плотность) Nt = 80, Ns — 15 и емкость дискеты составляет
D 512 х 80 х 15 х 2 = 1200 х 1024 = 1,2 Мбайт.
В конструктивном отношении различают накопители на гибких дискетах (ГД) и на жестких дисках (ЖД). Принцип действия обоих устройств одинаков, однако ЖД имеют значительно большую емкость и существенно меньшее время доступа (т. е. время поиска нужного сектора и обмена информации), чем ГД. С другой стороны, ГД могут легко выниматься и вставляться в накопитель, что делает их незаменимым средством обмена данными и программами между ПК. Увеличенная емкость ЖД достигается значительным по сравнению с ГД. увеличением скорости вращения диска, что позволяет записывать на нем данные с большей плотностью. В результате ЖД обычно имеет больше дорожек и секторов. Кроме того, в накопителях этого типа, как правило, имеется несколько дисков, закрепленных на одном шпинделе. С каждым диском взаимодействует своя пара головок (по одной на каждую поверхность). Головки закрепляются на одном держателе и одновременно перемещаются в радиальном направлении. Совокупность всех дорожек на всех дисках для одного какого-либо положения головок называется цилиндром.
Перемещение головок и информационный обмен данными осуществляется под управлением специального устройства, называемого контроллером диска. Обычно в ПК используются два контроллера — для ГД и ЖД, иногда оба типа дисковых накопителей управляются общим контроллером.
Логическая структура дисков
G физической точки зрения все секторы одинаковы — как уже говорилось, их емкость в MS-DOS всегда равна 512 байтам. Однако программы операционной системы закрепляют за каждым сектором некоторые определенные функции, что позволяет говорить о логической структуре дисков.
Любой диск в ДОС имеет четыре логических части: загрузочный сектор, таблицу распределения файлов, корневой каталог и архивное пространство.
Загрузочный сектор
Загрузочный сектор — это самый первый сектор на самой внешней (нулевой) дорожке. Загрузочный сектор содержит важную информацию о логической структуре диска и короткую программу начальной загрузки. Содержимое загрузочного сектора автоматически считывается в оперативную память после включения ПК, затем управление передается программе начальной загрузки. Эта программа считывает в память нужные части операционной системы и передает им управление. Таким образом, загрузочный сектор нужен прежде всего для начальной загрузки; операционной системы.
Начиная с байта с порядковым номером 12, в загрузочном секторе размещается так называемый блок параметров BIOS (ВРВ), содержащий информацию о длине и расположении остальных логических частей диска и некоторые другие важные параметры. Информация в ВРВ соответствует следующему типу данных:
type
=
record
•
SectSiz
Word;
{Количество байт в. секторе: для MS-DOS всегда 512 байт}
ClustSiz
Byte;
{Количество секторов в кластере}
ResSecs
Word;
{Количество секторов перед FAT)
FatCnt
Byte;
{Количество копий FAT}
RootSiz
Word;
{Количество элементов корневого каталога}
TotSecs
Word;
{Количество секторов на диске}
Media
Byte;
{Дескриптор носителя}
• •• , .
FatSize
Word
{Количество секторов в FAT}
end; (ВРВ_
.Туре}
В этой структуре поле SectSiz, определяющее размер сектора в байтах, для любых операционных систем, совместимых с MS-DOS, всегда имеет значение 512. Дисковые накопители могут читать и записывать секторы и другого размера — 128, 256 или 1024 байт. Такие секторы не могут обслуживаться ДОС и являются для нее "плохими" — при попытке доступа к ним программы ДОС выдадут сообщения об ошибке. Это обстоятельство широко используется для защиты программ от несанкционированного копирования (см. главу 30).
Несколько смежных секторов диска объединяются в группу, называемую кластером. Кластер — это минимальная порция дискового пространства, которую ДОС может выделить файлу. Поле ClustSiz содержит количество секторов в одном кластере. Обычно это число представляет собой степень двойки: 1, 2, 4, 8 и т. д. Причем, чем больше объем диска, тем крупнее кластер. Для ГД размер кластера чаще всего составляет 2 сектора (для дискет
диаметром 5 */4 дюйма) или 1 сектор (31 /2дюйма). Для ЖД в кластер обычно входят 4, 8 или 16 секторов.
Поле Media определяет тип носителя информации. Стандартными значениями этого поля могут быть:
П $FF — дискета 320 Кбайт (2 стороны, 8 секторов);
$FE — дискета 160 Кбайт (1 сторона, 8 секторов);
$FD — дискета 360 Кбайт (2 стороны, 9 секторов);
О $FC — дискета 180 Кбайт (1 сторона, 9 секторов);
П $F9 — дискета 1,2 Мбайт (2 стороны, 15 секторов);
П $Е8 — жесткий диск.
Как показывает практика, содержимое поля Media не всегда соответствует действительности (например, во многих случаях жесткий диск имеет дескриптор $оо). Пожалуй, единственным относительно надежным признаком может служить информация в трех младших битах этого поля (табл. 26.1).
Таблица 26.1. Значения младших битов поля Media
Разряд
Значение
Признак
0
1
Используются 2 стороны носителя
0
Используются не 2 стороны
1
1
На дорожке 8 секторов
0
На дорожке не 8 секторов
2
1
Съемный носитель информации
0
Несъемный носитель информации
Назначение остальных полей ВРВ будет пояснено дальше.
Сразу за ВРВ (начиная с байта с номером 25) в загрузочном секторе располагается следующая структура данных:
type
Add_BPB_Type = record
TrkSecs: Word;
HeadCnt: Word;
{Количество секторов на
(Количество головок}
дорожке}
HidnSec: Word end;{Add_BPB_Type)
{Количество спрятанных
секторов}
Поле HidnSec содержит общее количество "спрятанных" секторов, т. е. секторов, не относящихся к логической структуре диска (см. ниже). Начиная с вер-
сии 4.0 в ДОС вошли средства, поддерживающие жесткие диски большого размера (больше 32 Мбайт). Поэтому для версий 4.x и выше структура данных, располагающихся за ВРВ, такова:
type
Add_BPB_4 Type = record
trkSecs :секторов на дорожке}
HeadCnt : Word;{Количество головок)
HidnSecLo: Word(Младшее слово количества
спрятанных секторов}
HidnSecHi: Word{Старшее слово}
TotSecs : Longlnt{Полное количество секторов на диске}
end;(Add_BPB_4_Type}
Признаком того, что используется расширенный блок ВРВ, является значение 41 (символ )) в 39-м по счету байте загрузочного сектора.
Физический диск может быть разбит на несколько логических разделов, каждый из которых имеет свою логическую структуру. С точки зрения пользователя, каждый раздел выступает как самостоятельный диск и называется логическим диском. Разбивка физического диска на ряд логических устройств осуществляется утилитой FDisk операционной системы или подобными ей несистемными средствами (ADM, Disk Manager и т. п.). Если диск разбит на разделы, в конце загрузочного сектора выделяется 64-байтное поле описателей разделов (по 16 байт на каждый логический диск). Это поле начинается с байта с номером 447 (смещение $1ВЕ от начала сектора). Каждый описатель раздела, создаваемый утилитой FDisk, соответствует такой структуре данных:
type
Part_Type = record
BootF: BegHd:BegSCBootF: BegHd:BegSCByte ;Byte ;Word;Byte ;Byte ;Word;SysByte ;EndHd Byte ;EndSC Word;Secs : LonglntSizeLonglntend;{Part_Type}SysByte ;EndHd Byte ;EndSC Word;Secs : LonglntSizeLonglntend;{Part_Type}(Флаг активности раздела}(Начало раздела — номер головки}(Начало раздела — сектор/цилиндр}(Код системы}{Конецраздела — номер головки}{Конец раздела — сектор/цилиндр}(Относительный номер начального сектора} (Длина раздела в секторах}(Флаг активности раздела}(Начало раздела — номер головки}(Начало раздела — сектор/цилиндр}(Код системы}{Конецраздела — номер головки}{Конец раздела — сектор/цилиндр}(Относительный номер начального сектора} (Длина раздела в секторах}
BootF: BegHd:
BegSC
BootF: BegHd:
BegSC
Byte ;
Byte ;
Word;
Byte ;
Byte ;
Word;
SysByte ;
EndHd Byte ;
EndSC Word;
Secs : Longlnt
SizeLonglnt
end;{Part_Type}
SysByte ;
EndHd Byte ;
EndSC Word;
Secs : Longlnt
SizeLonglnt
end;{Part_Type}
(Флаг активности раздела}
(Начало раздела — номер головки}
(Начало раздела — сектор/цилиндр}
(Код системы}
{Конецраздела — номер головки}
{Конец раздела — сектор/цилиндр}
(Относительный номер начального сектора} (Длина раздела в секторах}
(Флаг активности раздела}
(Начало раздела — номер головки}
(Начало раздела — сектор/цилиндр}
(Код системы}
{Конецраздела — номер головки}
{Конец раздела — сектор/цилиндр}
(Относительный номер начального сектора} (Длина раздела в секторах}
распреде- на одном разделов)распреде- на одном разделов)
распреде- на одном разделов)
распреде- на одном разделов)
Описываемая логическая структура (загрузочный сектор, таблица ления файлов и т. д.) создается на каждом логическом диске, т. е. физическом диске существует несколько (по числу логических
загрузочных секторов. Самый первый загрузочный сектор называется главным — именно он используется для начальной загрузки операционной системы. Этот сектор программами BIOS считывается в буфер, начинающийся по адресу $7С00:$0000, и по этому же адресу передается управление. Получив управление, главный загрузчик просматривает таблицу разделов диска и выбирает раздел (логический диск), имеющий признак активности в поле BootF (если диск активен, это поле имеет значение $80, в противном случае — о). Затем в память считывается соответствующий загрузочный сектор и управление передается ему. Таким образом, если жд разбит на разделы, загрузка ДОС проходит в два этапа. Это позволяет иметь на одном диске несколько загрузчиков для разных операционных систем. Например, активный логический диск может нести сетевую операционную систему, в этом случае его загрузчик осуществляет загрузку этой системы.
В большинстве случаев разделы физического диска соответствуют одной и той же операционной системе и служат для более удобной структуризации больших объемов дисковой памяти.
При анализе главного загрузочного сектора следует иметь в виду важное соглашение ДОС: описатели логических разделов диска построены по принципу связанного списка (см. рис. 26.2).
Рис. 26.2. Связанный список описателей жесткого диска
В секторе главного загрузчика используются лишь два описателя Part_Type из четырех возможных: первый описатель (он называется описателем пер-
вичного раздела ДОС) определяет физическое расположение и параметры логического диска С:, а второй (описатель расширенного раздела ДОС) — расположение на диске следующего описателя. В секторе, соответствующем второму описателю, значащими являются только два поля Part_Type, первое из которых определяет логический диск D:, а второе содержит ссылку на новый описатель и т. д.
Таким образом, поля Partjrype во всех загрузочных секторах, кроме главного, не несут полезной информации и должны игнорироваться, а параметры дисков определяются связанным списком описателей, начато которого содержится в главном загрузчике.
Следующая программа (пример 26.1) считывает главный загрузчик и организует просмотр связанного списка описателей логических разделов первого жесткого диска.
: *♦“ v*“* • v ■ • ••■”.•••••••,•.•• • «а* • va- • ••••••»•• •^у^^м^^М'^чу^у у *? **1 ?“'*•’*-"■* * 4м*"А^ШЦй(^|й^• «^... v ••?••• ^у^ц*? у ••• •"' * Пример 26.1. Программа считывания главного загрузчика диска’;
•; V.'.. . >. Л. ^7 • Ль »«I.. . А" < «V Л..«• Л XiWftlflidiSftfta <«^И<ЖйУ..*»7?'.*?№М;х 7i$Wt#KWXnai3iBMmTwlt» RWWW^mrW^ro^№l^^wG^« XwWwmfl^^Rv? I :•. •
I Эта программа считывает ссылочную структуру \
I описателей разделов первого жесткого диска \
I и выводит на экран таблицу логических дисков \
}
Uses DOS;
Function ReadSec(Disk,Head: Byte;
CylSec: Word; var Buf) : Boolean;
{Обеспечивает считывание абсолютного сектора с помощью прерывания $13 и возвращает TRUE, если нет ошибки}
var
R: Registers; begin
with R do begin
ah :—2;
(Код операции чтения}
al : =
(Колич. читаемых секторов}
dl : = Disk;
{Номер диска: 0,1 - ГД,80,$81 -
dh = Head;
(Номер головки)
ex : = CylSec;
(Сектор и цилиндр}
es=
(Адрес буфера в ES:BX)
bx : = ofs(Buf);
Intr($13,R);
ReadSec := (Flags and FCarry)=0 end
end; {ReadSec}
{?
type
{Описатель раздела}
Part_Type = record BootF: Byte;
(Флаг активности раздела}
BegHd: Byte;
{Начало раздела — головка}
BegSC: Word;
{Начало раздела — сектор/цилиндр}
Sys : Byte;
{Код системы}
EndHd: Byte;
{Конецраздела — головка}
EndSC: Word;
{Конецраздела — сектор/цилиндр},
Secs :
{Относительныйномер начального сектора}
Size : Longlnt;
end;{Par t_ Type }
{Длина раздела в секторах}
{Структура ссылочного сектора)
BootSecType = record
array [0..$1BD] of Byte;
Part: array [1..4] of Part_Type;
array [1..2] of Byte
end;
const
MAXPART = 24; (Максимальное количество логических дисков}
var
Boot
: BootSecType;
Disk
: array [1..MAXPART] of record
Hd: Byte;{Головка}
Тг: Word;{Дорожка}
Se: Byte;{Сектор}
PT: Part_Type;
end;
k,d,j: Byte;
begin
d : = 0;{ Номер очередного логического диска}
(Читаем главный загрузчик}
if not ReadSec($80,0,1,Boot) then
begin
WriteLn('Ошибка чтения главного загрузчика'); Halt
end;
{Формируем адрес описателя диска С}
Disk[l].Hd := 0;
Disk[l].Tr := 0;
Disk[l]. Se := 1;
(Цикл чтения описателей логических дисков}
with Boot do
repeat
k1;(Ищем непустой описатель}
while (Part[k).BegSC=0) and (k<4) do inc(k);
if k<=4 then
begin{Найден непустой описатель}
if d<MAXPART then
begin
inc (d);(Номер очередного диска}
Disk[d].PT := Part[k] (Сохраняем описатель} end;
inc(k);(Part[k] -ссылка на следующий
описатель раздела}
if (k<=4) and (Part [k] .BegSCoO) then
with Part[k],Disk[d+1] do begin(Адрес очередного описателя)
Hd := BegHd;
Tr := (BegSC and 192) shl 2+BegSC shr 8;
Se := BegSC and 63 end
end;
{Про доджа ем цикл до ошибки чтения или пустой ссылки на очередной описатель)
untilor (k>4) or
not
таблицу логических
Write'
for j := 1 to d do
Write(chr(ord('B')+j):10) ;
Write(#13#10'Активный',' *:4);
for j := 1 to d do withdo
if
Write('Да':10)
else
Write('Нет':10);■
Write(#13#10'Код системы
for j1 to d do withdo
Write(Sys:10);
Write(#13#10*Объем (байт)');
for j := 1 to d do withdo
Write
WriteLn(#13#10'Начало:');
Write (' отн.сектор');
for j := 1 to d do withdo
Write(Secs:10);
Write(#13#10'головка
for j := 1 to d do withdo
Write(BegHd:10);
Write(#13#10'дорожка
for j := 1 to d do withdo
and 192)2+
BegSC shr
Write(#13#10*сектор
for j := 1 to d do withdo
and
WriteLn(#13#10'Описатель:’) ;
головка
for j := 1 to d do withdo
• V дорожка
for j := 1 to d do with Disk[j] do Write(Tr:10);
Write (#13#10' сектор ');
for j := 1 to d do with Disk[j] do Write (Se:10);
WriteLn
end.
В этой программе для доступа к физическим секторам используется прерывание BIOS $13, которое подробно описывается в разд. 26.2.
Поле Sys описателя раздела может иметь одно из значений, указанных в табл. 26.2.
Таблица26.2. Значения поля SYS
Значение
СмыслЗначение Смысл
0
Неизвестная операционная4Операционная система
системаMS-DOS, 16-битная FAT
1
Операционная система5Описатель логического
MS-DOS, 12-битная FATраздела
Таблица распределения файлов
За загрузочным сектором располагается так называемая таблица распределения файлов (File Allocation Table - FAT).Эта таблица начинается в секторе BPB.Ressecs и содержит информацию о принадлежности каждого кластера тому или иному файлу. Обычно на диске создается несколько копий FAT (чаще всего две копии), содержимое которых строго идентично. Дублирование информации в нескольких копиях FAT повышает надежность ее хранения. Количество копий FAT и размер каждой копии (в секторах) задаются полями FATCnt и FATSize блока ВРВ.
FAT представляет собой однородную структуру элементов, каждый из которых логически связан с одним кластером диска. Элементы FAT нумеруются, начиная с номера 0, однако первые два элемента используются особым образом, так что кластеры имеют номера, начиная с 2. Чтобы определить, занят ли кластер с номером N, нужно проанализировать N-й элемент FAT: если элемент содержит 0, кластер не занят полезной информацией, в противном случае он содержит ссылку на следующий связанный с ним кластер или специальный признак конца ссылок.
tin dat’ 08 ’I l I I I И Itin dat’ 08 ’I l I I I И Iевев00 01 82 038405 06 07eg8ft0Bec8DBE8F 1 I 1 1—1 г*-1 ( Г— Г~I Г> г—1 1 1 г-! — — — Г—100 01 82 038405 06 07eg8ft0Bec8DBE8F 1 I 1 1—1 г*-1 ( Г— Г~I Г> г—1 1 1 г-! — — — Г—1евев000 Вб][вв|16^17^191 11 L_|F7ia^ib-*ff| |вв | jвв11вв1100 j000 Вб][вв|16^17^191 11 L_|F7ia^ib-*ff| |вв | jвв11вв1100 j101000 0000 0003=»B1-*05-*FF 00 08 B9=*-0A-^DB=N-15 0003=»B1-*05-*FF 00 08 B9=*-0A-^DB=N-15 00
tin dat’ 08 ’
I l I I I И I
tin dat’ 08 ’
I l I I I И I
ев
ев
00 01 82 038405 06 07eg8ft0Bec8DBE8F
1 I 1 1—1 г*-1 ( Г— Г~I Г> г—1 1 1 г-! — — — Г—1
00 01 82 038405 06 07eg8ft0Bec8DBE8F
1 I 1 1—1 г*-1 ( Г— Г~I Г> г—1 1 1 г-! — — — Г—1
ев
ев
000 Вб][вв|
16^17^19
1 11 L_|
F7
ia^ib-*ff| |вв | jвв11вв1100 j
000 Вб][вв|
16^17^19
1 11 L_|
F7
ia^ib-*ff| |вв | jвв11вв1100 j
10
10
00 00
00 00
03=»B1-*05-*FF 00 08 B9=*-0A-^DB=N-15 00
03=»B1-*05-*FF 00 08 B9=*-0A-^DB=N-15 00
При выделении дискового пространства новому файлу ДОС ишет в FAT первый незанятый кластер и помещает номер этого кластера в связанный с файлом элемент каталога (см. ниже). Если файл целиком умещается в одном кластере, в соответствующий элемент FAT помещается признак занятости, в противном случае — номер того кластера, в котором находится продолжение файла (см. рис. 26.3).
Элементп— г—<
оглавления МV FILE I X Т|а
U । 1 । । । ।l_il. [-а 1
Рис. 26.3. Цепочка ссылок FAT
Как видно из этого рисунка, файл MYFILE.TXT начинается в кластере с номером $08 и занимает кластеры $08, $09, $0А, $ов, $15, $16, $17, $19, $1А И $1В.
В ДОС используются FAT двух типов: с 12- и 16-битными элементами. В первом случае два смежных кластера описываются трехбайтной последовательностью, так что на каждый кластер приходится полтора байта описателя. Такие таблицы используются на дискетах и жестких дисках небольшой емкости (до 10. Мбайт). В 16-битных FAT под каждый описатель выделяются два байта. Начиная с версии 4.0, в загрузочном секторе со смещением 54 байта от начала располагается 8-байтное поле с символами FAT 12 или FAT 16 в зависимости от типа элементов FAT. Для предыдущих версий ДОС такого явного указания типа FAT нет. Вместо этого действует следующее соглашение: если общее количество кластеров больше 4080 или общее количество секторов больше 20 740, FAT использует 16-битные элементы. Значение 4080 определяется как полная емкость 12-разрядного числа (4096) минус 16 номеров, зарезервированных для особых применений (указатели конца цепочки и плохих кластеров). Значение 20 740 определяет максимальную емкость логического диска в 10 Мбайт: если диск имеет большую емкость, его FAT оперирует с 16-разрядными элементами. Замечу, что поле sys в описателе логического диска содержит 1 или 4 в зависимости от типа FAT.
Первый байт FAT содержит значение ID, совпадающее с дескриптором носителя Media из ВРВ. Два следующих за ним байта для 12-разрядной или 3 байта для 16-разрядной FAT всегда имеют значение $FF. Таким образом, элементы 0 и 1 FAT используются особым образом, поэтому нумерация кла-
стеров начинается с номера 2. Старшие номера кластеров, начиная с номера $FFO для 12-разрядной и $FFFO для 16-разрядной FAT, служат для специальных целей (в скобках указаны значения для 16-разрядной FAT):
G от $FFO ($FFFO) до $FF6 ($FFF6) w^^™ зарезервированные кластеры;
$FF7 ($FFF7) — плохой кластер;
ОТ $FF8 ($FFF8) ДО $FFF ($FFFF) — конец цепочки ССЫЛОК.
Структура каталогов
Сразу за последней копией FAT на диске располагается корневой каталог. Содержимое каталога описывает все зарегистрированные в нем подкаталоги и файлы и выводится на экран командой DIR операционной системы. Каждый элемент корневого каталога и всех подкаталогов представлен такой 32- байтной структурой данных:
type
DirJType = record
Namearray[1..8]ofChar;{Имя файла}
Extarray[1..3]ofChar;{Расширение)
FAttrByte ;{Атрибуты файла}
Reservarray[1..10] of Byte;{Резерв}
TimeWord;{Время создания}
DateWord;(Дата создания)
FirstC Word;(Номер первого кластера}
Size : Longlnt{Размер файла в байтах}
end;{ Dir_Type}
Символы в полях Name и Ext обычно преобразуются к прописным и выравниваются влево, т. е. неиспользуемые символы имени и расширения заполняются ведомыми пробелами. Для имен каталогов оба поля рассматриваются как одно целое, таким образом имя каталога может состоять из 11 символов.
Байт FAttr содержит атрибут файла. ДОС использует значения этого поля, указанные в табл. 26.3.
Таблица 26.3. Значения поля FAttr
Значение
Смысл
Значение
Смысл
0
Обычный (архивный) файл
4
Системный файл
1
Файл защищен от изменений
8
Идентификатор тома
и стирания
$10
Подкаталог
2
Скрытый файл
$20
Признак архивации
Допускается комбинация перечисленных признаков. Например, элементы каталогов для системных файлов IBMBIO.COM и обычно содержат в поле FAttr значение = 1+2+4, что расшифровывается как скрытые системные файлы, защищенные от изменения и стирания.
Поле Firstc указывает на номер кластера, в котором располагается начало файла или каталога. Одновременно этот номер открывает цепочку ссылок FAT, если файл (каталог) не умещается в одном кластере.
Для каждого подкаталога элемент с именем содержит ссылку на корневой каталог, а с именем — на каталог вышележащего уровня. Если файл (подкаталог) удаляется из каталога, первая буква его имени заменяется на символ (#120) и освобождается вся связанная с этим элементом цепочка ссылок FAT. Поскольку поле Firstc сохраняет свое прежнее значение, это дает возможность в некоторых случаях восстановить ошибочно стертый файл.
Признаком конца использующейся части каталога является элемент, в котором первый символ в поле Name имеет значение
26.2.СредстваBIOSиДОС для работы с диском
Операционная система ПК состоит из двух частей: программ базовой системы ввода/вывода (BIOS) и собственно программ ДОС (MS-DOS, PC-DOS, OS/2, и т. п.). Программы BIOS располагаются в постоянном запоминающем устройстве (ПЗУ), программы самой ДОС -- в системных файлах (например, для операционной системы MS-DOS это файлы IBMBIO.COM и IBMDOS.COM). В этом разделе описываются средства операционной системы MS-DOS версии 4.0 и более ранних. При работе с другими операционными системами могут быть незначительные отличия в способах доступа к ним.
Прерывание BIOS $13
В состав BIOS входит программа работы с диском, доступ к которой открывает программное прерывание $13. Подобно большинству других прерываний BIOS в прерывании $13 предусмотрено несколько функций, связанных с тем или иным конкретным действием. Эти функции определяются содержимым регистра АН и имеют смысл, указанный в табл. 26.4.
Таблица 26.4. Функции прерывания $13
Функция
Действие
Класс ПК
$00
Сбросить контроллер диска
PC,XT,AT,PS/2
$01
Получить статус диска
II
Таблица 26.4 (окончание)
Функция
Действие
Класс ПК
$02
Читать сектор
$03
Записать сектор
$04
Проверить сектор
$05
Форматировать дорожку
—$0 6
Форматировать дорожку ЖД
PC,XT
$07
Форматировать ЖД
$08
Получить параметры диска
PC, XT, AT, PS/2
$09
Инициировать контроллер диска на основе дисковых таблиц
$ОА
Читать сектор с кодом коррекции
$ОВ
Записать сектор с кодом коррекции
*
$0С
Позиционировать головки на нужный цилиндр
И
$0D
Сбросить контроллер диска
$ ОЕ
Читать буфер сектора
PC, XT
-$ OF
Записать буфер сектора
И
$10
Получить состояние дисковода ЖД
PC, XT, AT, PS/2
$11
Осуществить рекалибровкудисковода ЖД
И
$ 12
Проверить память контроллера ЖД
PC, XT
$13
Проверить дисковод ЖД
•1
$14
Проверить контроллер ЖД
PC, XT, AT, PS/2
$15
Получить тип и размер диска
AT, PS/2
$16
Проверить флаг дискеты
$17
Установить тип дискеты
•I
$18
Установить среду для форматирования дискеты
*
$19
Парковать головки ЖД
*
$ 1А
Форматировать ЖД с контроллером типа ESDI
II
Номер диска при обращении к прерыванию $13 указывается как целое число: если это число меньше $80, номер относится к приводу гибкого диска (о означает первый привод ГД), если $80 или больше, к жесткому диску ($80 означает первый ЖД). Минимальный номер цилиндра и головки равен о, а минимальный номер сектора равен 1.
IIсссс с: с1 с ссссс с: с1 с сСсSS1 1 s s: ssСсSS1 1 s s: ss
I
I
с
с
с
с с: с
1 с с
с
с
с
с с: с
1 с с
С
с
S
S
1 1 s s: s
s
С
с
S
S
1 1 s s: s
s
Старший байтМладший байт I
7654 321076 5 4 3 Z 1 0
Рис. 26.4. Структура номеров цилиндраи сектора для прерывания $13
Номер цилиндра/сектора передается через регистр сх в упакованном виде (см. рис. 26.4): номер сектора занимает младшие 6 бит регистра CL, в то время как биты 6 и 7 этого регистра рассматриваются как старшие биты номера цилиндра; младшие биты номера цилиндра задаются значением регистра сн.
Большинство функций прерывания $13 возвращает в флаге переноса CF признак обнаруженной ошибки (о — нет ошибки, 1 — обнаружена ошибка), а в регистре АН — код ошибки. Если это не оговаривается особо, код ошибки трактуется согласно табл. 26.5. Рассмотрим подробнее все эти функции.
Таблица 26.5. Значения кода ошибкидля прерывания $13
Код
Значение
00
Нет ошибки
$01
Неправильная команда
$02
Не найдена адресная метка
$03
Попытка записи на диск, защищенный от записи
$04
Не найден сектор
$05
Ошибка при сбросе жд
$06
Произошла замена дискеты (изменялось положение запора дискового привода)
$07
Неправильные параметры дисковода ЖД
$08
Переполнение канала прямого доступа к памяти (КПДП)
$09
Выход за границу 64 Кбайт при работе КПДП
$ОА
Обнаружен плохой сектор
$ов
Обнаружена плохая дорожка
$0С
Неправильный номер дорожки
$0D
Неправильный номер сектора при форматировании
$0Е
Обнаружена адресная метка управляющих данных
Таблица 26.5 (окончание)
Код
Значение
$0F
Ошибка КПДП
$10
Обнаружена ошибка в данных
$11
Данные скорректированы схемами контроля
$20
Сбой контроллера
$40
Сбой при поиске дорожки
$80
Программа не успевает обрабатывать данные (ошибка таймаута)
$АА
Не готов дисководI
$вв
Неизвестная ошибка
$СС
Сбой при записи
$ЕО
Ошибка регистра состояния
$FF
Ошибка операции чтения
Сбросить контроллер диска
Вход:
АН = о;
DL — номер диска.
Функция $оо вызывает сброс и рекалибровку контроллера дисков (головки устанавливаются на нулевой цилиндр). Сброс рекомендуется выполнять после того, как произошла ошибка при выполнении других операций, например при чтении данных. После сброса можно попытаться выполнить неудавшуюся операцию еще раз.
Получить статус диска
Вход:
П АН = i;
П DL — номер диска.
Выход:
AL — признак ошибки предыдущего обращения к диску (совпадает со значением АН в остальных функциях)
Функция $01 осуществляет анализ предыдущей дисковой операции. Передаваемый в регистре AL код завершения операции берется из области данных BIOS — из байта по адресу $0000:$0441.
Читать/записать сектор
Вход:
АН = 2 ^^^^ читать сектор;
П АН з — записать сектор;
П AL — количество секторов, которые нужно прочитать (записать);
П сх — цилиндр/сектор;
П DH — номер головки;
П • DL — номердиска;
П ES : вх — адрес буфера.
Функции $02 и $оз служат соответственно для чтения и записи данных. Обмен данными с диском осуществляется порциями, кратными размеру сектора диска (для ДОС этот размер составляет 512 байт). При осуществлении операции используется контроллер прямого доступа к памяти (КПДП), схемы управления которым позволяют за одно обращение передать до 64 Кбайт данных, причем передача идет под управлением схем КПДП (без участия микропроцессора). КПДП не может проверить действительный размер буфера, выделенного для обмена данными с диском; ответственность за неправильную длину буфера возлагается на программиста.
Функции $02 и $оз позволяют обратиться к любому физическому сектору (или группе смежных секторов). В частности, только с их помощью возможен доступ к сектору главного загрузчика. Если данные читаются (записываются) из нескольких секторов, т. е. если регистр AL > 1, обмен данными идет начиная с цилиндра и сектора, заданных в регистре сх, по секторам до конца дорожки, далее по головкам до конца цилиндра, затем по цилиндрам.
Проверить сектор
Вход:
П АН = 4;
П AL — количество проверяемых секторов;
П сх — цилиндр/сектор;
П DH — номер головки;
П DL — номер диска.
С помощью функции $04 программа может убедиться в доступности указанных секторов. Данные проверяются по методу циклического контроля (CRC), однако обмен данными не осуществляется, поэтому при обращении
к функции ей не нужен адрес буфера. Перед обращением программа должна убедиться в готовности дисковода (мотор привода диска должен вращаться с рабочей скоростью), в противном случае будет установлен признак ошибки.
Форматировать дорожку
Вход:
АН = 5;
AL — для ГД — количество секторов на дорожке, для ЖД — фактор чередования секторов;
сх — цилиндр/сектор;
О DH — номер головки;
DL — номер диска;
ЕЗ:ВХ — адрес данных для форматирования.
Функция форматирования дорожки $05 предназначена для начальной записи служебных данных на поверхность диска. При выполнении операции уничтожаются все ранее записанные на дорожке данные. За одно обращение к функции можно отформатировать только одну дорожку (или ее часть) — от указанного сектора и до конца дорожки. Для этой функции необходимо задать фактор чередования секторов и определенным образом подготовить буфер данных.
Фактор чередования секторов определяет последовательность расположения секторов в пределах одной дорожки. Фактор 1 означает последовательное расположение секторов в порядке их номеров — сначала сектор 1, за ним сектор 2 и т. д., т. е. в этом случае чередование отсутствует. Фактор 2 задает расположение секторов через один. Например, при 9 секторах на дорожке секторы будут следовать в порядке 1-6-2-7-3-8-4-9-5. При последовательном расположении секторов может оказаться так, что процессор не будет успевать обрабатывать данные в смежных секторах за один оборот диска. В этом случае чередование секторов позволяет согласовать производительность процессора со скоростью обмена данными и за счет этого повысить общую скорость работы с диском.
Буфер данных для форматирования дискеты должен содержать NSec 4- байтных элементов, где NSec — количество форматируемых секторов. Иными словами, для каждого сектора необходимо подготовить 4 информационных байта, в которых указываются номера дорожки, головки, секторы и код
длины сектора. Последний определяет размер сектора и может иметь следующие значения:
0 — 128 байт;□ 2 — 512 байт;
. □ 1 — 256 байт;П з — 1024 байт.
Порядок следования в этом буфере секторных номеров определяет фактор чередования. Например, если форматируются девять 512-байтных секторов на дорожке с номером $10, головка 1, фактор чередования 2, то буфер должен содержать следующие данные:
* $10, $01, $01, $02, $10, $0,1, $06, $02,
$10, $01, $02, $02, $10, $01, $07, $02,
$10, $01, $03, $02, $10, $01, $08, $02,
$10, $01, $04,$02, $10, $01, $09, $02, $10, $01, $05, $02
Перед обращением к функции форматирования дискеты таблица параметров дискеты должна содержать правильные параметры, а контроллер диска должен быть настроен на нужную скорость передачи данных.
Таблица параметров дискеты (ТПД) — это 11-байтный информационный блок, начало которого указывает вектор прерывания $1Е. Этот блок содержит некоторые константы, которые используются драйвером диска для управления дисководом. Структура ТПД:
type
DPT record
StepRTm: Byte;
{Задержка переключения головок}
DMA : Byte;
{Признак использования КПДП)
DrvOff : Byte;
{Задержка выключения двигателя)
SectSiz: Byfe;
(Код размера сектора)
SectCnt: Byte;
{Количество секторов на дорожке}
GapSize: Byte;
(Длина межсекторного промежутка}
DataSiz: Byte;
{Максимальная длина передаваемых данных}
GapFSiz: Byte;
{Межсекторный интервал для форматирования}
FillChr: Byte;
{Символ-заполнитель для форматирования)
HeadTm : Byte;
{Время установки головок)
StartTm: Byte;
{Время запуска двигателя}
end;
Большинство полей ТПД содержит оптимальные технические параметры ГД, которые не следует изменять. Исключение составляют поля sectsiz, sectcnt и FillChr. Поле Sectsiz содержит код длины сектора. Поскольку
MS-DOS работает только с 512-байтными секторами, это поле имеет значение 2 во всех случаях, кроме специальных средств защиты программ (подробнее см. главу 30). Поле sectcnt должно содержать количество секторов на дорожке. Это поле может изменяться, если привод ГД позволяет работать с дискетами разного формата. Например, контроллеры ГД для дискет диаметром 5 */4 дюйма могут работать в режиме DSHD (емкость дискеты 1,2 Мбайт) и DSDD (360 Кбайт). В первом случае на дорожке размещается 15, а во втором — 9 секторов, поэтому при переходе с одного формата дискет на другой необходимо нужным образом скорректировать поле sectcnt. Наконец, символ-заполнитель FillChr может быть любым символом в диапазоне ASCII-кодов 0-255. Этот символ записывается в область данных при форматировании. Изменение полей тпд обычно требуется лишь в особых режимах форматирования, используемых для защиты программ от нелегального копирования. Если вы захотите поэкспериментировать с нестандартными значениями ТПД, учтите, что в некоторых случаях вектор $1Е содержит адрес таблицы, размещенной в ПЗУ. Изменить такую таблицу, разумеется, невозможно, поэтому следует скопировать ТПД в оперативную память, изменить в этой копии нужные параметры и установить в векторе $1Е адрес новой таблицы.
Скорость передачи данных изменяется для контроллеров, рассчитанных на разные форматы дискет, и устанавливается функцией $18 (см. ниже).
Для жесткого диска буфер форматирования всегда имеет длину 256 2- байтных слов. Младший байт каждого слова содержит признак хорошего сектора (о, если сектор хороший, и $80, если плохой), а старший — номер сектора. Как и в случае дискет, порядок следования секторных номеров определяет фактор чередования.
Примечание
Буфер формата задается только для ПК класса IBM AT. На машинах IBM XT при форматировании жесткого диска фактор чередования указывается в регистре AL.
Форматировать одну или несколько дорожек жесткого диска
Вход:
АН = б — форматировать одну дорожку;
АН = 1 — форматировать до конца диска;
AL — фактор чередования секторов;
V
П сх — цилиндр/сектор;
П DH — номер головки;
DL — номер диска;
□— адрес буфера.
Для форматирования жесткого диска могут использоваться специальные функции $06 ипараметры обращения к которым совпадают с параметрами функцииФункция $06 форматирует одну дорожку, а функция
$07 — все дорожки, начиная с указанной в регистре сх.
Получить параметры диска
Вход:
П АН = 8;
П DL — номер диска.
Выход:
П BL — тип дисковода (только для AT и PS/2);
DL — количество обслуживаемых контроллером
П — максимальный номер головки;
П сх — максимальный номер цилиндра/сектора;
П ES : DI — адрес таблицы параметров дисковода.
С помощью функции $08 программа может определить тип дисковода и другие его параметры. Тип дисковода возвращается в регистре BL и может иметь одно из следующих значений:
П 1 — 360 Кбайт, 40 дорожек, 5*/4 дюйма;•^
П2— 1,2 Мбайт,80 дорожек,дюйма;.
Пз— 720 Кбайт,80 дорожек,дюйма;
П4— 1,480 дорожек,дюйма.
Инициализировать контроллер диска на основе дисковых таблиц
Вход:
П АН =
П DL — номер диска.
Функцию инициализации контроллера $09 применяют после модификации таблиц параметров жесткого диска. BIOS читает таблицу параметров и нужным образом настраивает контроллер. Таблица параметров первого жесткого диска указывается вектором прерывания $41, а второго (если он есть в ПК) — вектором
Читать/записать сектор с кодом коррекции
Вход:
П АН = $ОА — читать сектор;
АН = $ов — записать сектор;
AL — количество читаемых/записываемых секторов;
сх — цилиндр/сектор;
DH — номер головки;
П DL — номер диска;
П ES:BX — адрес буфера.
Функции $ОА и $ов отличаются от функций $02 и $оз только тем, что дополнительно считывается (записывается) 4-байтный код циклического контроля (CRC), необходимый для контроля и коррекции данных.
Позиционировать головки на нужный цилиндр
Вход:
П АН = $ос;
сх — цилиндр/сектор;
П DH — номер головки;
П DL — номер диска.
С помощью функции $oc программа может установить головки диска на нужную дорожку. Замечу, что функции чтения и записи данных не требуют предварительного позиционирования головок — они делают это самостоятельно.
Сбросить контроллер диска
Вход:
П АН = $od; .
П DL — номер диска.
Если программе необходимо сбросить только контроллер жд, она может вызвать функцию $0D прерывания $13. В отличие от этого, описанная ранее функция АН = о сбрасывает все контроллеры, в том числе и контроллер ГД.
Читать/записать буфер сектора
Вход:
П АН = $0E — читать буфер;
АН = $OF — записать буфер;
П AL — количество читаемых/записываемых секторов;
сх — цилиндр/сектор;
— номер головки;
О — номер диска;
О ES : вх — адрес буфера.
Контроллерыв компьютерах PC и XT содержат внутренний буфер данных. Функциячитает данные из этого буфера, азаписывает данные
в буфер контроллера. Физического обращения к диску ни в том, ни в другом случае не происходит.
Получить состояние дисковода жесткого диска
Вход:
АН
DL — номер диска.
О готовности диска можно судить по значению байта состояния, возвращаемого в регистре АН. Это значение совпадает с байтом состояния функции $01.
Рекалибровка дисковода жесткого диска
Вход:
О АН =
G DL — номер диска.
Рекалибровка дисковода, осуществляемая функциейзаключается в по
зиционировании головок диска на нулевую дорожку. Дополнительно в регистре АН возвращается байт состояния дисковода.
Проверка памяти контроллера жесткого диска
Вход:
• АН = $12;
П DL — номер диска.
Функция $12 предназначена для запуска встроенной диагностики дискового контроллера. Она проверяет внутренний буфер сектора и возвращает байт состояния диска. Аналогичные действия осуществляют также функции $13 и
Проверка дисковода жесткого диска
Вход:
П АН = $13;
О DL — номер диска.
См. функцию $12.
Проверка контроллера жесткого диска
Вход: АН = $14.
См. функцию $12.
Получить тип и размер диска
Вход:
П АН = $15;
П DL — номер диска.
Выход:
П АН — тип дисковода;
П СХ: DX — количество секторов размером 512 байт.
Тип дисковода, возвращаемый в регистре АН функцией $15, соответствует следующим значениям:
о - диск отсутствует;
П 1 — ГД без аппаратных средств обнаружения замены дискеты;
П 2 — ГД с контролем замены дискеты;
а з -ЖД.
Проверить флаг дискеты
Вход:
П АН = $16;
П. DL — номер диска.
Выход:
АН — признак замены дискеты:
о — дискета не менялась;
П6 — дискета была заменена.
Контроль замены дискеты осуществляется схемами контроллера ГД по состоянию запора дискеты. Функция $16 возвращает в регистре АН признак изменения положения этого запора. С помощью этой функции программа может убедиться в том, что она работает с одной и той же дискетой, и потребовать установить старую дискету, если обмен данными с ней еще не закончился.
Установить тип дискеты
Вход:
П АН $17;
П AL — устанавливаемый тип дискеты;
П DL — номер диска.:
При обращении к функции $17 в регистр AL необходимо поместить одно из следующих значений:
О о — не используется;
□1— дискета360 Кбайтв дисководе360Кбайт;
О2— дискета360 Кбайтв дисководе1,2Мбайт;
Оз— дискета1,2 Мбайтв дисководе1,2Мбайт;
О4— дискета720 Кбайтв дисководе720Кбайт.
Функция сбрасывает флаг замены дискеты и настраивает контроллер ГД на нужную скорость передачи данных.
Установить среду для форматирования дискеты
Вход:
АН = $18;
DL — номер диска;
сх — количество цилиндров/секторов.
Выход:
CF — признак ошибки:
о — нет ошибки;
1 — есть ошибка;
АН = <код ошибкиХ
о — требуемое количество цилиндров/секторов допустимо;
$01 — функция недоступна;
$ОС — функция не поддерживается или неизвестен тип дисковода;
$80 — не установлена дискета.
Перед форматированием дорожки ГД контроллер диска должен быть настроен на нужную скорость передачи данных. Это достигается обращением к функции $18, которая дополнительно сбрасывает флаг замены дискеты.
Парковка головок жесткого диска
Вход:
О АН = $19;
□ DL — номер диска.
Парковка диска заключается в выводе головок за рабочую зону с тем, чтобы предотвратить возможную порчу поверхности дисков при транспортировке компьютера. Эту операцию реализует функция $19.
Форматирование жесткого с контроллером типа ESDI
Вход:
АН =
AL — количество элементов в таблице дефектов;
П DL — номер диска;
П CL — режим форматирования;
П ES : вх — адрес таблицы дефектов.
Выход:
CF, АН — признак и код ошибки.
Если диск работает под управлением контроллера с интерфейсомдля
его форматирования используется функцияЭта функция поддерживает
таблицу дефектных дорожек и имеет несколько режимов форматирования в зависимости от содержимого регистра CL при обращении к функции:
П бит 0 = 1 — игнорировать первичную таблицу дефектов;
бит 1 = 1 — игнорировать вторичную таблицу дефектов;••
П бит 2 = 1 — обновить вторичную таблицу дефектов;
бит 3 = 1 — выполнить анализ поверхности;
П бит 4 = 1 — периодически генерировать прерывание $15;
П биты— должны быть равны о.
При установке бита 2 регистра CL содержимое вторичной таблицы дефектов обновляется — в нее заносятся результаты тестирования диска. Для углубленного анализа поверхности диска вначале выполняется форматирование с третьим битом, равным о, а затем анализ поверхности с третьим битом, равным
Если при форматировании четвертый бит равен 1, то после форматирования очередной дорожки BIOS генерирует прерывание $15 с регистром
Это прерывание можно использовать для индикации процесса форматирования или для прекращения операции по требованию пользователя.
Поддержка дисков в ДОС
Функции 2 и 3 прерывания $13 позволяют прочитать или записать любой сектор на любом диске. Однако формат обращения к этому прерыванию требует задания нескольких несущественных для программиста, но важных для правильной работы программы параметров, таких как номера головки, цилиндра, сектора на дорожке. Кроме того, это прерывание игнорирует раз-
бивку жесткого диска на логические разделы, что обычно вызывает определенные неудобства, т. к. формат и размещение описателей логических разделов зависят от используемой программы обслуживания логических дисков, например FDisk или Disk Manager. Поэтому для операций чтения/ записи чаще используются два специальных прерывания ДОС с векторами $25 и $26. Первое из них реализует чтение, а второе — запись информации. В этих прерываниях ДОС (не путайте с функциями ДОС) принят одинаковый формат использования регистров для обмена управляющей информацией с программой пользователя:
Вход:
АХ — номер диска (о — А:, 1 — В:, 2 — С: и т. д.);
DX — номер сектора;
сх — количество секторов;
DS : вх = адрес буфера.
Выход:
П CF = 1, если была обнаружена ошибка, иначе о;
П АХ — код ошибки (если CF =
Для дисков большой емкости (больше 32 Мбайт) регистр DX не используется, регистр сх должен содержать $FFFF (признак обращения к большому диску), а регистры DS:BX содержат адрес такой структуры данных:
typeTLargeDisk = recordStartSec: Longlnt;Nsecs: Word;AdrBuf : Pointer;end;typeTLargeDisk = recordStartSec: Longlnt;Nsecs: Word;AdrBuf : Pointer;end;{Номер сектора} (Количество секторов} (Адрес буфера}{Номер сектора} (Количество секторов} (Адрес буфера}
type
TLargeDisk = record
StartSec: Longlnt;
Nsecs: Word;
AdrBuf : Pointer;
end;
type
TLargeDisk = record
StartSec: Longlnt;
Nsecs: Word;
AdrBuf : Pointer;
end;
{Номер сектора} (Количество секторов} (Адрес буфера}
{Номер сектора} (Количество секторов} (Адрес буфера}
При реализации доступа к прерываниям $25 и $26 следует учесть, что в процессе их обработки ДОС помещает в вершину стека "лишнее" слово (состояние регистра флагов). По этой причине обращение к прерываниям с помощью стандартной процедуры INTR невозможно, т. к. не обеспечивается правильный выход из процедуры. Кроме того, изменяется содержимое регистров DS и ВР, которые, следовательно, нужно где-то (обычно в стеке) сохранить перед вызовом прерывания, чтобы после завершения обработки прерывания восстановить сохраненные значения. Вот почему доступ к прерываниям $25 и $26 можно программировать только с помощью ассемблера (см., например, Процедуру ReadWriteSector В модуле F_Disk).
Среди многочисленных функций ДОС есть несколько весьма полезных при работе с диском. Это прежде всего функция $32. Стандартная документация
по MS-DOS не содержит никаких сведений об этой функции. Говорят, что эта функция недокументирована. В отличие от остальных функций, недокументированные функции могут не работать в других версиях ДОС (например, в более поздних версиях или в альтернативных разработках конкурирующих фирм). Функция $32 работает в версии 5.0, а также в версиях 4.x, 3.x и 2.x MS- DOS. Она возвращает информацию о диске, которую, в принципе, можно получить и из других источников, например из ВРВ или с помощью функции 8 прерывания $13. Достоинством функции $32 является то, что информация о диске собирается воедино в структуру данных следующего типа:
type
PDrvInfo_Type =ADrvInfo_Type;
DrvInfo_Type = record
Drv
Byte ;
(Номер диска: 0-A, 1-B и т. Д. }
SubDrv
Byte;
{Номер устройства внутри драйвера}
SecSize
Word;
{Размер сектора в байтах)
SecClus
Byte;
{Количество секторов в кластере минус 1}
ExpClus
Byte;
{ Сдвиг номера кластера}
Boots
Word;
{Количество загрузочных и резервных секторов}
.FatCnt
Byte;
(Количество копий FAT}
RootSiz:
Word;
(Количество элементов корневого каталога)
DataSec
Word;
{Номер первого сектора данных}
MaxClus
Word;
{Ма ксимальный номер кластера)
FatSize
Byte;
(Количество секторов в FAT)
—
Для MS-DOS 2.x}
RootSec
Word;
(Номер первого сектора корневого ка талога
Head
Pointer;
, (Адрес заголовка драйвера устройства}
Media
Byte;
(Дескриптор носителя)
AccesF
Byte;
{Флаг доступности устройства)
Next
PDrvInfо_Туре
(Ссылка на следующий элемент списка}
:
Word;
(Номер начального кластера текущего каталога или 0 для корневого каталога)
DirPath :
: array [1..64]
of Char; (Путь к текущему
каталогу
в формате ASCIIZ (с нулем в конце))
—
- Для MS-DOS
3.x ---}
RootSec:
Word;
{Номер первого сектора корневого ка талога
Head
Pointer;
(Адрес заголовка драйвера устройства}
Media
Byte;
(Дескриптор носителя}
AccesF
Byte;
(Флаг доступности устройства}
Next
PDrvInfo Type
(Ссылка на следующий элемент списка)
Reserv : Word;
{Имеет значение 0}
Build : Word;
[Имеет значение SFFFF, если данные построены заново}
{Для MS-DOS
4.x и 5. о;
Reservl Word;
(Имеет значение 0}
RootSec Word;
(Номер первого сектора корневого каталога}
Head: Pointer;
(Адрес заголовка драйвера устройства}
Media : Byte;
(Дескриптор носителя}
AccesFByte;
(Флаг доступности)
Next; PDrvInfo_Type{Ссылка на следующий элемент списка}
Reserv2 Word;
(Имеет значение 0}
Build : Word;
(Имеет значение $FFFF,.
если данные построены заново}
end;(Drvln fo_ Type}.
В этой структуре поле ExpClus содержит целое число, показывающее, на сколько разрядов влево нужно сместить номер кластера, чтобы получить номер сектора (количество секторов в кластере всегда равно степени 2, т. е.
Размер_кластера .=
таким образом Expcius
= N). Поле Boots фактически содержит номер сек-
тора, в котором начинается FAT, поля DataSec и Rootsec определяют начало соответственно области данных диска и корневого каталога.
При обращении к этой функции регистры процессора используются следующим образом.
Вход:
О АН = $32;
DL — номер диска (о — текущий, 1 — А: и т. д.).
Выход:
AL = о — нет ошибок;
О AL = $FF — задан неверный номер диска;
DS:BX — адрес блока информации.
Например, следующая программа (пример 26.2) читает блок информации о диске и выясняет, в соответствии с какими версиями MS-DOS он построен.
^^I^^^^^^^^^^^^^MMeMM^^^^^^^^^^^^^^^^^^^^W^^^^^WM^^^^M'jtttittMeW^W/AUil^f ЯДвЦМЮ to^MlMto’ItiWMitlV'" *^<у ДАг^ЛШП*.* >“ •
Пример 26.2. Программа определения версии MS-DOS. i
?АТ её а е^ИЙп-CTs^W^WWrT^Re а еелТеГ^ I Se’evTeRla^lWffTa» е^Уе. a a a i а а а е’ЯГа'а А » А'е at а^а е*а а V. . /• А А1. . . a i а е'е"ё I Ге a a a a eff/ly a a a el #а б"^Га"е'а а е'е а . /ГАУа аа < к Я а а а a a a a a eV е а а а'а . . . J . а а а а >
Uses ДОС;
type
PDrvInfo = ''Drvlnfo;
Drvlnfo = record
array [0..15] of Byte;
Reserv: Byte;(Признак версий 4.x и выше)
array [17..27] of Byte ;
Reservl: Word
end;
var
I
R: Registers;
P: PDrvInfo;
begin
.
with R do
begin
ah : = $ 3 2;{Номер функции ДОС)
al := 2;(Для диска С:}
MSDOS(R);
if al<>0 then
WriteLn('Ошибка обращения к функции: AL = ',al)
else begin
P := ptr(ds,bx);*
if PA.Reserv=0 then
WriteLn('Версия MS-DOS 4.0 и выше 1)
else
if Рл. ReservloO then
WriteLn('Версия MS-DOS 3.x')
else
WriteLn('Версия MS-DOS 2.x')
end
end
end.
Поскольку функция $32 может не работать в других версиях ДОС, ею следует пользоваться с осторожностью. Описываемый в разд. 26.3 модуль F_Disk выполняет приблизительно такие же операции, но с использованием только документированных возможностей MS-DOS. Эти возможности реализуются
так называемымвызовом IOCTL (Input/Output ConTroL — управле
ние вводом/выводом). IOCTL представляет собой соглашение об интерфейсе (способе обмена данными) между ДОС и работающими в ней драйверами устройств ввода/вывода. Этому соглашению обязаны следовать все драйверы (в том числе и разработанные пользователями) для версий MS-DOS, начиная с 2.0 и выше.
Общий вызов IOCTL реализуется прерыванием $21 ДОС со следующими значениями регистров:
АН = $44 (функция $44 ДОС реализует вызов IOCTL);
AL =(подфункция— общий вызов IOCTL);
П BL — номер диска (о — текущий, 1 — А: и т. д.);
П сн = $08 (код категории устройства: $08 — дисковое устройство);
П CL — операция:
$40 — установить параметры устройства;
— записать дорожку;
2 — форматировать дорожку;
$60 — получить параметры устройства;
прочитать дорожку;
$62 — проверить дорожку;
О DS : вх — адрес указателя на блок параметров.
При возврате из функции флаг переноса CF содержит о, если обращение было без ошибок, и 1, если обнаружена ошибка. В случае ошибки регистр АХ содержит код ошибки.
Формат блока параметров, адрес которого указывается в регистрах зависит от выполняемой операции.
Получить/установить параметры устройства
При использовании этой операции блок параметров должен соответствовать такой структуре данных:
type
= record
Byte;
Size: Byte
end;
= record
Control : Byte;Control : Byte;{Управляющий байт}{Управляющий байт}
Control : Byte;
Control : Byte;
{Управляющий байт}
{Управляющий байт}
TypeDrv : Byte;{ Тип диска }
AttribWord;{Атрибуты диска}
Cylindrs Word;{Число цилиндров}
Media: Byte;{Тип носителя)
BPB'
AddAdd BPB Type;
Res.erv : array [1.. 10] of Byte;
SectorsWord;{Количество секторов на дорожке}
Tracks(Образ дорожки}
array [ 1 - - Sectors] of Tracks
end;
При обращении к операции $60 (получить параметры) в поле control используется только один младший бит 0: значение о этого бита указывает на то, что надо дать умалчиваемое значение ВРВ (т. е. то, которое хранится в драйвере), а значение 1 требует от драйвера построить блок параметров BIOS заново. К сожалению, операция $60 не поддерживает диски большой емкости и в этом случае возвращает нулевое количество секторов в поле BPB.TotSecs.
В операции $40 (установить параметры) три младших бита поля control имеют следующие значения:
П бит 0 — значение о заставляет драйвер использовать ВРВ, подготовленный в этом вызове, а значение 1 — умалчиваемый ВРВ;
бит 1 — значение 1 указывает на то, что драйвер должен игнорировать все данные из блока параметров этого вызова, за исключением образа дорожки;
Пбит2 —значение1сообщаетдрайверу,чтовсе секторынадорожке
имеют одинаковый размер.
Поля TypeDrv, Attrib и Cylindrs в операциях $40 и $бо заполняются драйвером. В поле TypeDrv драйвер возвращает тип устройства:
0 — 360 Кбайт дискета диаметром 5'А дюйма;
1 - 1,2 Мбайт дискета 5 Ад дюйма;
О 2 — 720 Кбайт дискета ЗАг дюйма;
П з — 160 Кбайт дискета диаметром 8 дюймов;
П 4 — 320 Кбайт дискета 8 дюймов;
П 5 — жесткий диск;
П б — магнитная лента;
л — 1,44 Мбайт дискета, 3V2 дюйма и другие дисковые устройства.
Атрибуты устройства возвращаются драйвером в поле Attrib. В этом поле задействованы только два младших бита:
бит 0 — признак заменяемости носителя (1 — не заменяется);
бит 1 — признак возможности аппаратного контроля замены дискеты (.1 —контроль).
В поледрайвер записывает полное количество цилиндров на фи
зическом устройстве.
Поле Media заполняется драйвером при вызове операции $60 и программой при вызове операции $40. Это поле конкретизирует вид носителя для ГД, поддерживающих различные форматы дискет.Например, для
дюймового привода, рассчитанного на 1,2 Мбайт, значение о этого поля сообщает, что используется дискета емкостьюМбайт, а 1 - емкостью
360 Кбайт.
Поля ВРВ и Add заполняются драйвером при обращении к операции $60 и подготавливаются программой при обращении к операцииСтруктура
этих полей рассмотрена в разд.
Поля sectors и Tracks используются только в функции $40 и заполняются программой. В них драйверу сообщается полное количество секторов на дорожке (поле sectors) и описание каждого сектора (поле Tracks). Таким образом, длина поля Tracks зависит от текущего количества секторов на дорожке.
Записать/прочитать дорожку CL = $41/$61
Структура блока параметров:
type
= record
Control
Byte;
содержит;
Head
Word;
{Номер головки)
•
Cyl:
Word;
{Номер дорожки)
Sec
Word;
нумерацияс
MaxSec :
Word;
номер сектора на
Buf:
Pointer
буфера для обмена
end;
Внимание
При обращении к IOCTL нумерация секторов, головок и цилиндров начинается с нуля (при обращении к прерыванию $13 секторы нумеруются, начиная с единицы). Таким образом, поле MaxSec содержит количество секторов на дорожке минус 1.
Форматировать/проверить дорожку=
При обращении к этим функциям блок параметров имеет такую структуру: type
= record
Control: Byte;
Head : Word;
Track : Word;
end;
В поле Control значимым является только младший бит. При входе его значение о указывает на необходимость форматирования дорожки, а значение 1 — проверить, поддерживается ли нестандартный формат дорожки. При выходе значение о в этом бите означает, что указанный образ дорожки может использоваться, а значение 1 — нет.
В заключение обзора возможностей ДОС отмечу еще две полезные функции. Функция с номером $Е устанавливает текущий диск (диск по умолчанию). Диск задается содержимым регистра DL перед обращением к функции, после возврата регистр AL содержит общее количество логических дисков, установленных на данном компьютере. Функция $19 возвращает в регистре AL номер текущего (умалчиваемого) диска.
26.3. Модуль для работы с диском на физическом уровне
Описываемый в этом разделе модульсодержит подпрограммы, рас
считанные на работу с диском на физическом уровне. Все они основываются на изложенной в предьщущих разделах информации о логической организации диска и средствах ДОС. Полный текст модуля содержится в файле F_DISK.PAS на сопровождающей книгу дискете.
Описание модуля
В модуле объявляются описанные ранее типы врв_туре,и
а также глобальные переменныеи Disk_status, в ко
торых указывается признак ошибки и код ошибки последнего обращения к подпрограммам модуля. Поскольку многие процедуры модуля интенсивно используюто а недокументированная функция $32 не
надежна, в модуле используется специальный тип TDisk, собирающий в единое целое всю необходимую информацию о диске, и создается списочная структура, содержащая информацию о всех жестких дисках ПК.
Тип TDisk открывает доступ к следующей структуре данных:
TDisk = record
Number
Byte;
{Номердиска: 0=А,1=В, .. .)
TypeD ;
Byte;
{Тип диска)
AttrD
Word;
{Атрибуты диска}
Cyls:
Word;
(Число цилиндров}
Media
Byte;
{Дескриптор носителя)
SectSize
Word;
{Количество байт в секторе}
TotSecs
Word;
{Объемдиска в секторах)
ClusSize
Byte;
{Количество секторов в кластере)
MaxClus
Word;
/Максимальныйномер■ кластера}
FATLock
Word;
{Номер 1-го сектора FAT)
FATCnt
Byte;
{Количество FAT)
FATSize
Word;
{Длина FAT в секторах)
FAT16: Boolean;
{Признак 16-битовой FAT)
RootLock:
Word;
(Начало корневого■ ка талога}
RootSize:
Word;
{Количество элементов каталога)
DataLock:
Word;
{Начальный сектор данных}
end;
В этой структуре поля TypeD, AttrD и Cyls заполняются драйвером диска и соответствуют ПОЛЯМ TypeDrv, Attrib И Cylindrs записи IOCTL_Info_Type (см. выше). Поле Media представляет собой дескриптор носителя.
Как уже отмечалось, при вызове IOCTL для дисков большой емкости поле IOCTL_Info_Type.Add. TotSecs содержит о. В этом случае программа GetDisklnfo модуля F_Disk читает загрузочный сектор диска с помощью прерывания $25 и по его блоку BPB_Add_4_Type определяет полное количество секторов на логическом диске.
Поле FAT 16 определяет тип FAT: если это поле содержит значение TRUE, FAT состоит из 16-битных элементов, в противном случае — из 12-битных. Для контроля типа FAT в модуле выполняется рекомендуемая в MS-DOS проверка: если общее количество кластеров на диске превышает емкость 12- разрядного двоичного числа, т. е. больше 4096, значит FAT использует 16- разрядные элементы, в противном случае — 12-разрядные.
Значение поля RootLock вычисляется по формуле:
RootLock = FATLock + FATCntxFATSize
a ПОЛЯ DataLock — ПО формуле
DataLock = (RootLock + RootSizex32) div 16+2
При обращениям ко всем процедурам и функциям модуля номер диска соответствует принятому в IOCTL порядку нумерации: о — диск А:, 1 — диск В:, 2 — диск С: и т. д. Исключением является вызов процедур GetAbsSector и SetAbsSector, в которых номер диска соответствует обращению к прерыванию $13: о — диск А:, 1 — диск В:, $80 — 1-й ЖД, $81 — 2-й ЖД.
Процедура GetListDisk. Формирует связанный список дисковых описателей. Заголовок:
Procedure GetListDisk(var List: PListDisk);
Список определяется такой структурой:
type
{Список описателей диска)
PListDisk = ATListDisk;
TListDisk = record
Diskinfo: TDisk;{Описатель диска}
NextDisk: PListDisk{Ссылка на следующий}
end;
Поскольку параметры ГД могут меняться от дискеты к дискете, информация о них не заносится в список. В модуле предусмотрена следующая глобальная переменная:
const
Disks: PListDisk = NIL.
Полезно перед самым первым обращением к подпрограммам модуля F_Disk сформировать список обращением к процедуре GetListDisk:
GetListDisk(Disks);
После этого обращение к любому жесткому диску не будет приводить к обязательному чтению его загрузочного сектора с целью получения нужной информации..
Процедура FreeListDisk. С помощью этой процедуры из динамической памяти удаляется связанный список дисковых описателей, ранее созданный процедурой GetListDisk. Заголовок:
Procedure FreeListDisk(var List: PListDisk);
Функция changeDiskette. Возвращает логический признак, указывающий, изменялось ли положение запора на приводе гибкого диска с момента последней операции чтения/записи (True — менялось). Эта информация сохраняется контроллером ГД и возвращается функцией $16 прерывания $13. Заголовок функции:
Function ChangeDiskette(Disk: Byte): Boolean;
Процедура GetAbsSector. Осуществляет чтение содержимого нужного физического сектора диска. Заголовок:
Procedure GetAbsSector(Disk,Head: Byte; CSec: Word; var Buffer);
Параметры вызова функции соответствуют параметрам обращения к прерыванию $13:
О Disk — номер физического диска (о -- 1-й ГД, 1 — 2-й ГД, $80 — 1-й
жд, $81 - 2-й ЖД);
Head — номер головки (поверхности); нумерация начинается с нуля;
CSec — номер цилиндра и сектора в виде 16-разрядного упакованного слова (нумерация цилиндров начинается с нуля, а секторов — с единицы);
О Buffer — переменная длиной 512 байт, в которую помещается содержимое прочитанного сектрра.
Эта процедура использует прерывание BIOS $13. Она игнорирует логические разделы ЖД.
Функция GetCluster. Возвращает номер кластера для указанного номера сектора. Заголовок:
Byte; Sector:Word;
Здесь:
Disk — номер диска;
О Sector — номер сектора.
Функция возвращает значение о, если задан неверный номер сектора (меньше номера первого сектора архивной области диска или больше максимально возможного для данного диска).
Функция GetDefaultDrv. Возвращает номер диска, который установлен в ДОС как диск по умолчанию (текущий диск). Заголовок:
Function GetDefauitDrv: Byte;
Процедура GetDirltem. Возвращает элемент каталога для указанного файла. Заголовок:
Function GetDirltem(FileName: String; var Item: Dir_Type);
Здесь:
FileName — строковое выражение, содержащее имя файла (можно задавать имя с предшествующим ему маршрутом поиска);
Item — переменная типа Dir_Type, в которой возвращается соответствующий элемент каталога.
Если не найден каталог, содержащий заданный файл, в переменной Disk_Error устанавливается значение TRUE, а в переменной Disk_Status — Значение $FFFF (65535).
Процедура GetDirSector. Возвращает номер сектора, в котором начинается указанный каталог, или о, если каталог не найден. Заголовок:
Procedure GetDirSector (Path: String; var Disk: Byte; var Dirs, DirSize: Word);
Здесь:
Path — выражение строкового типа, в котором указывается полный путь к каталогу; можно задать пустую строку (т. е. строку нулевой длины) для ссылки на текущий каталог;
П Disk — переменная типа Byte, в которой возвращается номер диска;
П Dirs — переменная типа word, возвращает о, если каталог не найден, или номер сектора, в котором начинается каталог;
П DirSize — переменная типа word, возвращает количество элементов каталога.
Процедура GetDisklnfo. Возвращает информацию о.диске. Заголовок:
Procedure GetDisklnfo(Disk: Byte; var Info: TDisk);
Здесь:
Disk — номер диска;
Info — переменная типа TDisk, в которой возвращается информация о диске.
При каждом обращении к этой функции, а к ней обращаются многие другие подпрограммы модуля, вначале проверяется связанный список, ассоциированный с глобальной переменной Disks: если этот список пуст или в нем не содержится сведений о нужном диске, осуществляется общий вызов IOCTL с требованием построить ВРВ, по результатам которого и формируется переменная Info. Таким образом, для ускорения доступа к диску полезно вначале сформировать процедурой GetListDisk связанный список дисковых описателей и поместить начало этого списка в переменную Disks.
Функция GetDiskNumber. Возвращает номер логического диска по указанной букве его имени. Заголовок:
Function GetDiskNumber(Name: Char): Byte;
Здесь Name — имя диска (A:, B:, С: и т. д.).
Функция возвращает значение 255 ($FF), если указано недопустимое имя (если Name не принадлежит множеству A-Z или задает имя несуществующего логического диска).
Функция GetFATItem. Возвращает содержимое нужного элемента таблицы распределения файлов. Заголовок:
Procedure GetFATItem(Disk: Byte; Item: Word): Word;
Здесь:
Disk — номер диска;
О Item — номер элемента FAT.
В реализации этой функции учитывается то обстоятельство, что FAT может быть одного из двух типов: с 12-битными и 16-битными элементами. В первом случае под каждую пару смежных элементов выделяется три байта таблицы, во втором каждому элементу отводится два байта. Для определения типа FAT выполняется следующая проверка: если общее количество кластеров больше 4 080, FAT содержит 16-битные элементы, иначе — 12-битные.
В случае 12-битной FAT значение, возвращаемое функцией GetFATtem, приводится к 16-битному формату: если элемент FAT имеет значение больше, чем $FFO, к этому значению прибавляется код $FOOO. Это связано с тем, что 16 старших значений элементов FAT ($FFF0 —$FFFF для 16-битной и $FF0— $FFF для 12-битной FAT) используются ДОС для специальных целей — для указания конца цепочки FAT (коды $FF8—$FFF для 12-битной и $FFF8— $FFFF для 16-битной FAT), сбойных кластеров (коды $FF7 и $FFF7) и резервных кластеров (коды $FF0—$FF6 и $FFF0 —$FFF6).
Процедура GetlOCTLInfo. Реализует общий вызов IOCTL в формате функции $60 (дать информацию об устройстве). Заголовок:
Procedure GetlOCTLInfo(Disk: Byte; var Info: TOCTLJType);
Здесь:
Disk — номер диска;
О Info — переменная типа IOCTL_Type, в которой возвращается информация о диске.
Эта процедура используется для построения ВРВ при обращении к процедуре GetDisklnfo.
Процедура GetMasterBoot. Используется для доступа к главному загрузочному сектору, что может оказаться полезным в программах защиты от компьютерных вирусов (см. главу 29). Заголовок:
Procedure GetMasterBoot(var Buf);
Здесь Buf — любая переменная длиной не менее 512 байт; в эту переменную помещается содержимое главного загрузочного сектора.
Главный (системный) загрузчик всегда располагается на первом ЖД (MS- DOS может обсуживать один или два жестких диска) в самом первом секторе нулевой дорожки. Доступ к этому сектору можно получить только с помощью прерывания $13. Доступ к остальным частям любого диска реализуется функциями Readsector/WriteSector (см. ниже).
Функция GetMaxDrv. Возвращает общее количество логических дисков минус 1, т. е. максимально возможный номер диска. Заголовок:
Function GetMaxDrv: Byte;
Для определения количества дисков используется функция $ОЕ ДОС (установка диска по умолчанию), которая в регистре AL возвращает требуемое значение. Предварительно с помощью функции $19 программа получает номер текущего диска (диска по умолчанию), поэтому обращение к процедуре не меняет настройку ДОС.
Функция GetSector. Преобразует номер кластера в номер сектора для указанного диска. Заголовок:
Function GetSector(Disk: Byte; Cluster: Word): Word;
Здесь:
Disk — номер диска; '
cluster — номер кластера.
Функция возвращает значение $FFFF (65535), если задан неверный номер кластера (меньше 2 или больше MaxClus) или несуществующий диск.
Функция PackCylSec. Преобразует номер сектора и номер цилиндра в формат слова, используемого при обращении к функциям прерывания $13. Заголовок:
Function PackCylSec(Cyl,Sec: Word): Word;
Здесь:
Cyl — номер цилиндра;
sec — номер сектора.
Процедура ReadSector. Осуществляет чтение одного или нескольких последовательных секторов с дискового носителя в оперативную память. Заголовок:
Procedure ReadSector (Disk: Byte; Sector, Count: Word; var Buffer);
Здесь:
Disk — номер диска;
sector — номер дискового сектора, с которого начинается чтение информации;
Count — количество читаемых секторов;
П Buffer — переменная любого типа размером не менее Countx512 байт, в которую будет записана прочитанная информация.
В процессе работы процедура никак не контролирует истинного размера переменной Buffer, поэтому обращение к процедуре с переменной недостаточного размера может привести к непредсказуемым результатам.
В процедуре предусмотрена работа с дисками большой емкости.
Процедура SetAbsSector. Реализует запись на диск сектора по его абсолютному номеру с помощью прерывания $13. Заголовок:
Procedure SetAbsSector (Disk, Head: Byte; CSec: Word; var Buffer);
Здесь:
Disk — номер физического диска (о — 1-й ГД, 1 — 2-й ГД, $80 — 1-й жд, $81 - 2-й ЖД);
Head — номер головки (поверхности), нумерация начинается с нуля;
О CSec — номер цилиндра и сектора в виде 16-разрядного упакованного слова (нумерация цилиндров начинается с нуля, а секторов — с единицы);
Buffer — переменная длиной 512 байт, содержимое которой будет записано на диск.
Процедура SetDefauitDrv. Устанавливает указанный диск в качестве текущего (умалчиваемого) диска. Заголовок:
Procedure SetDefauitDrv(Disk: Byte);
.
Здесь Disk — номер диска.
Процедура SetFATItem. Записывает нужное значение в таблицу FAT и во все ее (таблицы) копии. Заголовок:
Procedure SetFATItem(Disk: Byte; Cluster,Item: Word);
Здесь:
Disk — номер диска;
О cluster — номер элемента FAT;
О Item — новое значение элемента.
Использование этой процедуры требует предельной осторожности, т. к. ошибочное изменение состояния FAT может привести к катастрофическим последствиям.
Как уже говорилось, на диске создается несколько следующих друг за другом копий FAT. Это обстоятельство приходится учитывать при записи скорректированного состояния FAT на диск в случае, если FAT использует 12-битные элементы. Вызвано это тем, что в целях уменьшения, требуемой памяти и упрощения алгоритма в процедуре считывается не вся FAT, а лишь та тройка секторов, в которой находится изменяемый элемент (три смежных сектора гарантируют цикличность изменения адресов FAT- в одном 512-байтном секторе сохраняется информация о 2 х 512/3 = 341 */з 12-разрядных элементах FAT). После коррекции копии FAT в памяти ее нужно записать на диск, предварительно исключив из записи те секторы, которые, возможно, не принадлежат первой копии FAT. Если этого не сделать, то при изменении последних элементов таблицы может быть испорчен корневой каталог.
Процедура SetMasterBoot. Записывает новое значение главного (системного) загрузчика. Заголовок:
Procedure SetMasterBoot(var Buf);
Здесь Buf — произвольная переменная; первые 512 байт этой переменной записываются в первый сектор нулевой дорожки первого жесткого диска.
Эта процедура может пригодиться в программах защиты от компьютерных вирусов (см. главу 29). Я настоятельно рекомендую не экспериментировать с процедурой SetMasterBoot, т. к. это может привести к разрушению системного загрузчика и ваш ПК перестанет нормально функционировать.
Процедура Unpack. CylSec. Преобразует упакованный формат цилиндр/сектор, используемый при обращениях к прерыванию $13, к двум 16-разрядным словам, т. е. эта процедура осуществляет обратные действия по сравнению с функцией PackCylSec. Заголовок:
Procedure UnpackCylSec (CSec: Word; var Cyl,Sec: Word).
ч,
Здесь:
csec — упакованный формат цилиндр/сектор;
О Cyl — номер цилиндра;
Sec — номер сектора.
Процедура WriteSector. Записывает содержимое переменной любого типа в один или несколько смежных секторов. Заголовок:
Procedure WriteSector (Disk: Byte; Sector, Count: Word; var Buffer);
Здесь:
Disk — номер диска;
sector — номер дискового сектора, с которого начинается запись информации;
count — количество записываемых секторов;
Buffer — переменная любого типа, содержимое которой требуется записать на диск.
Как и в случае использования процедуры SetFATItem, требуется тщательно проверить параметры обращения к этой процедуре перед запуском программы на счет. Запись в ошибочно указанный сектор может привести к непоправимым последствиям.
26.3.2. Тестовая программа
В следующей программе (пример 26.3) иллюстрируется применение некоторых из содержащихся в модуле F_Disk подпрограмм. Программа запрашивает имя любого уже существующего на диске текстового файла и осуществля-
ет вывод его содержимого на экран дисплея.
\ Пример 26.3. Вывод содержимого текстового файла
l/e44»*e3««ei*»etVt«'eVyee^ee*ee*^e*el^fc?eV»Mewe«ttS*A'kR6waw«4«sA1RTWe6eei%Teeewe eeW* ее» ••ЯУеееее eWee e«<^eeeewl ее <Теее*е eel
Uses DOS ,F_Disk;
var
Name string;
Text array [1..512] of Char;
k,j : Word;
N Word;
DI : TDisk;
Item: Dir_Type;
Disk: Byte;
SecWord;
begin
GetListDisk (Disks);{Формируем список дисков}
repeat
Write('Введите имя файла или нажмите Enter для выхода): ') ; ReadLn(Name);
if Name О ' ' then
begin
Name := FExpand(Name);{Полное имя файла}
Disk := GetDiskNumber(Name[1]) ;
GetDisklnfo(Disk,DI);
GetDirItem(Name, Item);(Элемент каталога)
if not Disk_Error then with Item do
begin
Sec := GetSector(Disk,FirstC) ;(Номер 1-го
сектора файла)
j := 0;(Количество прочитанных секторов)
if not Disk_Error then
repeat(Цикл печати файла)
ReadSector(Disk,Sec,1,Text);
if Size < 512 then N := Size
else N := 512;
for k := 1 to N do Write(Text[k]);
dec(Size,N);(Size — нераспечатанный
остаток файла)
inc(j);
if j < DI.ClusSize then inc(Sec) else begin j := 0; FirstC := GetFATItem(Disk, FirstC); Sec := GetSector(Disk,FirstC) end
until Size = 0
else
WriteLn('Ошибка чтения файла')
end
else
WriteLn('Ошибка поиска файла')
end
until Name =
FreeListDisk(Disks){Удаляемсвязанный список)
end.
Глава27
Работа с мышью
Устройство ввода типа "мышь" (далее просто мышь) значительно упрощает взаимодействие пользователя с интерактивной (диалоговой) программой.
В Турбо Паскале не предусмотрены стандартные средства работы с мышью. В этой главе описывается модуль F_Mouse, который даст вам возможность ввести в программу элементы взаимодействия с пользователем с помощью этого устройства.
Взаимодействие с драйвером мыши
Большинство аппаратных устройств, входящих в состав ПК, комплектуется драйверами — особыми программами, С помощью которых реализуется взаимодействие с аппаратурой на физическом уровне. Не является исключением и мышь: разработанный для нее фирмой Microsoft драйвер берет на себя всю черновую работу, связанную с обслуживанием устройства, предоставляя пользователю простые и удобные средства взаимодействия с ним. Драйвер Microsoft для мыши является своеобразным стандартом для драйверов этого типа. Ниже рассматриваются его функции.
Драйвер представляет собой достаточно сложную программу, состоящую из двух основных частей — монитора мыши и монитора экрана.
Перемещение мыши измеряется в шагах. Шаг — это минимальное расстояние, регистрируемое датчиками мыши. Обычно разрешающая способность датчиков соответствует 8 шагам на миллиметр, т. е. величина шага составляет 0,125 мм. Существуют устройства, имеющие более высокую точность — до 16 шагов на миллиметр. Монитор мыши подсчитывает количество шагов в каждом направлении и преобразует их в эквивалентное количество элементов изображения виртуального экрана. Элементы изображения определяются из стандартного текстового режима 80x25 путем умножения координат на 8, т. е. виртуальный экран — это максимальное разрешение CGA- адаптера в графическом режиме — 640x200. Координаты мыши могут выходить за пределы виртуального экрана, если реальный адаптер имеет более высокое разрешение.
Монитор экрана следит за соответствием положения указателя мыши на физическом экране текущим виртуальным координатам, полученным от монитора мыши. Поскольку дисплей может работать в различных режимах (текстовом или графическом), монитор экрана опрашивает текущий режим адаптера с помощью прерывания $10 и автоматически меняет изображение указателя при смене режимов. При быстрых перемещениях мыши монитор экрана может не успевать менять положение указателя на экране, поэтому драйвер следит также за скоростью смещения. Если эта скорость превосходит некоторую пороговую величину, драйвер удваивает количество шагов, соответствующих одному и тому же линейному перемещению мыши, в результате указатель начинает смещаться по экрану с удвоенной скоростью.
Создаваемое драйвером стандартное изображение указателя на экране соответствует инверсному прямоугольнику в текстовом режиме (т. е. прямоугольнику, цвет которого определяется операцией NOT, примененной к байту атрибутов текущего символа) и направленной справа налево и снизу вверх стрелкой — в графическом режиме. Этот указатель может меняться по требованию программы.
Доступ к функциям драйвера мыши реализуется с помощью прерывания $зз MS-DOS. В табл. 27.1 описываются функции этого прерывания так, как они реализованы в драйвере мыши фирмы Microsoft.
Таблица 27.1. Функции драйвера мыши
ФункцияНазначение
$00Сбросить драйвер
$01Показать указатель мыши
$02Убрать указатель мыши
$03Получить текущие координаты мыши и состояние ее кнопок
$04Переместить указатель в новое положение
$05Получить информацию о нажатии кнопки
$06Получить информацию об отпускании кнопки
$07Установить область перемещения указателя по горизонтали
$08Установить область перемещения указателя по вертикали
$09Задать форму указателя для графического режима
$ ОАЗадать форму указателя для текстового режима
$ОВПолучить счетчик шагов перемещения
$0СЗадать условия вызова программы пользователя
Таблица 27.1 (окончание)
Функция
Назначение
$0D
Включить эмуляцию светового пера
$0E
Выключить эмуляцию светового пера
$0F
Установить отношение числа шагов на единицу виртуального экрана
$13
Установить порог удвоения скорости смещения указателя мыши
$14
Заменить процедуру пользователя
$15
Получить размер буфера, необходимого для сохранения состояния драйвера
$16
Сохранить текущее состояние драйвера
$11
Восстановить состояние драйвера
$1D
Задать видеостраницу для отображения указателя
$1E
Получить номер видеостраницы, в которой отображается указатель
Сбросить драйвер
Вход: АХ = о
Выход:
□ АХ — состояние мыши и драйвера:
$оооо — мышь или драйвер не установлены;
$FFFF — сброс осуществлен, драйвер и мышь установлены;
П вх — количество кнопок у мыши:
о — количество кнопок отлично от двух;
2 — две кнопки;
з — три кнопки.
После обращения к функции $оо все параметры драйвера принимают значения по умолчанию: устанавливается стандартная форма указателя (прямоугольник или стрелка — в зависимости от текущего режима работы дисплея); указатель помещается в центр экрана и его изображение гасится; с указателем связывается нулевая видеостраница; драйвер автоматически определяет текущее разрешение экрана и устанавливает максимально возможную область перемещения указателя. Обычно эта функция вызывается после смены режима работы дисплея.
Показать указатель мыши
Вход: АХ = 1.
Функция $01 показывает указатель на экране. Поскольку драйвер отслеживает перемещения мыши и при погашенном указателе, последний может появиться в любом месте экрана — не обязательно там, где был погашен.
Убрать указатель мыши
Вход: АХ = 2.
Функция $02 гасит указатель мыши. Эту функцию следует вызывать перед каждым использованием прямого доступа к видеопамяти. Дело в том, что драйвер запоминает состояние видеопамяти перед выводом указателя на экран и восстанавливает это состояние при перемещении указателя в новое место. Если перед непосредственной записью данных в видеопамять не погасить указатель, драйвер выведет на экран неверный символ при смещении мыши. Точно так же при непосредственном считывании данных из видеопамяти можно считать указатель мыши вместо соответствующего символа.
Следует иметь в виду, что драйвер наращивает на единицу внутренний счетчик всякий раз, когда вызывается функция $02, и уменьшает этот счетчик при вызове функции $01. Указатель становится виден только в том случае, когда этот счетчик равен о, поэтому вызов функций $01 и $02 может не привести к желаемым результатам, если нарушено соответствие вызовов.
Получить текущие координаты мыши и состояние ее кнопок
Вход: АХ = з.
Выход:
П вх — байт состояния кнопок (биты 0 — левая кнопка; 1 — правая кнопка; 2 — средняя кнопка; 1 соответствует нажатой кнопке, о — отпущенной);
П сх — горизонтальная координата мыши;
П DX — вертикальная координата.
Функция $оз дает исчерпывающую информацию о текущем состоянии мыши: состояние кнопок указывается битами регистра вх, в то время как в регистрах сх и DX возвращаются текущие координаты указателя. Координаты указателя соответствуют виртуальному экрану. На практике они содержат положение координатной точки (пиксела) указателя в графическом режиме или умноженные на 8 координаты в текстовом режиме (отсчет координат от нуля). Таким образом, чтобы перевести виртуальные координаты в координаты пози-
ция-колонка, нужно разделить полученные значения на 8 и добавить к ним по единице (текстовые координаты в Турбо Паскале отсчитываются от единицы). Для графического режима такой пересчет обычно не
Переместить указатель в новое положение
Вход:
□ АХ =
□DX — требуемые виртуальные координаты (X, У) мыши.
Функцияустанавливает указатель мыши в заданную точку экрана. Функ
ция получает виртуальные координаты, поэтому при обращении к ней координаты текстового экрана нужно уменьшать на единицу и умножать на 8. Виртуальные координаты требуемогоуказателя должны находиться
в прямоугольнике, ограниченном обращениями к функциям $ov и $08.
Получить информацию о нажатии/отпускании кнопки
Вход:
П АХ = 5 — получить информацию о нажатии;
О АХ =— получить информацию об отпускании;
П — идентификатор нужной кнопки (биты О, 1, 2 выбирают соответственно левую, правую и среднюю кнопку).
Выход:
О АХ — текущее состояние кнопок (см. вх в функции
П вх — количество нажатийуказанной кнопки после послед
него вызова функции $05 ($06);
ПDX — виртуальные координаты мыши (X, У), соответствующие по
следнему нажатию (отпусканию) кнопки.
Функции $05 и $06 дают информацию о том, сколько раз нажималась (отпускалась) нужная кнопка мыши, какая (какие) кнопки нажаты в данный момент и координаты той точки, где указанная кнопка нажималась (отпускалась) в последний раз. После обращения к этим функциям сбрасываются внутренние счетчики драйвера, поэтому полученная информация соответствует интервалу времени, прошедшему от момента последнего вызова соответствующей функции.
Установить область перемещения указателя по горизонтали (вертикали)
Вход:
П АХ = 7 — по горизонтали;
П АХ =8 — по вертикали;
х — минимальная координата;
DX — максимальная координата.
Функции $07 и $08 определяют границы прямоугольной области соответственно по горизонтали и вертикали, в пределах которой может перемещаться указатель мыши. После вызова функции курсор автоматически помещается в указанную область, если он находится вне-ее. Если при обращении к функциям минимальная граница больше максимальной, границы меняются местами. Границы, задаваемые этими функциями, могут превышать реальное разрешение экрана. В этом случае указатель будет исчезать, если виртуальные координаты мыши выходят за границы экрана.
Задать форму указателя для графического режима
Вход:
О АХ =
вх — положение координатной точки от левого края изображения;
сх — положение координатной точки от верхнего края изображения;
— адрес битового поля (ES — сегментная часть адреса).
Функция $09 определяет форму графического указателя. По умолчанию этот указатель имеет вид стрелки, ориентированной слева направо и снизу вверх, причем координатная точка указателя соответствует острию стрелки. Программа может с помощью этой функции задать другую форму указателя. При обращении к функции регистрыдолжны содержать адрес 64-
байтного массива, который трактуется следующим образом. Первые 16 двухбайтных слов рассматриваются как битовая маска 16x16, каждый бит которой маскирует пикселы экрана (эти байты называются маской экрана); вторые 16 слов — матрица 16x16 бит для формирования изображения указателя (маска изображения). Размер указателя на графическом экране всегда составляет 16x16 пикселов. При создании изображения драйвер предварительно получает информацию о 16x16 пикселах соответствующего места экрана и к каждому пикселу применяется операция AND с маской экрана, а затем к полученному результату — операция XOR с маской изображения. Например, если маска экрана содержит одни нули, на экране будет вырезан пустой прямоугольник 16x16 пикселов и в него без изменения помещается маска изображения — это вариант "непрозрачного" указателя. Наоборот, если маска экрана состоит из битовых единиц (слова со значением $FFFF), изображениевзаимодействует с экранным изображением в режиме
xoRPut ("прозрачный" указатель). Расположение сформированного указателя относительно виртуальных координат мыши определяется смещением координатной точки относительно левого и верхнего краев маски экрана. Если, например, вх сх = о, координатная точка соответствует левому верхнему
углу маски, поэтому изображение указателя размещается правее и ниже виртуальных координат указателя, если вх = сх = 8, координатная точка соответствует центру изображения.
Задать форму указателя для текстового режима
Вход:
АХ = $оа;
вх — флаг указателя (о — программное управление формой; 1 — аппаратное управление формой);
сх — маска экрана для вх = о или верхняя строка развертки для вх = 1;
DX — маска указателя для вх = о или нижняя строка развертки для вх = 1.
Функция $ОА управляет формой указателя в текстовом режиме. При обращении к ней программа имеет две возможности: определить форму программным способом (вх = о) или аппаратным (вх = 1). Аппаратная форма указателя соответствует стандартному мигающему курсору. Замечу, что сам текстовый курсор в этом случае не меняет своего положения, но становится невидимым, т. к. мигающее изображение соответствует указателю мыши (если указатель погасить, курсор вновь станет виден). Если выбрана аппаратная форма, регистры сх и DX содержат номера строк развертки, ограничивающих изображение. Эти координаты задаются в диапазоне о - 7. Если сх = о и DX = 7, изображение представляет собой мигающий прямоугольник максимальных размеров; стандартный курсор задается значениями сх = 6 и DX = 7. В случае программного управления формой в сх и DX должны содержаться маски экрана (сх) и символа (DX). Истинное изображение указателя в этом случае определяется следующим образом: читается слово из видеопамяти, куда будет помещен указатель; к этому слову применяется операция AND с маской экрана, а к результату — операция XOR с маской символа; окончательный результат помещается в видеопамять.
Получить счетчик шагов перемещения
Вход: АХ = $ов.
Выход: сх, DX — количество шагов по горизонтали (сх) и вертикали (DX) после последнего вызова функции $ов.
С помощью функции $ов можно получить суммарное количество шагов, на которые смещалась мышь по каждой координате за время, прошедшее с момента последнего вызова функции. Положительные значения соответствуют смещениям вправо и вниз, отрицательные — влево и вверх. Обращение к функции сбрасывает внутренние счетчики драйвера.
Задать условия вызова программы пользователя
Вход:
АХ = $ос;
сх — условия вызова; определяются единичными значениями разрядов регистра при следующих событиях:
биты 0 — мышь перемешается;
1 — нажимается левая кнопка;
2 — отпускается левая кнопка;
3 — нажимается правая кнопка;
4 — отпускается правая кнопка;
5 — нажимается средняя кнопка;
6 — отпускается средняя кнопка;
П ES: DX — адрес программы пользователя (ES — сегмент).
Функция $0С определяет условия вызова внешней процедуры, с помощью которой программа может реагировать на манипуляции с мышью в момент, когда происходит то или иное событие. Драйвер мыши постоянно находится в оперативной памяти ПК и получает управление при каждом изменении состояния мыши. Функция $ос сообщает драйверу адрес процедуры и условия ее вызова: в момент возникновения указанного события драйвер вызывает эту процедуру, которая должна транслироваться в расчете на дальнюю модель памяти (с ключом FAR). При вызове процедуры в регистрах центрального процессора содержатся следующие данные:
П АХ — маска условий (то же слово, которое передается функции $ос в регистре сх);
П вх — состояние кнопок, как это определено для функции $оз;
П сх, DX — виртуальные координаты курсора;
П DI, SI — число шагов мыши по горизонтали и вертикали;
П DS — сегмент данных драйвера мыши.
Программа может использовать эти регистры по своему усмотрению. Работа программы завершается командой EAR RET.
Примечание
При вызове программы пользователя регистр DS содержит сегмент данных драйвера мыши. Этот сегмент никогда не соответствует сегменту данных программы на Турбо Паскале, поэтому процедура пользователя должна реализовываться с помощью встроенного ассемблера (см. процедуру MouseHandler в модуле F_Mouse).
Включить (выключить) эмуляцию светового пера
Вход:
АХ = $0D — включить;
АХ = $ОЕ — ВЫКЛЮЧИТЬ.
С помощью функции $0D включается, а функцией $ОЕ выключается режим эмуляции светового пера. В языке программирования MS Basic предусмотрен оператор PEN, с помощью которого можно получить доступ к световому перу. Эмуляция светового пера включается драйвером по умолчанию. Если в ПК есть это устройство, режим эмуляций необходимо отключить.
Установить отношение числа шагов на единицу виртуального экрана Вход:
П АХ = $OF;
П CX, DX — количество сотых долей дюйма по горизонтали (сх) и вертикали (DX) на 8 единиц виртуального экрана (на 8 пикселов).
Функция $OF позволяет задать масштабные коэффициенты пересчета перемещения мыши в виртуальные координаты экрана. По умолчанию драйвер устанавливает значения этих коэффициентов, равные 8, т. е. каждый пиксел экрана соответствует 8 сотым долям дюйма (2,032 мм) линейного перемещения мыши. Чем большее значение задается этими коэффициентами, тем меньше смещается на экране указатель мыши при одном и том же ее перемещении.
Установить порог удвоения скорости смещения указателя мыши
Вход:
АХ = $13;
DX — новое значение порога удвоения скорости.
I
Заменить процедуру пользователя
Вход: АХ = $14.
Остальные регистры такие же, как и при вызове функции $ос.
Выход:
сх — условия вызова ранее установленной процедуры;
П ES: DX — адрес этой процедуры.
При переходе из одного режима работы программы в другой бывает необходимо запомнить текущее состояние драйвера, чтобы при возврате в старый режим восстановить это состояние. Эти действия реализуются функциями $14, $15, $16 и $17. Функция $14 реализует те же действия, что и функция $ос, т. е. задает адрес и условия вызова процедуры пользователя, но при выходе из нее в регистрах сх и ES: DX содержатся условия вызова и адрес ранее установленной процедуры.
Получить размер буфера, необходимого для сохранения состояния драйвера
Вход:
АХ = $15;
ВХ 0.
Выход: вх — размер буфера в байтах.
См. пояснения к функции $14.
Сохранить текущее состояние драйвера
Вход:
АХ = $16;
ES : DX — адрес буфера (ES — сегмент).
См. пояснения к функции $14..
Восстановить состояние драйвера
Вход:
АХ = $17;
П ES: DX — адрес буфера (ES — сегмент).
См. пояснения к функции $14.
Задать видеостраницу для отображения указателя
Вход:
П АХ = $id;
П вх — номер видеостраницы (от 0).
См. пояснения к функции $1Е.
Получить номер видеостраницы, в которой отображается указатель
Вход: АХ = $1Е.
Выход: вх — номер видеостраницы (от 0).
При работе с несколькими видеостраницами могут оказаться полезными функции $1D и $1E. С ПОМОЩЬЮ первой выбирается нужная видеостраница, в которой будет отображаться указатель мыши, вторая позволяет определить номер страницы с указателем.
Модуль F_Mouse
Все подпрограммы модуля F_Mouse (их около сорока) в функциональном отношении делятся на несколько групп — подпрограммы инициализации мыши, обслуживания координат и т. д. Основную их часть составляют подпрограммы низкого уровня, реализующие интерфейс между описанными в разд. 27.1 функциями драйвера мыши и программой на Турбо Паскале.
В модуль включены также процедуры и функции, облегчающие разработку программ, рассчитанных одновременно на работу и с мышью, и с клавиатурой. Для их использования программист должен определить так называемые элементы выбора — прямоугольные участки экрана, выбор которых с помощью мыши порождает генерацию расширенных кодов заранее обусловленных клавиш.
Полный текст модуля вы найдете в файле F_MOUSE.PAS на сопровождающей книгу дискете.
Глобальные константы, типы и переменные
Для описания элементов выбора, используемых в подпрограммах эмуляции клавиатуры, в модуле определен следующий тип:
type
Mous e I temType = record
XI,Y1,X2,Y2: Integer;{Координа ты обла сти}
Butt: Word;I
Key : Word(Ра смиренныйкод клавиши)
end;
Условием выбора является нажатие одной из кнопок мыши в момент, когда указатель находится в границах элемента. Это условие кодируется в поле Butt как признак нужной кнопки.
Для указания процедуры, получающей управление непосредственно от драйвера в момент наступления нужного события, определен такой процедурный тип:
type
MouseHandlerType = Procedure (Mask, Buttons, X, Y-, DX, DY: Integer);
Формальным параметрам процедуры придается следующий смысл:
Mask — маска вызова процедуры;
Buttons — состояние кнопок мыши;
х, Y — горизонтальная и вертикальная координаты указателя;
DX, DY — полное количество шагов перемещения по горизонтали (DX) и вертикали(DY).
Для указания той или иной кнопки могут использоваться следующие константы:
const
LeftButton = 1; {Левая кнопка}
RightButton = 2; {Правая кнопка}
CenterButton = 4; {Средняя кнопка)
AnyButton = 7; {Любая кнопка}
Пересчет виртуальных координат в текстовые и обратно осуществляется с помощью двух масштабных коэффициентов:
const
TextRatioX: Byte = 8; (Для колонок)
TextRatioY: Byte = 8; {Для строк)
Вы должны изменить эти коэффициенты, если используете в графическом режиме шрифт, размеры которого не соответствуют 8 пикселам по горизонтали и вертикали: в TextRatioX следует поместить ширину символов в пикселах, а В TextRatioY — высоту. '
Инициализация мыши
Функция IsMouse. Возвращает True, если программе доступна мышь. Заголовок:
Function IsMouse: Boolean;
Для проверки доступности мыши функция контролирует вектор прерывания $33 — он не должен быть нулевым. Затем она обращается к функции з драйвера (дать статус мыши) и контролирует изменение регистра вх.
Примечание
Эти проверки осуществляются только в том случае, когда внутренняя переменная NButton, содержащая количество кнопок мыши, равна нулю. Если мышь оснащена двумя или тремя кнопками, в эту переменную помещается нужное число после первого же обращения к функции InitMouse.
Функция InitMouse. Осуществляет сброс драйвера мыши. Заголовок:
Function InitMouse: Boolean;
После вызова этой функции указатель гасится и устанавливается в центр экрана, он приобретает стандартную форму, границы перемещения указателя расширяются на весь экран, для его отображения в текстовом режиме выбирается нулевая видеостраница (основная). Если к этому моменту были определены элементы выбора, они сбрасываются.
Функция возвращает True после успешного вызова и False, если отсутствует драйвер мыши или к мыши нет физического доступа. Вызов функции должен предшествовать началу работы с мышью.
Процедура SetMousePage. Устанавливает видеостраницу, в которой будет отображаться указатель мыши. Заголовок:
Procedure SetMousePage(Page: Byte).
Здесь Page — номер устанавливаемой страницы (нумерация страниц начинается с нуля).
Замечу, что при смене видеостраниц с помощью прерывания $10 драйвер мыши автоматически меняет страницу отображения, поэтому надобности в этой процедуре обычно не возникает.
Функция GetMousePage. Возвращает номер видеостраницы с указателем мыши. Заголовок:
Function GetMousePage: Byte.
Процедура SetStepToPixel. Устанавливает чувствительность мыши. Заголовок:
Procedure SetStepToPixel(Hor,Ver: Integer) .
Здесь:
Hor — чувствительность по горизонтали;
ver — чувствительность по вертикали.
Чувствительность определяется как количество сотых долей дюйма (0,254 мм) перемещения мыши, приходящихся на каждый пиксел экрана. По умолчанию чувствительность соответствует 8 единицам по каждой оси (2,032 мм). Чем большее значение имеют параметры Hor и ver, тем на меньшее расстояние переместится указатель при одном и том же смещении мыши.
Процедура GetStepToPixel. Возвращает текущую чувствительность мыши. Заголовок:
Procedure GetStepToPixel(var Hor,Ver: Integer);
После обращения к процедуре переменные Hor и ver будут иметь значения, соответствующие сотым долям дюйма на каждый пиксел экрана.
Координаты
Функция MouseTextCooX. Возвращает текстовую координату положения указателя по горизонтали. Заголовок:
Function MouseTextCooX(X: Integer): Byte.
Здесь х — виртуальная координата мыши.
Для пересчета используется коэффициент TextRatiox.
Функция MouseTextCooY. Возвращает текстовую координату положения указателя по вертикали. См. функцию MouseTextcoox.
Функция MouseGraphCooX. Возвращает горизонтальную виртуальную координату по заданной текстовой. Заголовок:
Function MouseGraphCooX(X: Byte): Integer.
Здесь х — текстовая координата.
Для пересчета используется коэффициент TextRatiox.
Функция MouseGraphCooY. Возвращает вертикальную виртуальную координату по заданной текстовой. См. функцию MouseGraphCooX.
Процедура MouseWhereXY. Возвращает текущие координаты указателя. Заголовок:■ r
Procedure MouseWhereXY(var X,Y: Integer).
Здесь х, Y — переменные, в которых возвращаются текущие виртуальные координаты указателя.
Замечу, что координаты меняются при любом смещении мыши независимо от того, виден ли на экране указатель или нет.
Процедура MouseGotoXY. Перемещает указатель в заданную точку экрана. Заголовок:
Procedure MouseGotoXY(X,Y: Integer). 1
Здесь х, Y — виртуальные координаты требуемого положения указателя.
Функция MouseWhereX. Возвращает текущую горизонтальную текстовую координату указателя. Заголовок:
Function MouseWhereX: Byte.
Функция MouseWhereY. Возвращает текущую вертикальную текстовую координату указателя. Заголовок:
Function MouseWhereY: Byte.
Функция Mousein. Возвращает True, если указатель находится в заданном месте экрана. Заголовок:
Function Mousein(XI,Y1,X2,Y2): Boolean.
Здесь xi, Yl, X2, Y2 — виртуальные координаты, определяющие верхний левый и правый нижний углы прямоугольника, в котором должен находиться указатель.
Состояние КНОПОК
•
Функция NumButton. Возвращает количество кнопок у мыши. Заголовок:
Function NumButton: Byte.
Функция возвращает 2, если мышь имеет две кнопки, з — если три, и о во всех других случаях.
Процедура GetMouseState. Возвращает текущее состояние мыши. Заголовок:
Procedure GetMouseState(var Butt,X,Y: Integer). .
Здесь:
Butt — признаки состояния кнопок;
х, Y — текущие виртуальные координаты.
В переменной Butt процедура возвращает информацию о текущем состоянии кнопок мыши. Для каждой кнопки в этом слове выделен один бит (0-й бит для левой, 1-й бит для правой и 2-й бит для средней; 0-й бит соответствует младшему разряду). Если бит имеет единичное значение, кнопка нажата, в противном случае отпущена.
Функция MousePressed. Возвращает True, если нажата любая кнопка мыши. Заголовок:
Function MousePressed: Boolean.
Процедура MouseButtPressed. Возвращает информацию о нажатии кнопки мыши. Заголовок:
Procedure MouseButtPressed (Butt: Integer; var Stat, Count, X, Y: Integer).
Здесь:
О Butt — флаг выбора нужной кнопки;
О stat — состояние кнопок;
Count — количество нажатий указанной кнопки с момента последнего вызова функции;
П х, Y — координаты, соответствующие последнему нажатию.
Процедура MouseButtReleased. Возвращает информацию об отпускании кнопки мыши. Заголовок:
Procedure MouseButtReieased (Butt: Integer; var Stat, Count, X, Y: Integer).
Здесь:
Butt — флаг выбора нужной кнопки;
stat — состояние кнопок;
count — количество отпусканий указанной кнопки с момента последнего вызова функции;
, Y — координаты, соответствующие последнему отпусканию.
,
Окна
Процедураобласть перемещения указателя. Заго
ловок:
Procedure
ЗдесьX2, Y2 — виртуальные координаты левого верхнего и правого
нижнего углов.
После вызова процедуры указатель скачком перемещается в указанную область, если перед этим он не находился в ней.
ПроцедураДелает доступным для перемещений указателя весь
экран. Заголовок:
Procedure MouseScreen.
В процедуре используется обращение к функции $F прерываниядля
того, чтобы определить текущий видеорежим.
Форма указателя
ПроцедураПоказывает указатель мыши. Заголовок:
Procedure
Процедура HideMouse. Гасит указатель мыши. Заголовок:
Procedure
Как уже отмечалось, драйвер мыши ведет подсчет обращений к функциям 1 и 2 (показать/убрать указатель). Чтобы исключить неоднозначную реакцию, процедуры showMouse и HideMouseпроверяют внутренний флаг
и осуществляют обращение к этим функциям только в том случае, когда это действительно необходимо. Если указатель уже виден на экране, игнорируется вызов showMouse, наоборот, если указатель погашен, игнорируетсявызовHideMouse.
Следует заметить, что при работе со стандартными процедурами модуля CRT, а также при нормальном выводе на экран процедурамиТурбо
Паскаль организует прямой доступ к видеопамяти. Поэтому полезно вызывать HideMouse перед любым изменением содержимого экрана.
Процедура MouseTextCursor. Определяет вид указателя для текстового режима. Заголовок:
Procedure MouseTextCursor(ScrMask,CurMask: Word).
Здесь:
scrMask — маска экрана;
О curMask — маска указателя.
Напомню, что драйвер мыши формирует указатель следующим образом: читается слово видеопамяти, содержащее символ и атрибуты нужной позиции экрана; к этому слову применяется операция AND с маской экрана, а к полученному результату — операция XOR с маской указателя. Таким образом, если ScrMask о, указатель имеет форму символа, содержащегося в младшем байте маски curMask, а атрибуты указателя (цвет и признак мерцания) задаются страшим байтом этого слова.
Процедура MouseBlink. Определяет указатель в виде мигающего курсора. Заголовок:
Procedure MouseBlink(YUp,YDn: Byte);
Здесь YUp, YDn — номера верхней (YUp) и нижней (YDn) строк развертки.
После обращения к этой процедуре указатель мыши имеет вид мигающего курсора. Замечу, что сам текстовый курсор не меняет своего положения при перемещении мыши — драйвер "заимствует" его изображение для создания указателя. Параметры YUp и YDn определяют размер указателя: первый параметр задает номер верхней, а второй — нижней строк развертки, в пределах которых мигает курсор. Стандартный курсор соответствует YUp = 6 и YDn = 7, т. е. занимает две нижние строки (нумерация строк от 0 до 7). При YUp = о и YDn = 7 курсор занимает все знакоместо. Если при обращении YUp = $20, указатель не будет виден.
Процедура MouseGraphCursor. Определяет вид указателя в графическом режиме. Заголовок:
Procedure MouseGraphCursor(var ScrCurMask; X,Y: Byte).
Здесь:
О ScrCurMask — 64-байтный массив, определяющий маску экрана и маску указателя;
х, Y — смещение координатной точки относительно левого верхнего угла изображения.
Массив ScrCurMask должен содержать две 32-байтных маски, которые трактуются следующим образом. При выводе указателя драйвер осуществляет
операцию AND над участком экрана размером 16x16 пикселов и первой половиной массива (маской экрана); затем к результату применяется операция XOR с второй половиной ScrCurMask (с маской указателя). Полученное рассматривается как битовая маска 16x16 пикселов, которая выводится в нужное место экрана как указатель мыши. Если, например, первая половина массива ScrCurMask (маска экрана) состоит из одних нулей, изображение указателя, определяемое второй половиной массива (маска указателя), выводится следующим образом: каждое 16-разрядное слово маски рассматривается как образ 16 пикселов в одной из строк развертки; если слово имеет значащий разряд, соответствующий пиксел светится, если о — не светится.
Параметры х и Y определяют смещение координатной точки указателя от верхнего левого угла изображения. Если х = Y = о, координатная точка мыши соответствует верхнему левому углу изображения, если х = Y = 8 — центру изображения и т. д. .
Сохранение/восстановление состояния
Функция MouseBuffsize. Возвращает размер памяти (в байтах), необходимой для сохранения текущего состояния драйвера мыши. Заголовок:
Function MouseBuffSize: Word.
Процедура SaveMouse. Сохраняет текущее состояние драйвера мыши. Заголовок:
Procedure SaveMouse(var Buff).
Здесь Buff — переменная нужного размера, в которой будет сохранено текущее состояние драйвера (минимальный размер переменной возвращается функцией •MouseBuffSize).-
Процедура RestoreMouse. Восстанавливает состояние драйвера. Заголовок:
Procedure RestoreMouse(var Buff).
Здесь Buff — переменная, содержащая состояние драйвера.
Предполагается, что к моменту вызова процедуры RestoreMouse состояние драйвера было сохранено в переменной Buff с помощью процедуры SaveMouse.
•
Эмуляция клавиатуры
В модуль F_Mouse включены процедуры и функции, облегчающие разработку программ, в которых диалог с пользователем реализуется с помощью как клавиатуры, так и мыши. Многие диалоговые программы имеют различного рода указатели (опции), обеспечивающие выбор одного из нескольких воз-
24 Зак. 94]
можных вариантов. В модуле такие элементы называются элементами выбора. Для задания элементов выбора предусмотрен следующий тип данных:
type
MouseltemType = record
X1,Y1,X2,Y2: Integer;{Текстовые координаты области)
Butt: Word;{Условия вызова)
Key : Word{Скан-код клавиши)
end;
Как нетрудно заметить, в этом типе предусмотрены поля для определения положения каждого из элементов выбора на экране (XI—Y2), а также условие выбора (Butt) и код той клавиш и (кеу), с помощью которой выбирается этот элемент. Предполагается, что пользователь (программист) сам создает на экране изображение диалогового элемента. После этого он обращается к процедуре SetMouseltem, вызов которой делает элементы "известными" для подпрограмм KeyOrMousePressed И ReadKeyOrMouse.
Процедура setMouseitem. Задает один или более элементов выбора. Заголовок:
ProcedureByte; var4
Здесь:
О NItem — количество определяемых элементов;
□ items — массив из Nitem элементов типа MouseltemType (описание типа см. в разд. 27.2. Г).
Если к моменту обращения к этой процедуре уже был определен список элементов выбора, он безвозвратно теряется. Процедура копирует список элементов в динамическую память, поэтому последующее изменение списка без обращения к процедурам SetMouseltem/AddMouseltem игнорируется.
Процедура AddMouseltem. Добавляет еще один элемент к списку элементов выбора. Заголовок:
Procedure AddMouseItem(Item: MouseltemType).
После обращения к процедуре старый список элементов выбора дополняется еще одним элементом.
Процедура ClearMouseltem. Очищает список элементов выбора. Заголовок:
Procedure CiearMouseitem.
Процедура освобождает динамическую память, выделенную для списка элементов выбора, и присваивает внутренней переменной NMouseltem (количество элементов выбора) значение о.
Функция KeyOrMousePressed. Возвращает True, если нажата любая клавиша или выбран один из элементов выбора. Заголовок:
Function KeyOrMousePressed: Boolean.
Функция ReadKeyOrMouse. Возвращает расширенный код нажатой клавиши или выбранного элемента из списка. Заголовок:
Function ReadKeyOrMouse: Char.
При обращении к функции вначале проверяется состояние клавиатуры: если к этому моменту была нажата любая клавиша, функция возвращает расширенный код этой клавиши точно так же, как это делает стандартная функция ReadKey. Если ни одна клавиша не была нажата, проверяется состояние мыши: когда указатель находится в поле одного из элементов и обеспечено условие выбора этого элемента (например, нажата нужная кнопка мыши), функция возвращает расширенный код, связанный с этим элементом. Если ни одно из условий не выполнено, т. е. не нажата клавиша или не выбран элемент, функция повторяет опрос клавиатуры и состояния мыши.
В поле кеу нужного элемента выбора пользователь должен поместить расширенный код клавиши, связанной с этим элементом. Если младший байт этого слова равен нулю, функция устанавливает внутренний флаг DoubleChar в значение True, что обеспечивает выдачу второго байта этого слова при повторном обращении к функции. Например, если вы хотите, чтобы с некоторым элементом была связана клавиша <F1>, то необходимо поместить в младший байт поля Key значение о, а в старший — значение 59. Тогда при первом обращении к функции программа получит значение #0, а при втором — #59, что и представляет собой расширенный код клавиши <F1>.
Обработка событий от мыши
В интерфейсной части модуля F_Mouse определены три процедуры, с помощью которых пользователь может установить собственную процедуру обработки событий от мыши, изменить эту процедуру на новую или сбросить ее. Кроме того, в теле модуля имеется внутренняя процедура MouseHandler, реализующая интерфейс между драйвером мыши и процедурой пользователя. Эта процедура является фактическим обработчиком событий от мыши: при возникновении условий вызова драйвер мыши передает управление именно ей. Процедура MouseHandier проверяет корректность вызова (ненулевое значение адреса процедуры пользователя) и обеспечивает нужный формат вызова последней. Для использования процедур этого раздела программист должен подготовить собственную процедуру с заголовком, соответствующим такому формату:
type
MouseHandlerType = procedure (Mask,Buttons,X,Y,DX,DY: Integer);
Здесь:
Mask — условие вызова процедуры;
Buttons — состояние клавиатуры в момент вызова;
х, У — координаты положения мыши;
DX, DY — общее количество шагов мыши в горизонтальном (6х) и верти- кальном(DY) направлениях.
Условие вызова процедуры пользователя определяется состоянием разрядов параметра Mask по такому соглашению:
бит 1 — вызов при нажатии левой кн• опки мыши;
О 2 — при нажатии правой кнопки;
3 — при нажатии средней кнопки;
4 — при отпускании левой кнопки;
—\
О 5 — при отпускании правой кнопки;
6 — при отпускании средней кнопки.
Если параметр Mask имеет значение о, драйвер не вызывает процедуру пользователя.
Процедура пользователя не может быть локальной (т. е. не может определяться в теле другой процедуры), ее параметры должны соответствовать типу MouseHandlerType.
Процедура SetMouseHandler. Извещает драйвер о необходимости передачи управления процедуре пользователя. Заголовок:
Procedure SetMouseHandler(Mask: Word; Proc: MouseHandlerType). .
Здесь:
Mask — условие вызова;
I
П Proc — процедура пользователя.
Процедура ChangeMouseHandier. Заменяет ранее объявленную процедуру пользователя на новую. Заголовок:
Procedure ChangeMouseHandier (Mask: Word; Proc: MouseHandlerType; var OldMask: Word; var OldProc: Pointer).
Процедура возвращает в переменной OldMask условие вызова, а в OldProc — адрес ранее установленной процедуры пользователя.
Процедура ClearMouseHandler. Удаляет ранее установленный обработчик событий от мыши. Заголовок:
Procedure ClearMouseHandler.
Если к моменту обращения к этой процедуре была объявлена процедура пользователя (с помощью вызова драйвер перестает ее вызывать.
Тестовая программа
Следующая довольно длинная программа (пример 27.1) иллюстрирует неко-
торые приемы работы с подпрограммами модуляВ программе соз
дается экран, показанный на рис.
Демонстрация возможностей модуля F_Mouse
Завершить работу (Esc)
Рис.Вид экрана для тестовой программы
После запуска программы она ожидает одно из трех возможных
нажатие клавиши <F1> — в этом случае предлагается возможная форма указателя мыши; <F2> — переход из текстового режима в графический и обратно; <Esc> — завершение работы программы и возврат в ДОС. Замечу, что для сохранения идентичности экранов независимо от текущего режима работы (текстового или графического) в программе используются процедуры модулей и F_GrText. При этом изображения на экране оказываются
Модуль F_Text предназначен для поддержки многостраничной работы в текстовом режиме. Поскольку объем этой книги оказался чрезмерно большим, пришлось пожертвовать описанием этого модуля и особенностей работы в текстовом режиме. Текст модуля вы найдете в файле F_TEXT.PAS на сопровождающей книгу дискете.
настолько похожими, что для неискушенного взгляда единственным отличием режимов является лишь форма указателя мыши.
Вы можете выбирать эти продолжения не только с помощью клавиш, но и мышью. Для этого необходимо сместить указатель мыши так, чтобы он попал внутрь соответствующего прямоугольника, и нажать левую кнопку мыши.
При выборе формы указателя в текстовом режиме на экране разворачивается окно, содержащее все символы ПК в диапазоне от 1 до 255. Сместив указатель к нужному символу и нажав левую кнопку мыши, можно выбрать форму указателя. В графическом режиме программа предлагает меню из четырех возможных форм: стандартный указатель, перекрестие, кружок и квадрат. Для выбора формы указателя в графическом режиме используется процедура, получающая управление от драйвера мыши при любом перемещении ее по столу. В этой процедуре определяется текущее положение указателя и высвечивается негативным изображением указатель той или иной формы.
i Пример 27.1. Иллюстрация возможностей F_Mouse
•..• >. • X. •«.. . • • .vf//• Л е.I•. <."A *•... ь. v;’.‘ ।
Г*f
I Программа, демонстрирующая возможности \
Iмодуля F_Mouse|
i7 I1
Uses CRT,DOS,Graph,F_Mouse,F GrText,F_Text;
const
tl = ' Демонстрация возможностей модуля F_Mouse * ;
t2 = ' Состояние мыши:;
t3: array [1..4] of String [22] =
(1Координаты: Х=Y=', 'Левая кнопка
'Правая кнопка: * , ' Средняякнопка: * ) ; NItem = 3;
t4: array [1..NItem] of String [24] =
( ’ Изменить указатель (Fl) ' , ' Сменить режим(F2) ' , 'Завершитьработу(Esc) ' ) ;
type
ItemT = array [1. .NItem] of MouseltemType;
const
Items : ItemT = (
(XI: 9,- Yl: 4; X2:35; Y2 : 6; Butt: LeftButton; Key:59 shX 8) , (XI: 4 4; Yl: 4; X2:68; Y2 : 6; Butt: LeftButton; Key:60 shl 8) , (Xl:26; Yl:18; X2:51; Y2:20;utt: LeftButton; Key:27));
Text: Boolean = True;
var
k,n,b,x,y,bb,xx,yy: Integer;
Font: Pointer;
Fontsize: Word;
type
GotoXYType = procedure (XrY> Byte);
ColorsType = procedure (Text,Back: Byte);
SetWindowType = procedure (XI,Y1,X2,Y2,Border: Byte;
Header: String;lip, Build,Play: Boolean);
ChangeCursorType = procedure;
var
Colors: ColorsType;
GotoXY: GotoXYType;
Setwindow: SetWindowType;
Changecursor: ChangeCursorType;
Procedure SetScreen;
{Создает экран в текстовом или графическом режиме)
begin
{Создаем основное окно}
Colors(Black,LightGray);
Setwindow(1,1,80,25,DoubleBorder,tl,True,False,False);
{Создаем элементы выбора)
Colors(Black,Green);
for k := 1 to NItem do with Items[к] do
begin
Setwindow(XI,Y1,X2,Y2,SingleBorder, '', True, False, False)
Write(t4[k])
end;
{Создаем панель статуса)
Colors(White,Red);
Setwindow(24,9,55,16,DoubleBorder,t2,True,False,False);
n := (30-Length(t2)) div 2;
for k := 1 to 4 do
begin
GotoXY(n-3,k+l);
Write(t3[к])
end
end; (SetScreen)
Procedure ChangeTextCursor; Far;
(Изменяет курсор в текстовом режиме)
const
t = ' Выберите символ для указателя мыши var
Byte;
x, у: Integer;
begin
(Создаем окно выбора)
HideMouse;
PutWindow(8,7,73,12,White,Blue, DoubleBorder,l,t,True,False, False) ;
(Выводимв нем все символы, кроме Bel,BS,LF,CR) for k := 1 to 255 do
if k in [7,8,10,13] then
Write С ')
else
Write(chr(k)) ;
{Формируем пределы перемещения указателя} Mousewindow(MouseGraphCooX(9),MouseGraphCooY(8),
MouseGraphCooX(72),MouseGraphCooY(11));
(Цикл до отпускания левой кнопки) •
Repeat
GetMouseState(b,x, y);
until b and LeftButton=0;
ShowMouse;
(Цикл до нажатия левой кнопки)
Repeat
GetMouseState(b,x, y);
until b and LeftButtonOO;
HideMouse;
{Определяем выбранный СИМВОЛ}
x := MouseTextCooX(x)-8;
у := MouseTextCooY(y) -7;
x := (y-l)*64+x;
if x>255 then
x := 1;
{Меняем форму курсора)
MouseTextCursor($FF00,$7F00+x);
{Восстанавливаем экран}
GetWindow(1);
MouseScreen;
ShowMouse
end;
{-
--}
{Глобальные определения для обработчика событий от мыши, используемого в режиме выбора графического указа теля} const
РХ = 40;
{Шаг смещения изображений}
NCur = 4;
(Количество изображений}
var
kk,
{Ранее выбранное изображение}
хО,
(Левая граница изображения)
yO,yl: Integer;
{Вертикальные границы)
РР: Pointer;
{Изображение указа теля}
SizePP: Word;
(Его размеры в памяти}
Procedure MouseHandler(Mask,Buttons,X,Y,dDX,DY: Integer); Far; (Отслеживает перемещения мыши и инвертирует изображение, на которое указывает в данный момент указатель мыши}
var
k: Integer;
begin
{Текущее изображение)
k := (х-ХО) div DX;
if k<l then
k := 1;
if k>NCur then
kNCur;
if k=kk then
Exit;{Текущее изображение не изменилось}
{Гасим старое и выделяем новое изображение}
Putlmage(х0+(2*кк-1)*DX div 2,yO,PPA,NormalPut) ;
GetImage(xO+(2*k-l)*DX div 2,yO,xO+(2*k+l)*DX div 2,yl,PPA);
Putlmage(x0+(2*k-l)*DX div 2,yO,PPA,NotPut); kk := k
end; {MouseHandler}
Procedure ChangeGraphCursor; Far;
(Изменяет курсор в графическом режиме}
const
t = ' Выберите указатель: *;
var
у,х1: Integer;
Size,Ь: Word;
Pointer;
Cur: array [1..NCur,0..1,0..15] of Word;
Proc: MouseHandlerType;
г/
Procedure PutCur(N: Byte);
(Сканирует изображение в левом верхнем углу экрана и формирует маски указателя)
var
х,у: Byte;
Word;
beginч
for у := 0 to 15 do
begin
b := 0;
for x := 0 to 15 do
if GetPixel (х, у) OGetBkColor then
b := b or ($8000 shr х) ;
Cur[N,1,у] := b;
Cur IN, 0, у] := not b
end;
ClearViewPort
end; {PutCur}
;
begin{ChangeGraphCursor}
{Готовим окно 16x16 в левом верхнем углу экрана}
Size := Imagesize(0,0,15,15);
GetMem(P,Size);
Getlmage (0, 0,15,15, PA) ;
SetViewPort(0,0,15,15,ClipOn);
ClearViewPort;
(Готовим маски указателей}
HideMouse;
if InitMouse then
ShowMouse;(Получаем стандартное изображение}
SetMouseItem(NItem,Items);
MouseGotoXY(0,0);(Стандартныйуказатель}
PutCur(1);
HideMouse;
Circle (8,8,4);(Кружок)
PutCur(2);
Line(0,8,15,8);(Перекрестие}
Line (8, 0, 8,15) ;
PutCur(3) ;
Rectangle(О,0,15,15);(Квадратик)
PutPixel(7,7,15);
PutCur(4) ;
SetViewPort(0,0,GetMaxX,GetMaxY,ClipOn);
{Готовим окно для меню указателей)
;
FreeMem(P,Size);
xO : = GraphCooX(9) ;
yO : = GraphCooY(7) ;
xl := GraphCooX(36);
yl := GraphCooY(10);
Size := Imagesize(xO,yO,xl,yl);
GetMem(P,Size);
Getlmage (xO, yO, xl, yl, PA) ;
Colors(White,Blue);
Setwindow(9,7,35,9,DoubleBorder,t,True,False,False);
{Выводим изображения указателей}
yl := yO+16; '
for kk := 1 to NCur do
for у0 to 15 do
begin
xl := xO+kk*DX;
b := Cur [kk, 1, y]; for x := 0 to 15 do if b and ($8000 shr x)<>0 then
PutPixel(xl+x,yl+y,White) end;
{Устанавливаем пределы перемещения указателя) уО := yO+16; yi := MouseWindow(xO,yO,xl+DX,yl) ;
(Выделяем первое изображение} SizePP := ImageSize(xO+DX div 2,yO,xO+3*DX div 2,yl); GetMem(PP,SizePP);
Getlmage(xO+DX div 2,yO,xO+3*DX div 2,yl,PPA);
Putlmage(xO+DX div 2,yO,PPA,NotPut); kk1;
{Устанавливаем свой обработчик событий) SetMouseHandler(l,MouseHandler); (Цикл до нажатия левой кнопки) repeat
GetMouseState(k,x,y) until k and LeftButtonOO; (Удаляем обработчик событий) ClearMouseHandler; FreeMem(PP,SizePP) ; {Формируем новый указа тель} kk := (x-xO) div DX; if kk>NCur then kk := NCur; if kk<l then kk := 1; MouseGraphCursor(Cur[kk,0,0],0,0); {Восстанавливаем экран) Putlmage (xO,yO-16,PA,NormalPut) ; FreeMem(P,Size); MouseWindow(0,0,GetMaxX,GetMaxY); F_GrText.Window(25,10,54,15); Colors(White,Red); ShowMouse
end;{ChangeGraphCursor)
/1
Procedure SetTextScreen;
{Создает текстовый экран)
begin if not Text then
begin{Был графический режим)
FreeMem(Font,Fontsize) ;{Удаляемшрифт)
GraphWriteOff;{Восстанавливаем ввод/вывод}
CloseGraph;{Переходим к текстовому режиму)
TextTrue
end;
/Указываем нужные для текстового режима процедуры).
GotoXY := F_Text.GotoXY;
Colors : = F_Text.Colors;
Setwindow :=
ChangeCursor :=
{Устанавливаем стандартный коэффициент пересчета}
TextRatioY := 8;
{Создаем текстовый экран)
SetScreen
end; (Set ТехtScreen)
Procedure SetGraphScreen;
{Создает графический экран) var
d,r: Integer;
h: Byte;
Name: String;
F: File;
begin
if Text then
{Инициализируем графику)
begin
d Detect;
InitGraph(d,r, ';
if GraphResultogrOk then
Exit ;
(Выбираем шрифт в зависимости от разрешения экрана} case GetMaxY of
349: h := 14; (EGA 8x14}
479: h := 19; (VGA 8x19}
else
h := 8 {Прочие 8x8}
end;
{Загружаемшрифт из файла 8xH.fnt}
Str (h, Name);
Name' 8x1 +Name+ ' . f nt 1 ;
Assign (F,Name) ;
{$!-}
Reset (F,1)
($1+1
if IOResult<>0 then
Exit;
FontSize := FileSize (F) ;
GetMem(Font,FontSize);
BlockRead (F,FontA, Fontsize, r) ;
Close(F);
if roFontSize then
begin
FreeMem(Font, FontSize) ;
Exit
end;
{Регистрируем шрифт и переназначаем ввод/вывод}
SetFont(Font,8,h) ;
GraphWriteOn;
Text := False
end;
{ Указываем нужные для графического режима процедуры}
GotoXY := F_GrText.GotoXY;
Colors := F_GrText.Colors;
SetWindow := F_GrText . Setwindow;
ChangeCursor := ChangeGraphCurs or;
{Задаем коэффициент пересчета вертикального положения указателя мыши в текстовый размер: он равен высоте шрифта}
TextRatioYh;
{Создаем графический экран}
SetScreen
end; {SetGraphScreen}
I1
I7
beginfОсновная программа)
CursorOff; (Удаляем курсор)
SetTextScreen; {Формируем текстовый экран)
{Инициализируеммышь. Эту процедуру всегда следует осуществлять после установки или смены режима работы дисплея)
if not InitMouse then
Halt ;
ShowMouse;
{Указываем элементы выбора}
SetMouseItem(NItem,Items);
{Основной цикл работы продолжается до выбора элемента "Завершить работу" или нажатия Esc) (Запоминаем начальное состояние мыши)
GetMouseState(b,х,у);While (True) dobegin{Проверяем выбор элементов) if KeyOrMousePressed then case ReadKeyOrMouse of#27: Halt; {Esc}#0: case ReadKeyOrMouse of#59: ChangeCursor;#60: beginHideMouse;if Text thenSetGraphScreenelseSetTextScreen;if InitMouse thenShowMouse;GetMouseState(b,х,у);While (True) dobegin{Проверяем выбор элементов) if KeyOrMousePressed then case ReadKeyOrMouse of#27: Halt; {Esc}#0: case ReadKeyOrMouse of#59: ChangeCursor;#60: beginHideMouse;if Text thenSetGraphScreenelseSetTextScreen;if InitMouse thenShowMouse;{"Бесконечный" цикл}{"Бесконечный" цикл}(Есть выбор}(Fl - Изменить курсор)(F2 - Сменить режим)(Убираем мышь перед сменой режима}(Был текстовый режим){Был графический режим){Инициализируеммышьпосле смены режима !}(Есть выбор}(Fl - Изменить курсор)(F2 - Сменить режим)(Убираем мышь перед сменой режима}(Был текстовый режим){Был графический режим){Инициализируеммышьпосле смены режима !}
GetMouseState(b,х,у);
While (True) do
begin
{Проверяем выбор элементов) if KeyOrMousePressed then case ReadKeyOrMouse of
#27: Halt; {Esc}
#0: case ReadKeyOrMouse of
#59: ChangeCursor;
#60: begin
HideMouse;
if Text then
SetGraphScreen
else
SetTextScreen;
if InitMouse then
ShowMouse;
GetMouseState(b,х,у);
While (True) do
begin
{Проверяем выбор элементов) if KeyOrMousePressed then case ReadKeyOrMouse of
#27: Halt; {Esc}
#0: case ReadKeyOrMouse of
#59: ChangeCursor;
#60: begin
HideMouse;
if Text then
SetGraphScreen
else
SetTextScreen;
if InitMouse then
ShowMouse;
{"Бесконечный" цикл}
{"Бесконечный" цикл}
(Есть выбор}
(Fl - Изменить курсор)
(F2 - Сменить режим)
(Убираем мышь перед сменой режима}
(Был текстовый режим)
{Был графический режим)
{Инициализируеммышь
после смены режима !}
(Есть выбор}
(Fl - Изменить курсор)
(F2 - Сменить режим)
(Убираем мышь перед сменой режима}
(Был текстовый режим)
{Был графический режим)
{Инициализируеммышь
после смены режима !}
SetMouseltem (NItem, Items){Заново указываем элементы
выбора, т.к. они сбрасываются при инициализации мыши)
end;
end (case) end;{case}
(Получаем текущее состояние мыши) GetMouse'State(bb,xx,УУ) ;
if (bbob) or (хх<>х) or (ууоу) then
begin{Состояние изменилось — сообщить)
b := bb; x : = хх; У := уу; GotoXY(п+11,2);
Write(MouseTextCooX(х):2);
GotoXY(n+16,2);
Write(MouseTextCooY(у): 2); GotoXY(n+13,3);
if b and LeftButtonOO then Write('нажата ')
else
Write('отпущена');
GotoXY(n+13,4);
if b and RightButtonOO then Write('нажата ')
else
Write('отпущена’);
GotoXY(n+13,5);
if NumButton=3 then
if b and CenterButtonOO then Write('нажата ') else , Write('отпущена');
end (if (bboO) or...)
end {while TRUE)
end.
Глава 28
Доступ к отображаемой памяти
Что такое отображаемая память
Ранние IBM-совместимые ПК типа IBM PC/XT оснащались микропроцессорами 8088 или 8086, способными работать с оперативной памятью емкостью не более 1 Мбайт. Несмотря на значительные размеры этой памяти, в некоторых прикладных программах ее оказывается недостаточно. Такие программы вынуждены интенсивно использовать диск для размещения больших объемов данных, что сильно снижает их производительность. Поэтому почти одновременно с появлением IBM PC/XT начались поиски путей повышения производительности крупных прикладных программ (табличных процессоров, систем управления базами данных и т. п.) за счет взаимодействия с оперативной памятью большего, чем 1 Мбайт размера. Эти поиски привели к выработке соглашения между ведущими фирмами — разработчиками программно-аппаратных средств, которое известно как EMS LJM (от Expanded Memory Specification — спецификация отображаемой памяти, удовлетворяющая стайдарту фирм Lotus—Intel—Microsoft/ В соответствии с этим стандартом ПК оснащаются специальными EMS-платами, содержащими собственно отображаемую память и микросхемы, обеспечивающие доступ к ней. В настоящее время EMS стал промышленным стандартом, которому следуют все поставщики программно-аппаратных средств.
В специальной технической литературе, употребляются термины "дополнительная память", "отображаемая память", "верхняя память" и т. д. Обилие терминологических определений, связанных с памятью ПК, требует пояснений. ' Прежде всего замечу, что операционная система MS-DOS, а следовательно, и подавляющее большинство работающих под ее управлением программ могут работать лишь с первым мегабайтом памяти, который часто называют
базовой памятью. Эта память в рамках DOS поделена на две неравные части: первые 640 Кбайт отводятся для программ пользователя и самой DOS и называются стандартной памятью, а оставшиеся 384 Кбайт зарезервированы для памяти видеоадаптеров и ПЗУ и называютсяблоком памяти
Memory Block). В то же время в компьютерах IBM AT имеется возможность адресовать память объемом до 16 Мбайт, если они оснащены микропроцессором 80286, или до 4 Гбайт, для микропроцессоров 80386 или 80486.
£Примечание
Объем непосредственно адресуемой оперативной памяти определяет разрядность адресной шины микропроцессора: 8088 и 8086 имеют 20-разрядную шину, поэтому адресуют = 1 048 576 = 1 Мбайт; 80286 оснащен 24-разрядной шиной и адресует= 16= 16 Мбайт; 32-разрядная шина 80386 и
80486 адресует = 4 294 967 296 = 4 Гбайт.
Память ПК, остающаяся за вычетом первого мегабайта (т. е. за вычетом базовой памяти),(Extended Memory). Эта память дос
тупна только в специальном, так называемом защищенном, режиме работы микропроцессоров 80286/80386/80486. Поскольку защищенный режим не поддерживается в MS-DOS, его не использует и подавляющее большинство прикладных программ, работающих под управлением этой операционной системы. Таким образом, владелец современного компьютера IBM AT, оснащенного памятью, скажем, в 8 Мбайт, работая с MS-DOS-программами, часто либо вообще не использует дополнительные 7 Мбайт, либо размещает в них электронный диск или буфернуюдля дисков. В версии Турбо
Паскаля 7.0 (точнее, в пакете Borland Pascal with Objects 7.0) введена поддержка защищенного режима микропроцессоров 80286/80386/80486, в котором используется дополнительная память (см. главы
Помимо базовой, отображаемой и дополнительной памяти, в IBM AT существует еще так называемая верхняя память (не путайте с верхним блокомКак известно, 20-разрядный адрес на IBM AT в реальном
режиме и на компьютерах IBM PC/XT образуется путем сложения двух 16-разрядных слов — сегмента и смещения. Передсегмент сме
щается на 4 разряда влево (умножается на 16), что и обеспечивает 20-разрядный результат сложения — адрес конкретного байта. Формальное сложение сегмента и смещения может привести к переполнению 20-разрядной адресной сетки. Действительно, если, например, сегмент seg = $FFFF, а смещение ofs = $0010, то сложениедает
значениедля представления которого требуется уже 21 разряд.
Поскольку в IBM PC/XT применяется 20-разрядная шина, переполнение результата не может использоваться и "лишний" 21-й разряд просто теряется, т. е. на таких ПК адресация с сегментом seg = $FFFF и смещением ofs > $OOOF эквивалентна адресации с сегментом Seg = $0000 и смещени-
ем Ofs =В адресной шине IBM AT имеется 21-й разряд, но его
использование обычно запрещено. Однако в таких компьютерах предусмотрена возможность программного управления 21-м разрядом. Если этот разряд разблокировать, программе, работающей в реальном режиме процессора, станут доступны еще почти 64 Кбайт (без 16 байт). Эта часть памяти и называется верхней (High Memory Area). MS-DOS версии 5.0 и более поздних, а также некоторые совместимые с ней операционные системы других фирм (например, DR-DOS фирмы Digital Research версии 4.0 и позже) могут размещать в верхней памяти свои резидентные части, тем самым освобождая драгоценную стандартную память для программ пользователя.
Итак, существуют 4 вида оперативной памяти:
базовая — с адресам от $00000 до $fffff;
верхняя — с адресами от $100000 до $ioffef;
дополнительная — с адресами от $100000 до $FFFFFFFF;
отображаемая — организуется специальными аппаратными средствами на компьютерах с микропроцессорами 8088, 8086, 80286 и может программно эмулироваться на процессорах 80386 и 80486.
Каким образом микропроцессоры типа 8088/8086, имеющие 20-разрядную адресную шину, получают доступ к отображаемой памяти, т. е. к памяти, лежащей за пределом 1 Мбайт? Для этого аппаратным способом реализуется перенос небольшого участка отображаемой памяти, называемого окном, в адресное пространство микропроцессора. Отображаемое окно занимает адреса, которые не используются в ПК. Обычно эти адреса лежат за границей 640 Кбайт, т. е. принадлежат верхнему блоку памяти (рис. 28.1).
Возможность отображения EMS-памяти в участок верхнего блока связана с тем, что ни видеоадаптер, ни ПЗУ не используют все адреса этого блока. Действительно, для адресации к видеопамяти адаптера VGA требуется не более 640 х 480/8 = 38 400 байт адресного пространства, а адаптеров EGA и CGA — и того меньше; даже для видеопамяти SVGA с разрешением 1024x768 требуется менее 100 Кбайт; с учетом того, что для ПЗУ обычно выделяется 64 или 128 Кбайт, остается 150 Кбайт и более свободного адресного пространства. В эти адреса и отображается окно EMS-памяти.
Программа может управлять аппаратными средствами EMS-платы, в частности, она может связывать окно с различными участками отображаемой памяти. Таким образом, для использования отображаемой памяти программа должна указать, какую именно часть EMS-памяти связать с окном, и затем
работать с данными по адресам отображаемого окна, т. е. по адресам выше 640 Кбайт (но, разумеется, ниже 1 Мбайт): при каждом обращении к окну аппаратура EMS-платы "подставляет" вместо несуществующей базовой памяти с этими адресами нужный участок отображаемой памяти.
Рис. 28.1. Память ПК
Отображаемое окно -взаимодействует с EMS-памятью порциями по 16 Кбайт. Эти порции называются страницами. Одновременно в окне может отображаться от одной до четырех страниц. Таким образом, максимальная емкость окна не может превышать 64 Кбайт, в то время как максимальная емкость EMS-памяти составляет 32 Мбайт (2048 страниц).
Еще раз подчеркну, что наличие на вашем ПК памяти за границей 1 Мбайт (обычно это легко выявляется в ходе автотестирования сразу после включения компьютера) еще не означает, что эта память — отображаемая. Главное отличие отображаемой памяти от дополнительной заключается в том, что первая может отображаться в адресное пространство до 1 Мбайт, а вторая — не может. Отображение реализуется аппаратным способом, поэтому для доступа к EMS-памяти компьютер с процессором 80286 или более ранним (8088, 8086, V20 и т. п.), должен оснащаться специальной платой. Начиная с версии 4.0, операционная система MS-DOS поставляется с драйвером отображаемой памяти XMA2EMS.SYS И эмулятором EMM386.SYS. Последний на ПК с микропроцессорами 80386/80486 обеспечивает эмуляцию отображаемой памяти, т. е. программно реализует функции EMS-платы и преобразует дополнительную память в отображаемую.
Функции драйвера отображаемой памяти
Работа EMS-платы управляется специальным драйвером, который называется EMM (Expanded Memory Manager — администратор отображаемой памяти). Существует несколько версий EMM. В этом разделе описываются функции драйвера отображаемой памяти так, как они реализованы в EMM версии 4.0. Однако перед тем как рассматривать функции EMM, полезно обсудить некоторые подробности его работы.
Как уже говорилось, отображаемая память выделяется программе страницами по 16 Кбайт каждая. Эти страницы отображаются в окне, имеющем размеры 64 Кбайт, т. е. окно способно отобразить до четырех страниц одновременно. Условимся в дальнейшем называть страницы окна физическими, а страницы EMS-памяти логическими.
Поскольку логических страниц может быть гораздо больше, чем физических, необходимо каким-то образом управлять процессом отображения в окне, т. е. устанавливать связи между окном и отображаемой памятью. Эта задача в EMM решается следующим образом. Для использования EMS- памяти программа должна создать так называемый дескриптор EMM. С дескриптором связывается одна или несколько (до 2048) логических страниц. В программе может создаваться произвольное количество дескрипторов, однако память, выделенная одному из них, не может использоваться никакими другими дескрипторами, поскольку каждый дескриптор владеет своей памятью монопольно. При обращении к большинству функций EMM программа должна указывать дескриптор и тем самым определять логические страницы, о которых идет речь.
Для установления связи между логическими страницами дескриптора и физическими страницами окна предусмотрена специальная процедура, которая называется картированием окна. При картировании вы должны указать номер физической страницы и номер той логической страницы, содержимое которой должно отображаться в этой физической странице окна. Нумерация как логических, так и физических страниц начинается с нуля, т. е. допустимыми являются номера О—3 для физических и от 0 до N — 1 для логических страниц (N — полное количество страниц, которыми владеет дескриптор).
В табл. 28.1 приводится краткая характеристика функций EMM версии 4.0.
Таблица 28.1. Функции драйвера отображаемой памяти
ФункцияДействия
$40Дать статус EMM
$41Получить сегментную часть адреса отображаемого окна
$42Получить информацию о EMS-памяти
Таблица28.1 (окончание)
Функция
Действия
$43
Создать дескриптор EMM и распределить память
$44
Картировать окно
$45
Уничтожить дескриптор EMM и освободить память
$ 4 6
Дать номер версии EMM
$47
Сохранить картирующий контекст
$48
Восстановить картирующий контекст (предварительно контекст должен быть сохранен с помощью функции $47)
$4В
Дать количество активных дескрипторов
$4С
Дать количество связанных с дескриптором страниц
$40
Дать информацию о распределении памяти
$4Е
Дать/установить карту всех страниц
$ 4 F
Дать/установить карту части страниц
$50
Картировать/освободить несколько страниц одновременно
$51
Изменить количество связанных с дескриптором страниц
$52
Получить/установить флаг сохранения страниц
$53
Установить/получить имя дескриптора
$54
Дать список имен дескрипторов или искать дескриптор по имени
$55
Картировать память и выполнить инструкцию JUMP
$56
Картировать память и выполнить инструкцию CALL
$57
Переместить/обменять блок памяти
$58
Получить полную карту EMS-окна
Для обращения к EMM используется программное прерывание с вектором $67. Чтобы вызвать нужную функцию EMM, следует поместить в регистр АН номер функции, соответствующим образом сформировать остальные регистры и вызвать прерывание $67. При выходе из прерывания регистр АН содержит статус возврата — код, характеризующий успешность запроса. Ненулевое значение статуса указывает на ошибку обращения (см. табл. 28.2).
Таблица 28.2. Коды ошибок EMM
КодЗначение
$00Нетошибок,функциявыполнена(вовсехостальных случаях —невы
полнена)
Таблица 28.2 (продолжение)
КодЗначение
$80Внутренняя ошибкаEMM
$81Сбой EMS-платы
$82EMM занят обработкой предыдущего запроса
$ 8 3Неверный дескриптор окна
$84Запрошена неопределенная в данной версии EMM функция
$8 5Нет доступных дескрипторов окон
$86Ошибка при сохранении или восстановлении окна
$87Запрос распределения превышает общую EMS-память
$88Запрос распределения превышает число доступных страниц
$89Попытка создать дескриптор с нулевым количеством страниц
$ 8АДескриптор не располагает таким числом страниц
$8ВПопытка картировать больше 4 страниц
$8СПереполнение области сохранения картирующего контекста
$8DПопытка повторного сохранения картирующего контекста
$8ЕПопытка восстановления несохраненного контекста
$8FHe определен параметр подфункции
$90Неизвестный тип атрибутов
$91Нет аппаратной поддержки сохраняемых страниц
$92Источник и приемник информации одновременно находятся в EMS-
памяти (предупреждение, не ошибка)
$93Размер приемника информации слишком мал
$ 94Стандартная память частично перекрывает отображаемую память
$95Слишком большое смещение в перемещаемом блоке
$96Размер перемещаемого блока больше 1 Мбайт
$97Источник и приемник связаны с одним дескриптором и частично пере
крываются
$98Недопустимый тип памяти источника или приемника
$9АНет аппаратной поддержки альтернативной установки страниц
$9ВИсчерпаны все допустимые установки альтернативных регистров
$9СНе поддерживается установка альтернативных регистров
$9DНеправильная установка альтернативных регистров
Таблица 28.2 (окончание)
КодЗначение
$ 9ЕНазначенные каналы прямого доступа к памяти (КПДП) не поддерживаются
$9FУказанный КПДП не поддерживается
$АОДескриптор не поименован
$А1Указанное имя дескриптора уже существует
$А2 Источник выходит за границу 1 Мбайт
$АЗСодержимое указанной области данных разрушено
$А4Нет доступа к указанной функции
Дать статус EMM
Вход: АН = $40.
Выход: АН — статус EMM.
Эта функция позволяет профамме проконтролировать состояние только что завершившейся операции с EMS-памятью. Содержимое регистра АН кодирует состояние EMM так, как это указано в табл. 28.2.
Получить сегмент отображаемого окна
Вход: АН = $41.
Выход: вх — сегмент окна.
После обращения к этой функции программа сможет определить адрес участка верхнего блока памяти, в котором создано отображаемое окно EMS. Начало каждой физической страницы соответствует адресу
BX+1024xN:0000
где:
О вх — сегмент окна;
□ N — номер физической страницы (о-з).
Поскольку положение окна не меняется, профамма может обратиться к этой функции лишь один раз и затем использовать полученный адрес при любых операциях с отображаемой памятью.
Получить информацию о EMS-памяти
Вход: АН = $42.
Выход:
□ DX — общее количество страниц (по 16 Кбайт каждая);
О вх — количество свободных страниц.
С помощью функции $42 программа может получить информацию об общем количестве логических страниц (регистр DX) и количестве свободных в данный момент страниц (регистр вх).
Создать дескриптор и распределить память
Вход:
П АН = $43;
.
П вх — требуемое количество страниц (от 1 до 2 048).
Выход: DX — дескриптор EMM.
Новый дескриптор создается функцией $4 3. При обращении к ней в регистре вх указывается количество логических страниц, связываемых с дескриптором. Это количество не должно превышать значения, возвращаемого в регистре вх функцией $42. EMM не гарантирует, что выделенные дескриптору страницы будут располагаться в смежных физических блоках (т. е. например, соответствовать непрерывному адресному пространству дополнительной памяти, если отображаемая память эмулируется драйвером EMM386.SYS), однако он строго следит за тем, чтобы никакой другой дескриптор не использовал их. Программа должна сохранить полученный в регистре DX дескриптор для работы с выделенной ему памятью.
Картировать окно
Вход:
П АН = $44;
П AL — номер логической страницы (от 0 до 3);
П вх — физический номер страницы (от о до N - 1, где N — количество страниц, связанных с данным дескриптором);
П DX — дескриптор EMM.
Функция $44 осуществляет привязку одной логической страницы к конкретной физической странице, т. е. картирование окна. После выхода из этой функции указанная физическая страница будет отображать новую логическую страницу, в то время как содержимое остальных физических страниц останется неизменным. Замечу, что любая физическая страница может быть связана с любой логической страницей, т. е. одна и та же логическая страница при необходимости может отображаться в двух, трех или даже во всех четырех страницах окна одновременно. Если какая-либо физическая страница останется не связанной ни с одной логической, EMM блокирует доступ к странице: при чтении из нее любой байт будет содержать код $FF (255) и запись по любому байту в пределах несвязанной страницы не меняет
этого значения.
Уничтожить дескриптор EMM и освободить память
Вход:
-
О АН = $45;
□ DX — дескриптор.
Функция $45 уничтожает дескриптор и освобождает связанные с ним логические страницы. Не забывайте вызывать эту функцию в конце работы с EMS-памятью, т. к. в отличие от кучи отображаемая память не освобождается автоматически в момент завершения работы программы! Более того, я настоятельно рекомендую перехватить процедуру выхода (как это сделать см. в главе 25) и в новой процедуре при необходимости сделать вызов этой функции, т. к. программа может завершиться аварийно и выделенная дескриптору (дескрипторам) память будет потеряна для остальных программ впредь до перезагрузки ДОС. Этот прием избавит вас от дополнительных хлопот при отладке программы.
Дать номер версии EMM
Вход: АН = $46.
Выход: AL — номер версии в двоично-десятичном формате.
С помощью функции $46 программа может получить номер версии того EMM, с которым она работает в данный момент. Если вы собираетесь писать коммерческую программу, такая проверка будет весьма кстати, т. к. описываемые ниже функции $4F—$58 доступны только для версии 4.0 и недоступны в ранних. После возврата из этой функции регистр AL содержит номер версии в двоично-десятичном формате: старший полубайт — первую цифру, младший — вторую. Например, для версии 4.0 в регистре содержится $40=64, а для более ранней версии 3.2 — $32=50.
Сохранить/восстановить картирующий контекст
Вход:
П АН = $47 — сохранить контекст;
П АН = $48 — восстановить контекст;
DX — дескриптор.
Функции $47 и $48 используются в ситуациях, когда нужно временно сохранить текущую карту страниц. Такие ситуации могут возникнуть, например, перед вызовом процедур, использующих тот же самый дескриптор. Вызовы функций $47 и $48 должны быть парными. Это означает, что если вы для одного и того же дескриптора дважды подряд попытаетесь вызвать функцию $47 без промежуточного вызова функции $4 8, это приведет к возникновению ошибки $80, а повторный вызов $48 без вызова $47 — ошибки $8Е.
Дать количество активных дескрипторов
Вход: АН $4В.
Выход: вх — количество дескрипторов.
Функция $4В возвращает в регистре вх количество активных в данный момент дескрипторов, а функция $4С — количество связанных с дескриптором логических страниц (см. замечание к функции $4с).
Дать количество связанных с дескриптором страниц
Вход:
АН = $4с;
DX — дескриптор.
Выход: вх — количество страниц.
С помощью этой функции вы можете получить количество связанных с дескриптором логических страниц.
При выполнении следующей программы (пример 28.1) на экран выводится общее количество дескрипторов и свободная EMS-память к моменту начала работы программы, затем создается новый дескриптор и ему передается вся доступная память. В заключение вновь выводится общее количество дескрипторов и количество страниц, которыми владеет вновь созданный дескриптор, после чего дескриптор уничтожается.
ММ^jjy^Mj^j ; Пример 28.1
Uses DOS;
var
Reg: Registers;
Procedure Int r 67 ;
{Выполняет прерывание $67 с регистрами, заданными значениями переменной Reg}
begin
Intr($67,Reg);
if Reg.ah<>0 then
begin
WriteLn('Ошибка ’,Reg.ah);
Halt
end
end;
var
Handle: Word;
begin
with Reg do begin
{Определяем общее количество дескрипторов) ah := $4В;
Intr67;
WriteLn('Используется', Ьх: 3, ' дескрипторов');
{Определяем объем всей и доступной EMS-памяти} ah := $42;
Intr67;
WriteLn(dx:15,' страниц EMS-памяти'); WriteLn(bx:15,' страниц — свободно'); '
{Создаем новый дескриптор) ah := $43;
Intr67;
Handledx;
{Определяемколичество страниц, которыми он владеет) ah := $4С;
Intr67;
WriteLn('Создан новый дескриптор, владеющий Ьх,' страницами');
{Проверяем общее количество дескрипторов} ah := $4В;
Intr67;
WriteLn('(Общее количество дескрипторов':35,Ьх:2,')'); {Уничтожаем новый дес криптор} ah := $45;
dx := Handle;
Intr67;
WriteLn('Новый дескриптор уничтожен: 1) ;
{Указываем количество оставшихся дескрипторов} ah :=•
Intr67;
Write('осталось ',bx, ' дескрипторов, 1 ) г
ah := $42;
Intr67;
WriteLn(bx,' свободных страниц')
end
end.
#
Дать информацию о распределении памяти
Вход:
АН = $ 4 d;
ES:DI — адрес буфера.
Выход: вх — количество активных дескрипторов.
С помощью функции $4D программа может получить информацию обо всех активных дескрипторах. Она возвращает общее количество дескрипторов в регистре вх, а информацию о каждом из них — в массиве, адрес которого передается функции в регистрах ES (сегмент) и DI (смещение). Для каждого дескриптора в этом массиве должно быть предусмотрено два 16-разрядных слова, в которые функция помещает сам дескриптор (в первое слово) и количество связанных с ним страниц (во второе).
Дать/установить карту всех страниц
Вход:
П АН = $4Е;
П AL — номер подфункции:
о — получить в буфере по адресуES:DI карту страниц;
1 — установить карту страниц из буфера по адресуDS:Si;
2 — комбинация подфункций 0 и 1;
з — дать размер буфера для подфункций 0—2.
Выход: AL — размер буфера (для подфункции 3).
Буфер ES: DI заполняется картой страниц (подфункции 0 и 2).
Функция $4Е используется для быстрой смены содержимого окна. Для этого следует предварительно получить текущую карту страниц с помощью подфункции 0, а затем изменить картирующий контекст. После этого быстрый возврат к прежнему контексту осуществляется с помощью подфункций 1 или 2. Подфункция 3 возвращает размер буфера ES:DI, необходимый для подфункций 0 и 2.
Дать/установить карту части страниц
Вход:
АН = $ 4 f;
AL — номер подфункции:
о — получить карту;
• 1 — установить карту;
2 — получить размер буфера для подфункции 0;
П вх — количество страниц (для подфункции 2);
П DS: SI — адрес описателя запроса (для подфункции 0) или карты страниц (для подфункции 1);
П ES:DI — адрес буфера (для подфункции 0).
Функция $4F аналогична функции $4Е, но используется для сохране- ния/восстановления картирования не всех, а только указанных физических страниц. Подфункция 0 обеспечивает сохранение картирования. При обращении к ней регистры DS: SI должны содержать адрес описателя запроса, а регистры ES:DI — адрес буфера, куда будет помешена нужная информация. В первое 2-байтное слово описателя запроса программа помещает общее количество физических страниц, для которых запоминается картирующий контекст, а в остальные слова — сегментные части адресов тех страниц, о которых идет речь.
Функции $4Е и $4F предназначены для обслуживания многозадачных систем.
Картировать/освободить
несколько страниц одновременно
Вход:
П АН = $50;
П AL — номер подфункции:
о — использовать номера физических страниц;
1 — использовать сегменты физических страниц;
П DX — дескриптор EMM;
П сх — количество 4-байтных элементов в буфере запроса (по одному элементу на каждую страницу);
О DS: SI — адрес буфера запроса.
Функция $50 обеспечивает картирование сразу нескольких (до четырех) физических страниц. При обращении к ней регистр AL должен содержать указание на то, как выбираются физические страницы: если AL = о — по номерам, если AL1 — по сегментам. В регистре сх должно содержаться
количество картируемых страниц, в регистре DX — дескриптор, а в DS: SI — адрес описателя запроса. Описатель запроса содержит по два слова на каждую картируемую страницу. В первом слове указывается номер логической страницы, а во втором — номер (для подфункции AL = о) или сегмент (AL = 1) физической страницы. Если в качестве номера логической страницы указан код $FFFF, соответствующая физическая страница перестает быть связанной с какой-либо логической: чтение из нее возвращает значение $FF (255) в любом байте, и запись в нее не меняет этого значения.
Изменить количество связанных
с дескриптором страниц
Вход:
□ АН = $51;
П DX — дескриптор EMM;
П вх — новое количество страниц.
С помощью функции $51 программа может изменить количество связанных с дескриптором страниц. При обращении к ней регистр DX содержит ранее созданный дескриптор, а вх — новое количество логических страниц, связываемых с этим дескриптором. Если вх меньше того количества, которым располагает дескриптор к моменту вызова функции, то освобождаются страницы со старшими номерами.
Получить/установить флаг сохранения страниц
Вход:
П АН = $52;
П AL — номер подфункции:
о — получить флаг;
1 — установить флаг;
2 — дать признак аппаратной поддержки функций 0 и 1.
Выход: AL — флаг сохранения — для подфункции 0 или признак аппаратной поддержки для подфункции 2.
Некоторые EMS-платы поддерживают так называемые сохраняемые страницы. Содержимое таких страниц не меняется при "горячей" перезагрузке ДОС, т. е. при перезагрузке без выключения питания (реализуется кнопкой <RESET> или клавишами <Ctrl>+<Alt>+<Del>). С помощью функции $52 программа может объявить сохраняемыми страницы, связанные с некоторым дескриптором. Для этого необходимо обратиться к подфункции AL = 1, указав в регистре DX дескриптор, а в вх флаг сохранения. Если флаг равен единице, страницы становятся сохраняемыми, если нулю — не сохраняемы-
ми. С помощью подфункции AL = о можно проверить флаг сохранения страниц (о — не сохраняются, 1 — сохраняются), а подфункция 2 возвращает в AL признак аппаратной поддержки сохраняемых страниц (о — нет поддержки, 1 — есть поддержка).
Установить/получить имя дескриптора
Вход:
□ АН = $53;
О AL — номер подфункции:
о — получить имя;
1 — установить имя;
DX — дескриптор EMM;
О ES : DI — адрес буфера для подфункции 0;'
□ DS:SI — адрес имени для подфункции 1.
Для более удобной реализации связей внутри многозадачных систем можно организовать доступ к дескрипторам по их именам. С этой целью в EMM предусмотрены функции $53 и $54. Первая позволяет установить новое имя дескриптора или получить ранее установленное имя. Вторая обеспечивает поиск дескриптора по его имени. При обращении к подфункции 0 функции $53 (получить имя) в регистре DX указывается дескриптор, а в регистрах ES : DI — адрес буфера длиной 8 байт. В этот буфер помещается связанное с дескриптором имя, соответствующее структуре
array [1. . 8] of Char
Если имя ранее не было определено, буфер содержит пробелы. При обращении к подфункции 1 (задать имя) в регистрах DS: SI указывается адрес массива из восьми символов. Эти символы без какого-либо изменения помещаются во внутренний буфер EMM и в дальнейшем могут использоваться для поиска дескриптора. В качестве символов имени можно задавать любые СИМВОЛЫ ПК ОТ #0 ДО #255.
Дать список имен дескрипторов или искать дескриптор по имени
Вход:
О АН = $54;
AL — номер подфункции:
о — дать список имен;
1 — искать дескриптор;
2 — дать количество активных дескрипторов;
ES : DI - адрес буфера для подфункции 0;
— адрес имени для подфункции 1.
Выход: AL — количество дескрипторов (для подфункции 2) или дескриптор (для подфункции 1).
Буфер по адресузаполняется списком имен (для подфункции 0).
Функция $54 предназначена в основном для поиска дескриптора по его имени (подфункция 1). Однако с ее помощью можно также получить список имен (подфункция 0) и количество активных дескрипторов (подфункция 2). При обращении к подфункции 0 в ES:DI указывается адрес буфера. Каждому активному дескриптору в этом буфере должно быть выделено по 10 байт: первые два байта при выходе из функции содержат сам дескриптор (его идентификационный номер), а остальные восемь байтов — связанное с ним имя.
Картировать память
и выполнить инструкцию JUMP .
Вход:
а АН = $55;
AL — номер подфункции:
0 — использовать номера физических страниц;
1 — использовать сегменты страниц;
DX — дескриптор EMM;
О — адрес буфера запроса.
Функции $55 и $56 используются при размещении в отображаемой памяти исполняемых программ. При обращении к функции $55 (картировать и выполнить JUMP) в регистрахсодержится адрес описателя запроса.
Описатель должен иметь длину 9 байт и соответствовать следующей структуре: первые четыре байта содержат сегмент и смещение точки, куда передается управление по инструкции JUMP FAR, следующий за ними байт указывает количество N картируемых страниц, а остальные четыре байта определяют полный адрес (т. е. сегмент и смещение) массива, описывающего новуюкартустраниц.долженсостоятьизNпарслови
содержать номер логической страницы (первое слово каждой пары) и номер (для подфункции AL = о) или сегмент (AL = физической страницы (второе слово). Функция $55 сначала осуществляет нужное картирование, а затем реализует инструкцию JUMP FAR.
25941
Картировать память и выполнить инструкцию CALL
Вход:
О АН = $56;
□ AL — номер подфункции:
о — использовать номера физических страниц; '
1 — использовать сегменты страниц;
2 — получить требуемый размер стека;
П DX — дескриптор EMM;
П DS: SI — адрес буфера запроса.
Выход: вх — размер стека (для подфункции 2).
Работает приблизительно так же, как функция $55, но после картирования реализует инструкцию вызова подпрограммы CALL. При обращении к подфункциям 0 и 1 в регистрах DS:SI указывается адрес 22-байтного описателя вызова. Первые 9 байт этого описателя аналогичны описателю для функции $55. За ними следует еще один байт длины и FAR-адрес той карты страниц, которую необходимо установить перед выходом из подпрограммы. Последние 8 байт описателя зарезервированы для использования в EMM. С помощью подфункции 2 программа может получить размер области стека, используемой при вызове подфункций 0 и 1 функции $56.
Переместить/обменять блок памяти Вход:
П АН = $57;
П AL — номер подфункции:
о — переместить блок;
1 — обменять блок;
П DS : SI — адрес буфера запроса.
С помощью функции $57 программа может перемещать большие блоки данных как внутри EMS-памяти, так и между отображаемой и стандартной памятью. Максимальная длина перемещаемого блока составляет 1 Мбайт. Допускается полное ИЛИ , частичное перекрытие сегментов. Подфункция О осуществляет перемещение блока на новое место, а 1 — обмен блоков (источник помещается на место приемника и наоборот). При обращении к ним в регистрах DS:SI указывается адрес следующего описателя запроса:
MoveBlockType = record
Size : Longlnt;{Длина блока в байтах)
SourceType: Byte;SourceType: Byte;{Типисточника: 0 — основная память,
SourceType: Byte;
SourceType: Byte;
1 — отображаемая память)
DestinHand:DestinHand:Word;Word;Word;Word;Word;Byte;Word;Byte;Word;Word;Word;Word;Word;Word;если находится вилиесли вначала источника внутри страницы или или страница — основная память,1 —EMM, если приемник находится в(Смещение приемника} или страницаесли находится вилиесли вначала источника внутри страницы или или страница — основная память,1 —EMM, если приемник находится в(Смещение приемника} или страница
DestinHand:
DestinHand:
Word;
Word;
Word;
Word;
Word;
Byte;
Word;
Byte;
Word;
Word;
Word;
Word;
Word;
Word;
если находится вили
если в
начала источника внутри страницы или или страница — основная память,
1 —
EMM, если приемник находится в
(Смещение приемника} или страница
если находится вили
если в
начала источника внутри страницы или или страница — основная память,
1 —
EMM, если приемник находится в
(Смещение приемника} или страница
end;
Получить полную карту EMS-окна Вход:
= $58;
П AL — номер подфункции:
о — получить карту;
1 — получить длину карты.
Выход:
П сх — длина карты (для подфункции 1);
О ES : DI — буфер (для подфункции 0).
С помощью функции $58 программа может получить полную карту EMS- окна. При обращении к подфункции 0 регистрысодержат адрес буфе
ра, в который будет помещена карта, а при вызове подфункции 1 драйвер помещает в регистр сх количествоэлементов этойПервое
слово каждого элемента содержит сегмент физической страницы, а второе слово — номер той логической страницы, которая в данный момент ото- в этой физической. Эта карта содержитэлементов, где N — ко
личество активных физических страниц (некоторые физические страницы могут не быть связаны с
28.3. Модуль
Достаточно подробное описание функций EMM, приведенное в предыдущем разделе, позволит вам при необходимости создать собственный интерфейс низкого уровня для доступа к этим функциям. В описываемом ниже
модуле F_EMS я попытался реализовать более удобный, как мне кажется, способ использования отображаемой памяти. Полный текст модуля содержится в файле F_EMS.PAS на прилагаемой дискете.
С этой целью в интерфейсной части модуля определена иерархия объектов, показанная на рис. 28.2.
Базовым объектом иерархии является объект TEMSArea. EMS-память в рамках этого объекта рассматривается как массив вида^
type
EMSMemory = array [O..Size-l] of Byte,
где size — длина резервируемой объектом памяти.
Объект TEMSvector порожден от TEMSAre ли предназначен для начального структурирования данных. Этот объект рассматривает отображаемую память как набор фрагментов, каждый из которых имеет одинаковую длину в байтах. В стандартном Паскале ему в какой-то степени соответствует одномерный массив из элементов произвольного типа.
От TEMSvector порожден объект TEMSMatrix, имитирующий двумерные массивы стандартного Паскаля. Он перекрывает методы родителя, чтобы обрабатывать обращение к элементам с помощью двух индексов. Как и в стандартном Паскале, элементы в объекте TEMSMatrix располагаются по столбцам:заэлементомсиндексами(1,1)расподагаетсяэлемент(1,2)
и т. д.
Объект TEMSArray также порожден от TEMSvector и имитирует массив с произвольным количеством измерений. Фактические индексы элемента при обращении к процедурам Setitem и Getltem задаются в виде списка, что не очень удобно, вот почему для моделирования одно- и двумерных массивов используются отдельные объекты.
Объект TEMSType порожден от TEMSArea и имитирует файл с последовательным доступом (ленточный накопитель). Его элементы могут иметь различную длину, поэтому доступ К очередному элементу возможен только после чтения (записи) предыдущего элемента.
Объект TEMSText также имитирует последовательный файл, но его элементами являются строки.
Наконец, потомок TEMSScreen базового объекта TEMSArea предназначен для хранения в EMS-памяти копии графического экрана.
Использование объектов модуля F_EMS снимает многие ограничения Турбо Паскаля на объем обрабатываемых данных. Более того, поскольку индексы массивов и максимальные размеры сохраняемых данных определяются как длинные целые числа, использование процедур модуля может упростить программу. Например, максимальная длина любого массива, размещаемого в динамической памяти, не может быть больше 64 Кбайт, поэтому более крупные массивы приходится размещать в куче порциями; при использовании объектов модуля F_EMS этого делать не нужно. Однако следует помнить, что доступ к EMS-памяти осуществляется с помощью более или менее сложных объектных методов и поэтому происходит значительно медленнее, чем обращение к базовой памяти.
Объекты модуля
TEMSArea
Этот объект является основой иерархии объектов модуля F_EMS. Методы объекта интерпретируют доступную ему EMS-память как одну очень длинную цепочку байтов. Единственное, что умеет делать объект с этой памятью, это записывать в нее данные или читать данные из нее. Для этого в объект включены базовые методы WriteEMS (писать в EMS-память) и ReadEMS (читать из памяти). Эти методы можно рассматривать как модификацию стандартной процедуры Move на случай обмена данными с EMS-памятью: при обращении к ним нужно указать адрес источника, адрес приемника и количество пересылаемых байтов. Отличие от Move состоит в том, что это количество задается параметром типа Longlnt, что позволяет обмениваться массивами данных произвольного размера (стандартная процедура Move обеспечивает передачу не более 65 535 байт).
Для большинства практических целей более удобно пользоваться наследниками этого объекта.
Поля
□ Size: Longint; Только чтение. Определяет размер (в байтах) связанной с объектом отображаемой памяти.
□ MaxPage: word; Только чтение. Определяет количество EMS-страниц по 16 Кбайт каждая, в которых размещаются Size используемых объектом байтов.
П Handle: Word; ТОЛЬКО чтение. Дескриптор EMM, владеющий MaxPage страницами EMS-памяти.
Методы
П constuctor Init (ASize: Longlnt); Перекрывается — часто. Инициирует объект: определяет количество MaxPage страниц EMS-памяти, достаточное для размещения ASize байт, создает дескриптор Handle и связывает СНИМMaxPage страниц.
П Destructor Done; Virtual; Перекрывается — редко. Освобождает EMS-память, связанную с дескриптором Handle. He забывайте использовать вызов этого метода перед тем как завершить работу с объектом или перед окончанием работы программы, поскольку выделенная дескриптору память остается в его монопольном распоряжении, даже если сам дескриптор уже не существует.
П Procedure EMSError (NError: Byte); Virtual; ЭТОТ метод вызывается всеми другими методами объекта TEMSArea и его ПОТОМКОВ в случае, если обнаружена какая-либо ошибка. Параметр NError определяет номер ошибки. Если NErrorOO, метод устанавливает глобальный признак ошибки EMSErrorFlag, который может проанализировать вызывающая программа, затем выводит на экран текстовое сообщение о характере возникшей ошибки. Сообщение выводится с помощью стандартной процедуры writeLn. Вы должны перекрыть метод EMSError в графическом режиме работы дисплея, если не используете модуль F_GrText или не осуществляете самостоятельной интерпретации вызова процедуры writeLn. Этот метод следует перекрывать также в потомках для вывода сообщений о нестандартных ошибках. Исходный метод выдает нужное сообщение в случае, если номер ошибки лежит в диапазоне $80—$AD. Несколько необычная левая граница диапазона связана с тем, что диапазон $80—$A4 соответствует стандартным ошибкам драйвера EMM.SYS (см. разд. 28.2). В табл. 28.3 указаны номера ошибок и соответствующие им сообщения.
Таблица28.3. Сообщения об ошибках, выдаваемые процедурой
TEMSArea.EMSError
КодЗначение
$80Внутренняя ошибка EMM
$81Сбой EMS-платы
$82EMM занят обработкой предыдущего запроса
Таблица 28.3 (продолжение)
КодЗначение
$83Неверный дескриптор окна
$84Запрошена неопределенная в данной версии EMM функция
$85Нет доступных дескрипторов окон
$86Ошибка при сохранении или восстановлении окна
$87Запрос распределения превышает общую EMS-память
$88Запрос распределения превышает число доступных страниц
$89Попытка создать дескриптор с нулевым количеством страниц
$8АДескриптор не располагает таким числом страниц
$8ВПопытка картировать больше 4 страниц
$8С Переполнение области сохранения картирующего контекста
$80Попытка повторного сохранения картирующего контекста
$8ЕПопытка восстановления несохраненного контекста
$8FHe определен параметр подфункции
$ 90Неизвестный тип атрибутов
$ 91Нет аппаратной поддержки сохраняемых страниц
$92Источник и приемник информации одновременно находятся в EMS-памяти
$93Размер приемника информации слишком мал
$94Стандартная память частично перекрывает отображаемую память
$95Слишком большое смещение в перемещаемом блоке
$96Размер перемещаемого блока больше 1 Мбайт
$97Источник и приемник связаны с одним дескриптором и частично пере
'крываются
$98 Недопустимый тип памяти источника или приемника
$99Неопознанная ошибка
$ 9АНет аппаратной поддержки альтернативной установки страниц
$9ВИсчерпаны все допустимые установки альтернативных регистров
$9СНе поддерживается установка альтернативных регистров
$ 9DНеправильная установка альтернативных регистров
$ 9ЕНазначенные каналы прямого доступа к памяти (КПДП) не поддерживаются
$9FУказанный КПДП не поддерживается
Таблица 28.3 (окончание)
КодЗначение
$АОДескриптор не поименован
$А1Указанное имя дескриптора уже существует
$А2Источник выходит за границу 1 Мбайт
$АЗСодержимое указанной области данных разрушено
$А4Нет доступа к указанной функции
$А5Нет EMS-памяти или не установлен драйвер EMM.SYS
$А6Неверный формат файла
$А7Ошибка при чтении объекта из файла
$А8Ошибка при записи объекта в файл
$А9Попытка чтения за границей доступной EMS-памяти
$ААПопытка записи за границей доступной EMS-памяти
$АВНеверные границы индексов
$АСРазмер элемента вектора не соответствует длине переменной
$ADИзменен режим работы дисплея (объект TEMSScreen)
Примечание
Если при обращении к методу указана ошибка, выходящая за диапазон $80— ,$AD, выдается сообщение Ошибка номер ххх.
С) Procedure ReadEMS (Source: Longlnt; var Dest; Sz: Longlnt); virtual; Метод никогда не перекрывается. Этот метод обеспечивает низкоуровневый интерфейс с EMS-памятью для чтения данных из нее в переменную Dest. Параметр Source определяет номер байта отображаемой памяти, с которого начинается обмен данными (самый первый байт соответствует source = о). Sz определяет общее количество передаваемых байтов. Dest — переменная или участок кучи, куда будет помещена прочитанная из EMS-памяти информация. Фактически метод осуществляет обмен данными порциями, не превышающими 16 Кбайт, однако разбивка всего массива на эти порции и картирование нужных страниц EMS-памяти делается внутри метода и вызывающая программа не должна об этом заботиться.
О constructor Load (var F: File) ; Перекрывается — часто. С помощью этого метода программа может прочитать из файла F объект TEMSArea, ранее записанный в файл методом store. Файл F должен быть нетипи-
файлом. Перед обращением к методу его следует открыть, указав длину блока в 1 байт. Позиционирование указателя внутри файла должно осуществляться в вызывающей программе. Вначале метод читает из файла заголовок объекта — строку, соответствующую глобальной константе RegHeadTEMSArea. Затем читаются ПОЛЯИ Size. После
этого создается дескриптор Handle и с ним связываются махРаде страниц. Наконец из файла считываются и помещаются в EMS-память size байт данных.
Procedure Store (var F: File) Virtual; Перекрывается — часто. Зав файл F текущее состояние объекта TEMSArea. Файл F должен быть нетипизированным файлом. Перед обращением к методу программа должна открыть его, указав длину блока в 1 байт. Метод вначале записывает в файл заголовок объекта — глобальную константу RegHeadTEMSArea, затем поля МахРаде, size и, наконец, все size байт из EMS-памяти.
Следующий пример (пример 28.2) иллюстрирует технику использования методов Store И Load.
!28.2. Использование методов store и Load
- -
»t^KrtSkTtVinriVa^fftVr^vMJtrirrnTmt’AKfATWVNVVnMt'rtVbVVlvvMAfkkkMMA&vlNVfrwwVlmMVWVMVWWWIv/rirMt^VWWwWjlVXTRT^rArejTSvwCMn**»»»»**»**
Uses F_EMS;
var
Area:
F: File;
begin
Area.Init(100000) ;
в EMS 100000
{}
EMS-память
Assign(F,'Test.dat');
.
файловую переменную с именем вновь создаваемого
Rewrite(F,1);
длина блока данных
равна 1
Store(F);
данные в
Area.Done;
(Освобождаем EMS-память}
Seek(F,0);
указатель на начало
Area.Load(F) ;
все поля и данные
объекта TEMSArea из файла F)
{
{Используем данные}
Area.Done
(Освобождаем EMS-память/
end.
О WriteEMS — Procedure WriteEMS (var Source; Destin, Sz: Longlnt); virtual; Метод никогда не перекрывается. Записывает sz байт данных из переменной Source в EMS-память, пропустив Destin байт от ее начала. Вместе с методом ReadEMS обеспечивает низкоуровневый доступ к отображаемой памяти. Фактический обмен данными осуществляется порциями по 16 Кбайт. Разбивка всего массива sz байт на порции и необходимое картирование EMS-памяти осуществляется внутри метода.
TEMSVector
Объект TEMSVector имитирует обычный одномерный массив Паскаля. Его элементами могут быть любые данные одинаковой длины — методы Setitem и Getltem могут соответственно записать в EMS-память или прочитать из нее любую переменную либо область памяти подходящей длины. В этом полиморфизме методов заключается главное отличие объекта от обычных одномерных массивов Паскаля. Для удобства доступа к структурированным данным в объект включены интерфейсные методы XXXXItem, преобразующие элемент данных к одному из стандартных типов. При обращении к конструктору Init указывается длина элемента, а также минимальное и максимальное значения индексных выражений: для доступа к любому элементу данных необходимо указать его индекс. В отличие от массивов Турбо Паскаля, индексные выражения и границы индексов задаются значениями типа Longlnt, что позволяет хранить в EMS- памяти массивы произвольной длины. Объекты метода осуществляют проверку значений индексных выражений на принадлежность заданному при обращении к Init диапазону. Такая проверка приводит к дополнительным потерям времени и уменьшает и без того невысокую скорость доступа к элементам данных. Если вы уверены в корректности индексных выражений и хотите несколько увеличить скорость доступа, то должны включить в начало модуля директиву компилятора i$DEF NOTCHECK) или указать слово Notcheck в поле Options | Compiler | Conditional defines диалоговой среды. Такая настройка заставит компилятор исключить из программы фрагменты, связанные с проверкой индексныхвыражений.
Поля
Items: Longlnt; Только чтение. Хранит длину элемента данных. Интерфейсные методы xxxxitem проверяют значение этого поля и блокируют доступ к элементу, если значение items не соответствует длине типа данных.
Minlnd: Longlnt; Только чтение. В это поле метод Init помещает левую границу индексных выражений.
О Maxlnd: Longlnt; Только чтение. В это поле метод Init помещает правую границу индексных выражений.
Методы
□ Constructor (ASize,Max:Создает объект: внача
ле по границам индексных выражений Min, мах и длине элемента ASize определяет требуемую длину EMS-памяти, затем вызывает родительский МеТОДИ помещает значения ASize, Min, Мах В ПОЛЯ
И
Constructor Load (var F:Перекрывается — иногда. Создает
объект, считывая все его поля и данные из файла F. Вначале обращается к родительскому методуLoad, затем читает из файла заголовок
RegHeadTEMSVector (СМ. разд. 28.3.2) И ПОЛЯ ItemS, Minlnd, Maxlnd. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для чтения блоками длиной в байт.
Procedure Store (var F: File)Virtual; Перекрывается — иногда.
Сохраняет текущее состояние объекта в файле F: вначале вызывает унаследованный методзатем записывает в файл заголовок
RegHeadTEMSVector И ПОЛЯ ItemS, Minlnd, Maxlnd. Перед обращением К методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для блоками длиной в 1 байт.
Procedure(varIndex:Перекрывается —
редко. Помещает в переменную Dest элемент с индексом index: вначале определяет положение items*элемента относительно начала EMS-памяти, затем вызываетдля чтения из памя
ти ItemS байт.
П Procedure(var Source; Index:Перекрывается —
редко. Читает из переменной Source items байт и помещает их в элемент с индексом index: вначале определяет положение items* (index-Minind) элемента относительно начала EMS-памяти, затем вызывает ДЛЯВItemS байт.
П Function(Index:Byte; Возвращает значение
' • мента с индексом index в типе Byte: вначале проверяет поле items, которое должно иметь значение(Byte), затем вызываетem и
преобразует полученное значение элемента index к типу Byte.
О FunctionDouble; Возвращает значение
элемента с индексом index в типе Double: вначале проверяет поле items, которое должно иметь значение sizeofзатем вызывает
и преобразует полученное значение элемента index к типу Double.
FunctionExtended; Возвращает зна
чение элемента с индексом index в типе Extended: вначале проверяет по-
ле items, которое должно иметь значение sizeof (Extended), затем вызывает Getltem и преобразует полученное значение элемента index к типу Extended.
Function Integeritem (Index: Longlnt) : Integer; Возвращает значение элемента с индексом index в типе integer: вначале проверяет поле items, которое должно иметь значение SizeOf(Integer), затем вызывает Getitem и преобразует полученное значение элемента index к типу integer.
Function Longlntltem (Index: Longlnt): Longlnt; Возвращает значение элемента с индексом index в типе Longlnt: вначале проверяет поле items, которое должно иметь значение sizeof (Longlnt), затем вызывает Getltem и преобразует полученное значение элемента index к типу Longlnt.
О Function Realltem (Index: Longlnt): Real; Возвращает значение элемента с индексом index в типе Real: вначале проверяет поле items, которое должно иметь значение SizeOf(Real), затем вызывает Getitem и преобразует полученное значение элемента index к типу Real.
П Function Singleltem (Index: Longlnt): Single; Возвращает значение элемента с индексом index в типе Single". вначале проверяет поле items, которое должно иметь значение sizeof (Single), затем вызывает Getitem и преобразует полученное значение элемента index к типу Single. .
TEMSMatrix
Объект содержит средства для работы с двумерными массивами в EMS- памяти. Он перекрывает методы своего родителя TEMSVector для обеспечения доступа к элементам массива по двум индексам. Для этого в объект включен метод GetVecIndex, с помощью которого два индекса преобразуются к одному индексу массива TEMSVector с нулевой левой границей. Пересчет осуществляется по стандартному правилу Паскаля: элементы двумерного массива располагаются в памяти таким образом, что при переходе от одного элемента к соседнему наиболее быстро изменяется второй индекс.
Поля
П Minlndl: Longlnt. Только чтение. Содержит левую границу первого измерения.
О Maxlndl: Longlnt. Только чтение. Содержит правую границу первого измерения.
П Minlnd2: Longlnt. Только чтение. Содержит левую границу второго измерения.
П Maxlnd2: Longlnt. Только чтение. Содержит правую границу второго измерения.
Методы
Constructor Init (ASize, Mini, Maxi, Min2, Max2: Longlnt); Создает объект TEMSMatrix: по границам измерений Mini. . . Max2 определяет общее количествоSz элементов, вызыва'ет родительский метод
TEMSVector.Init с границами о, Sz-1 и помещает границы Mini. . . Max2 в ПОЛЯ Minlndl...Maxlnd2.
П Constructor Load (var F: File)Перекрывается — ИНОГДа. Считывает
все поля и данные объекта TEMSMatrik из файла f: вначале вызывает родительский метод TEMSVector.Load, затем читает из файла заголовок RegHeadTEMSMatrix (см. разд. 31.3.2) И ПОЛЯ Minlndl, Maxlndl, Minlnd2, Maxlnd2. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для чтения блоками длиной в 1 байт.
П Procedure Store (var F: File) ; Virtual; Перекрывается — ИНОГДа. Сохраняет текущее состояние объекта в файле F: вначале вызывает унаследованный метод TEMsvector. store, затем записывает в файл заголовок RegHeadTEMSMatrix И ПОЛЯ Minlndl, Maxlndl, Minlnd2, Maxlnd2. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для записи блоками ДЛИНОЙ в 1 байт.
П Function GetVecIndex (Indi, Ind2: Longlnt): Longlnt; Осуществляет преобразование двух индексов Indi, Ind2 к единственному индексу родительского объекта TEMsvector с нулевой левой границей.
Все остальные методы объекта — Getltem, Setitem, Syteltem, Doubleltem, Extendedltem, Integeritem, Lognlntltem, Realltem И Singleltem — представляют собой двумерные варианты одноименных методов объекта TEMSVector.
TEMSArray
Объект TEMSArray содержит средства для создания в EMS-памяти массива произвольной размерности и работы с ним. Все индексные выражения передаются методам объекта в виде списка, что не очень удобно, поэтому помимо объекта TEMSArray в модуле содержатся также объекты TEMsvector и TEMSMatrix. Многомерный МасСИВ TEMSArray Порожден ОТ TEMSVector И осуществляет соответствующий пересчет индексов в единственный индекс объекта-родителя с нулевой левой границей. Для этого служит метод GetVecIndex, использующий стандартное правило Паскаля: элементы массива располагаются в памяти так, что при переходе от одного элемента к соседнему наиболее быстро меняется самый правый индекс.
Поля
NIndex: Longlnt; Только чтение. Содержит количество измерений массива.
index: PIndex; Указатель на размещенный в динамической памяти массив из Nindex троек чисел типа Longint. Каждая (^-я) тройка содержит левую (первое число), правую (второе число) границы индексов по данному (Л-му) измерению и множитель (третье число), упрощающий пересчет многомерных индексных измерений в единственный индекс базового объекта TEMSVector. Множитель показывает на сколько нужно умножить ^й индекс многомерного массива, чтобы получить положение элемента внутри одномерного массива с нулевой левой границей.
Методы
П Init —(ASize, NInd: Longint; var Ind) ; Создает
объект TEMSArray. Параметры ASize и NInd задают соответственно длину элемента и количество измерений массива. Переменная Ind внутри метода считается переменной типа TIndexBond (см. разд. 28.3.2) и должна содержать NInd пар чисел типа Longint, определяющих границы индексов по каждому измерению (первое число пары — левая, второе — правая границы). Метод помещает в поле Nindex значение NInd, затем по списку Ind определяет общее количество sz элементов массива, после чего вызывает унаследованный метод TEMSVector.Init с границами о и sz - 1. Если обращение к методу было успешным, в куче резервируется NIndxl2 байт и указатель на этот участок памяти помещается в поле index (указатель на массив TIndex, см. разд. 28.3.2). Для всех NInd троек элементов типа Longint массива IndexA в первый элемент помещается левая граница, во второй — правая граница, а в третий — масштабный множитель, показывающий на сколько нужно умножить индекс по данному измерению, чтобы получить положение элемента в базовом объекте TEMSVector. '
П constructor Load (var F: File); Перекрывается — редко. Считывает все поля и данные объекта TEMSArray из файла f: вначале вызывает родительский метод TEMSVector.Load, затем читает из файла заголовок RegHeadTEMSArray и поле Nindex. Резервирует в куче участок памяти длиной Nindex x 8 байт, указатель на него помещает в поле index и считывает в него из файла Nindex элементов массива типа Tindex. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для чтения блоками длиной в 1 байт.
П Procedure Store (var F: File) ; virtual; Перекрывается — редко. Сохраняет в файле F текущее состояние объекта: вызывает родительский метод TEMSVectore.store, затем записывает в файл заголовок RegHeadTEMSArray, поле Nindex И Все Nindex троек массива IndexA. Пе-
ред обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для записи блоками длиной в 1 байт.
О Destructor Done; virtual; Перекрывается — редко. Уничтожает объект: освобождает участок кучи, связанный с указателем index, затем вызывает родительский метод TEMSArea.Done для освобождения EMS-памяти.
Function GetVecIndex (var Ind) :Метод никогда не Пере
крывается. По массиву индексов, заданному в переменной Ind, возвращает единственный индекс одномерного базового объекта TEMSVector. Переменная Ind внутри метода считается массивом TIndexList и должна содержать NIndex элементов типа Longlnt, определяющих значения индексов (первый элемент — индекс первого измерения, второй — второго и т. д.). Этот метод можно использовать для доступа к типизированным интерфейсным функциямXXXXItem объекта-родителя TEMSVector.
Procedure Getltem (var Dest, Ind) ; Метод никогда не перекрывается. Помещает в переменную Dest значение элемента массива, заданного СПИСКОМ ИНДеКСОВ Ind типа TindexList.
Procedure Setitem (var Зоитсе, Ind); Метод никогда не перекрывается. Помещает значение переменной source в элемент массива, заданный списком индексов Ind типа TindexList.
TEMSType
Объект TEMSType содержит средства, позволяющие зарезервировать участок EMS-памяти нужной длины и работать с этой памятью как с файлом последовательного доступа. С этой целью в объекте содержится поле POS, в котором указывается текущее положение указателя записи/чтения (смещение указателя от начала памяти в байтах). Методы Setitem и Getltem позволяют записать в EMS-память или прочитать из нее массив байтов нужной длины. В объекте нет средств позиционирования указателя и контроля за длиной записывае- мых/считываемых элементов. Ответственность за правильное использование записанной в EMS-память информации возлагается на программиста.
Поля
POS: Longint. Чтение/запись. Содержит смещение в байтах указателя чте- ния/записи от начала EMS-памяти. Методы setitem и Getltem используют это поле для определения текущего участка EMS-памяти и после чтения или записи информации увеличивают его значение на длину прочитанной (записанной) информации. Программа пользователя может произвольным образом распоряжаться этим полем, чтобы обеспечить доступ к - нужному участку памяти. Обычно она должна записать в это поле ноль, чтобы заставить читать/записывать данные с самого начала памяти.
Методы
О Constructor Init (ASize: Longlnt); Создает объект: вызывает родительский метод TEMSArea.Init, чтобы зарезервировать в EMS-памяти ASize байт, затем помещает в поле Pos значение о.
О Constructor Load (F: File); Перекрывается — часто. Считывает объект из файла F: вызывает родительский метод TEMSArea.Load, затем читает из файла заголовок RegHeadTEMSType и поле POS. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизи- рованным файлом, а сам файл должен быть открыт для чтения блоками длиной в 1 байт.
О Procedure Store (var F: File); Virtual; Перекрывается — часто. Сохраняет в файле F текущее состояние объекта: вызывает унаследованный метод TEMSArea. store, затем записывает в файл заголовок RegHeadTEMSType и поле Pos. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизирован- ным файлом, а сам файл должен быть открыт для записи блоками длиной в 1 байт.
Procedure Getltem (var Best; Sz: Longlnt); Перекрывается — часто. Читает из участка EMS-памяти со смещением Pos байт от ее начала Sz байт в переменную Dest: вызывает родительский метод TEMSArea.ReadEMS с параметрами POS, Dest и Sz, затем увеличивает значение поля Pos на величину sz.
О Procedure Setitem (var Source; Sz: Longlnt); Перекрывается — часто. Помещает в участок EMS-памяти со смещением Pos байт от ее начала Sz байт из переменной Source: вызывает родительский метод TEMSArea.WriteEMS с параметрами source, Pos и sz, затем увеличивает значение поля Pos на величину sz.
TEMSText
Объект позволяет разместить в EMS-памяти набор текстовых строк. В какой-то степени этому объекту соответствует текстовый файл стандартного типа Text. Объект порожден от TEMSType и наследует его поле Pos, содержащее текущее положение указателя чтения/записи от начала памяти. Метод Setitem размещает в памяти очередную строку s и увеличивает значение Pos на Length(3) + 1. Метод Getltem читает строку из памяти и точно так же смещает указатель Pos. Строка в EMS-памяти хранится в стандартном формате Паскаля: первый байт строки содержит ее длину, затем следует цепочка символов. Эта форма не совпадает с форматом ASCII-строк, в соответствии с которым строка содержит цепочку символов, заканчивающуюся признаком EOL (последовательностью символов #13#10). Таким образом, объект нельзя считать полным аналогом текстового файла.
Методы
Function Getltem: string; Возвращает размещенную в EMS-памяти текстовую строку, на которую указывает POS, и увеличивает значение POS на длину строки плюс 1 байт.
Procedure Setitem (S: String); Помещает в участок EMS-памяти, на который указывает POS, строку s и увеличивает значение POS на величину Length(S) + 1. .
TEMSScreen
Объект TEMSScreen предназначен для копирования текущего состояния экрана (текстового или графического) в EMS-память и восстановления экрана по его копии. Его метод Init определяет текущий режим работы экрана с помощью прерывания BIOS $10 и в зависимости от этого резервирует EMS- память нужной длины. Код режима помещается в поле Mode и контролируется при вызове других методов объекта. Это означает, что если ваша программа переключает режим работы дисплея, например переходит из текстового в графический, для сохранения копий экрана у вас должны быть разные копии объекта TEMSScreen. Напротив, в рамках одного и того же режима единственная копия TEMSScreen может сколько угодно раз использоваться для сохранения/восстановления экрана. Поскольку унаследованные методы TEMSArea.ReadEMS И TEMSArea.WriteEms He перекрываются, С ИХ ПОМОЩЬЮ вы можете обойти это ограничение (например, использовать объект, рассчитанный на работу с графическим экраном EGA/VGA, для сохране- ния/восстановления текстового экрана). В табл. 28.4 указывается объем резервируемой EMS-памяти для наиболее распространенных дисплейных адаптеров.
Таблица28.4. Объем резервируемой EMS-памяти для различных дисплейных адаптеров
Тип адаптера
Режим
Резервируемая память в страницах по 16 Кбайт
MDA
Все режимы
1
CGA
Все режимы
1
EGA
Текстовые
1
Графический 640x350, 16 цветов
7
VGA
Текстовые
1
'•• '
,
II
Графический 640x480, 16 цветов
10
SVGA
Графический 1024x768, 16 цветов
24
II
Графический 1024x768, 256 цветов
48
Поля
Mode: Byte; Только чтение. Код режима работы экрана на момент создания объекта. Все методы объекта (кроме Init и store) проверяют соответствие текущего режима работы режиму, указываемому этим полем.
Методы
О constructor Init; Создает объект: с помощью прерывания $10 (подфункция $F) определяет текущий режим работы экрана, затем с помощью вызова родительского метода TEMSArea.Init резервирует EMS- память нужного размера, после чего помещает в поле Mode код режима дисплея.
О Constructor Load (F: File); Считывает копию объекта из файла F: вызывает TEMSArea, затем читает из файла заголовок RegHeadTEMSScreen и поле Mode. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для чтения блоками длиной в 1 байт.
О Procedure Store (var F: File); Virtual; Сохраняет В файле F текущее состояние объекта: вызывает унаследованный метод TEMSArea. store, затем записывает в файл заголовок RegHeadTEMSscreen и поле Mode. Перед обращением к методу файловую переменную F необходимо связать с уже существующим нетипизированным файлом, а сам файл должен быть открыт для записи блоками длиной в 1 байт.
О Procedure GetScreen; Перекрывается — редко. Сохраняет в EMS- памяти текущее состояние экрана. Все текстовые режимы, а также графические режимы адаптера CGA используют менее 16 Кбайт видеопамяти, поэтому их копирование осуществляется в единственную страницу EMS-памяти и происходит очень быстро. Графические режимы EGA/VGA/SVGA требуют более одной страницы для размещения копии экрана, поэтому копирование идет no частям и осуществляется намного медленнее. Части экрана, копируемые в одну страницу EMS, представляют собой узкие горизонтальные полосы. Для EGA/VGA эти полосы содержат по 50 строк развертки, поэтому полная копия состоит из 7 (EGA) или 10 (VGA) полос. Копии полос создаются с помощью процедуры Getlmage стандартного МОДУЛЯ Graph.
Procedure PutScreen; Перекрывается — редко. Восстанавливает вид экрана, который был к моменту предыдущего обращения к методу GetScreen, т. е. переносит копию из EMS-памяти на экран.
Глобальные ссылки
В этом разделе описываются все определения интерфейсной части модуля F_EMS.
Процедура DisposeEMSHandle.
Объявление:
ProcedureHandle:
Уничтожает дескриптор Handle и освобождает связанную с ним
Переменная EMSErrorFlag.
Объявление:
const EMSErrorFlag: Boolean = False;
Хранит признак успешности предыдущего обращения к объектам и подпрограммам модуля: если после обращения к функциям EMM регистр АН содержит ненулевой код возврата, а также в случае обнаружения других ошибочных ситуаций, в эту переменную помещается значение True, в противном случае — False. Программа пользователя может проверить этот признак для анализа только что завершившегося обращения к методам и подпрограммам модуля.
EMSSeg.
• ' , •.
Объявление:
'
Function EMSSeg: Word;
Возвращает сегментную часть адреса окна, в которое отображаются логические страницы EMS-памяти.
Переменная
Объявление:
.
const EMSStatus: Byte;
Хранит статус завершения (код возврата) предьщущего обращения к функциям EMM. Смысл различных значений этой переменной указан в табл. 28.3.
ФуНКЦИЯ
Объявление:
FunctionString;
Возвращает номер версии EMM в формате Х.Х. Например, для EMM версии 4.0 возвращается строка "4.0".
Процедура GetEMSInfo
Объявление:
Procedure GetEMSInfo(var AllPage, Pages: Word);
Возвращает в переменной AllPage общее число EMS-страниц, а в Pages количество свободных страниц (т. е. не связанных ни с одним дескриптором).
Функция IsEMS
Объявление:
Function IsEMS: Boolean;
Определяет наличие в компьютере EMS-памяти и возвращает True, если память обнаружена. В электронном справочнике "Help!" фирмы Flambeaux Software, Inc. для проверки факта подключения драйвера EMS-памяти рекомендуется прочитать имя драйвера — строку "EMMXXXXO", содержащуюся в его заголовке (со смещением 10 от начала заголовка); вектор прерывания $67 всегда содержит адрес заголовка, поэтому следующая подпрограмма может определить, загружен ли EMM:
Function IsEMM: Boolean;
type
THead = record
Beg: array [1. .10] of Byte;
Head: array [1.-8] of Char
'
end;
var
PHead:^Thead;
P: Pointer absolute PHead;
begin
GetlntVec($67,P);
IsEMM := PHeadA.Head='EMMXXXXO'
end;
Примечание
На практике эта проверка не всегда дает правильный результат, т. к. вектор $67 может быть подменен другими резидентными программами, активно использующими EMS-память. После такой подмены этот вектор будет указывать на новый обработчик прерывания, в котором осуществляется контроль за использованием EMS-памяти и который вовсе не обязан содержать заголовок драйвера со смещением 10 байт от точки входа. В функции ISEMS используется более аккуратный прием, также рекомендуемый в справочнике "Help!": в рамках MS-DOS драйвер EMM объявляется как внешний файл с именем "ЕММХХХХО"; функция создает дескриптор файла с этим именем с помощью вызова функции $3D MS-DOS и от-
крывает его для чтения, после чего с помощью вызова IOCTL (функция $44 MS- DOS) проверяется статус файла. Если файл существует и его статус не равен нулю (что означает — устройство готово, нет конца файла), считается, что драйвер EMM подключен и EMS-память существует. В этом случае функция определяет версию EMM и помещает ее во внутреннюю переменную EMSVer. Поскольку вызов функции IsEMM осуществляется перед любым обращением к EMM, ненулевое содержимое этой переменной служит признаком того, что проверка уже произведена и EMM установлен. Если EMSVerOO, функция IsEMS не делает описанную выше проверку, а сразу возвращает True, экономя несколько миллисекунд на каждом обращении.
Процедура MapEMSPage
Объявление:
Procedure MapEMSPage(Handle,Physic,Logic: Word);
Картирует окно, связывая его физическую страницу physic с логической страницей Logic дескриптора Handle. Поскольку картирование окна выполняется практически перед каждым обращением к EMS-памяти, эта процедура реализована с помощью встроенного ассемблера. Кроме того, в ней запоминаются параметры обращения и, если очередное обращение не изменяет существующей карты страниц, блокируется вызов EMM. Эти меры экономят до 25% времени при интенсивной работе с EMS-массивами.
Процедура NewEMSHandle
Объявление:
Procedure NewEMSHandle(var Handle: Word; Pages: Word);
Открывает новый дескриптор Handle и связывает с ним Pages страниц EMS-памяти.
Типы PEMSXXXX
Объявление:
type
PEMSArray=ATMSArray;
PEMSMatrix=ATEMSMatrix;
PEMSScreen=ATEMSScreen;
PEMSText=ATEMSText;
PEMSType^ATEMSType;
PEMSVector=ATEMSVector; .
Определяют типы-указатели для размещения объектов модуля в динамической памяти.
ТИПPIndex
Объявление:
type PIndex=ATIndex;
Используется для размещения списка индексов многомерного массива TEMSArray в динамической памяти.
KOHCTaHTbl RegHeadTEMSXXXX
Объявление:
const
[14]='F_EMS TEMSArea'; [14]=' F_EMS TEMSAr ra'; [14]='F_EMS TEMSMatr'; [14]='F_EMS TEMSScre'; [14]='F_EMS TEMSType'; [14]='F_EMS TEMSVect';[14]='F_EMS TEMSArea'; [14]=' F_EMS TEMSAr ra'; [14]='F_EMS TEMSMatr'; [14]='F_EMS TEMSScre'; [14]='F_EMS TEMSType'; [14]='F_EMS TEMSVect';RegHeadTEMSArea : String
[14]='F_EMS TEMSArea'; [14]=' F_EMS TEMSAr ra'; [14]='F_EMS TEMSMatr'; [14]='F_EMS TEMSScre'; [14]='F_EMS TEMSType'; [14]='F_EMS TEMSVect';
[14]='F_EMS TEMSArea'; [14]=' F_EMS TEMSAr ra'; [14]='F_EMS TEMSMatr'; [14]='F_EMS TEMSScre'; [14]='F_EMS TEMSType'; [14]='F_EMS TEMSVect';
RegHeadTEMSArrayString
RegHeadTEMSMatrix: String RegHeadTEMSScreen String RegHeadTEMSType : String RegHeadTEMSVector: String
Служат для идентификации объектов модуля при работе с файлами.
Тип TIndex
Объявление:
typeTIndex = array [l..MaxInt div 6,1..3] of Longlnt;
Описывает массив индексов, используемый внутри объекта TEMSArray.
В программе не следует объявлять переменные типа TIndex, т. к. длина этого массива слишком велика. Тип используется как базовый для определения типа-указателя PIndex.
Тип TIndexBond
Объявление:
type TIndexBond = array [1..Maxlnt div 4, 1. .2] of Longlnt;
Используется для передачи конструктору TEMSArray. Init списка граничных пар многомерного массива.
См. примечание к TIndex.
ТИП TIndexList
Объявление:
type TIndexList = array [L.MaxInt div 2] of Longlnt;
Используется для указания списка индексов многомерного массива при обращении к методам объекта TEMSArray.
См. примечание к Tindex.
Тестовая программа
В следующей программе (пример 28.3) демонстрируются основные приемы работы с объектами и подпрограммами модуля F_EMS.
Работа программы заключается в выполнении нескольких тестов. Вначале с помощью функции ISEMS определяется доступность EMS-памяти: если функция возвращает False, выводится сообщение
Нет EMS-памяти или не установлен EMM
и программа завершает работу. Если память доступна, сообщается общий объем памяти в страницах и количество свободных EMS-страниц.
Второй тест заключается в сопоставлении скорости доступа к стандартной и EMS-памяти. Для этого в куче создается несколько массивов длинных целых чисел по 16 Кбайт каждый, которые заполняются последовательно возрастающими значениями, после чего подсчитывается сумма всех элементов всех массивов. На экран выводится контрольная сумма и время, затраченное на работу. Затем создается объект TEMSVector такой же длины, как суммарная длина всех массивов в куче, его элементы заполняются монотонно возрастающими значениями, после чего подсчитывается контрольная сумма массива. Эта сумма и время счета также выводятся на экран. Разбивка на массивы по 16 Кбайт сделана, с одной стороны, чтобы приблизительно уравнять условия теста, а с другой, — это неизбежное следствие известного ограничения Турбо Паскаля: за одно обращение к менеджеру кучи нельзя зарезервировать больше 65 521 байт. Этот тест показывает, что доступ к элементам EMS-массива идет в 5-10 раз медленнее, чем к массивам в стандартной памяти.
Следующий тест сохраняет созданный объект •TEMSVector на диске, затем уничтожает объект и создает его вновь с помощью конструктора Load, после чего еще раз подсчитывает и выводит контрольную сумму массива.
В ходе выполнения четвертого теста создается объект TEMSText и в него считывается исходный текст тестовой программы. Затем средствами модуля F_Text на экране создается окно, в которое выводится эталонный текст программы, считанный непосредственно из Р AS-файла, и текст, полученный из объекта. Если объект функционирует нормально, обе колонки окна должны содержать идентичные строки.
В следующем тесте проверяется работа объекта TEMSScreen сначала для текстового, а затем графического режима работы экрана. При инициализации графического режима предполагается, что нужный В G 1-драйвер располагается в текущем каталоге. Если это не так, следует скорректировать вызов процедуры InitGraph, указав путь к каталогу с этим драйвером.
»щ« у»у WMMMlMMftUbto$iMM#6M^WI/'16WlAMf1*''*iAU6^M№tf ДМ WMM MJbMMM ¥AH8W4Sy*W',1'6/**f Л1» -ti4i6VM14М^^4МЛV, W?МI ЩУКУ * *ЧЯ • »*мж» \*у • • • • •»щ« у»у WMMMlMMftUbto$iMM#6M^WI/'16WlAMf1*''*iAU6^M№tf ДМ WMM MJbMMM ¥AH8W4Sy*W',1'6/**f Л1» -ti4i6VM14М^^4МЛV, W?МI ЩУКУ * *ЧЯ • »*мж» \*у • • • • •— — .—-------
»щ« у»у WMMMlMMftUbto$iMM#6M^WI/'16WlAMf1*''*iAU6^M№tf ДМ WMM MJbMMM ¥AH8W4Sy*W',1'6/**f Л1» -ti4i6VM14М^^4МЛV, W?МI ЩУКУ * *ЧЯ • »*мж» \*у • • • • •
»щ« у»у WMMMlMMftUbto$iMM#6M^WI/'16WlAMf1*''*iAU6^M№tf ДМ WMM MJbMMM ¥AH8W4Sy*W',1'6/**f Л1» -ti4i6VM14М^^4МЛV, W?МI ЩУКУ * *ЧЯ • »*мж» \*у • • • • •
; Пример 28.3. Демонстрация работы с объектами и подпрограммами модуля f_ems
I Демонстрация основных возможностей \
Iмодуля F_EMS\
}
Uses CRT, Graph, F_EMS,F_Text;
type
TV = array [O..MaxInt div 2-1] of Longlnt; var
All,Pages: Word;
j, x: Longlnt;
Vec: TEMSVector;
Tex: TEMSText;
Scr: TENSScreen;
Longlnt;
r,e: Integer;
PV: array [0..40] of aTV;
Sz: Longlnt;
Sum: Comp;
f: file;
ft: Text;
s,ss: String;
const
LPage=16*1024;
label
L1,L2;
Function HexW(X: Word): String;
{Возвращает 16-ричное написание X) const
HS: array [0..15] of Char = '0123456789ABCDEF'; var
S: String;
Byte;
begin
S
for k := 30 do
S := S+HS[(X shr (k*4) ) and 15]; HexWS
end;
I1
Procedure GetTime; в t системное время, выровненное на границу begin t := MemL[0:$46CJ; whiledo;
end;
(,
begin {Основная программа)
наличие
If notthen
begin
или не установлен
Halt
end;
Colors(LightGray,Black);
ClrScr;
(Сообщаем номеробщую и доступную память)
EMM версии
Сегмент окна отображения
GetEMSInfo(All,Pages);
Всего страниц:
round(All*16.0*1024),' байт)');
Sz := Свободно страниц:
(Определяем количество Xстраниц в
х := if x>Pages then x := Pages;с
(Распределяем в куче массивы из 4096 длинных целых dec(x);
for k := 0 to x do
if MaxAvail>=LPage then GetMem(PV[к],LPage); x := k;
Sz :=succ(x)*LPage div 4;
WriteLn('Создаем массив из Sz, ' длинных целых чисел в куче...');
GetTime; {Засекаем время}
for j := 0 to x do
for k := 0 to LPage div 4-1 do
begin
PV[j]A[k) :=div 4)+k;
if PV[j]A[k] mod 1000=0 then
begin
Write(PV[j]A[k]:10); GotoXY(l,WhereY) end end;
(Подсчитываем его сумму}
WriteLn('Подсчитываем его сумму...');•
Sum := 0; ••
for j := 0 to x do
for k := 0 to LPage div 4-1 do
begin
Sum := Sum+PV[j]Atk];
if PV[j]A[k] mod 1000=0 then
begin
Write (PV[j ]A [k]: 10) ; GotoXY(l,WhereY) end end;
WriteLn('Сумма элементов = ':30,Sum:11:0,',времясчета = ', (MemL[0:$46C]-t)*55/1000:5:2,' секунд');
(Освобождаем кучу)
for k := 0 to x do
FreeMem(PV[k],LPage);
{Создаем массив в EMS)
WriteLn('Tot же массив в EMS-памяти...');
Vec. Init (4, 0, Sz-1) ;{4 — длинаэлемента; .. Sz-1 — индексы)
if not EMSErrorFlag then with Vec do
begin{Если создан успешно}
GetTime;/Засекаем время}
for k := 0 to Sz-1 do begin Setltem(k, к);{Помешаем очередной эл-т}
if k mod 1000=0 then begin{Сообщаем о каждом 1000-м}
Write(к:10);
GotoXY(1,WhereY) end end;
{Вычисляем сумму элементов}.
WriteLn('Суммируем элементы.. . ') ; Sum := 0; for k := 0 to Sz-1 do begin
Sum := Sum+Longlntltem(k); if k mod 1000=0 then begin{Сообщаем о каждом 1000-м}
Write(к:10);
GotoXY(1,WhereY) end end end else Halt;
WriteLn('Сумма элементов = ':30,Sum:ll:0,', времясчета = ', (MemL[0:$46C]-t)*55/1000:5:2,' секунд');
WriteLn('Сохраняем массив в файле TESTEMS.$$$...'); Assign(f,'testems.$$$'); {$!-} ReWrite (f,1); {$!+} if TOResultoO then begin
WriteLn('Ошибка при создании файла! ') ;
Vec.Done;
Goto LI end;
Vec.Store(f);{Сохраняеммассив}
if EMSErrorFlag then
begin{Была ошибка при сохранении}
Vec.Done;
Goto LI-
end;
Vec. Done;{Освобождаем EMS}
{Читаеммассив из файла}
WriteLn('Читаем массив из файла...');
Seek (f, 0);{Начало файла}
Vec.Load(f);{Загружаем массив}
Close(f);
if EMSErrorFlag then
Goto LI;{Была ошибка}
WriteLn('Еще раз подсчитываем его сумму-- ■ ’);
Sum := 0;
for k0 to Sz-1 do
begin
Sum := Vec.Longlntltem(k)+Sum; if k mod 500=0 then begin
Write(к:10);
GotoXY(l,WhereY) end
end;
WriteLn('Сумма =
Vec.Done;
LI:
{Определяем длину файла с текстом программы}
s := copy(ParamStr(0),1,Pos(’.’,ParamStr(0)))+*pas’;
Assign(f,s) ;
Reset(f,1);
{Определяем количество SZ требуемых ему страниц}
Sz := (Filesize(f)+LPage-l) div LPage;
Close(f);
Assign(ft,s);
Reset(ft);
{■Создаем объект TEMSText}
WriteLn('Создаем объект TEMSText');
Tex.Init(Sz*Lpage);
if EMSErrorFlag then
Halt;
{Читаем файл и записываем его в TEMSText} WriteLn('Помещаем в него файл •+S);
while not (EOF(ft) or EMSErrorFlag) do begin
ReadLn(ft,s);
{Читаем строку)
Tex.Setltem(s) end;
{Помещаем ее в EMS}
if EMSErrorFlag then Goto L2;
{Проверяем записанное} Reset(ft);
{Начало файла)
Tex.Pos : = 0;
(Начало текста в EMS}
for k := 1 to 16 do
WriteLn;
{Готовим место для окон)
Colors(Yellow,Blue);
'
Setwindow(1,10,80,23,1, 1 Исходный файл: '+
#196#196#19б#196#19б#19б#196#196#196#196#196#196#196+
#196#196#196' Текст в EMS: ', True,False,False);
while not EOF(ft) do
begin
ReadLn(ft,s);
ss := Tex.Getltem;
if Length(s)>38 then s[0] := #38;
While Length(s)<39 do s := s+' '; .
s := s+ss;
if Length(s)>77 then
s[0] :=•
WriteLn(s)
end;
Tex.Done;
Colors(LightGray,Black);
Window(1,1,80,25);
GotoXY(1,25);
L2:
{Проверка объекта TEMSScreen}
Write('Сохраняем копию текстового экрана. 1);
Scr.Init;
if not EMSErrorFlag then
Scr.Put Screen;{Сохраняем копию в EMS I
if EMSErrorFlag then
Halt;{Была ошибка}
x := WhereX;{Запоминаем положение курсора}
WriteLn('Нажмите любую клавишу...');
if ReadKey=#0 then k := ord(ReadKey);
{Создаем окно в центре экрана и заполняем его случайным цветом)
ClrScr;-
SetWindow(10,7,70,18,2,' Нажмите любую клавишу...',
False,True,False);
while not KeyPressed do
begin
GotoXY(Random(59)+2,Random(10)+2);.
TextBackground(Random(8));
Write (' ')
end;
if ReadKey=#0 then k := ord(ReadKey);
Scr.GetScreen;{Восстанавливаемэкран)
Window(1,1,80,25);
TextBackground(Black);
GotoXY(x,25);
Scr.Done;
{Проверяем графический режим)
WriteLn(#13#10'Для перехода в графический 1 +
’режим нажмите любую клавишу...');
if ReadKey=#0 then k := ord(ReadKey);
d := detect;
InitGraph(d, r,'');
e := GraphResult;
if e о 0 then
begin
{Ошибка инициализации графики}
WriteLn(GraphErrorMsg(е));
Halt
end;
( Инициализируем Scr заново — новый режим экрана!)
Scr.Init;
Line(0,0, GetMaxX, GetMaxY) ;{Диагональные линии)
Line(0,GetMaxY,GetMaxX,0);
Scr. Put Screen;{Сохраняем изображение}
while not (KeyPressed or EMSErrorFlag) do
begin
ClearDevice;
Scr.GetScreen;
end;
{Очищаем экран)
{Восстанавливаем изображение)
Scr.Done;
CloseGraph;
end.
Глава 29
Защита программ
от компьютерных вирусов
В главе анализируется механизм распространения и функционирования вирусов в операционной системе MS-DOS и на основе анализа предлагаются достаточно эффективные способы борьбы с ними. Приводятся описания трех разных программ, обеспечивающих выявление и ликвидацию вирусов./ Модуль F_Anti может использоваться для автоматической защиты вновь разрабатываемых на Турбо Паскале программ. Программа Antivir осуществляет выявление и ликвидацию загрузочных вирусов и контроль любых исполняемых файлов на основе сопоставления наиболее уязвимых для вируса частей файлов с их эталонными копиями. Наконец, комплекс программ SETFAG.EXE и FAG.PRG даст вам возможность установить антивирусную программу-фаг на любой уже созданный ЕХЕ-файл: в момент запуска программы фаг проверит ее состояние, если программа поражена вирусом, сообщит об этом и удалит вирус.
Что такое компьютерные вирусы
Если вы имеете опыт продолжительной работы с ПК, то, возможно, уже сталкивались с компьютерными вирусами или хотя бы слышали о них. Компьютерный вирус — это программа, производящая в вашем ПК действия, в которых вы не нуждаетесь и о которых не подозреваете. Главной ее особенностью является способность к "размножению", т. е. к созданию множества готовых к дальнейшей работе экземпляров вируса. Вирусы "цепляются" к обычным исполняемым файлам типа EXE, COM или к загрузочным секторам физических носителей информации (дискет) и таким образом перемещаются от одного ПК к другому.
Являвшиеся первоначально вполне невинным развлечением скучающих программистов компьютерные вирусы сегодня стали настоящим бедствием для пользователей ПК: количество и типы таких программ растут с ужасающей скоростью, а сами вирусы в ряде случаев приобрели весьма непри-
Глава 29. Защита программ компьютерных вирусов783
р
свойства — некоторые из них способны уничтожать файловую структуру дисков со всеми катастрофическими для пользователя последствиями. В литературе [14] описывается беспрецедентный случай, когда вирус на три дня (с 2 по 4 ноября 1988 г.) вывел из строя фактически всю компьютерную сеть США. Были парализованы компьютеры Агентства национальной безопасности, Стратегического командования ВВС США, локальные сети всех крупных университетов и исследовательских центров. Лишь в последний момент удалось спасти систему управления полетом космических кораблей Шаттл. Положение было настолько серьезным, что к расследованию немедленно приступило ФБР. Виновником катастрофы, причинившей ущерб более чем в 100 миллионов долларов, оказался студент выпускного курса университета Р. Моррис, придумавший достаточно хитрую разновидность вируса. Он был исключен из университета с правом восстановления через год и приговорен судом к уплате штрафа в 270 тысяч долларов и трем месяцам тюремного заключения.
Трудно объяснить, для чего программисты тратят силы время на создание все более изощренных типовпоскольку их авторы почти всегда
остаются или надеются остаться анонимными, так что естественное для человека стремление к известности здесь исключено. Может быть это неудачная шутка (этой версии придерживался Р. Моррис), возможно это связано с патологическими отклонениями в психике, а может быть объяснение кроется в стремлении заработать на создании антивирусных программ? Как бы там ни было, нам нельзя не считаться с возможностью заражения ПК компьютерным вирусом.
Цикл функционирования вирусов
В цикле существования любого вируса можно выделить три этапа. Первоначально вирус находится в неактивном состоянии. В этом состоянии он внедрен в тело исполняемого файла или находится в загрузочном секторе диска и "ждет" своего часа. Именно в неактивном состоянии вирусы переносятся вместе с программами или дискетами от одного ПК к другому (обмен программами между пользователями ПК — явление обыденное, и вы сами, читатель, возможно копировали полюбившуюся вам игру или текстовый редактор, не подозревая, что копируете еще и вирус). Разумеется, в неактивном состоянии вирус ничего не может сделать. Для того чтобы он начал свою работу, необходимо запустить исполняемый файл или загрузиться с зараженной дискеты. В этот момент активизируется вирус, который либо создает резидентную в памяти программу, способную порождать копии или производить какие-то разрушительные действия, либо немедленно приступает к работе.
26 Зак 941
Если вирус создал резидентную программу, то ее активизация осуществляется различными способами — все зависит от фантазии автора вируса. Обычно вирус перехватывает прерывание $21, являющееся ключевым для доступа к любым операциям с MS-DOS. Таким образом, любая попытка чтения или записи информации на диск или обращение к клавиатуре дисплея приводит к активизации резидентной программы вируса.
После получения управления (или активизации резидентной программы) вирус приступает к "размножению": он отыскивает подходящий исполняемый файл и внедряет свой код в его тело (см. ниже). Как правило, вирус заражает лишь один исполняемый файл за раз, чтобы пользователь не обратил внимания на чрезмерное замедление в работе программ: второй этап жизнедеятельности вируса — это этап активного размножения, поэтому вирусная программа стремится максимально скрыть от пользователя ПК результаты своей деятельности.
После того как заражено достаточно много файлов, может наступить третий этап, связанный с внешними проявлениями работы вируса. Ваш компьютер вдруг начнет вести себя странно: зазвучит ли музыкальная фраза, или начнут "сыпаться" символы на экране дисплея — не суть важно, главное, что S только В этот момент вы с ужасом вспомните, что на жестком диске находятся чрезвычайно важные для вас данные или программы, которые вы не успели или не смогли скопировать на дискеты. Увы! Некоторые вирусы к этому моменту могут уже безвозвратно нарушить файловую структуру.
Что делать при обнаружении вируса? Прежде всего не паниковать, ведь далеко не все вирусы отличаются "кровожадностью". Следует выключить компьютер, вставить в привод гибкого диска заранее припасенную эталонную системную дискету (никогда не снимайте с нее защиту!) и снова включить компьютер. Если на ПК имеется специальная кнопка для перезагрузки (<RESET> или <INIT>), можно использовать ее и не выключать/включать компьютер, но во всех случаях не пытайтесь перезагрузиться с помощью <Ctrl>+<Alt>+<Del>, поскольку от некоторых типов вируса вы не избавитесь таким образом. Затем нужно запустить какую-либо программу- антивирус (например, AIDSTEST Д. Н. Лозинского) и с ее помощью локализовать и удалить вирус. Если антивирусная программа не может обнаружить вирус или у вас под рукой нет такого рода программ, следует попробовать обратиться к нужному вам жесткому диску и, если это удастся сделать, скопировать все наиболее ценное (но только не исполняемые файлы!) на дискеты. После этого заново переформатировать жесткий диск, перенести на него эталонную копию DOS и восстановить с дискет то, что удалось спасти.
"Вакцинация" программ
Что же следует сделать, чтобы эта безрадостная картина не стала реальностью? Один ответ очевиден — периодически (и по возможности чаще) сохранять жизненно важные для вас результаты работы на дискетах. Нет ничего проще, чем дать этот совет, гораздо сложнее заставить себя следовать ему: я сам, честно говоря, далеко не каждый день трачу время на архивирование. Второй ответ менее очевиден. С его простой идеей я впервые познакомился в прекрасной статье Ф. Н. Шерстюка [38].
Вот эта идея: нужно произвести "вакцинацию" исполняемых программ, т. е. придать им свойство самодиагностики, позволяющее произвести контроль собственного файла и выяснить, заражен он или нет. Если факт заражения установлен, программа может попытаться восстановить свой исходный вид, т. е. удалить прицепившийся к ее файлу вирус. Если эту идею последовательно воплощать в жизнь, то большинство ваших программ приобретет стойкий "иммунитет" к вирусам, во всяком случае, они смогут достаточно быстро сообщить вам о факте заражения.
Преимущества этой идеи очевидны: в отличие от разработчиков многочисленных антивирусных программ, которые борются с конкретными разновидностями вирусов, вы можете сохранить в файле программы некоторую ключевую информацию о ее незараженном виде, и поэтому факт заражения любым видом вируса может быть легко установлен в момент запуска вашей программы.
Заголовок исполняемых файлов
Какую именно информацию о незараженном файле следует сохранять? Для ответа на этот вопрос необходимо знать соглашение MS-DOS о формате исполняемых файлов. Как известно, существуют два формата: СОМ и ЕХЕ. Любая программа, обрабатываемая системой Турбо Паскаль версии 4.0 и выше, может быть оттранслирована только в ЕХЕ-файл, поэтому все дальнейшие рассуждения относятся именно к этому формату.
В начале ЕХЕ-файла располагается заголовок, в котором содержится вся информация, необходимая для преобразования дискового файла в готовую к работе программу. Первые 28 байт заголовка соответствуют следующей структуре данных:
type
HeadExeType = record
Sign: Word; (Признак ЕХЕ-файла}
PartPag: Word; (Часть неполного сектора в конце файла)
PageCnt: Word; { Количество секторов, включая неполный}
ReloCnt
Word;
{Количество элементов в таблице перемещения}
HdrSize:
Word;
{Длина заголовка в параграфах)
MinMem ;
Word;
{Минимальныйразмер кучи (в параграфах)}
MaxMem :
Word;
{Максимальныйразмер кучи (в параграфах)}
ReloSS
Word;
{Начальное значение сегмента стека SS)
ExeSP
Word;
{Начальное значение указателя стека SP)
ChkSum :
Word;
{Контрольная сумма всех слов файла)
ExelP :
Word;
{Смещение точки запуска программы) -
ReloCS
Word;
{Начальное значение сегмента кода CS)
TablOff
Word;
{Смещение первого элемента таблицы перемещения)
Overlay
Word;
{Номер оверлея или 0 для основной программы)
end;{HeadExe)
Остальные элементы заголовка содержат так называемую таблицу перемещения, предназначенную для настройки адресов загруженной программы. Таблица начинается с байта TablOff от начала файла и содержит ReloCnt четырехбайтных элементов следующего вида:
type
ReloTablltem = record
ItemSeg: Word; {Сегмент перемещаемого адреса)
ItemOfs: Word; /Смещение перемещаемого адреса)
end;
Признак ЕХЕ-файла хранится в поле sign в виде символов "MZ (код $5A4D) — с этого признака должен начинаться любой ЕХЕ-файл. Поле HdrSize содержит длину всего заголовка в параграфах (участках памяти длиной по 16 байт каждый). Поля PartPag и PageCnt определяют общую длину загружаемой в память части ЕХЕ-файла по следующей формуле:
L = (PageCnt-1) x512 + PartPag — HdrSizexl6
Остальная часть файла (длина ЕХЕ-файла может быть больше L+HdrSizexl6) при загрузке программы не учитывается. Обычно в остатке файла, созданного системой Турбо Паскаль (если, разумеется, есть остаток), содержится информация, используемая встроенным отладчиком, или оверлеи.
Подавляющее большинство ЕХЕ-вирусов пристыковывают свою программу в конец файла, а для того чтобы эта программа была загружена в память и ей было передано управление, изменяют поля PartPag, Pagecnt, ReloCS, ExelP (адрес точки, куда передается управление после окончания загрузки) и, возможно, некоторые другие поля. При таком способе внедрения общая длина загружаемой в память части файла должна составлять
ExeSize = FileSize + VirusSize
где:
FileSize — полная длина ЕХЕ-файла;
virussize — длина программы вируса.
Так как в остатке файла могут храниться оверлеи (или архив для самораз- гружающихся архивных программ), длина Exesize может оказаться чрезмерно большой, так что профамма не загрузится в память или не заработает нормальным образом. Некоторые безфамотно написанные вирусы не учитывают это обстоятельство и быстро выдают себя, т. к. зараженные программы перестают работать.
Другой способ внедрения вируса — пристыковка кода вируса до начала загружаемой части программы и сразу за заголовком файла (рис. 29.1).
файлафайлаL - загружаемая в память частьL - загружаемая в память частьВирусВирусОстаток файлаОстаток файлаОстаток ФайлаОстаток ФайлаЗаголовок ФайлаЗаголовок ФайлаЗаголовок ФайлаЗаголовок Файла
файла
файла
L - загружаемая в память часть
L - загружаемая в память часть
Вирус
Вирус
Остаток файла
Остаток файла
Остаток Файла
Остаток Файла
Заголовок Файла
Заголовок Файла
Заголовок Файла
Заголовок Файла
абв
Рис. 29.1. Варианты внедрения вируса в тело файла:а — незараженный файл; б — вирус в конце файла;в — вирус в середине файла
Такой способ внедрения позволяет не зафужать в память весь ЕХЕ-файл, а длина загруженной профаммы увеличивается только на длину кода вируса. Несмотря на кажущееся преимущество такого способа, он используется достаточно редко. Его реализация значительно сложнее, т. к. перед передачей управления основной профамме вирус должен перенести 256 байт префикса профаммного сегмента (PSP) в конец собственного кода так, чтобы они непосредственно предшествовали телу профаммы — в противном случае будет нарушена важная связь профаммы С PSP или относительная адресация в самой профамме. Кроме того, в процессе заражения он должен увеличить на величину virussize поле ItemOfs каждого элемента таблицы перемеще-
ния и абсолютного адреса, указываемого этим элементом. В отличие от стандартного загрузчика ДОС вирусу приходится корректировать не загруженную программу, а ее файловый образ. Так как в ЕХЕ-программе средней сложности может быть несколько сотен элементов таблицы перемещения, процесс настройки таблицы вирусом приводит к заметному увеличению времени запуска программы, что может обнаружить пользователель. На этапе размножения вирусы стремятся по возможности скрыть от пользователя результат своей деятельности, поэтому ЕХЕ-файлы редко поражаются вирусами, пристыкованными в начало файла.
Разумеется, существует возможность внедрения вируса непосредственно в тело исполняемой программы. Однако на практике это почти всегда означает разрушение логики работы программы, поэтому такой вирус немедленно обнаруживается.
Анализ сказанного позволяет сделать важный вывод: практически любой существующий вирус (или вирус, который еще только будет создан!), рассчитанный на поражение ЕХЕ-файла, пристыковывает свой код в конец файла и изменяет его заголовок. Следовательно, для контроля факта заражения программы и ликвидации вируса необходимо где-то сохранить заголовок файла и его эталонную длину и периодически сопоставлять действительный заголовок и длину с эталонными значениями. При этом следует учитывать то обстоятельство, что некоторые вирусы контролируют любое обращение к дисковым секторам, в которых расположена их программа, и "подсовывают" незараженные копии этих секторов. Такие вирусы (их называют вирусы-невидимки) вряд ли удастся обнаружить с помощью стандартного обращения к функциям ДОС. Для борьбы с ними используют прямое обращение к BIOS-прерыванию $13 подобно тому, как это реализовано в программе ANTIVIR (см. разд. 29.3.6).
Защита вновь создаваемых программ
Ключевую информацию (будем для краткости называть ее ключ) о незара- женной программе можно хранить в отдельном файле, но в этом случае существует опасность потерять дополнительный файл при копировании программы или ошибочно уничтожить его. Гораздо надежнее хранить ключ в теле самого защищаемого файла. К сожалению, его нельзя подобно вирусу пристыковать в конец файла, т. к. в случае заражения вирус изменит поля PartPag и PageCnt и мы никогда не сможем определить то место в файле, где он располагается. Вспомним, что все константы (в том числе и типизированные) создаются на этапе компиляции программы, таким образом в файле обязательно имеется область данных, содержащая значения этих констант. Эта область в программах на Турбо Паскале располагается в самом конце загружаемой части файла (рис. 29.2).
HeapEnd-HeapOrg-OurHeapOrg-SSeg • „QQQQ-HeapEnd-HeapOrg-OurHeapOrg-SSeg • „QQQQ-Динамическая памятьОверлейный буферСтекДинамическая памятьОверлейный буферСтекГлобальные переменныеГлобальные переменныеDSeg " 0830DSeg " 0830Типизированные константыПроцедуры модуля SystemТипизированные константыПроцедуры модуля SystemКод первого модуляКод первого модуляКод последнего модуляКод последнего модуляОбразэтой частихранитсяв ЕХЕ-файлеОбразэтой частихранитсяв ЕХЕ-файле
HeapEnd-
HeapOrg-
OurHeapOrg-
SSeg • „QQQQ-
HeapEnd-
HeapOrg-
OurHeapOrg-
SSeg • „QQQQ-
Динамическая память
Оверлейный буфер
Стек
Динамическая память
Оверлейный буфер
Стек
Глобальные переменные
Глобальные переменные
DSeg " 0830
DSeg " 0830
Типизированные константы
Процедуры модуля System
Типизированные константы
Процедуры модуля System
Код первого модуля
Код первого модуля
Код последнего модуля
Код последнего модуля
Образэтой частихранитсяв ЕХЕ-файле
Образэтой частихранитсяв ЕХЕ-файле
CSeg:0000PrefixSegCSeg:0000PrefixSegКод главной части программы
CSeg:0000
PrefixSeg
CSeg:0000
PrefixSeg
Префикс программного сегмента
Младшие адреса
Рис. 29.2. Распределение памяти для загруженной программы
Следовательно, мы должны объявить в программе типизированную константу, предназначенную для хранения ключа, а затем в область файла, отведенную для ее размещения, поместить нужную информацию.
Каким образом отыскать в ЕХЕ-файле место, занимаемое ключом? Конечно, можно перед ним в программе разместить какую-либо типизированную константу с характерным значением (например, заранее обусловленную текстовую строку) и затем отыскивать ее в файле. Однако такое решение вряд ли можно признать удовлетворительным: во-первых, всегда существует вероятность того, что какой-то фрагмент кодов программы содержит ту же цепочку байтов, что и заголовок ключа; во-вторых, придется просматривать подчас большой по объему ЕХЕ-файл в поисках нужной константы. Значительно изящнее выглядит решение, основанное на точном вычислении смещения от начала файла до ключа.
Для этого нужно определить начало области данных в файле. В заголовке файла не предусмотрено никакой информации о начальном значении регистра DS, в котором хранится сегмент данных. Перед передачей управления программе загрузчик устанавливает значение этого регистра так, чтобы он указывал на начало так называемого префикса программного сегмента (PSP), а запущенная программа уже сама должна установить его надлежащим образом.
Префикс программного сегмента имеет длину 256 байт (16 параграфов) и размещается загрузчиком в оперативной памяти непосредственно перед загруженной программой. В PSP содержится различная служебная информация, которая может быть достаточно важной для ДОС и работающей про-
граммы (в PSP, например, хранятся параметры обращения к программе), но в нем нет указаний на содержимое регистра DS. Таким образом, найти начало области данных в файле достаточно трудно. Однако это легко сделать в работающей программе — для этого предусмотрена стандартная функция DSeg, возвращающая сегмент данных. Если бы программа могла получить также информацию о сегменте PSP, с которого начинается загруженная программа, можно было бы вычислить смещение в файле от его начала до начала области данных. Такая возможность есть — функция MS-DOS с номером $62 возвращает в регистре вх значение сегмента PSP. Следовательно, программа должна получить значения обоих сегментов, вычислить их разницу и таким образом определить место в файле, с которого начинается область данных.
Остается последняя проблема — как найти нужную константу. Турбо Паскаль размещает константы в области данных по мере их объявления в программе. Зная размер каждой константы и порядок их объявления, можно вычислить место размещения нужной нам типизированной константы. Однако этот метод не годится для универсальной программы защиты, т. к. порядок объявления констант может меняться от программы к программе. К счастью, мы можем использовать операцию получения адреса @. Результатом применения этой операции к адресу константы, выбранной в качестве ключа, является указатель (четырехбайтный адрес); смещение адреса, который он содержит, и является нужным нам смещением начала ключа относительно начала области данных.
Модуль F_Anti
В этом разделе описывается модуль F_Anti, в котором осуществляются все необходимые действия по установке ключа во вновь создаваемый ЕХЕ-файл и последующей проверке этого ключа при каждой загрузке программы. Полный текст модуля вы найдете в файлеFANTI.PASна сопровождающей книгу дискете.
В качестве ключа для контроля факта заражения ЕХЕ-файла в модуле F_Anti используется типизированная константа Head, соответствующая такой структуре данных:
type
HType = record
НЕ : HeadExeType;(Эталонный заголовок файла}
HL : Longlnt; (Эталоннаядлина файла}
HF : Boolean;(Флаг установки ключа}
Key: Word(Шифр для защиты ключа}
end;
При создании этой типизированной константы компилятор в поле HF помещает значение False. Запущенная программа анализирует это поле. При первом прогоне HF False, в результате чего программа вызывает процедуру save, в ходе выполнения которой в файле сохраняются эталонный заголовок и начальная длина файла. При этом одновременно в поле HF (в файле профаммы!) помещается значение True, поэтому при любом последующем запуске профаммы вместо save будет вызвана процедура CheckFile, которая осуществит нужный контроль файла. Если в ходе контроля обнаруживается хотя бы малейшее отличие заголовка файла от его эталона, хранящегося в Head, программа сообщает о факте заражения и предлагает восстановить испорченный заголовок и начальную длину файла.
При восстановлении зараженного файла осуществляются следующие действия:
Зараженный файл копируется в файл с расширением VIR — это позволит вам в случае неудачной попытки восстановления повторить ее еще раз; кроме того, при желании вы сможете передать разработчикам антивирусных профамм копию вируса для выработки методов его обнаружения и уничтожения.
Анализируется адрес запуска зараженной профаммы: если относительное значение сегмента точки запуска у зараженной программы меньше, чем в эталоне, то это означает, что вирус располагается в начале или в середине файла; в этом случае профамма предупреждает пользователя о невозможности восстановления (если вас заинтересовал описываемый способ борьбы с вирусами, попробуйте модифицировать профамму защиты так, чтобы она работала и в этом случае).
Если вирус пристыкован в конец файла, то в восстанавливаемый файл переносится эталонный заголовок и незараженная часть, начиная с байта TablOff + ReloCntx4 от начала файла и до байта Filesize - HL.
Отмечу следующее обстоятельство. Описанная система защиты дублирует заголовок файла в тело профаммы. Ничто не мешает вирусу проверить тело профаммы на наличие дубликата и соответствующим образом изменить его. Я далек от мысли, что вы, уважаемый читатель, после знакомства с этим разделом захотите создать собственный вирус, учитывающий это обстоятельство. Тем не менее я считаю, что нелишней будет какая-то защита самого ключа. Такая защита реализуется очень просто: для этого достаточно все 16-битные поля эталона заголовка сложить по модулю 2 со случайной
константой Key. С этой целью в процедуру save помещен следующий фрагмент (см. файл F_ANTI.PAS на прилагаемой дискете):
{Зашифровать ключ:} Randomize;
Head. Key : = Random ( FFFF)
with Head,Head. HE do
for k 1 to 14 do
Hem[k] :=xor Key;
(массив нет совмещен в памяти с сохраняемым в файле эталонным заголовком, а HE.Hem — с заголовком файла в момент первого запуска).
Инициализация генератора случайных чисел с помощью процедуры Randomize гарантирует, что шифр Head.Key не будет повторяться в различных программах. В процедуре CheckFile с помощью операторов
(Дешифровать ключ:}
with H,H.HE do
for k := 1 to 14 do
Hem[k] :=xor Key;
восстанавливается исходный вид ключа.
Процедура CheckFiie, осуществляющая установку или контроль ключа, вызывается в ходе выполнения установочной части модуля F_Anti, поэтому для использования описанного метода защиты достаточно указать имя модуля в предложении uses.
Примечание
В случае разработки программы с оверлеями модуль F_Anti можно объявить
оверлейным, если в установочной части любого неоверлейного циализируется работа администратора оверлея.
модуля ини-
В распоряжениипрограммистаимеетсяглобальная
CheckVirusResult, сигнализирующая о результатах проверки
переменная программы.
Значения этой переменной интепретируются следующим образом:
О о — не обнаружен факт заражения;
□ 1 — первый запуск, в программе установлена защита;
□ -1 — вирус обезврежен с согласия пользователя;
О -2 — вирус обезврежен автоматически;
О -3 — контроль подавлен ключом /NOANTI;
□ -4 ■■^■e вирус расположен в начале программы.
Работа процедуры checkFile может быть подавлена, если программа запускается с ключом /NOANTI. Ключ /NOQUERY разрешает автоматическое удаление обнаруженного вируса без разрешения пользователя. Ключ /NOALARM также разрешает процедуре автоматически удалить вирус, но запрещает выдавать на экран предупреждающее сообщение. Наконец, ключ /NOCOPY запрещает создание резервной копии зараженного файла (с расширением VIR).
Следующая простая профамма (пример 29.1) иллюстрирует технику использования модуля F_Anti. Если вы скомпилируете эту программу в файл TESTANTI.EXE, то после команды testanti
на экране появится сообщение
Установлена защита файла TESTANTI.EXE.
при первом запуске программы и
В файле TESTANTI.EXE вирус не обнаружен.
;
при каждом следующем запуске.
Если запустить программу командой
testanti /noanti
на экран будет выведено сообщение
Контроль блокирован ключом /NOANTI.
Uses F Ainti; —
begin
case CheckVirusResult of
0: WriteLn('B файле ',ParamStr(0) , ' вирус не обнаружен. ');
WriteLn('Установлена защита файла ’, aramStr(0), *. *) ;
1: WriteLn('Вирус удален с разрешения пользователя.') ;
2: WriteLn('Вирус удален автоматически.’);
3: WriteLn('Контроль блокирован ключом /NOANTI. ') ;
4: WriteLn('Вирусрасположен в начале '+
'файла — удаление невозможно.')
end
end.
Защита существующих ЕХЕ-файлов
Итак, "вакцинация вновь разрабатываемых программ не представляет особой сложности. А как защитить уже существующую ЕХЕ-про грамму? Для этого существуют две возможности: либо создать отдельную программу, которая хранит ключевую информацию и осуществляет проверку по требованию пользователя (такая программа обсуждается в следующем разделе), либо пристыковать к защищаемой программе небольшой код "вирусного фага" — тогда проверка будет осуществляться автоматически при каждом запуске программы. В этом разделе мы обсудим технику создания и внедрения в защищаемую программу антивируса-фага, т. е. небольшой ассемблерной программы, которая использует тот же механизм перехвата управления, что и обычный вирус, но осуществляет нужную защиту программы.
Программа-фаг устанавливается специальной программой-установщиком и к моменту начала своей работы уже должна иметь в своем распоряжении эталонный заголовок файла. Получив управление, фаг проверяет заголовок соответствующего файла и, если обнаружены изменения, сообщает об этом пользователю и удаляет вирус. После окончания работы фаг передает управление защищаемой программе. Поскольку фаг пишется целиком на ассемблере, его работа протекает очень быстро без заметного замедления загрузки программы. Если вы установите такой фаг на большинство часто используемых программ, ваш компьютер будет защищен очень надежно.
Чтобы правильно спроектировать фаг, нужно хорошо представлять себе механизм запуска ЕХЕ-программ. Стандартный загрузчик MS-DOS реализует следующую последовательность действий при запуске программы.
Создается префикс программного сегмента PSP. Обычно для этого используется функция MS-DOS $26.
В некоторую локальную область памяти считываются начальные 28 байт заголовка ЕХЕ-файла, соответствующие структуре данных HeadExeType.
Определяется размер загружаемой части файла по формуле
LengExe = (PageCnt-1)х512 + PartPag
Определяется файловое смещение загружаемой части:
SeekExe = HdrSize x 16
Выбирается сегментный адрес startseg для размещения программы. Обычно startseg = Segment (PSP) + 16, т. е. программа размещается сразу за PSP, который имеет длину 256 байт (16 параграфов).
Считывается загружаемая часть программы в непрерывную область памяти длиной LengExe, начинающуюся по адресу startseg: оооо.
Указатель файла устанавливается на начало таблицы перемещения TablOff.
Для каждого элемента перемещения (этих элементов ReloCnt):
считывается элемент как два 16-битных слова ItemOf s, ItemSegJ
вычисляется ReloSeg = startseg + ItemOfs, т. е. сегментная часть смещения абсолютного адреса перемещаемой ссылки;
извлекается слово по адресуReloSeg:ItemOfs — сегментная часть перемещаемой ссылки;
к этому слову прибавляется startseg (осуществляется так называемая привязка сегмента);
результат помещается обратно по адресуReloSeg:ItemOfs.
Выделяется память за концом программы в соответствии со значениями MinMem И MaxMem.
Инициализируются регистры и запускается программа:
регистры ES и DS получают значение сегмента, в котором располагается psp;
регистр АХ отражает корректность идентификаторов дисков в командной строке (при нормальном запуске содержит о);
SS=StartSeg+ReloSS;
SP=ExeSP;
CS=StartSeg+Relocs;
IP=Exelp;
содержимое остальных регистров не имеет значения.
Регистры сегмента кода cs и указателя инструкций IP обычно инициализируются следующими тремя командами:
PUSH StartSeg + ReloCs
PUSH Exelp
RETF
(команда RETF дальнего возврата из подпрограммы извлекает из стека два слова — смещение и сегмент адреса перехода — и помещает их соответственно в IP и cs).
Таким образом, сразу после получения управления фаг должен сохранить значения регистров АХ и DS и поместить в DS значение собственного сегмента данных. На практике сегмент данных в коротких ассемблерных программах обычно совпадает с сегментом кода, т. е. программа и данные размещаютсяв одномсегменте.Сегментстекаssможнонеизменять,т.к.
программа — установщик фага должна позаботиться о том, чтобы стек не разрушил код самого фага, и соответствующим образом настроить Reioss и/или ExelP. Обычно в ЕХЕ-программе начальное значение ReLoSS таково, что стек размещается сразу за концом программы, т. е. в том месте, куда программа-установщик помещает код фага. Длина стека ExeSP как правило более чем достаточна для того, чтобы работа фага со стеком не привела к разрушению кода фага, поэтому в большинстве случаев установщик оставляет начальные значения Reioss и ExeSP без изменения.
Описание программ SetFag.pas и Fag.asm
В этом разделе описываются программы SETFAG.PAS и FAG.ASM, с помощью которых реализуется описанный выше механизм защиты. Программа setFag осуществляет установку фага, а программа FAG.ASM содержит сам фаг. Тексты программ находятся в файлах SETFAG.PAS и FAG.ASM на сопровождающей дискете. Если вы захотите воспользоваться предлагаемыми программами, откомпилируйте Турбо Ассемблером файл FAG.ASM командой tasm fag /L
В ходе компиляции на экран будет выведено 13 предупреждений вида *Warning* Open procedure: XXXXXX а в конце — сводка
Error message:None
,.
Warning message:13
Passes:1
Remainig memory: XXX К
Если в строке Error message вместо None указано число обнаруженных ошибок, просмотрите файл листинга компиляции FAG.LST, отыщите в нем сообщения компилятора об ошибках и устраните их. Затем преобразуйте полученный файл FAG.OBJ в программу FAG.PRG командой
tlink fag, fag.prg
Компоновщик должен сообщить
Warning: No stack
Примечание
Программа FAG.PRG не может работать самостоятельно без предварительной настройки установщиком SETFAG.EXE, поэтому в целях предосторожности ей присваивается нестандартное расширение PRG.
Для установки защиты на любой ЕХЕ-файл следует дать команду setfag NAME
где NAME — имя защищаемого файла. Иными словами, имя файла передается программе установки фага SETFAG.EXE с помощью параметров запуска. В имени NAME можно опускать стандартное расширение ЕХЕ, а также разрешается указать маршрут поиска файла и/или символы-заменители DOS "* и "? для определения группового имени — в этом случае защита будет установлена на каждый файл, соответствующий групповому имени. Например, команда
setfag d:\mydir *
означает требование установить защиту на все ЕХЕ-файлы из каталога MYDIR на диске D:.
Перед установкой защиты программа осуществляет серию проверок файла. Она проверяет заголовок файла и блокирует установку защиты, если первые два байта заголовка не соответствуют сигнатуре "MZ (признаку ЕХЕ- файла). Кроме того, она проверяет "хвост" файла с тем, чтобы убедиться в отсутствии кода фага, и блокирует повторную установку защиты на уже защищенный файл. Далее, защита не устанвливается также в том случае, если длина загружаемой части файла станет слишком большой (превысит доступную память). Если в конце файла обнаружена незагружаемая часть, программа информирует об этом пользователя и запрашивает у него подтверждение на установку защиты. После завершения всех проверок программа создает резервную копию исходного файла с расширением ВАК. Создание ВАК-файла можно запретить, если команду вызова дополнить ключом /NOBAK, например
setfag myprog /nobak
Для защиты используется ключ, соответствующий такой структуре данных:
type
HeadType = record case Byte of
(
Sign: Word; (Сигнатура 'MZ'= $5A4D}
PartPag: Word; (Часть неполного сектора)
PageCnt: Word; (Количество секторов}
ReloCnt
Word;
{Количество элементов в таблице перемещения)
HdrSize
Word;
(Длина заголовка в параграфах)
MinMem
Word;
{Минимальныйразмер кучи}
MaxMem
Word;
(Максимальный размер кучи)
ReloSS
Word;
(Начальное значение сегмента стека SS}
ExeSP
Word;
(Начальное значение указателя стека SP)
ChkSum
Word;
(Контрольная сумма всех слов файла)
ExelP .
Word;
(Смещение точки запуска программы)
ReloCS
Word;
(Начальное значение сегмента кода CS)
2: (W: array [1. -12] end;
of Word)
TAVir = record
Head24: HeadType;
( 24 байта эталонного заголовка)
Starts: Word;
{Относительный сегмент)
StartO: Word;
(и смещение точки запуска программы)
Leng24: LongInt;
(Длина незараженной программы минус 24 байта)
Key: Word;
end;
(Ключ шифровки)
Как видим, этот ключ несколько отличается от использованного в модуле F_Anti: сохраняются только 24 байт заголовка (вряд ли вирус изменит смещение таблицы TablOff и номер оверлея Overlay), исключено ненужное теперь поле HF, добавлены поля starts и starto для запоминания относительного адреса точки запуска защищаемой программы. Поле Key по- прежнему содержит шифр для защиты ключа. Суммарная длина ключа SizeOf (TAVir) составляет 34 байт.
Процесс установки защиты состоит из следующих этапов.
В динамическую память считывается код фага из файла FAG.PRG. Вы можете создать свой вариант фага и заставить программу SetFag использовать его, если в команду запуска установщика добавите ключ /f: NameFag.Ext, где NameFag.Ext — имя и расширение файла, содержащего разработанный вами фаг. В этом случае учтите, что SetFag помещает 34- байтный ключ в самое начало кода фага (см. файл FAG.ASM на дискете) и поэтому при считывании из файла пропускает 34 байт от начала его загружаемой части. Вьщеление кода фага в отдельный PRG-файл понадобилось мне на этапе разработки и отладки кода фага. Я решил сохранить возможность загрузки кода из внешнего файла для того, чтобы вы смогли при желании поэкспериментировать с этим кодом.
В поле Head24 переменной нн типа TAVir считывается заголовок ЕХЕ- файла и осуществляется настройка ключа нн: в полях starts и starto запоминается относительный адрес точки запуска защищаемой программы; вычисляется файловое смещение LS в параграфах, соответствующее полной длине файла и выровненное на границу параграфа — с этим смещением от начала файла в него будет помещен ключ и тело фага (выравнивание на границу параграфа необходимо для того, чтобы обеспечить корректность внутрисегментной адресации кода фага); в ReloCS помещается новое значение относительного сегмента точки запуска фага, а в ExelP — смещение этой точки; рассчитывается новое значение длины загружаемой части файла с учетом ключа и тела фага и соответствующим образом изменяются поля PageCnt и PartPag; проверяются и при необходимости корректируются поля MinMem и ExeSP так, чтобы стек не разрушил код фага.
В начало ЕХЕ-файла записывается новый заголовок HH.Head24, затем осуществляется смещение файлового указателя на LS*16 байт от начала файла и в него записывается зашифрованный ключ и тело фага.
Ассемблерная программа FAG. ASM работает следующим образом.
Сразу после получения управления фаг сохраняет в стеке регистр АХ, запоминает в переменной PSP значение регистра сегмента данных DS (в этот момент он указывает на префикс программного сегмента) и помещает в DS сегмент кода cs (данные и код фага расположены в одном сегменте). Кроме того, в переменной SPO запоминается вершина стека, а в cso — сегмент кода фага. Затем вычисляется абсолютный сегмент точки запуска защищаемой программы(какужеговорилось,онравенPSP+16)и найденное значение
помещается в starts — таким образом готовится запуск защищаемой программы.
Вся основная работа фага запрограммирована в серии последовательно вызываемых процедур (при разработке фага использовался метод нисходящего программирования). Вначале с помощью процедуры GetExeName фаг определяет полное имя защищаемого ЕХЕ-файла. При этом учитывается то обстоятельство, что в версиях MS-DOS 3.0 и выше стандартный загрузчик помещает полное имя загружаемого файла в расширенное окружение ДОС. Окружение ДОС — это область памяти длиной До 32 Кбайт, в которой ДОС сохраняет переменные окружения типа COM3PEC, PATH, PROMPT и т. п. Каждая переменная окружения представляет собой текстовую строку, составленную из кодов ASCII, в конце которой ставится байт 0 как признак конца строки — фирма IBM называет такой код ASCIIZ (Z — Zero, ноль). Переменные окружения располагаются в памяти последовательно друг за другом. В конце "стандартной" части окружения (эта часть поддерживается и в ранних версиях MS-DOS) ставится дополнительный нулевой байт. За стандартной частью следует расширенная часть, куда загрузчик новых версий MS-DOS по-
мещает полное имя файла (с указанием диска и маршрута поиска) и, возможно, параметры обращения к программе. Таким образом, чтобы найти имя файла, нужно отыскать в окружении ДОС два нуля подряд — это признак начала расширенной части окружения. Слово, следующее за этим признаком, содержит количество переменных в расширенной части, за ним помещаются сами переменные. Например, в терминах ассемблера структура окружения может быть такой:
db ' COMSPEC=C: \COMMAND. COM', 0 ; Переменная COMSPEC
db \PATH=C:\/C:\DOS/D:\TP',0 /Переменная PATH
db 'PROMPT=$p$g', 0/Переменная PROMPT
db 0/Признак конца
В этом месте кончается стандартная часть окружения и начинается его расширенная часть (только для MS-DOS 3.0 и выше!).
dw 2/Количество переменных в расширенной части
db 'D:\MYDIR\SETFAG.EXE',О /Имя файла
db 1/NOBAK',0/Параметр вызова
Перед передачей управления программе загрузчик копирует окружение в отдельную область памяти и помещает сегмент этой области в PSP (в слово со смещением 44 байт от начала PSP).
В заключение следует сказать, что программы SetFag и Fag.asm не являются эталоном. Просто мне показалось, что такой способ организации защиты ЕХЕ-файлов будет достаточно удобным в использовании и эффективным в работе. Действительно, тестовые заражения программ специально разработанным вирусом, а также вирусом Yankee показали, что фаг успешно выполняет свои функции.
Поскольку программу Fag.asm без особого труда можно изменить, существует потенциальная опасность, что этот материал может быть использован для разработки вирусов. Я очень надеюсь, что к вам, уважаемый читатель, это не относится.
Программа AntiVir
Итак, мы рассмотрели способ, позволяющий придать вновь создаваемой программе свойства самоконтроля. А как быть с СОМ-файлами или защитить громоздкую программу? Кроме того, существуют вирусы, которые поражают не файлы, а загрузочные секторы дисков. Для таких вирусов (их называют загрузочными) контроль PSP может оказаться неэффективным. Одним из возможных способов решения задачи является разработка специальной программы, которая проверяет первый сектор наиболее важных ЕХЕ- и СОМ-файлов при каждом включении ПК. Эта же программа может проверить главный загрузочный сектор или даже все загрузочные секторы
Глава 29. Защита программ от компьютерных вирусов 801 на всех дисках, чтобы убедиться в отсутствии загрузочных вирусов, а при их обнаружении удалить их.
Мною разработана программа ANTIVIR (см. файл ANTIVIR.PAS на сопровождающей дискете), реализующая описанные функции. Эта программа создает и поддерживает архив загрузочных секторов и секторов PSP. Она может работать в двух режимах — автоматическом и диалоговом.
В автоматическом режиме ANTIVIR проверяет текущее состояние загрузочных секторов и первых секторов для заданных файлов и сравнивает их с эталонными копиями, хранящимися в архиве. В случае обнаружения отличий программа сообщает пользователю об этом и предлагает восстановить эталонное состояние соответствующих секторов. •
В диалоговом режиме программа предоставляет пользователю возможность просмотреть и скорректировать список проверяемых файлов. Диалоговая часть реализована с помощью объектно-ориентированной библиотеки Turbo Vision. Выбор нужного режима осуществляется автоматически: если программа вызывается командой
ANTIVIR
она переходит к диалогу с пользователем, если командой
ANTIVIR /AUTO
реализуется режим автоматического контроля.
При контроле загрузочных секторов программа использует меры, позво- ляющие'ей обнаруживать так называемые вирусы-невидимки. Такие вирусы контролируют обращение к функциям и прерываниям MS-DOS, а также к прерыванию $13 BIOS и при попытке чтения зараженного вирусом участка диска "подсовывают" программе сохраненную вирусом копию незаражен- ного участка. Единственным способом обнаружения подобных вирусов является непосредственное обращение к контроллеру диска или прямой вызов прерывания $13 в постоянной памяти BIOS. Поскольку BIOS обычно учитывает особенности конкретного контроллера диска, второй путь будет более простым, однако для его реализации необходимо каким-то образом определить начало в BIOS программы, обрабатывающей прерывание $13 (сразу после загрузки MS-DOS вектор $13 перехватывается программами IBMIO.COM и показывает на резидентную в оперативной памяти часть MS- DOS). Для определения "чистого" вектора $13 в программе используется мультиплексное прерывание $2Е, которое для подфункции $13 возвращает в регистрах DS •. DX требуемый адрес. Поскольку при этом прежнее содержимое регистров DS:DX автоматически помещается в вектор $13, это прерывание необходимо вызывать дважды подряд с промежуточным запоминанием регистров DS: DX в стеке (см. процедуру BuildArch в программе ANTIVIR). В ходе контроля загрузочных секторов программа переназначает обычно
пустующий вектор $62 так, чтобы он указывал на вход в обработчик $13, и использует затем этот вектор для скрытого от вируса чтения секторов жесткого диска. К сожалению, этот прием нельзя использовать для контроля архивных файлов, т. к. в последнее время широкое распространение получили программы динамического сжатия/раскрытия информации (самыми популярными из таких программ являются Double Space и Stacker). Контроль динамически сжатых дисков непосредственным чтением секторов невозможен, т. к. в этом случае отключается не только возможный вирус, но и резидентная программа-деархиватор. В результате не удается найти начальный сектор защищаемого файла и проверить PSP программы.
29.5. Некоторые рекомендации
В заключение приведу еще несколько советов.
Вставляйте имя модуля F_Anti в предложение Uses — "вакцинация" программы при ее рождении гарантирует стойкий иммунитет на весь срок ее эксплуатации.
Если вы часто используете ЕХЕ-файлы, содержащие инструментальные программы (например, NC.EXE, LEXICON.EXE и т. п.), рекомендую поставить на них фаг FAG.PRG. Кстати, если вы устанавливаете фаг на программу, защищенную средствами модуля F_Anti, встроенный в нее контроль обнаружит инородное тело фага.
Все СОМ-файлы операционной программы и слишком громоздкие ЕХЕ- файлы следует защитить программой ANTIVIR. Вызов этой программы в автоматическом режиме (с ключом /дито) полезно включить в файл AUTOEXEC.BAT.
Старайтесь как можно чаще архивировать на дискетах жизненно важные для вас (и вашего компьютера) файлы.
Глава 30
Защита программ
от нелегального копирования
В главе рассматриваются проблемы создания программ, нелегальное (без ведома официального распространителя) копирование которых приводит к потере программами способности нормально работать.
Суть защиты
Вряд ли во всем мире найдется хотя бы десяток программистов или людей, повседневно использующих компьютер, которые с гордостью могут похвастаться тем, что никогда в жизни не использовали нелегально приобретенные программные продукты. Во всяком случае, среди отечественных программистов я таких не встречал. Сегодня сложилась парадоксальная ситуация, когда в большинстве отечественных ПК в основном используется "ворованное" программное обеспечение. Редкое исключение составляют те немногие профессионалы, которые работают в частных фирмах или на совместных предприятиях: как правило, здесь очень дорожат репутацией фирмы и поэтому стараются работать только с лицензионными (официально купленными) программами.
Все мы понимаем, что труд автора (авторов) программы, которая пришлась нам по душе, должен быть вознагражден, но, увы! — слишком велик искус — и мы вставляем дискету в накопитель и вызываем опцию Сору программной оболочки Norton Commander (которая, кстати, тоже почти никогда не приобретается законным путем).
В соответствии с международным правом труд автора программы приравнен к труду других творцов интеллектуальной собственности, таких как писатели, музыканты, художники, и подлежит защите. Однако в отличие от книги или картины массовое копирование программы не вызывает каких-либо технических трудностей и не требует специального оборудования (если, разумеется, не считать ПК). В этом отношении программа намного беззащитнее, чем, скажем, скульптура или книга. Общий ущерб от нелегального ко-
пирования программ по оценкам западных экспертов составляет от 2 до 10 млрд. долларов в год. Вряд ли в ближайшем будущем что-либо кардинально изменится в этом отношении, особенно в нашей стране.
Вполне понятным поэтому кажется стремление авторов придать своим программам такие свойства, которые бы делали невозможным их нелегальное (помимо воли автора) распространение. В этом разделе мы обсудим технику разработки программ, в той или иной мере обладающих этими свойствами. Однако я должен сразу же оговориться, что не существует такой защиты, которую было бы невозможно — опять-таки помимо воли автора — снять. Вы должны отчетливо представлять себе, что опытный программист, обладающий соответствующими навыками, всегда сможет найти то место (те места) в вашей программе, где принимается решение о легальности копии, и, изменив несколько байтов в коде программы, лишить ее возможности к самозащите. Именно по этой причине ведущие фирмы — производители массового программного обеспечения практически никогда не ставят защиту на свои программы, справедливо полагая, что не стоит тратить силы и средства на создание систем защиты (тем более, что эти системы, как правило, причиняют пользователю определенные неудобства), если все равно программа будет "вскрыта". Эти фирмы получают свои дивиденды за счет массовости объема продаж при сравнительно низкой цене за каждую копию. Они делают ставку на "цивилизованных", покупателей и, что примечательно, таким способом воспитывают их! Более того, в последнее время получили распространение программы, которые как раз рассчитаны на механизм "пиратского" копирования и распространяются преимущественно именно таким способом: обычно эти программы сопровождаются "электронным письмом" (файлом с текстом), в котором содержится краткая инструкция по работе с программой и указываются необходимые координаты автора с просьбой выслать небольшую сумму в случае, если пользователь считает, что достоинства программы заслуживают этого.
Несколько иначе обстоит дело с программами, которые не рассчитаны на массового пользователя. К таким относятся различного рода системы автоматизированного проектирования, издательские системы, специализированные базы данных и т. п. Как правило, это весьма наукоемкие товары, т. е. в них особенно велик вклад высококлассных специалистов в конкретных прикладных областях. Труд таких специалистов обычно высоко ценится, поэтому общие затраты на создание подобного рода программ могут быть весьма велики, что в сочетании со сравнительно небольшим кругом потенциальных пользователей делает цену каждой копии очень большой. Такие программы обычно снабжаются защитой, т. к. ограниченный спрос на них формирует весьма специфичный (и чаще всего "цивилизованный") рынок: покупателями этих программ являются специалисты в конкретной прикладной области деятельности, которые не имеют нужных навыков и, главное, желания заниматься "вскрытием" программ.
Будем считать программу защищенной от нелегального копирования, если в ней есть встроенные средства, позволяющие проверить саму программу и/или характерные признаки ПК, на котором она выполняется, с целью определить, была ли копия программы сделана с соблюдением всей необходимой технологии. Если технология создания копии была нарушена, программа перестает работать нормальным образом. Таким образом, легальное копирование подразумевает использование некоторой особой технологии создания копий, в необычности которой и заключается весь фокус. Лишь посвященный в этот секрет человек с помощью специального оборудования или особой программы может создать легальную копию — на это и делается ставка.
Любая копия защищенной программы должна содержать в себе или во внешнем файле "ключ" — одно или несколько кодовых чисел. В момент проверки программа сравнивает некоторые специфические признаки рабочей среды с заранее закодированными в ключе и по результатам сравнения формирует соответствующий признак. Таким образом, мало создать копию программы: для того чтобы эта копия стала работоспособной, ей необходимо передать ключ, настроенный на работу с вполне определенным компьютером.
Анализ индивидуальных признаков среды
Какими специфическими признаками может обладать тот компьютер, точнее та программно-аппаратная среда, в которой работает программа? Для IBM-совместимых ПК этими признаками могут быть:
П тип ПК и тип (версия) операционной системы;
G дата изготовления ПЗУ BIOS и/или его контрольная сумма;
□ физическое положение файлов на дисковом носителе;
О состав аппаратных средств;
П наличие скрытых частей программы;
П физические особенности (в том числе дефекты) носителя.
Некоторые из этих признаков очень индивидуальны (например, физические особенности некачественного носителя), другие обладают меньшей индивидуальностью (тип ПК, версия DOS). Для проверки легальности копии программа может использовать один или несколько признаков. При этом особое значение приобретает способ использования программы: если программа рассчитана на работу на конкретном ПК, выбираются одни признаки, если она может свободно перемещаться с одного компьютера на другой без потери работоспособности, — другие. Назовем программы первого типа стационарными, а второго — мобильными. Защита тех и других
программ имеет свои особенности, которые мы будем по мере необходимости обсуждать.
Во всех случаях проверка легальности не должна существенно сказываться на быстродействии программы или требовать от пользователя каких-то дополнительных действий. Например, вряд ли можно считать сколько-нибудь эффективной систему, с паролем (кодовым словом), который должен ввести пользователь: во-первых, пользователь может забыть это слово и тем самым лишиться законно приобретенной программы, а во-вторых, ничто не мешает ему сообщить это слово любому другому пользователю. Система защиты не должна проверять пользователя, она должна проверить копию! На мой взгляд, это утверждение не вызывает сомнений.
Проверка типа ПК и версии DOS
Эти проверки очень просты, но не обладают высокой степенью индивидуальности в том смысле, что могут существовать многие сотни тысяч ПК одного типа, в которых используется одинаковая DOS. Поэтому обычно эти проверки используются в сочетании с проверками других индивидуальных признаков и предназначены для защиты стационарных программ.
Тип ПК записан в ПЗУ по адресу $F000:$FFFE, т. е. в предпоследнем байте мегабайтного адресного пространства ПК. Значения этого байта приведены в табл. ЗОЛ.
Таблица 30.1. Коды, соответствующие типам ПК
Код
Тип ПК
Код
Тип ПК
FF
PC
FD
PCjr
FE
XT
FC
AT
Следующая программа выводит на экран тип ПК: begin
case Mem[$FOOD:$FFFE] of
$FF: WriteLn('PC');
$FE: WriteLn('XT');
$FD: WriteLn('PCjr');
$FC: WriteLn('AT');
else
WriteLn('Нестандартный тип ПК ' ,Mem[$F000:$FFFE])
end
end.
Версию DOS можно получить с помощью функции MS-DOS $зо. При обращении к этой функции в регистре AL возвращается старший, а в АН — младший номера версии. Например, для MS-DOS версии 3.30 AL содержит 3, а АН — 30:
Uses DOS;
. var
R: registers;
begin
with R do
begin
AH := $30;
MSDOS(R);
WriteLn('Версия ДОС ',AL, '.',AH)
end
end.
Регистр AL может содержать о, если используется более ранняя версия MS- DOS чем 2.0.
Ценность этих проверок заключается в их исключительной простоте, однако, согласитесь, что могут найтись тысячи однотипных компьютеров, на которых установлена одинаковая версия DOS, поэтому ограничиваться только этими проверками вряд ли имеет смысл.
Проверка даты изготовления и контрольной суммы ПЗУ
Постоянное запоминающее устройство (ПЗУ) является неотъемлемой составной частью любого IBM-совместимого ПК. Содержимое ПЗУ учитывает особенности реализации конкретного ПК и может отличаться в компьютерах разного типа. Более того, в конце ПЗУ (по адресу $F000:$FFF5) обычно записывается дата его изготовления, поэтому даже для ПК одного типа (и одной и той же фирмы-изготовителя) контрольная сумма ПЗУ отличается в разных экземплярах ПК.
Дата изготовления ПЗУ находится по адресу $F000:$FFF5 и занимает 8 смежных байт. Данные хранятся в символьном виде в формате MM/DD/YY (ММ — символы номера месяца, DD — номера дня, YY— номера года), например "06/26/92". Следующая программа выводит на экран дату изготовления ПЗУ:
var
k: Integer;
begin
for0 to 7 do
Write(chr(Mem[$F000:$FFF5+k]));
WriteLn
end.
Как показывает практика,уважающая себя фирма — изготовитель
ПЗУ для IBM-совместимых ПК тщательно следит за корректностью этой даты. Конечно, каждый день во всем мире изготавливаются тысячи микросхем ПЗУ с одинаковой датой, однако вероятность того, что на конкурирующей с вами фирме используются ПК того же типа и с такой же датой изготовления, как и у вашего компьютера, согласитесь, крайне мала. Разумеется, при массовой закупке ПК, например, для оснащения учебного класса многие или даже все одновременно приобретенные компьютеры могут иметь одну и ту же дату изготовления ПЗУ. Однако в таких случаях способность защищенных программ свободно переноситься с одного родственного компьютера на другой можно рассматривать как вполне естественную.
Эта проверка используется для защиты стационарных программ.
Проверка места расположения файла на диске
Хорошей индивидуальностью обладает физический номер кластера, начиная с которого на жестком диске располагается файл с защищенной программой. Действительно, вряд ли что-либо другое в аппаратно-программной среде ПК (кроме, разумеется, содержимогопамяти) меняется
столь же динамично, как файловая структура жесткого диска. При создании легальной копии номер начального кластера для файла программы на жестком диске в общем случае совершенно случаен. Если в момент запуска программа проверит этот номер, то в подавляющем большинстве случаев она легко обнаружит факт нелегального копирования.
Я сам довольно долго использовал эту характеристику для защиты одной из своих программ. Насколько мне известно, десятки попыток нелегального переноса этой программы окончились неудачей. Тем не менее такой способ защиты нельзя считать идеальным по многим причинам. Проверка номера кластера выполняется далеко не так просто, как проверка типа ПК или даты изготовления ПЗУ, поскольку в стандартном Турбо Паскале нет средств для работы с дисками на физическом уровне. Однако главный недостаток заключается в другом: любое изменение местоположения файла даже в пределах одного каталога приводит к тому, что ранее установленная копия становится нелегальной. Это крайне неудобно для пользователя, в особенности если он часто прибегает к процедуре переупорядочения файловой структуры, например, с помощью утилиты типа программы П. Нортона Speed Disk.
Для проверки номера кластера можно воспользоваться модулем F_Disk, описанным в главе 26. В него входит процедура GetDirltem, возвращающая элемент каталога для заданного имени файла. Параметр Firstc этого элемента содержит интересующий нас номер.
Следующая программа выведет номер кластера для "своего" файла (т. е. ЕХЕ-файла, в котором она расположена). Для нормальной работы программы в среде Турбо Паскаль она должна компилироваться на диск.
Uses F_Disk;
var
DI: Dir_Type;
begin
GetDirItem(ParamStr(0) ,DI);
WriteLn('Файл ',ParamStr(0),
' начинается с кластера ',DI.FirstC) end. •
Если вы считаете, что номер единственного кластера не обладает необходимой степенью индивидуальности, то можете проверять цепочку кластеров по таблице FAT или начальные кластеры нескольких файлов.
Разумеется, такая проверка пригодна только для защиты стационарных программ.
Проверка состава аппаратных средств
Программа может проверить объем доступной оперативной памяти, наличие и объем расширенной памяти, тип центрального процессора и приблизительную скорость его работы, наличие математического сопроцессора, количество и тип дисководов для гибких дисков, параметры физического жесткого диска, количество логических дисков, количество и тип каналов для подключения внешних устройств. Вот, пожалуй, и все. Каждая из этих характеристик может повторяться в тысячах других ПК, однако все они в комплексе будут достаточно индивидуальны и поэтому могут с успехом использоваться для защиты стационарных программ.
Некоторые иностранные фирмы для защиты мобильных программ выпускают так называемые электронные ключи — относительно дешевые устройства, которые перед запуском защищаемой программы подсоединяются к стандартному каналу последовательного или параллельного ввода/вывода (например, для одной из используемых мною программ этот ключ вставляется как переходник между разъемом параллельного порта и кабелем принтера). Электронные ключи реализуются на основе заказных микросхем и осуществляют нужное интерфейсное взаимодействие с защищаемой программой.
Объем доступной оперативной памяти на IBM-совместимых ПК может составлять от 64 до 640 Кбайт (речь идет о стандартной памяти, см. разд. 28. Г). При инициализации DOS в двухбайтное слово по адресу $0000:$0413 заносится количество килобайт доступной оперативной памяти. Следует заметить, что к моменту загрузки DOS в памяти могут устанавливаться резидентные программы (чаще всего это драйверы устройств), так что доступная остальным программам память в общем случае будет меньше физической памяти ПК. Чтобы определить объем физически установленной в ПК оперативной памяти, следует предварительно определить тип ПК. Дело в том, что в компьютерах класса IBM AT и PS/2 устанавливается специальная энергонезависимая КМОП-память, хранящая некоторую весьма полезную информацию о составе аппаратных средств ПК и в том числе — о доступной памяти, в то время как на компьютерах IBM PC и IBM XT такой памяти нет.
Структура КМОП-памяти может быть описана следующим образом:
type
CMOS_Type = record case Byte of 1: (
Sec
: Byte;
{Секунды системного времени}
ASec
Byte;
{Секунды будильника}
Min
Byte;
{Минуты системного времени}
AMin
Byte;
[Минуты будильника}
Hour
Byte;
(Часы системного времени)
AHour
Byte;
(Часы будильника}
DOW
Byte;
(День недели текущей даты}
Day
Byte;
[День текущей даты}
Month
Byte;
(Месяц)
Year
Byte;
(Год)
StatA
Byte;
{Состояние порта А}
StatB
Byte;
{Состояние порта В)
State
Byte;
{Состояние порта С}
StatD
Byte;
{Состояние порта D}
POST
Byte;
{Статус завершения программы POST
(проверка работоспособности ПК)}
Shut : Byte; (Байт состояния программы перезагрузки DOS)
(Начало массива из 17 байт, защищаемого контрольной суммой)
Diskette: Byte ; {Тип накопителя ГД)
Resl : Byte; (Не используется)
HD : Byte; (Тип накопителя ЖД)
Res2Byte; {Не используется }
Equipin ". Byte; (Байт оборудования)
BaseMem Word; (Объем основной памяти}
ExpnMem: Word;(Объемрасширенной памяти)
HD1 Byte; (ТипЖД N1)
HD2 Byte; (ТипЖД N2)
Res3: array [$1В..$20] of Byte; (He используется)
(Конец защищаемого массива)
Res4 rray [$21. . $2D) of Byte;{Не используется}
Checks Word; (Контрольная сумма}
ExtendM: Word; /Дополнительная память)
Century Byte; (Текущее столетие)
Miscell Byte; (Разная информация)
Res5 array [$34..$3F] of Byte);
2:(CMOS : array [$0..$3F] of Byte).
end;
Первые 10 байт этой структуры предназначены для обслуживания системных часов. Информация в этих байтах хранится в так называемом двоичнодесятичном формате (старший полубайт хранит десятки, а младший — единицы). Поскольку КМОП-память обычно питается от подзаряжаемого аккумулятора, системные часы работают даже после отключения ПК от сети и способны сохранять правильную дату практически неограниченно долго, если вы регулярно включаете ПК.
Заметим, что поля ASec, AMin и AHour определяют момент времени инициализации аппаратного прерывания IRQ8, с помощью которого можно автоматически запустить какую-либо программу реального времени (т. е. программу, которая должна исполняться в строго определенный момент реального времени).
Семнадцать байт, начиная с поля Diskette, защищены контрольной суммой, хранящейся "задом наперед" в поле Checks: младший байт содержит старшие разряды суммы, в то время как в соответствии со стандартом IBM в младшем байте должны храниться младшие биты слова. Проверка кон-
трольной суммы осуществляется в ходе выполнения программы POST (программы контроля работоспособности ПК) при каждом включении компьютера и служит для оценки степени разряженности батарейки автономного питания КМОП-памяти.
Поле BaseMem хранит суммарный объем (в килобайтах) оперативной памяти ПК, а поля ExtdMem и ExtendM — объем дополнительной памяти.
Доступ к содержимому КМОП-памяти возможен только как к внешнему устройству, подключенному к портам $70 и $71: сначала в порт $70 следует поместить номер нужного байта (нумерация начинается с нуля), а затем прочитать значение этого байта из порта $71. КМОП-память отличается сравнительно большим временем доступа, поэтому между двумя последовательными обращениями к портам полезно сделать небольшую паузу.
Объем установленной оперативной памяти можно определить с помощью приведенной ниже несложной программы (пример 30.1), осуществляющей анализ КМОП-памяти для ПК типа AT или непосредственное тестирование микросхем ОЗУ (для PS/XT). При тестировании учитывается то обстоятельство, что память ПК наращивается так называемыми банками памяти по 64 Кбайт, причем чтение из любого байта несуществующего банка возвращает код 233. Таким образом, достаточно убедиться в наличии одного байта из каждых 64 Кбайт.
зз^ум^^у||^^^^1^у^®мм^у^|||Мй1||М|ММШММЬ^|йу^^й8Мйй^^й^'^^8ЯМ№й6ЙК1й8^^^^ЙЙ1^^#х*;^йй^^}^>'"''"' ; Пример 30.1. Программа, определяющая объем оперативной памяти ПК
IЭта программа определяет количество \
I оперативной памяти, установленной в ПК |
"тI/
var
b : Byte;
к,j,m,e: Word;
begin
{Проверяем тип ПК}
if Mem[$FOOO:$FFFE]=$FC then
{Компьютер типа IBM AT — читаем КМОП-память} begin
Port[$70] := $15;
m := Port [ $71];{ Основная память)1
Port[$70] := $16;
m := m+Port[$71] shl 8;
:= $17;
е : = Port[$71];(Дополнительная память}
Port[$70]:= $18;
е := е1Рогг[$711 shl 8 '
end
else
beginтипа IBM
e0;дополнительной
m := 1;есть 64 Кбайт стандартной памяти)
for k := 1 to 9 do
begin
j := k shlочередного
ifthen
(m)
else
beginкод 233)
:= 0;
I ifthen
begin
inc(m) := 233
end
end
end;
m :=
end;
ПК установлено'основной
if е<>0 then
и ' Кбайт
end.
Информация о других устройствах, входящих в состав ПК, может быть получена на основании анализа так называемого регистра оборудования. Этот регистр для машин класса IBM XT формируется программами ПЗУ при загрузке DOS и располагается в двухбайтном слове по адресу
В табл. 30.2 прокомментирован смысл разрядов этого слова.
Таблица 30.2. Назначение разрядов регистра оборудования ПК
Разряд
Значение
Смысл
0
1
В ПК имеются накопители ГД
1
1
Есть арифметический сопроцессор
2-3
3
Есть стандартная память 64 Кбайт
4-5
Тип активного видеоадаптера:
•
0
О адаптер EGA или VGA;
1
О цветной адаптер 40x25;
2
О цветной адаптер 80x25;
3
□ монохромный адаптер
6-7
Число НГД (если бит 0 имеет значение l)
8
Не используется
9-11
Количество адаптеров связных каналов
12
1
Есть игровой порт
13
Не используется
14-15
Количество принтеров
Для IBM AT информацию о периферийных устройствах содержит поле Equipm в КМОП-памяти. Назначение разрядов этого байта аналогично назначению разрядов О—7 регистра оборудования, за исключением разрядов 2 и 3, которые не используются. Количество связных каналов последовательного типа для компьютеров этого класса можно получить путем анализа области памяти BIOS: начиная с адреса $0000:50400 в памяти располагаются четыре двухбайтных слова, содержащие адреса последовательных портов; порту СОМ1 соответствует адрес в слове $0000:$0400, а COM4 - в слове $0000:$040б; нулевое содержимое этих слов говорит об отсутствии соответствующего канала связи. Точно так же можно анализировать адреса параллельных ПОрТОВ ($0000:$0408, . . . , $0000: $О4ОЕ).
Следующая программа (пример 30.2) сообщает объем памяти, указываемый в слове $0000: $0413, и расшифровывает регистр (байт) оборудования и область адресов каналов связи (для AT).
г •••“;• v“«--v«4-*£**»v,y**M£‘M..*..vMv«?4^^^ *2*3*'” ’•••••••••••••••еуу.-*-л“"*^••••\чг\м**^;*м«**пг*л»
Пример 30.2. Программа, анализирующая регистр оборудования ПК
(+
I Программа анализирует байт оборудования \
I и выводит на экран его расшифровку \
++)
var
a, b: Word;
begin
{Сообщаем объем оперативной памяти}
WriteLn(MemW[0:$413] , ' Кбайт оперативной памяти');
{Определяем тип ПК}
if Mem[$FOOO:$FFFE] = $FC then
begin(Тип AT)
WriteLn( 'Компьютер типа IBM AT содержит: ');
Port [$70] := $14;
a := Port [$71];
if a and 1О0 then
case (a shr 6) and 3 of
0: WriteLn('~ один накопитель ГМД');
WriteLn!'- два накопителя ГМД');
end;
' • •1.
if a and 2<>0 then
WriteLn('- арифметический сопроцессор');
case (a shr 4) and 3 of
0 : WriteLn ('- адаптер EGA или VGA’ ) ;
WriteLn('- цветной адаптер 40x25’);
WriteLn('- цветной адаптер 80x25’);
WriteLn('- монохромный дисплей')
end;
{Анализируем количество последовательных портов}
а := 0;
for b 0 to 3 do
if MemW[0:$400+b shl 2]<>0 then
inc (a) 7•
WriteLn('- ',a, ' последовательных порта');
{Анализируем количество параллельных портов}
а := 0;
for b 0 to 3 do
if MemW[0:$408+b shl 2] <>0 then
inc(a);
WriteLn('- ',a, ' параллельных порта')
end
21 Зак 941
else
begin {Тип XT!
WriteLn(1 Компьютер типа IBM XT содержит:’);
a := MemW[0:$410];
if a and 1<>0 then
WriteLn('- ', (а shr 6) and 3, ’ накопителей ГМД');
if a and 2<>0 then
WriteLn('- арифметический сопроцессор');
if a and 1200 then
WriteLn('- базовую память 64К');
case (a shr 4) and 3 of
О : WriteLn('- адаптер типа EGA или VGA’) ;
WriteLn('- цветной адаптер 40x25’);
WriteLn('- цветной адаптер 80x25');
WriteLn('- монохромный адаптер')
end;
WriteLnC- * г (a shr 9) and 7, ' каналов связи');
if (a shr 12) and 1O0 then
WriteLn('- игровой порт');
WriteLnC- 1 I (a shr 14),' принтеров')
end
end.
Производительность микропроцессора обычно измеряется с помощью многократного прогона тестового участка программы за заданный интервал времени. Количество прогонов и является мерой производительности ПК. Для определения нужного интервала времени удобнее всего использовать переменную типа Longlnt по адресу $0000:$046C, в которой DOS хранит текущее системное время в виде количества 55-миллисекундных интервалов, прошедших от 0 часов.
Следующая программа (пример 30.3) оценивает производительность процессора и выводит на экран среднее количество прогонов тестового участка программы за один 55-миллисекундный интервал.
: Пример 30.3, Программа оценки производительности ПК
•f•еVИIw•3••V™V13•ГАеееее5«••'••«••• «Л, Л in»..vKrtff. Ilf ••..•
/ Определение производительности ПК }
const
N = 10;{Количество повторов}
var
T ime: Longl nt;(Моменты времени}
k : Longlnt;{Счетчиквычислений}
j : Integer;{Счетчик повторов}
begin
k0;
for j 1 to N do
begin
Time := MemL [ 0: $ 0 4 60 ];(На чальное время)
while MemL[0:$046C]=Time do
;,(Ждать, пока не изменится время}
inc(Time,2);(Конечное время}
whileMemL[0:$046С] ОТ imedo
inc (к);{ Цикл вычислений}
end;
WriteLn(к div N)
end.
В этой программе цикл
while MemL[0:$0460]=Time do;
введен для синхронизации начала вычислений с началом очередного 55- миллисекундного интервала. Как показывает практика, получаемая такой программой оценка производительности очень индивидуальна. Мне, например, не удалось найти среди 8 однотипных компьютеров ни одной пары с одинаковой числовой мерой производительности. Следует, правда, оговориться, что на двух ПК эта мера менялась от одного прогона программы к другому приблизительно на 0,5%, что, судя по всему, является следствием нестабильности работы тактовых генераторов. В связи с этим при разработке коммерческой системы защиты на основе измерения производительности ПК необходимо вводить некоторый доверительный интервал, связанный с нестабильностью работы тактовых генераторов, а это ■■— увы! — существенно уменьшает степень индивидуальности оценки.
Использование скрытых частей программы и особенностей физических носителей информации
Достаточно эффективным способом защиты (главным образом для мобильных программ) может служить создание и использование скрытых частей программы и/или особенностей физических носителей информации.
Скрытые части программы — это участки дискового носителя, тем или иным способом связанные с программой, но не зафиксированные в качестве файлов DOS. В подавляющем большинстве случаев программе нет необходимости искусственно создавать такие участки, поскольку они уже имеются в "хвосте" любого файла. Дело в том, что MS-DOS распределяет дисковое пространство кластерами, имеющими длину от 512 до 4096 и более байт. Даже если полезная длина файла составляет всего несколько байтов, MS-DOS выделяет такому файлу целый кластер, большая часть которого будет заполнена "мусором" — случайной информацией, сохранившейся от предыдущего использования кластера в составе другого файла. При копировании файлов стандартными средствами копируется только полезная часть кластера, так что на новом месте "хвост" файла в общем случае будет заполнен другой информацией.
Пример 30.4 иллюстрирует использование скрытой части файла для защиты программы от нелегального копирования. В этом примере программа управляется с помощью параметра запуска /s: если команда запуска программы дополнена этими символами, программа вводит с клавиатуры произвольную строку и помещает ее в "хвост" файла, в противном случае читает строку с диска и выводит ее на экран.
Пример 30.4. Защита программы путем анализа скрытой части файла
I Программа создает и контролирует скрытую \
I часть файла. При запуске без ключа /S \
I осуществляется контроль файла, при запуске \
I с ключом — создание скрытой части. \
v__
var
F: File;(Файловая переменная для файла программы)
S: String;{Переменная для создания скрытой части)
L: Longlnt;(Полезнаядлина файла)
begin
Assign(F,ParamStr(0));
Reset(F,1);
L :=;
WriteLn('Гарантированный остаток: ' , mod 512,' байт'); Seek(F,L);(Указатель —в конец файла)
if ParamStr(1)='/S' then
begin(Создаем скрытую часть)
WriteLn('Введите произвольную текстовую строку');
Seek(F,L);Truncate(F)endelse beginSeek(F,L+256);■BlockWrite (F,S,1)Seek (F,L) ;BlockRead(F,8,256);WriteLn('Скрытая частьWriteLn(S);Seek(F,L);Truncate (F)end;Close(F)end.Seek(F,L);Truncate(F)endelse beginSeek(F,L+256);■BlockWrite (F,S,1)Seek (F,L) ;BlockRead(F,8,256);WriteLn('Скрытая частьWriteLn(S);Seek(F,L);Truncate (F)end;Close(F)end.
Seek(F,L);
Truncate(F)
end
else begin
Seek(F,L+256);■
BlockWrite (F,S,1)
Seek (F,L) ;
BlockRead(F,8,256);
WriteLn('Скрытая часть
WriteLn(S);
Seek(F,L);
Truncate (F)
end;
Close(F)
end.
Seek(F,L);
Truncate(F)
end
else begin
Seek(F,L+256);■
BlockWrite (F,S,1)
Seek (F,L) ;
BlockRead(F,8,256);
WriteLn('Скрытая часть
WriteLn(S);
Seek(F,L);
Truncate (F)
end;
Close(F)
end.
ReadLn(S);
BlockWrite(F,S„succ(LengthGS) } ):;
{Восстанавливаем прежнюю длину файла)
{Проверяем скрытую- часть} (Увеличивай длину файла
. на 256 байт, чтобы исключитьошибку чтения’ из "хвоста" файла}
файла:');
(Восстанавливаемпрежнюю длину файла}
Более изощренный, но ничуть не более эффективный способ защиты состоит в создании и использовании дополнительных скрытых кластеров. Такие кластеры могут помечаться в FAT как сбойные или "потерянные" (т. е. не относящиеся ни к какому зарегистрированному файлу). Во всех случаях, помещается ли ключ в "хвост" файла или в отдельный кластер, защита может быть легко нейтрализована путем копирования дискеты "блок в блок" с помощью системной утилиты DISKCOPY или аналогичных несистемных программ.
Существенно лучшей способностью противостоять попыткам нелегального копирования обладает система защиты, основанная на учете индивидуальных особенностей дискет, прежде всего на анализе неустранимых дефектов. В этом случае система проверки защиты "знает" список дефектных секторов оригинальной дискеты и пытается их отформатировать. Если после форматирования обмен информацией с сектором проходит нормально, значит соответствующий сектор — бездефектный и, следовательно, мы имеем дело с нелегальной копией дискеты. Главное достоинство этого способа защиты заключается в принципиальной невозможности создать программными средствами на нормальной дискете неустранимые дефекты.
Как показывает практика, лишь очень небольшое число дискет (менее 1%) имеет заводские дефекты изготовления, поэтому при массовом тиражирова-
1
нии коммерческих программ приходится создавать такие дефекты искусственно. Для этого иногда используются лазеры, а чаще — обыкновенная булавка. После некоторой тренировки вы всегда сможете наносить царапины на слой носителя или даже прокалывать дискету так, чтобы по возможности сохранить работоспособность большей ее части. Однако следует иметь в виду, что царапины и проколы на поверхности дискеты могут повредить считывающие головки накопителя.
Следующая программа (пример 30.5) выводит на экран номера всех секторов дискеты, имеющих неустранимые дефекты. Программа сначала определяет истинное количество дорожек Trk и секторов NS на дорожке дискеты (в привод дисковода, рассчитанного на дискеты 1,2 Мбайта, может быть вставлена дискета емкостью 360 Кбайт). Проверка этих параметров осуществляется непосредственно по блоку параметров BIOS (BPB), считываемому из загрузочного сектора дискеты. После этого соответствующим образом изменяется таблица параметров дискеты (ТПД) и осуществляется цикл чтения по всем Trk дорожкам. Если в ходе чтения возникла ошибка, программа приступает к детальному анализу дорожки. Она вначале пытается прочитать и запомнить информацию, содержащуюся в каждом из ее NS секторов на обеих поверхностях, затем форматирует дорожку и восстанавливает прежнее содержимое секторов. Если после такой операции на дорожке останутся сбойные секторы, программа выводит на экран их номера.
му^УУЛУ^^^ЛУЦШЛ^У^Умммммjyjjjjjjjjujjjijyjjjjjyu^j—ЦЦ——j^jjjjjjjjjjj^--—^цщл^^ллицm^„j
Пример 30.5. Программа анализа информации на дискете
(+*
I Программа считывает информацию с дискеты по \
I дорожкам. При обнаружении сбойной дорожки \
I определяется сбойный сектор, информация из \ | остальных секторов запоминается, а дорожка \ | форматируется заново, после чего восстана- \
I вливается содержимое несбойных секторов. \
| Если при повторной проверке вновь возникла \
I ошибка, печатается фраза\
\ "Дефектный сектор NNN". \
+и I
Uses DOS, F_Disk;
type
PDBT_Type =ADBT_Type; {Указательна ТПД)
{Таблица параметров дискеты}
DBT_Type = record
Reservl : array [0..2] of Byte;
SizeCode: Byte;
(Код размера сектора)
LastSect: Byte;
(Количество секторова дорожке)
Reserv2 : array 15..7] of Byte;
FillChar: Char;
{Символ-заполнитель для форматирования)
Reserv3 : Word
end;
{Элемент буфера форматирования)
F_Buf = record Track: Byte;
(Номер дорожки)
Head : Byte;
Sect : Byte;
{ Номер головки} (Номер сектора)
Size : Byte end;
(Код размера)
TrackType = array [О..Maxlnt] of Byte;
const
DSK= 0;
{Номер диска)
var
Info: TDisk;
{ Информация о диске)
Old: PDBT_Type;
DBT: PDBT_Type;
TRK: Word;
(Указатель на исходную ТПД)
(Указатель на новую ТПД)
{Количество дорожек)
NS : Word;
{Количество секторов на дорожке)
i,{Счетчик поверхностей)
{Счетчик секторов)
(Счетчик дорожек)
nn: Integer;
Р: ATrackType;
R: registers;
BF : array [0..20]
{Номер сектора в цилиндре}
(Буфер чтения/записи)
(Регистры ЦП)
of F_Buf;(Буфер для форма тирования}
begin
-
{Определяем количество дорожек, головок и количество секторов на дорожке)
GetDisklnfo(DSK,Info);
if Disk_Error then
begin
WriteLn('Ошибка доступа к диску1);
822
ЧастьIII. Практика программирования
Halt
end;
Trk := Info.Tracks;
NS:= Info.TrackSiz;
(Резервируем память для буфера I GetMem(P, 2*NS*512) ;.
{Готовим нужную ТПД)
Old := ptr(MemW[0:$lE*4+2],MemW[0:$lE*4]);
New(DBT);
DBTA:= 01dA;
(Получаемкопию ТПД в ОЗУ)
SetlntVec($1Е,DBT);
(Изменяем ссылку на ТПД)
DBTA.LastSect := NS;
{Устанавливаемнужное
количество секторов на дорожке)
{Цикл проверки дорожек)
for k := 0 to TRK-1 do with R do
begin
ah = 2;
(Код операции чтения)
al=
/Количество секторов чтения)
ch = k;
(Номердорожки} , ,
cl— 1 *
(Начать с 1-го сектора}
dh = 0;
(Начать с 0-й поверхности}
dl =
es =
bx := ofs(pA);
{ Номер диска)
Intr($13,R);
(Читаем всю дорожку)
if (Flags and FCarry)<>0 then begin {Обнаружена сбойная дорожка) (Читаемдорожку по секторам}
for i := 0 to 1 do
f<эг 1 := 1 to NS do
(2 поверхности)
begin
ah = 2;
(Код операции чтения}
al :^1;
(Читаем один сектор}
ch = k;
{ Номер дорожки }
cl =
{Номер сектора)
dh = i;
dl = DSK;
(Номер головки)
{Номер диска}
nn := i*NS+pred (j);
es := seg(pA[nn*512]);
bx := seg(PA[nn*512]);
Intr($13,R)
end;
{Читаем сектор)
/ Форма тируем дорожку)
for i := 0 to 1 do
begin
(Цикл по головкам)
for j := 1 to NS do with BF[j] do
begin
(Готовим буфер)
Track = k;
Sect =
Head = i;
Size =Old" . SizeCode end;
ah = 5;
(Операция форматирования)
al = NS;
/Количество секторов на дорожке}
ch = k;
(Номер дорожки)
cl = 1;
(Начать с 1-го сектора)
= i;
(Номер головки)
dl =DSK
{ Номер диска )
es = seg (BF);
bx =ofs(BF) ;
Intr($13,R);
(Форма тируем поверхность}
end;
{Восстанавливаем содержимое
дорожки
и заново проверяем секторы)
for i := 0 to 1 do
(2 поверхности)
for j := 1 to NS do
begin
/Восстанавливаем содержимое сектора}
ah = 3;
(Код операции записи)
al = 1;
(Записываем 1 сектор/
ch = k;
(Номер дорожки)
cl=
(Номер сектора)
dh =
{Номер головки)
dl =
{Номер диска}
nn :=
es := seg(PA[nn*512]);
bx := ofs(PA[nn*512]);
Intr($13,R);
{Читаем сектор}
ah
= 2;
{Код операции чтения}
al
= 1;
(Читаем 1 сектор}
ch
=
(Номер дорожки}
cl
=
(Номер сектора}
dh
= i;
(Номер головки)
dl
= DSK;
{Номер диска}
es = seg(PA[nn*512]); bx = ofs(PA[nn*512]);
Intr($13,R);
if (Flags and FCarry)<>0 then
WriteLn('Дефектный сектор ', (k*2+i)*NS+j) end end end;
SetlntVec($1E, Old)(Восстанавливаем прежнюю ТПД)
end.
Недостатком описанного способа защиты являются относительно большие затраты времени на контроль всей дискеты и потеря работоспособности некоторой ее части. От этих недостатков можно избавиться, если на дискете создать программным способом нестандартные для ДОС особенности. Этими особенностями могут быть:
О нестандартная длина секторов на всей дискете или какой-либо одной ее дорожке;
О специальное расположение (фактор чередования) секторов на дорожке;
□ нестандартное количество дорожек.
Разумеется, надежность защиты с помощью программно создаваемых особенностей структуры дискеты будет значительно меньше. Тем не менее следует помнить, что в большинстве случаев "взломщик" программы стремится изменить ее код, а не имитировать особый способ копирования дискет, так что несложная защита, связанная с использованием дополнительной дорожки, может оказаться ничуть не хуже, чем защита с помощью "лазерной дырки".
Как известно, MS-DOS может оперировать только с секторами длиной по 512 байт (исключением является создание утилитой VDISK виртуальных
Глава 30. Защита программ от нелегального копирования 825 электронных дисков, размер секторов которых может отличаться от 512 байт). В то же время контроллеры ГД способны создавать и использовать секторы другого размера — по 128, 256 или 1024 байт. При обнаружении сектора нестандартного размера DOS считает этот сектор сбойным, что можно использовать для защиты программы.
Далее, дорожки на диске располагаются так, что самой внешней является нулевая дорожка, а самой внутренней — дорожка с максимальным номером. Обычно DOS работает с одинаковым количеством секторов на всех дорожках, поэтому угловой размер каждого сектора постоянен, а следовательно, плотность записи информации растет с ростом номера дорожки. Количество секторов на дорожке и дорожек на дискете DOS назначает так, чтобы даже для самой внутренней дорожки эта плотность не превысила некоторого значения, еще гарантирующего уверенную работу схем контроллера ГД. На практике оказывается, что подавляющее большинство современных контроллеров способно обслуживать большее количество дорожек, чем принято в DOS. В целях защиты от копирования программа может создать и использовать одну или несколько дополнительных дорожек, но не вносить их в список "видимых для DOS", т. е. не изменять поле BPB.TotSecs в загрузочном секторе, указывающее общее количество секторов на диске. Разумеется, возможен и другой вариант: можно "украсть" у DOS несколько дорожек, уменьшив стандартное значение этого поля, однако нестандартная емкость дискеты легко обнаруживается, что снижает эффективность защиты. Заметим, что дополнительные дорожки могут отделяться от основной рабочей зоны дискеты неотформатированным интервалом, что может затруднить их обнаружение специальными программами копирования.
Наконец, программа может использовать нестандартный фактор чередования секторов. Напомню (см. главу 26), что этот фактор влияет на время чте- ния/записи группы смежных секторов. Если какую-либо дорожку отформатировать с намеренно неоптимальным фактором чередования, время чтения этой дорожки может оказаться заметно больше, чем при чтении любой другой дорожки — вот вам и еще один способ защиты!
В файле DISKETT.PAS на сопровождающей книгу дискете содержится программа DISKETTE, в которой используются все три перечисленных метода. На дискете емкостью 360 Кбайт создается 41-я дорожка с девятью секторами размером по 256 байт, причем с обратным фактором чередования, т. е. секторы на дорожке размещаются в последовательности 9, 8, ..., 2, 1. После этого в первый сектор новой дорожки записывается произвольная информация, затем сектор читается и проверяется правильность операций записи/чтения. В конце программы измеряется время доступа к новой дорожке и стандартной дорожке.
30.3. Защита программ от трассировки
Анализ индивидуальных признаков программно-аппаратной среды, в которой работает программа, дает ей возможность проверить легальность копии и принять решение о характере дальнейшей работы. Вот тут-то и кроется наиболее уязвимое место любой системы защиты. Действительно, сама задача защиты программ по сути своей дихотомична, т. е. предполагает выбор одного из двух возможных вариантов: либо копия легальна, либо нет. Выбор либо-либо в конечном счете реализуется машинной инструкцией условного перехода: перейти, если больше (меньше, равно и т. п.). Если в коде программы заменить эту инструкцию на команду безусловного перехода, программа всегда будет работать так, как если бы проверка легальности копии дала положительный результат. Таким образом, каким бы хитроумным ни был алгоритм проверки, он легко нейтрализуется, если в точках дихотомического ветвления программы заменить инструкции условного перехода на команды безусловного перехода.
Если вы хотите защитить свою программу от нелегального копирования, то должны составить ее таким образом, чтобы всячески усложнить задачу нескромному наблюдателю, пытающемуся проследить логику ее работы и отыскать ключевые точки ветвления. Фактически, это означает вести борьбу с человеком, осуществляющим дизассемблйрование программы (т. е. перевод машинных кодов в инструкции ассемблера) и ее трассировку (прослеживание логики работы). В теоретическом плане такую борьбу можно считать проигранной — ведь в конечном счете человек всегда умнее машины. Однако здесь уместна, как мне кажется, параллель с шахматами: известно, что теоретическое положение "белые начинают и выигрывают" вовсе не означает абсолютной бесперспективности черных! Точно так же имеет шансы устоять от попыток "взлома" и грамотно составленная система защиты. Суть борьбы со "взломщиком" заключается в том, что многочисленные программные "ловушки", блокирующие работу программ-трассировщиков, и крайне запутанная логика работы системы защиты могут сделать ее "взлом" весьма трудным делом даже для опытного взломщика.
Для дизассемблирования программ обычно используются различные программы-переводчики, преобразующие машинные коды в текстовые строки ассемблерных инструкций. Чтобы затруднить дизассемблйрование, можно зашифровать часть программы. В этом случае в работающей программе осуществляется необходимая дешифровка этих фрагментов перед тем, как им будет передано управление, а после дизассемблирования исходного ЕХЕ- файла ключевые фрагменты программы оказываются совершенно бессмысленными. Реализация этой идеи на Турбо Паскале не так проста, как это может показаться на первый взгляд. Дело в том, что компилятор Турбо Паскаля создает ЕХЕ-файл, содержащий многочисленные адресные ссылки с использованием относительной адресации. В процессе загрузки файла в
оперативную память осуществляется замена относительных адресов на абсолютные. Шифровка может нарушить относительную адресацию и дешифрованная программа потеряет свою работоспособность. Выходом из положения может служить разработка на ассемблере зашифрованных COM- программ, вызываемых из основной программы и обменивающихся с нею данными через общее поле памяти. Ссылку на это поле можно поместить в заранее обусловленный и неиспользуемый DOS вектор прерывания. Замечу, что более или менее сложная программа на Турбо Паскале насчитывает многие десятки, а часто и сотни килобайт и после дизассемблирования дает гигантские листинги объемом во многие сотни тысяч и даже миллионы символов. Разобраться в этой мешанине кодов без пошаговой трассировки практически невозможно, а следовательно, можно не тратить силы на шифровку громоздких программ.
Защита от трассировки заключается в том, что программа распознает факт пошаговой трассировки и пытается тем или иным способом противодействовать этому процессу. Чаще всего для этих целей используется остроумный прием, основанный на особенностях архитектуры микропроцессоров семейства Intel 80x86. Для повышения производительности этих кристаллов в них применяется конвейерный способ обработки команд, в соответствии с которым очередная инструкция загружается во внутреннюю очередь команд процессора до завершения обработки текущей инструкции. Если текущая инструкция изменяет следующую за ней команду, то при реальной работе программы это изменение не сказывается на алгоритме, т. к. к этому моменту содержимое изменяемой ячейки памяти уже загружено в очередь команд. Если же программа работает под управлением трассировщика, внутренняя очередь оказывается заполненной командами трассировщика и модификация ячейки приводит к изменению алгоритма работы.
Следующая программа (пример 30.6) иллюстрирует описанный прием. В ассемблерной процедуре TrassBlock модифицируется однобайтная команда RET выхода из процедуры на команду NOP (пустая команда). Если программа управляется трассировщиком, произойдет реальная замена команды и вступит в действие остальная часть процедуры, которая реализует "горячую" перезагрузку DOS (используется недокументированное соглашение о том, что точка входа в BIOS-программу перезапуска имеет адрес $F000:$FFF0; фирмы — изготовители BIOS не обязаны следовать этому правилу). Чтобы убедиться в этом, запустите программу под управлением какого-либо отладчика, например, Turbo Debugger, и проследите ее по шагам после вызова CALL oooo — это и есть вызов процедуры TrassBiock. Более простой способ — пошаговое прослеживание программы в среде Турбо Паскаль с помощью клавиши <F7>, однако в этом случае будет нарушена "чистота эксперимента" — ведь у взломщика нет исходного текста программы.
.•••Л••••••• 4 »Vk ..».".;..'•<•'« •••.••¥ AvliiVrflM^n¥Vr?lrW#» At1i\ •.. • • ••#•#•« < vC rnrm7hm^nRWflmmX wieWwt^R^Rn^wZXWWfWtVfrtWMWiZKvVi
I т
I Иллюстрация использования процедуры \
\ блокировки режима пошаговой I
I трассировки программы|
-
ProcedureAssembler;
реализует вызов программы перезапуска DOS в случае, если она работает под управлением
ASM
команды по метке
mov BYTE PTR
команда неесли нет трассировки,
в противном случае она заменяется командой ret
управление передается только в том
когда работа программы прослеживается
mov
push ax
mov
push ax
retf
end;
begin
TrassBlock
•
точки
точки
{ Перезапуск DOS}
end.
Как еще программа может определить факт трассировки? Пожалуйста:
О с помощью контроля отладочных прерываний INT и INT з;
с помощью замера времени выполнения некоторого эталонного участка профаммы;
с помощью перехвата прерывания от клавиатуры;
□ с помощью перехвата прерывания от таймера.
В архитектуре микропроцессора предусмотрены два отладочных прерывания с векторами 1 и з. Первое активизируется после выполнения каждой очередной инструкции профаммы, если взведен флаг трассировки TF. Второе вызывается однобайтнойи реализует останов в контрольной точке. Сразу
после загрузки DOS эти векторы указывают на расположенные в ПЗУ программы обработки прерываний, которые ничего не делают и просто возвращают управление прерванной программе. Таким образом, если сегментная часть этих векторов указывает на ОЗУ (меньше $АООО), есть большая вероятность того, что программа прослеживается (может оказаться, что векторы изменены какой-то ранее запускавшейся программой, но не восстановлены после ее завершения). Чтобы убедиться в этом, программа может проанализировать флаг TF режима трассировки и/или попытаться заменить эти векторы так, чтобы они указывали на собственную процедуру обработки.
Например, в следующей программе (пример 30.7) имеется небольшой фрагмент, написанный на встроенном ассемблере, чтобы проанализировать флаг трассировки. В результате анализа на экран выводится"Нет
трассировки" или "Есть трассировка". Замечу, что некоторые отладчики, в том числе — встроенный отладчик Турбо Паскаля и мой любимый Turbo Debugger, не используют аппаратной трассировки по прерыванию INT и, следовательно, программа, работающая под их управлением, не обнаруживает факт трассировки. Чтобы это все-таки произошло, трассируйте программу стандартным отладчиком Debug.
''4>^2ЖаЖ^'' -<у. * **1! ••• vv ••"■••: Пример 30.7. Программа, анализирующая флаг трассировки* • ;
И^+
Программа проверяет флаг трассировки \
I TF и выводит соответствующее сообщение \
*;+;
Uses -
var
Flags: Word;флагов
begin
Flags := 0;
asm*
pushfфлаги в
pop axих в AX}
Flags, axв
end;
if (Flags andthen
WriteLn('Нет трассировки')
else
WriteLn('Есть трассировка')
end.
Замер времени выполнения эталонного участка программы во многих случаях оказывается более эффективным средством обнаружения трассировки. Для получения отсчетов времени удобно использовать уже упоминавшийся системный счетчик так, как это сделано в примере 30.8.
i /;........!".Х.^.'... 7Й#» .1.. }...#*..Л....... 5#а ,;......'. ,t.. DK^ffJ9Kto«vliRA?!Sst^«j*ff^t1ff..". к. и .л".^ЛКИДЛДК?УМ1к^11№^^Ж .Л.. а ..... •. У. ^.....• г
•-1
\ Контроль трассировки с помощью замера \
\выполнения участка программы \
var
tl,t2: Longlnt;
begin
t20;
:=начальный момент
whiledo;начала очередного 55-мс
{Цикл измерения)
whiledo
inc(t2);
{Анализируем резуль та т}
if t2<500 then
WriteLn('Есть трассировка')
else
WriteLn(t2)
end.
Проверка
if t2<500
учитывает минимальную производительность компьютера класса IBM PC с тактовой частотой 4,77 МГц (около 780 единиц для этой программы).
Перехват прерывания от клавиатуры использует тот очевидный факт, что для управления трассировщиком взломщик должен использовать клавиатуру. Для обнаружения этого программа переназначает вектор прерывания $09 от клавиатуры на собственную процедуру, после чего выполняет достаточно длинный цикл. Если в ходе выполнения цикла будет нажата любая клавиша, управление получитобработчик прерывания $09 и таким образом
программа сможет обнаружить факт трассировки.
В следующем примере 30.9 процедура обработки прерывания $09 осуществ-
ляет "горячую" перезагрузку DOS.
; Пример 30.9. Перехват прерывания от клавиатуры
• •........ ..<<•............*.-.?■..'..'....;..•.••«...•?.■; Лга Л. .Г.» .T.rt.>..T?wr^sT«........X;..'»7f^W^^^^^WR.^^«.^^l
{ 4--_—,+
I Блокировка трассирования программы |
I с помощью контроля прерывания от \
Iклавиатуры\
т- +}
Uses DOS;
var
Oldlnt9: pointer;
Procedure Int9; Assembler;
{Получаетуправление после нажатия
на любую клавишу и перезагружает DOS}
ASM
mov ax, $FOOO
push ax
mov ax, $FFFO
push ax
retf
end;
var
k,n: Longlnt;
begin
Swapvectors;
GetIntVec($9,01dInt9);
SetlntVec($9,@Int9);
for k := 1 to 20000 do
inc(n);
SetlntVec($9,Oldlnt9);
WriteLn('Нет трассировки')
end.
Приблизительно таким же способом можно использовать прерывание от таймера $16. Перед заменой векторов программа должна запретить прерывания командой CLI, затем выполнить цикл. Если программа не трассируется, управление процедуре обработки прерывания не будет передано ни разу.
Напротив, трассировщик обязательно откроет прерывания и тем самым обнаружит себя.
Модуль Р_Рго1дпя защиты программ
В заключение рассмотрим программный модуль F_Prot, реализующий некоторые из описанных мер борьбы с нелегальным копированием (см. файл F_PROT.PAS на сопровождающей дискете). Разумеется, было бы неразумно в книге, рассчитанной на массового читателя, излагать все подробности сложной системы защиты. Средства, используемые в модуле, вызовут улыбку у профессионалов, тем не менее они достаточны, чтобы отпугнуть многих любителей легкой наживы.
Описание модуля
Модуль рассчитан на защиту как мобильных, так и стационарных программ. Основной процедурой модуля является Protcheck, осуществляющая контроль копии. Легальность мобильного варианта программы устанавливается за счет контроля скрытого сектора на ключевой дискете, в случае стационарного варианта проверяется дата создания ПЗУ. Две другие подпрограммы модуля позволяют установить защиту файла на жестком диске (подпрограмма setonHD) и снять защиту со стационарного варианта программы (подпрограмма RemoveFromHD).
Поскольку до сих пор мы почти не касались разницы в способах защиты мобильного и стационарного вариантов программы, имеет смысл рассмотреть эту проблему более подробно. Как уже говорилось, стационарная программа учитывает индивидуальные характеристики компьютера и может исполняться только на одном конкретном ПК. Защита таких профамм обычно не вызывает серьезных проблем, во всяком случае для этого у программиста есть очень большой выбор индивидуальных признаков (см. разд. 30.2). Наоборот, мобильная профамма не может связываться с конкретным ПК и должна учитывать какие-то привносимые признаки, т. е. признаки, которые относительно просто создать на любом ПК на время работы программы. Здесь возможности намного скромнее: чаще всего для этих целей используются дополнительные аппаратные устройства (ключевая дискета или аппаратный ключ), которые придаются каждой легальной копии и без которых профамма не может работать нормальным образом.\ Если отбросить достаточно экзотичный для отечественного рынка способ защиты с помощью аппаратного ключа, в распоряжении профаммиста остается единственный вариант защиты мобильной профаммы — с помощью эталонной (ключевой) дискеты. Именно этот вариант и реализуется в модуле F_prot.
Создание легальной копии программы осуществляется с помощью особой технологии подготовки дискеты. Эта особенность в данном случае заключается в том, что на стандартной дискете диаметром 5^4 дюймов, рассчитанной на емкость 360 Кбайт, создается дополнительная дорожка из нестандартных 256-байтных секторов. Один из секторов этой дорожки служит для записи ключевой информации. В процессе проверки легальности копии программы процедура Protcheck считывает этот сектор и контролирует его содержимое. Для создания ключа можно использовать программу Diskette, описанную в разд. 30.2.5.
Можно утверждать, что практически в любом случае коммерческого распространения программы необходимо защитить прежде всего ее мобильный вариант. Для этого покупатель программы получает как минимум одну ключевую дискету, гарантирующую нормальную работу программы на любом стандартном ПК (именно с этой целью в модуле используется широко распространенный формат дискет емкостью 360 Кбайт). В то же время жесткая привязка программы к дискете создает вполне понятные неудобства пользователю. Эти неудобства можно устранить, если разрешить пользователю осуществлять самостоятельную установку программы на жесткий диск, т. е. создавать легальные стационарные варианты программы. Чтобы процесс тиражирования был контролируем, каждая установка программы на ЖД осуществляется только при наличии ключевой дискеты, при этом программа ведет подсчет общего количества созданных с ее помощью стационарных копий. Как показывает практика, ограничение количества установок программы на ЖД является очень важным психологическим фактором, заставляющим покупателя крайне бережно относиться к ключевой дискете и не тиражировать программу без действительной надобности.
В модуле F_Prot реализована несложная защита программы от трассировки. С этой целью в ключевых местах проверки легальности предусмотрены простенькие ловушки TrassBlockl и TrassBlock2, обеспечивающие зацикливание программы в случае ее трассировки. Замечу, что процедура TrassBiocki использует модификацию кода программы с последующим его восстановлением в случае, если трассировка не обнаружена. Таким образом, вы можете многократно повторять выполнение этой процедуры без "зависания" программы. Однако она реализована в виде тмыыЕ-макроса, поэтому взломщику придется бороться с ней всякий раз, когда она вызывается (чем чаще вызывается эта процедура, тем больше ее копий в коде программы и тем труднее трассировка программы и ее взлом). Процедура TrassBlock2 контролирует скорость выполнения эталонного участка программы.
Процедура Protcheck вызывается из защищаемой программы всякий раз, когда требуется установить легальность копии. Ее заголовок имеет следующий вид:
Procedure ProtCheck (var Norma, Alarm; var Res: Integer)
В теле процедуры параметры-переменные Norma и Alarm трактуются как параметры процедурного типа
type
ProcType = Procedure;
т. е. являются адресами двух процедур без параметров. Процедура Alarm вызывается в случае, если программа обнаружила признаки нелегального копирования, a Norma — если эти признаки отсутствуют. В переменной Res возвращается результат работы Protcheck:
О — если выполнялась процедура Norma (легальная копия);
О 1 — выполнялась Alarm (нелегальная копия);
2 — не выполнялась ни та, ни другая процедура, т. к. программа не обнаружила дискету в приводе ГД и не смогла проверить легальность копии.
Процедура Protcheck начинает работу с проверки поля Hard в глобальной типизированной константе Key. Это поле используется для анализа стационарности программы: если поле имеет значение о, реализуется контроль мобильного варианта, в противном случае — стационарного варианта (установка поля Hard и всей константы Key осуществляется с помощью функции SetOnHD, см. ниже).
При контроле мобильного варианта программа пытается прочитать сначала на диске A:, а если это не удается — на диске В: ключевой сектор (первый сектор на нулевой поверхности дорожки номер 40) размером 256 байт. Этот сектор должен содержать информацию согласно табл. 30.3.
Таблица 30.3. Назначение байтов ключевого сектора диска
НомерНазначение
байта
Ключ для шифровки содержимого сектора с помощью операции XOR
Количество уже созданных стационарных копий
200Максимальное количество стационарных копий (если 255 — количество
копий не ограничено)
256Контрольная сумма со 2-го по 255-й байт
//
Если поле Hard константы Key содержит ненулевое значение, осуществляется контроль стационарного варианта. В этом случае поле Dat содержит эталон даты создания ПЗУ, а поле Hard является ключом для шифровки поля Dat с помощью операции XOR.
Важно заметить, что если контроль стационарного варианта дает отрицательный результат (нелегальная копия), автоматически осуществляется ана-
лиз мобильного варианта (контроль дискеты). Таким образом, любая копия программы гарантированно работает, если в распоряжении пользователя есть ключевая дискета. Однако после правильной установки программы на жесткий диск с помощью процедуры SetOnHD программа может работать и без этой дискеты до тех пор, пока она не будет перенесена на новый компьютер, дата создания ПЗУ которого отличается от эталонной.
Для правильного создания стационарной копии программы служит функция SetonHD, имеющая следующий заголовок:
Function SetOnHD: Integer;
Перед использованием функции SetonHD необходимо любыми стандартными для ДОС средствами скопировать программу в один из каталогов жесткого диска. Эта функция вызывается в такой "нелегальной" копии, перед этим в любой привод ГД необходимо вставить ключевую дискету со снятой защитой от записи (с незаклеенной прорезью). Результат, возвращаемый функцией, имеет следующий смысл:
-1 — в привод ГД не вставлена дискета;
-2 — в привод вставлена дискета не эталонного типа (не 360 Кбайт или нет скрытого сектора);
-з — дискета защищена от записи или при записи на нее возникла ошибка;
-4 — данный вариант программы не скопирован предварительно на жесткий диск;
-5 — ошибка доступа к жесткому диску (программа не может прочитать собственный файл или не может записать в него новое значение константы Key);
-6 — исчерпан лимит стационарных копий;
-7 — данная программа уже представляет собой стационарный вариант программы, т. е. константа Key в ней уже определена.
Любое неотрицательное значение результата свидетельствует об успешном создании стационарной копии и равно количеству еще не использованных . установок программы на жесткий диск.
В ходе установки программа контролирует ключевую дискету, затем определяет характерные признаки данного ПК и заносит эти признаки в константу Key. Поскольку защищается только тот экземпляр программы, в котором вызвана функция SetonHD, программист должен предусмотреть соответствующий вариант запуска программы. Например, он может проанализировать ключи команды запуска с целью проверки специального ключа создания стационарного варианта или предусмотреть соответствующую опцию в диалоговом меню, создаваемом программой в ходе работы.
Функция RemoveFromHD осуществляет обратные действия: уничтожает текущую стационарную копию и соответствующим образом увеличивает запас неизрасходованных установок программы. Она возвращает одно из следующих значений:
-1 — в привод ГД не вставлена дискета;
-2 — в привод вставлена дискета неэталонного типа (емкостью не 360 Кбайт или нет скрытого сектора);
тз — дискета защищена от записи или при записи на нее возникла ошибка;
-4 — данный вариант программы не скопирован предварительно на жесткий диск;
-5 — ошибка доступа к жесткому диску (программа не может прочитать собственный файл или не может записать в него новое значение кон- стантыKey).
Любое неотрицательное значение свидетельствует об успешном удалении стационарной копии и равно количеству еще не использованных установок программы на жесткий диск.
30.4.2. Тестовая программа
Следующая программа (пример 30.10) иллюстрирует некоторые приемы работы с модулем F_Prot. Программа анализирует ключи запуска: если используется ключ /SET, осуществляется установка программы на жесткий диск, если ключ /REMOVE, уничтожается стационарная копия программы, если этих ключей нет в команде запуска, программа осуществляет контроль легальности копии. Ключевая дискета должна быть предварительно подготовлена с помощью программы Diskette (см. файл DISKETT.PAS на прилагаемой дискете).
ммммм|мммммuju^^ui^^лл^^^шin11м1цл„-м^™—^^^щ|ммлм^^лмммммм|мм|м|м|мм^м^^мм|^^м^^||мм| : Пример 30.10. Проверка легальности копии с помощью установочной дискеты ^I*\wГS№r№гflг!•^^R^^B^^^R^^^^WI^^^^^^^^^^^^^^Ж^^^№^^^^^№?^^^№^^^^^ff№Яm№w^'flлfi11rCW>wV№т^#'йz•*wt'fx^та№vi^5wSiM?SMwЙiФ^bll^У#^v^i,#
| Эта программа проверяет легальность своей \
I КОПИИ за счет контроля ключевой дискеты. \
Uses F_Prot;
Procedure Alarm; Far;
{Получа е т упра вление, если копия нелегальна} begin
WriteLn('Нелегальная копия')
end;
I1
ProcedureFar;
управление в случае легальной копии) begin WriteLn('Легальная копия') end;
{-
Function ParStr: String; параметр вызова (заглавными
var
String;
k: Byte;
begin
S :=
for k1 todo S[k] :=
ParStrS
end;
-I
var
pl,p2: Pointer;
d: Integer;
dsk:
begin
:=
p2 :=
ifthen
на ВД:
else
ifthen
с ВД:
else
begin
ProtCheck(pl,p2,d);
проверки: end
end.
Глава 31Глава 31
Глава 31
Глава 31
Работа в среде Borland Pascal
Одновременно с Турбо Паскалем 7.0 фирма Borland выпустила на рынок более мощную систему программирования Borland Pascal with Objects 7.0. В состав этой системы входят три среды программирования: Borland Pascal for Windows (BPW), Borland Pascal (ВР) и Турбо Паскаль. Среда BPW предназначена для создания программ, работающих под управлением операционной системы Windows, Турбо Паскаль — для работы с MS-DOS, а ВР служит своеобразным мостом между этими системами, позволяя создавать программы как для MS-DOS, так и для Windows. Все программы, созданные с помощью Турбо Паскаля, могут без каких-либо переделок транслироваться средой ВР, но не наоборот, т. к. BP имеет более мощные возможности. Специфика создания программ для Windows такова, что программы Турбо Паскаля либо вовсе не могут использовать возможности этой операционной системы, либо вынуждены прибегать к помощи специальных средств модуля WinCRT для имитации среды MS-DOS.
Особенности среды
Интегрированная среда ВР содержится в файле ВР.ЕХЕ и запускается как и любая другая программа, рассчитанная на работу под управлением MS-DOS, например:
\bp\bin\bp
(в этом примере предполагается стандартное расположение файла ВР.ЕХЕ в подкаталоге BIN каталога ВР на текущем диске). Сразу после запуска среда создает окно, внешне практически не отличающееся от окна среды TURBO.EXE.
Отличия в меню
Главными отличиями среды ВР от Турбо Паскаля являются:
О возможность выбора типа создаваемых программ (будет ли программа работать под управлением Windows, MS-DOS или она будет работать в защищенном режиме процессора);
включение в ее меню средств работы с браузером — утилитой просмотра иерахических стурктур и поиска идентификаторов. С помощью браузера можно просмотреть иерархию (подчиненность) объектов и список модулей программы, просмотреть объявления в ней всех глобальных символов — процедур, функций, типов, констант и переменных, а при необходимости — получить список тех операторов программы, которые ссылаются на символ, и т. д.
Эти особенности следующим образом отражены в всплывающих меню:
в опции меню Search добавлены средства вызова браузера (подопции Previous browser — просмотр результата предыдущего обращения к браузеру; Objects — просмотр иерархии объектов; Units — просмотр используемых модулей; Globals — просмотр всех глобальных символов; Symbol — поиск конкретного символа в программе);
в опции Compile вместо подопции Destination, управляющей размещением исполняемой программы в среде Турбо Паскаля (в оперативной памяти или на диске) используется подопция Target, с помощью которой можно выбрать тип программы (Windows, MS-DOS или защищенный режим);
О в опции Tools к утилите Grep добавлены вызовы компилятора Turbo Assembler, отладчика Turbo Debugger и средства наблюдения за исполняемыми программами Turbo Profiler;
О в опции Options добавлена подопция Browser, с помощью которой можно настроить браузер.
Замечу, что после выбора типа программы, отличного от MS-DOS, в меню исчезнут или станут запрещенными для выбора все опции встроенного отладчика, т. к. отладка программ для защищенного режима и для Windows может проходить только под управлением отладчика Turbo Debugger, входящего в комплект поставки Borland Pascal with Objects 7.0.
Использование защищенного режима
Защищенный режим работы процессоров 80x86 — это особое состояние процессора, в котором ему доступны команды, не используемые в обычном, незащищенном режиме. В защищенном режиме процессор применяет другую схему адресации (см. разд. 31.2.1), что дает ему возможность адресовать память практически неограниченного размера.
Среда ВР не только позволяет создавать программы, рассчитанные на защищенный режим работы процессора, но и сама использует этот режим для доступа к дополнительной памяти, в которой она размещает таблицы компилятора и компоновщика. Это дает возможность создавать крупные программные проекты непосредственно в среде ВР без помощи внешнего компилятора В PC. EXE.
Работа с браузером
Чтобы браузер смог нормально работать с вашей программой, она должна быть откомпилирована в режимет. е. с подключением к ней
всей отладочной информации. Поместите указанную директиву компилятора в начало вашей программы или убедитесь в том, что активны (отмечены крестиком) опции Debug Information, Local symbols и Symbol information диалогового окна Options | Compiler, и откомпилируйте программу перед тем, как обратиться к браузеру.
Просмотр объектов
Для просмотра иерархии объектов (если, разумеется, программа использует ООП) следует вызвать меню Search | Objects. На экране появится дерево наследования всех объектов программы. Если дважды щелкнуть мышью на каком-либо объекте или выделить объект указателем и нажать <Enter>, раскроется дополнительное окно, в котором можно просмотреть информацию об объекте: если щелкнуть по кнопке S в верхней строке окна или нажать клавишу <S>, в окне будут перечислены все дополнительные поля и дополнительные и перекрытые методы объекта; если щелкнуть по I или нажать клавишу— будет показан непосредственный родитель объекта; если
щелкнуть (нажать) R — в окне появится список всех строк исходной программы, в которых есть ссылки на объект. Для перехода к тексту программы необходимо либо дважды щелкнуть по объекту, полю или методу мышью при нажатой и удерживаемой клавише <Ctrl>, либо выделить нужную строку окна указателем и нажать
Просмотр модулей
Для просмотра модулей необходимо обратиться к опции Search | Units. Браузер покажет список всех используемых в программе модулей в алфавитном порядке. После двойного щелчка по имени модуля раскроется окно с перечислением всех объявленных в модуле глобальных определений. Двойной щелчок с удерживаемой <Ctrl> покажет место в программе, где есть ссылка на модуль или его интерфейсный объект.
Глобальные символы
Для показа всех глобальных символов в программе и во всех ее модулях предназначена опция Search | Globals. Символы перечисляются в алфавитном порядке с указанием их типа (переменная, константа, метка, функция и т. д.). Двойной щелчок на любом символе показывает более подробную информацию о
Для символов стандартных модулей, откомпилированных без отладочной инфор-
мации, получить детали
Поиск символа
Эту возможность удобно использовать для быстрого поиска в тексте крупной программы тех или иных подпрограмм. Для этого установите текстовый курсор на имени процедуры или функции и вызовите опцию Search | Symbols. В строке ввода диалогового окна опции будет показано имя и для поиска текста подпрограммы теперь достаточно нажать <Enter>.
Особенности защищенного режима
Сразу после включения ПК процессор начинает работать в обычном режиме, который называется реальным. В этом режиме он способен адресовать оперативную память в пределах первого мегабайта и никак не вмешивается в работу прикладной программы, позволяя ей использовать все системные ресурсы по своему усмотрению. В реальном режиме могут работать любые процессоры в IBM-совместимых ПК. В отличие от этого, защищенный режим возможен только в процессорах 80286 и более поздних — вплоть до Pentium. В этом режиме процессор осуществляет аппаратную защиту системных областей памяти (отсюда — название режима) и применяет иную, чем в реальном режиме, схему адресации, позволяющую программам использовать всю имеющуюся в ПК оперативную память. В дальнейшем я буду описывать особенности защищенного режима применительно к современным процессорам 80386, 80486 и Pentium и при необходимости упоминать об устаревших процессорах 80286, чьи возможности гораздо скромнее.
К сожалению, MS-DOS не поддерживает защищенный режим. Однако прикладные программы, стартующие под ее управлением, могут перевести процессор в этот режим и использовать все его возможности, в том числе: □ адресацию памяти до 4 Гбайт (для 80286 — до 16 Мбайт);
О аппаратную поддержку параллельно выполняемых программ (многозадачность);
□ аппаратную поддержку так называемой виртуальной памяти, которая подобна оперативной памяти, но может существенно ее превышать (эта возможность отсутствует у процессора 80286).
Адресация памяти
Для адресации памяти в реальном режиме процессор использует два регистра, в одном из которых хранится сегмент, а в другом — смещение. Их совокупность определяет логический адрес памяти. Физический адрес получается сдвигом содержимого регистра сегмента на 4 разряда влево и сложением полученного с содержимым регистра смещения. Такая схема реализуется
аппаратно и дает 20-разрядный физический адрес в пределах первого мегабайта оперативной памяти.
В защищенном режиме также используются два регистра для логического адреса: селектор и смещение. Регистр селектора не сдвигается влево, как в реальном режиме, а указывает на один из элементов так называемой таблицы дескрипторов. В отличие от реального режима регистр смещения содержит 32 разряда, т. к. в защищенном режиме сегмент может быть любого размера вплоть до 4 Гбайт (в процессоре 80286 применяется 16-разрядный регистр смещения и поэтому размер сегментов в нем по-прежнему составляет 64 Кбайт). Совместное использование выбираемого селектором дескриптора и смещения в конечном счете формирует физический адрес (рис. 31.1).
Логический адресЛогический адресЛогический адресЛогический адресisis161616163232IСелектор I СмещениеIСелектор I Смещение2020323216л16л1616| Доступ | Базовыйадрес | Предел || Доступ | Базовыйадрес | Предел || Резерв | Доступ | Базовыйадрес | Предел || Резерв | Доступ | Базовыйадрес | Предел || Селектор | Смещение| Селектор | СмещениеФизический адрес24Физический адрес24Таблица дескрипторовТаблица дескрипторовббРис. 31.1. Адресация в защищенном режиме: а — в процессорах 80386/80486; б — в процессорах 80286Рис. 31.1. Адресация в защищенном режиме: а — в процессорах 80386/80486; б — в процессорах 80286ТаблицадескрипторовТаблицадескрипторовМеханизм трансляциистраницМеханизм трансляциистраницФизический адресФизический адрес
Логический адрес
Логический адрес
Логический адрес
Логический адрес
is
is
16
16
16
16
32
32
IСелектор I Смещение
IСелектор I Смещение
20
20
32
32
16л
16л
16
16
| Доступ | Базовыйадрес | Предел |
| Доступ | Базовыйадрес | Предел |
| Резерв | Доступ | Базовыйадрес | Предел |
| Резерв | Доступ | Базовыйадрес | Предел |
| Селектор | Смещение
| Селектор | Смещение
Физический адрес
24
Физический адрес
24
Таблица дескрипторов
Таблица дескрипторов
б
б
Рис. 31.1. Адресация в защищенном режиме: а — в процессорах 80386/80486; б — в процессорах 80286
Рис. 31.1. Адресация в защищенном режиме: а — в процессорах 80386/80486; б — в процессорах 80286
Таблицадескрипторов
Таблицадескрипторов
Механизм трансляциистраниц
Механизм трансляциистраниц
Физический адрес
Физический адрес
Важную роль в этой схеме играет таблица дескрипторов, 8-байтные элементы которой хранят три поля: базовый адрес, предел и поле доступа. Базовый адрес используется для получения физического адреса, предел содержит размер сегмента в байтах или страницах по 4096 байт, а поле доступа — ряд признаков, определяющих способ использования сегмента. В защищенном режиме сегменты могут быть либо сегментами кода, либо сегментами дан-
ных. В сегмент кода нельзя записывать информацию, а в сегменте данных нельзя исполнять программу или какую-либо ее часть. Тип сегмента хранится в поле доступа и, если программа нарушила правила доступа к сегменту (например, попыталась изменить собственный код), возникает аппаратное прерывание, которое обычно приводит к выгрузке программы из памяти. Другим признаком, хранящимся в поле доступа, является уровень привилегий, которыми обладает владеющая сегментом программа. Для этого признака выделено 2 бита, поэтому в защищенном режиме есть 4 уровня привилегий, причем нулевой уровень — высший.
. . .,
Многозадачность
Программы с нулевым уровнем привилегий используют все возможности процессора, в том числе они способны изменять содержимое таблицы дескрипторов и переводить процессор обратно в реальный режим. Обычно этот уровень назначается наиболее важным программам операционной системы. Прикладные программы, как правило, используют низший, третий уровень привилегий. Таким образом, в защищенном режиме на аппаратном уровне реализован механизм регулируемого доступа к ресурсам компьютера и прежде всего — к памяти. Этот механизм обеспечивается привилегированностью программы, т. е. теми "полномочиями", которыми наделила программу операционная система.
Поскольку обычно прикладная программа имеет лишь минимальный уровень привилегий, она не в состоянии изменить текущего распределения ресурсов и вынуждена обращаться за помощью к операционной системе. В защищенном режиме одновременно в памяти должны присутствовать как минимум две программы: операционная система (точнее, ее наиболее важная часть, называемая резидентным ядром) и прикладная программа. Это и есть многозадачность. Конечно, можно написать программу, которая будет совмещать в себе обе функции и, следовательно, монопольно владеть всеми ресурсами. Однако на практике это не делается, т. к. операционные системы и специальные расширители DOS, работающие в защищенном режиме, берут на себя массу черновой работы, избавляя программиста от множества ненужных хлопот.
Таким образом, важной отличительной особенностью защищенного режима является то, что прикладная программа уже не владеет всеми ресурсами процессора монопольно, как это было в реальном режиме. В частности, чтобы обратиться к тому или иному порту ввода/вывода или получить в свое распоряжение память, она должна обратиться к ядру операционной
системы.
Как уже отмечалось, среда ВР готовит профаммы для работы под управлением трех типов операционных систем: Windows, MS-DOS и так называемого расширителя DOS. Последний представляет собой набор профамм, обеспечивающих интерфейс высокого уровня для профаммы, работающей в защищенном режиме.
Виртуальная память и перемещение сегментов
В описывающих сегменты дескрипторах имеются признаки того, находится ли сегмент в данный момент в оперативной памяти. Если профамма обращается к сегменту, который не загружен в память, возникает аппаратное прерывание и управление передается резидентному ядру операционной системы, которая может зафузить образ этого сегмента с диска. Так реализуется механизм виртуальной памяти. Виртуальная память работает подобно оверлею и делает ненужным его использование в защищенном режиме.
При зафузке дискового сегмента ОС проверяет наличие свободного пространства памяти. Если памяти недостаточно, ОС выгружает часть сегментов на диск и/или перемещает сегменты так, чтобы освободить требуемый объем. При перемещении сегмента соответствующим образом изменяется связанный с ним дескриптор, однако владеющая сегментом профамма обычно не замечает этого изменения, т. к. доступ к сегменту она осуществляет с помощью селектора, указывающего на нужный дескриптор, а содержимое селектора не меняется. Если профамма хочет получить доступ к конкретному физическому просфанству памяти, она должна предварительно дать знать об этом ОС, которая в этом случае помечает сегмент как неперемещаемый. Такая процедура называется фиксацией сегмента. Фиксация обычно необходима при работе с кучей, т. к. только в этом случае связанный с кучей указатель может служить в качестве физического адреса. После завершения обмена данными с сегментом профамма должна снять фиксацию (разблокировать) сегмент, чтобы ОС смогла при - необходимости его переместить или выгрузить на диск.
Глава 32
Программирование
для защищенного режима
Расширитель ДОС
Программы для защищенного режима, создаваемые средой ВР, работают под управлением расширителя ДОС, который называется RTM (Run-Time Manager — администратор периода исполнения программы). Расширитель ДОС автоматически пристыковывается к программе и первым получает управление после ее загрузки. Он переводит процессор в защищенный режим работы и предоставляет программисту богатый набор возможностей этого режима с помощью процедур и функций, реализованных в модуле При попытке программы завершить свою работу с помощью обращения к функции $4С прерываниярасширитель переводит процессор в
реальный режим и передает управление ДОС.
Расширитель ДОС является посредником между программой и так называемым сервером DPMI, который, собственно, и реализует все запросы программы (рис. 32.1). Специально для целей работы в защищенном режиме разработан промышленный стандарт DPMIMode Interface —
интерфейс для защищенного режима ДОС). В этом стандарте определен набор системных прерываний, с помощью которых программа удовлетворяет все свои запросы в защищенном режиме. ВР использует сервер DPMI (файл DPMI16BI.OVL), основанный на версии 0.9 стандарта, и дополняет его возможности по части вызова программных прерываний ДОС и BIOS.
Интерфейс DPMI документирован и программа при желании может непосредственно обращаться к серверу DPMI с помощью программного прерывания $31. Однако низкоуровневое программирование доступа к серверу менее эффективно, чем использование функций winAPi, и в этой книге не рассматривается.
MS-ДОС
Рис. 32.1. Взаимодействие программыс MS-DOS в защищенном режиме
Модуль WinApi
Расширитель RTM с помощью средств модуля WinAPI реализует подмножество интерфейса Windows для прикладных программ. Это означает, что некоторые возможности, используемые Windows-программами, становятся доступными также программе в защищенном режиме. Прежде всего это касается так называемых динамически подключаемых библиотек DLL (Dynamic-Link Library). DLL-библиотеки подобно TPU-файлам хранят откомпилированные и готовые к использованию подпрограммы, но в отличие от модулей Турбо Паскаля они не связываются компоновщиком с телом программы, а существуют в виде отдельных дисковых файлов. При обращении к той или иной подпрограмме DLL расширитель проверяет ее наличие в оперативной памяти и при необходимости считывает библиотеку с диска. Отработавшая библиотека может удаляться из оперативной памяти, чтобы освободить место другой библиотеке или загружаемому с диска сегменту. Интерфейс взаимодействия программы с библиотекой в точности соответствует принятому в Windows, что, с одной стороны, открывает программе доступ к уже существующим в Windows библиотекам, а с другой стороны, позволяет программисту готовить собственные DLL-библиотеки для использования в защищенном режиме и/или в Windows (техника создания DLL- библиотек обсуждается в разд. 32.3).
Другой особенностью модуля winAPi является включение в него Windows- средств работы с ресурсами. Ресурсы — это специальным образом откомпилированные двоичные файлы, которые содержат данные и которые на этапе компоновки пристыковываются к исполняемому файлу программы. Обычно в ресурсах хранятся различного рода изображения (пиктограммы, курсоры, рисунки и т. п.), используемые Windows-программой для создания графиче-
ского экрана. При работе в защищенном режиме программе недоступны мощные графические возможности Windows и поэтому нет смысла хранить в ресурсах графические образы. В связи с этим в WinAPI включены ограниченные средства доступа к ресурсам: с их помощью можно получить только строковые ресурсы и ресурсы, определяемые пользователем.
Многие из рассматриваемых ниже подпрограмм работают с так называемым экземпляром программы — специальным дескриптором (instance), связанным с текущей программой. Необходимость в этом дескрипторе вытекает из многозадачности Windows, под управлением которой одновременно может выполняться несколько программ, в том числе — несколько копий одной и той же программы. Экземпляр программы определяет, является ли данная программа единственной или в оперативной памяти существуют ее копии. В последнем случае Windows не загружает в память сегменты кода программы, а предоставляет копии только новые сегменты данных. Кроме того, копии используют ресурсы первого экземпляра в режиме разделяемой библиотеки. При работе в защищенном режиме расширитель RTM создает для программы дескприптор экземпляра и помещает его в переменную hlnstance системного модуля System.
И последнее замечание, прежде чем мы перейдем к рассмотрению средств модуля winAPi. Windows-программа имеет доступ к двум кучам — к локальной и глобальной. Локальная куча имеет небольшой размер и принадлежит только данному экземпляру программы. Глобальная куча — это вся свободная в данный момент память. В защищенном режиме нет локальной кучи, поэтому модуль winAPi содержит средства работы только с глобальной кучей (фуНКЦИИ GlobalXXXX).
В табл. 32.1 перечислены процедуры и функции модуля WinAPI.
Таблица 32.1. Назначение процедур и функций W in API
Имя
Назначение
Управление памятью
Get Free Space
Возвращает общее количество свободной памяти
GlobalAlloc
Резервирует блок памяти
GlobalAllocPtr
Резервирует память и возвращает ее физический адрес
GlobalCompact
Реорганизует память так, чтобы обеспечить непрерывное свободное пространство заданного размера
GlobalDiscard
Выгружает блок памяти на диск
GlobalDosAlloc
Распределяет память в первом мегабайте
GlobalDosFree
Освобождает память, полученную с помощью GetDosAlloc
Global Fix
Фиксирует блок памяти
28 Зак. 941
Таблица 32.1 (продолжение)
Имя
Назначение
Управление памятью
GlobalFlags
Возвращает флаги и счетчик блокировок блока памяти
GlobalFree
Освобождает блок памяти
GlobalFreePtr
Освобождает блок, выделенный с помощью GlobalAllocPtr
GlobalHandle
Возвращает дескриптор блока памяти по его адресу
GlobalLock
Фиксирует блок памяти, связанный с дескриптором
GlobalLockPtr
Фиксирует блок памяти, связанный с указателем
GlobalLRUNewest
Делает блок выгружаемым в последнюю очередь
GlobalLRUOldest
Делает блок выгружаемым в первую очередь
GlobalNotify
Устанавливает процедуру оповещения программы о выгрузке блока памяти
GlobalPageLock
Фиксирует страницу памяти ;
GlobalPtrHandle
Возвращает дескриптор блока по указателю на него
GlobalReAlloc
Перераспределяет блок памяти
GlobalReAllocPtr
Перераспределяет блок, выделенный с помощью GlobalAllocPtr
GlobalSize
Возвращает размер блока памяти
GlobalUnfix
Исключает блок из линейного адресного пространства и делает его перемещаемым
GlobalUnlock
Отменяет фиксацию блока, связанного с дескриптором
LockSegment
Фиксирует сегмент
UnLockSegment
Разблокирует сегмент
Управление селекторами
AllocDStoCSAlias
Преобразует селектор сегмента данных к селектору кодового сегмента .
AllocSelector
Создает новый селектор
Changeselector
Изменяет тип селектора
FreeSelector
Очищает селектор
GetSelectorBase
Возвращает базовый адрес селектора
GetSelectorLimit
Возвращает предел селектора
PrestoChangoSelector
Изменяет тип селектора
Таблица32.1 (окончание)
Имя
Назначение
Управление селекторами
SetSelectorBase
Устанавливает новый базовый адрес селектора
SetSelectorLim.it
Устанавливает новый предел селектора
Управление модулями
FreeLibrary
Освобождает динамическую библиотеку
GetModuleFileName
Возвращает маршрут поиска и полное имя выполняемого файла загруженной программы (модуля)
GetModuleHandle
Возвращает дескриптор загружаемого модуля
GetModuleUsage
Возвращает счетчик вызовов модуля
GetProcAddress
Возвращает точку входа в процедуру модуля
LoadLibra'ry
Загружает DLL-библиотеку
Управлениересурсами
AccessResource
Открывает доступ к ресурсу
FindResource
Ищет ресурс
FreeResource
Освобождает ресурс
LoadResource
Загружает ресурс пользователя
Loadstring
Загружает строковый ресурс
LockResource
Фиксирует ресурс
SizeOfResource
Возвращает размер ресурса
Функции разного назначения
D0S3Call
Реализует доступ к прерыванию ДОС
FatalExit
Посылает состояние программы отладчику (используется в отладочном режиме)
GetDosEnvironment
Передает программе переменные окружения ДОС
GetWinFlags
Возвращает конфигурацию памяти
MessageBox
Выводит текстовое сообщение и ждет выбора пользователем одного из вариантов продолжения
Управление памятью
В описании процедур и функций, связанных с управлением памятью, используется понятие "дескриптор" (ему соответствует тип THandle). Для защищенного режима дескриптор совпадает с селектором, для Windows это не так.
Функция GetFreeSpace. Заголовок:
Function GetFreeSpace(Flags: Word): Longlnt;
Возвращает размер свободной памяти в байтах. Параметр Flags игнорируется и может быть любым.
Функция GlobalAlloc. Заголовок:
Function GlobalAlloc(Flags: Word; Bytes: Longlnt): THandle;
Резервирует в памяти блок нужного размера Bytes и возвращает его дескриптор или о, если нет памяти. Параметр Flags указывает тип блока и задается комбинацией флагов, перечисленных в табл. 32.2.
Таблица 32.2. Назначение флагов функции GiobaiAiioc
Флаг
Смысл
gHND
Комбинация флагов gmem_Moveable И gmem_ZeroInit
gmem_DDEShare
Определяет разделяемую память. Используется в операциях динамического обмена данными DDE и эквивалентен gmem_Shared
gmem_Di s ca rdab1e
Определяет выгружаемый на диск блок. Этот флаг не может комбинироваться С gmem_Moveable
gmem_Fixed
Определяет фиксированный блок. Этот флаг не может комбинироваться С gmem_Moveable
gmem_Lower
Этот флаг игнорируется
gmem_Moveable
Определяет перемещаемый блок
gmem_NoCompact
Этот блок не выгружается и не участвует в реструктуризации памяти, выполняемой функцией GlobalCompact
gmem_NoDiscard
Определяет невыгружаемый блок
gmem_Notify
Требует оповещения программы при выгрузке блока на диск
gmem_Share
Определяет блок, который будет использоваться несколькими программами
gmem_ZeroInlt
Этот блок при распределении в памяти заполняется нулями
gPTR
Комбинация флагов gmem_Fixed и gmem_ZeroInit
Функция GlobalAllocPtr. Заголовок:
Function GlobalAllocPtr(Flags: Word; Bytes: Longlnt): Pointer;
Резервирует блок памяти и возвращает указатель на него. Эквивалент вызовов GiobaiAiioc И GlobalLock.
Функция GlobalCompact. Заголовок:
Function GlobalCompact(MinFree: Longlnt): Longlnt;
Реструктурирует память так, чтобы обеспечить не менее MinFree байт свободного непрерывного пространства (если это возможно), и возвращает наибольшее непрерывное пространство в байтах. Если MinFree = о, функция не выгружает блоки на диск и возвращает размер непрерывной области, которая получится в результате выгрузки. Во всех случаях функция вначале перемещает блоки с флагом gmem_Moveable. Если после этого нет MinFreeOO байт непрерывного пространства, она выгружает блоки с флагом gmem_Discardable.
Функция GlobalDiscard. Заголовок:
Function GlobalDiscard(Mem: THandle): THandle;
Выгружает на диск блок памяти, связанный с дескриптором мет, и возвращает дескриптор блока, если операция прошла успешно, или NIL, если закончилась неудачей.
Функция GlobalDosAlloc. Заголовок:
Function GlobalDosAlloc(Size: Longlnt): Longlnt;
Распределяет память размером size байт в первом мегабайте памяти. Возвращаемое значение содержит сегмент в старшем слове и селектор в младшем. Программа может использовать сегмент для доступа к памяти в реальном режиме и селектор — в защищенном. Если расширитель ДОС не может выделить память, функция возвращает о.
Функция GlobalDosFree. Заголовок:
Function GlobalDosFree(Selector: Word): Word;
Освобождает память, выделенную с помощью GiobaiDosAiioc. selector — селектор блока памяти. Возвращает селектор блока, если операция прошла успешно, или о, если неудачно.
Процедура GlobalFix. Заголовок:
Procedure GlobalFix(Mem: THandle);
Делает блок, связанный с дескриптором мет, неперемещаемым. Фиксированные блоки препятствуют эффективной работе менеджера памяти, поэтому следует разблокировать блок с помощью GlobalUnFix сразу, как только это станет возможным.
Функция GlobalFlags. Заголовок:
Function GlobalFlags(Mem: THandle): Word;
Возвращает информацию о блоке, связанном с дескриптором мет: флаги памяти в старшем байте и счетчик блокировок — в младшем. Для тестиро-
вания флагов следует использовать маски из флагов gmem_Shared, gmem_Discard И gmem_Discarded (последний флаг означает, ЧТО блок выгружен на диск, остальные см. GlobalAlloc). Значение счетчика блокировок МОЖНО ПОЛУЧИТЬ С ПОМОЩЬЮ маски gmem_LockCount.
Функция GlobalFree. Заголовок:
Function GlobalFree(Mem: THandle): THandle;
Освобождает глобальный блок памяти, связанный с дескриптором мет. Возвращает десриптор блока, если операция прошла успешно, или ноль — в противном случае.
Функция GobalFreePtr. Заголовок:
Function GobalFreePtr(P: Pointer): THandle;
Освобождает блок памяти, созданный с помощью обращения к GlobalAllocPtr.
Функция GlobalHandle. Заголовок:
Function GlobalHandle(Mem: Word): Longlnt;
Возвращает селектор (в старшем слове) и дескриптор (в младшем) блока по его селектору мет или ноль, если с блоком не связан дескриптор.
Функция GlobalLock. Заголовок:
Function GlobalLock(Mem: THandle): Pointer;
Фиксирует блок в памяти и возвращает его физический адрес. Функция увеличивает на единицу счетчик блокировок блока. Блок остается зафиксированным до тех пор, пока счетчик не станет равен нулю.
Функция GlobaiLockPtr. Заголовок:
Function GlobalLockPtr(P: Pointer): Pointer;
Аналогична GlobaiLock, но использует указатель вместо дескриптора.
Функция GlobalLRUNewest. Заголовок:
Function GlobalLRUNewest(Mem: THandle): THandle;
Делает блок, связанный с дескриптором мет, самым "свежим" и тем самым уменьшает вероятность его выгрузки на диск.
Функция GlobalLRUOldest. Заголовок:
Function GlobalLRUOldest(Mem: THandle): THandle;
Делает блок, связанный с дескриптором Mem, самым "старым" и тем самым увеличивает вероятность его выгрузки на диск.
Процедура GlobalNotify. Заголовок:
Procedure GlobalNotify(NotifyProc: TFarProc);.
Устанавливает процедуру оповещения программы: NotifyProc — адрес процедуры, которая будет вызыватся всякий раз перед выгрузкой на диск блока, Помеченного флаГОМ gmem_Notify.
Функция GlobalPageLock. Заголовок:
Function GlobalPageLock(Selector: THandle): Word;
Увеличивает на единицу счетчик блокировок страниц памяти, связанной с селектором Selector, и. возвращает новое значение счетчика. Для каждого вызова этой функции программа должна обеспечить вызов функции GlobalPageUnlock, которая осуществляет обратные действия. Программа должна использовать эту функцию только при крайней необходимости.
ФунКЦИЯ GlobalPageUnLock. Заголовок:
Function GlobalPageUnLock(Selector: THandle): Word;
Уменьшает на единицу счетчик блокировок страниц памяти, связанной с селектором selector, и возвращает новое значение счетчика.
Функция GlobalPtrHandle. Заголовок:
Function GlobalPtrHandle (P: Pointer) : THandle;
По заданному адресу блока возвращает его дескриптор.
Функция GlobalReAlloc. Заголовок:
Function GlobalReAlloc(Mem: THandle; dwSize: Longlnt;
Flags: Word): THandle;
Перераспределяет блок памяти, устанавливая для него новый размер и флаги. Возвращает дескриптор блока, если операция выполнена, или ноль — в противном случае.
Функция GlobalReAllocPtr. Заголовок:
Function GlobalReAllocPtr(P: Pointer; Size: Longlnt;
Flags: Word): Pointer;
Разблокирует, перераспределяет и вновь блокирует связанный с указателем р блок памяти.
ФункцияGlobalSize.Заголовок:
Function GlobalSize(Mem: THandle): Longlnt;
Возвращает размер в байтах блока памяти, связанного с дескриптором мет.
Функция GlobalUnfix. Заголовок:
Function GlobalUnfix(Mem: THandle): Boolean;
Уменьшает на единицу счетчик блокировок блока, связанного с дескриптором мет. Если счетчик стал равен нулю, блок можно перемещать.
Функция GlobalUnlock. Заголовок:
Function GlobalUnlock(Mem: THandle): Boolean;
Уменьшает на единицу счетчик блокировок блока, связанного с дескриптором мет. Если счетчик стал равен нулю, блок можно перемещать и/или выгружать.
Функция Locksegment. Заголовок:
Function LockSegment(Segment: Word): THandle;
Фиксирует сегмент segment и возвращает сегмент данных, если функция выполнена, или ноль, если сегмент выгружен на диск или обнаружена ошибка. Если segment = -1, фиксируется текущий сегмент данных.
Функция SegmentUnlock. Заголовок:
Function SegmentUnlock(Segment: Word): THandle;
• \
Уменьшает на единицу счетчик блокировок сегмента segment. Результат, возвращаемый функцией, должен игнорироваться. .
Управление селекторами
Функция AllocDStoCSAlias. Заголовок:
Function AllocDStoCSAlias(Selector: Word): Word;
Преобразует селектор данных selector к селектору кода и возвращает его. Эта функция позволяет программе готовить в сегменте данных исполняемый код и, после вызова функции, исполнить его.
ФункцияAllocSelector. Заголовок:
FunctionAllocSelector(Selector: Word): Word;
Создает новый селектор. Если параметр Selector — правильный селектор, функция создает точную его копию, если параметр равен нулю, — создает новый, неинициализированный селектор.
Функция FreeSelector. Заголовок:
Function FreeSelector(Selector: Word): Word;
Освобождает селектор selector и делает его непригодным для дальнейшего использования. Размер связанного с селектором сегмента не должен превышать 64 Кбайт, в противном случае могут быть разрушены селекторы, которые еще нужны программе.
•• 1
Управление модулями
Процедура FreeLibrary. Заголовок:
Procedure FreeLibrary(Lib: THandle);
Уменьшает на единицу счетчик ссылок библиотеки, связанной с дескриптором Lib.
Функция GetModuleHandle. Заголовок:
Function GetModuleHandle(FileName: PChar): THandle;
Создает и возвращает дескриптор файла, содержащего исполняемую программу или библиотеку. FileName — ASCIIZ-строка с именем файла.
ФуНКЦИЯ GetModuleFileName. Заголовок:
Function GetModuleFileName(Module: THandle; FileName: PChar; Size: Integer) : Integer;
Возвращает в переменной FileName полное имя файла (с маршрутом поиска), связанного с дескриптором Module. size — размер переменной FileName. Возвращает длину скопированного в FileName имени, если функция выполнена успешно, или о, если возникла ошибка.
ФуНКЦИЯ GetModuleUsage. Заголовок:
Function GetModuleUsage(Module: THandle): Integer;
Возвращает счетчик ссылок для модуля, связанного с дескриптором Module. Счетчик ссылок увеличивается на единицу при каждом вызове LoadModule и уменьшается на единицу при вызове FreeModule.
ФуНКЦИЯ GetModuleAddress. Заголовок:
Function GetModuleAddress(Module: THandle; ProcName: PChar): TFarProc;
В модуле, связанном с дескриптором Handle, ищет процедуру ProcName и возвращает адрес ее точки входа или NIL, если процедура не найдена. Параметр ProcName может содержать либо ASCIIZ-строку с именем процедуры, либо ее порядковый номер. В последнем случае порядковый номер помещается в младшее слово ProcName, а старшее должно содержать о.
Функция LoadLibrary. Заголовок:
Function LoadLibrary(LibFileName: PChar): THandle;
Загружает файл DLL-библиотеки с именем LibFileName и возвращает его дескриптор. Функция ищет файл в следующем порядке:
в текущем каталоге;
П в каталоге Windows;
П в системном каталоге Windows;
П в каталоге, из которого была запущена программа;
в каталогах, определяемых переменной Path окружения ДОС;
П в сетевых каталогах.
Если значение, возвращаемое функцией, меньше Mnstance_Error, оно определяет одну из следующих ошибок (табл. 32.3).
Таблица 32.3. Коды ошибок функции LoadLibrary
Значение
Смысл
О
Системе не хватает памяти для загрузки или файл поврежден и не читается
2
Файл не найден
3
Не найден маршрут поиска файла
5
Попытка доступа к файлу из динамически связанной задачи или ошибка разделения сетевого файла
6
Библиотека требует отдельных сегментов данных для каждой задачи
8
Не хватает памяти
10
Неверная версия Windows
11
I
Исполняемый файл испорчен или не соответствует формату исполняемого файла Windows
12
Библиотечный файл создан для другой операционной системы
13
Библиотечный файл создан для MS-DOS 4.0
14
Неизвестный тип исполняемого файла
15
Попытка загрузить файл, рассчитанный на реальный режим работы Windows ранних версий
16
Попытка загрузить второй экземпляр библиотеки, которая загружена не в режиме "только для чтения"
19
Попытка загрузить сжатый исполняемый файл
20
Неправильный файл DLL-библиотеки
21
Библиотека рассчитана на 32-разрядную среду
При вызове функции она вначале проверяет, была ли уже загружена требуемая библиотека. Каждый вызов функции LoadLibrary наращивает на единицу счетчик ссылок библиотеки, а каждый вызов функции FreeLibrary уменьшает на единицу этот счетчик. •
Управление ресурсами
Функция AccessResource. Заголовок:
Function AccessResource(Instance, Reslnfo: THandler): Integer;
Здесь:
instance — экземпляр программы (модуля);
Reslnfo— дескриптор ресурса.
Функция открывает исполняемый файл программы (модуля), заданной дескриптором instance, и перемещает указатель файла на начало нужного ресурса. Дескриптор Resinfo должен быть предварительно создан с помощью функции FindResource. Возвращает о, если операция прошла успешно, или -1, если это не так.
После использования ресурса программа должна закрыть файл с помощью вызова функции _lClose. На практике доступ к ресурсу чаще осуществляется С ПОМОЩЬЮ функций LoadResource И LockResource.
Функция FindResource. Заголовок:
Function FindResource(Instance: THandle;
ResName, ResType: PChar): THandle;
Для экземпляра программы instance ищет ресурс с именем ResName и типом ResType. Возвращает дескриптор ресурса, если ресурс найден, или о — в противном случае. Для указания типа ресурса предусмотрены константы, перечисленные в табл. 32.4.
Таблица 32.4. Типы ресурсов и соответствующие им константы
Константа
Тип ресурса
rt_Acselerator
Клавиша быстрого выбора
rt_BitMap
Растровое изображение
rt_Cursor
Указатель мыши
rt_Dialog
Диалоговое окно
rt_Font
Шрифт
rt_Menu
Меню
rt_Icon
Пиктограмма
rt_RCData
Ресурс пользователя
rt_String
Строковый ресурс
Для защищенного режима доступны только строковые ресурсы и ресурсы пользователя.
В параметрах ResName и ResType могут передаваться ASCIIZ-строки или целочисленные идентификаторы. В последнем случае старшее слово соответствующего параметра должно содержать о, а младшее — идентификатор. Ec-
ли текстовая строка начинается символом #, остальная часть строки должна представлять собой правильную форму представления целого числа, которое считается целочисленным идентификатором. Например, строка '#100' означает целочисленный идентификатор 100.
Функция FreeResource. Заголовок:
Function FreeResource(Resource: THandle): Boolean;
Уменьшает на единицу счетчик ссылок на ресурс, связанный с дескриптором Resource. Если счетчик стал нулевым, выгружает ресурс из памяти.
Функция LoadResource. Заголовок:
Function LoadResource(Instance, Reslnfo: THandle): THandle;
Для экземпляра программы instance загружает в память ресурс, связанный с дескриптором ResInfo. Дескриптор должен быть ранее создан с помощью обращения к FindResource. Каждый вызов LoadResource наращивает на единицу счетчик ссылок ресурса.,
Функция Loadstring. Заголовок:
Function Loadstring(Instance: THandle; ID: Word;
Buffer: PChar; MaxSize: Integer): Integer;
Для экземпляра программы instance загружает из строкового ресурса в буфер Buffer строку с идентификатором ID. Длина буфера (вместе с терминальным нулем) задается параметром MaxSize. Возвращает длину загруженной строки или о, если строка не найдена.
ФуНКЦИЯ LockResource. ЗаГОЛОВОК:
Function LockResource(Res: THandler): Pointer;
Фиксирует в памяти ресурс, связанный с дескриптором Res, и возвращает указатель на первый байт ресурса. Каждый вызов функции LockResource увеличивает на единицу счетчик ссылок ресурса, каждый вызов UniockResource уменьшает на единицу этот счетчик. Ресурс становится перемещаемым и может выгружаться, если его счетчик содержит о.
Функция sizeOfResource. Заголовок:
Function SizeOfResource(Instance, Res: THandle): Longlnt;
Для экземпляра программы instance возвращает размер в байтах ресурса, связанного с дескриптором Res.
Функция uniockResource. Заголовок:
Function UnlockResource(Res: THandle): Boolean;
Уменьшает на единицу счетчик ссылок на ресурс, связанный с дескриптором Res. Если счетчик стал нулевым, ресурс может быть выгружен из памяти.
Функции разного назначения
ПроцедураЗаголовок:
Procedure
Позволяет программе вызвать прерывание ДОСДоступ к этой процеду
ре возможен только на ассемблере.
ПроцедураЗаголовок:
Procedure FatalExit(Code: Integer);
Передает текущее состояние защищенной программы внешнему отладчику. Используется только в отладочном режиме. Вызов этой процедуры в рабочем режиме приведет к завершению работы программы.
Заголовок:
Function
Возвращает переменные окружения ДОС. Используется в DLL, т. к. в отличие от основной программы, DLL не получает копию переменных окруже- нияДОС.
Функция Getversion. Заголовок:
Function GetVersion: Longlnt;
Возвращает версию Windows или MS-DOS: старшее слово содержит номер версии, младшее — номер ревизии.
ФункцияЗаголовок:
Function GetwinFiags: Longlnt;
Возвращает флаги Windows, содержащие информацию о конфигурации ОС и памяти. Возвращаемое значение может быть комбинацией признаков, перечисленных в табл. 32.5.
Таблица 32.5. Зачения признаков, возвращаемых функцией
Признак
Смысл
wf_80x87
в системе имеется арифметический сопроцессор
wf_CPU286
Используется процессор 80286
wf_CPU386
Используется процессор 80386
4 8 6
Используется процессор 80486
wf_Enhanced
Windows работает в расширенном 386-режиме
wf_Paging
Windows работает на системе со страничной памятью
Таблица 32.5(окончание)
Признак
Смысл
wf_PMode
защищенный режим процессора
wf_Standard
Windows работает в стандартном режиме
wf_Win286
Эквивалент wf_standard
wf_Win386
Эквивалент
wf_DPMI
Используется DPMI-сервер
Функция MessageBox. Заголовок:\
FunctionWord; Text, Header:
Flags:Word;
Выводит текстовое сообщение, состоящее из двух строк, и ожидает ответа пользователя. Возвращает идентификатор ответа. Параметр Handle в защищенном режиме игнорируется и может быть любым.
Первая строка сообщения состоит из двух текстовых строк, разделенных двоеточием:
Header — текстовая строка до двоеточия;
Text — строка после двоеточия.
Вторая строка сообщения определяется параметром Flags, который должен быть комбинацией флагов из табл. 32.6.
Таблица 32.6. Флаги функции MessageBox
Флаг
Смысл
mb_Ok
Вставить текст
mb_OkCancel
Вставить текст ' Ok
mb_AbortRetryIgnor^
Вставить текст(A) Retry (R) Ignore
mb_YesNoCancel
текст Yes (Y) NoCancel (C) : '
mb_YesNo
текст Yes (Y) No (N) :
mb_RetryCancel
текст Retry (R) Cancel (C) : '
mb_DefButtonl
Сделать умалчиваемым 1-й символ
mb_DefButton2
Сделать умалчиваемым 2-й символ
mb_DefButton3
Сделать умалчиваемым 3-й символ
В табл. 32.7 перечислены значения, одно из которых возвращает эта функция.
Таблица 32.7. Значения, возвращаемые функциейMessageBox
Значение
Смысл
Значение
Смысл
id_Ok
Нажата клавиша <0>
id Ignore
Нажата клавиша <I>
id Cancel
Нажата клавиша <с>
id_Yes
Нажата клавиша <у>
id Abort
Нажата клавиша <А>
id_No
Нажата клавиша <N>
id Retry
Нажата клавиша <R>
Если, например, сделан такой вызов функции:
MessageBox(0,'прекратить печать 7 r f
'Принтер.не отвечает',mb_YesNo+mb_DefButton2)
то на экран будет выведено:
Принтер не отвечает: прекратить печать?
Yes (Y) No (N) :l
Если в ответ нажата клавиша <Enter>, функция вернет значение id_No, т. к. Параметр Flags СОДерЖИТ mb_DefButton2.
Работа с памятью
Для создания программы, работающей в защищенном режиме, выберите в среде ВР опцию Compile | Target и в появившемся диалоговом окне укажите Protected-mode. Получающийся в результате компиляции ЕХЕ-файл имеет два заголовка: стандартный заголовок MS-DOS и заголовок Windows. При запуске программы под управлением MS-DOS командный процессор COMMAND.COM использует заголовок ДОС для запуска небольшой стартовой программы, которая автоматически пристыковывается компилятором к исполняемому файлу. Эта программа осуществляет следующие действия:
Проверяет наличие DPMI-сервера и, если сервер не инсталлирован, загружает его из файла DPMI16BI.OVL; если сервер инсталлирован (обычно это означает, что программа запускается из расширенного режима Windows), стартовая программа не загружает его вновь.
Проверяет наличие в памяти расширителя ДОС и при необходимости загружает его из файла RTM.EXE (если программа запускается из другой программы, работающей в защищенном режиме, обе программы будут использовать один и тот же расширитель RTM);
Переключает процессор в защищенный режим и передает управление загрузчику исполняемых файлов расширителя ДОС.
Загрузчик расширителя читает Windows-заголовок исполняемого файла, загружает все указанные в нем DLL-библиотеки, затем загружает саму программу и передает ей управление.
Таким образом, для нормальной загрузки программы необходимо, чтобы в текущем каталоге или в одном из каталогов, перечисленных в переменной окружения PATH, находились все используемые программой DLL-библиотеки, а также файлы DPMI16BI.0VL и RTM.EXE.
Программирование в защищенном режиме почти не отличается от программирования для MS-DOS. Единственное, о чем не должен забывать программист, это о специфике работы с памятью.
В защищенном режиме нельзя использовать абсолютные адреса, задаваемые директивой absolute. Точнее, их можно указывать только в сочетании с одним из следующих стандартных селекторов:
Seg0040 — определяет начало области параметров BIOS;
SegAOOO - - начало видеопамяти адаптеров EGA/VGA в графическом режиме;
П SegBSOO — начало видеопамяти адаптеров CGA/EGA/VGA в текстовом режиме;
О SegBOOO — начало видеопамяти монохромных адаптеров.
Только с правильными селекторами можно использовать массивы памяти Mem, MemW и MemL. Селектором должен быть и первый параметр обращения к функции ptr, которая служит для создания указателей.
В защищенном режиме размер кучи определяется объемом всей оперативной памяти компьютера за вычетом загруженной программы и резидентных частей MS-DOS, расширителя ДОС и DPMI-сервера. Хотя функции GlobalAlloc и GlobalAllocPtr способны распределять в куче очень большие блоки (до 64 Мбайт), работать с данными в крупных блоках можно только порциями по 64 Кбайт, т. к. смещение в указателях задается 16- разрядным словом. Учитывая это, расширитель ДОС при распределении в куче крупного блока создает несколько последовательно расположенных селекторов, каждый из которых (кроме, возможно, последнего) ссылается на участок памяти длиной 64 Кбайт. Возвращаемый функциями GlobalLock и GiobalAllocPtr указатель содержит селектор и смещение, определяющие адрес самого первого байта крупного блока. Для доступа к остальным байтам в этом случае можно использовать следующую функцию:
Function GetPtr(P: Pointer; Offset: Longlnt): Pointer;
begin
GetPtr := Ptr (
HiWord(Longlnt(P)) + HiWord(Offset)*SelectorInc,
LoWord(Longlnt(P)) + LoWord(Offset))
end;
При рализации этой функции учитываются два обстоятельства. Во-первых, старшее слово 32-разрядного параметра Offset содержит количество блоков по 65 535 байт и поэтому для получения селекторной части указателя это слово умножается на Selectorlnc
Вариант
round(Offset/65535)*SelectorInc
дает такой же результат, но реализуется значительно медленнее.
Во-вторых, предполагается, что сложние
LoWord(Longlnt(P)) + LoWord(Offset))
не приведет к переполнению значения word. Это будет только тогда, когда смещение указателя р нулевое. Если это не так, то в общем случае потребуется дополнительная проверка
if Longlnt(LoWord(Longlnt(P)))+LoWord(Offset)>$FFFF then
{Наращиваем селекторную часть на Selectorlnc)
К счастью, смещение в адресах, возвращаемых функциями GlobalLock и GiobaiAiiocPtr, всегда нулевое и функция не осуществляет ненужную проверку.
В следующей несложной программе (пример 32.1) иллюстрируется техника работы с большими блоками. Программа резервирует блок размером почти 1 Мбайт для распределения в нем 250 000 значений типа Longlnt, заполняет этот массив монотонно возрастающими значениям от 1 до 250 000 и затем находит сумму всех его элементов. Найденная сумма выводится вместе с теоретическим значением, что позволяет убедиться в достоверности результата.
: Пример 32.1. Иллюстрация работы с большими блоками
{$N+,E+){Используем сопроцессор для типа Comp)
Uses WinAPI;
Function GetPtr(P: Pointer; Offset: Longlnt): Pointer; begin
GetPtr := Ptr (
HiWord(Longlnt(P)) + HiWord(Offset)*SelectorInc,LoWord(Longlnt(P)) + LoWord(Offset))
end;
type
PLonglnt =ALongInt;
const
MaxSize = 250000;
864Часть III. Практика программирования
в
var
:
k :
Buffer: Pointer;
begin
Buffer :=
for k := 1 todo
(Buffer,:=k;
Summ0
for k := 1 to MaxSize do
Summ := Summ + PLonglnt(GetPtr(Buffer, (k-1)*4))A;
GlobalFreePtr(Buffer);
WriteLn(Summ:10:0, (1.O*MaxSize*MaxSize+MaxSize)/2:15:0) end.
Примечание
Умножение на 1.0 в выражении (1./2 необ
ходимо для приведения операндов к вещественному типу; если этого не сделать, возникнет переполнение значения типа word и будет напечатан неверный результат.
Расширитель ДОС для каждого загружаемого сегмента кода, который, как известно, не может изменяться, создает селектор-псевдоним, ссылающийся на тот же сегмент кода, но определенный как сегмент данных. Селектор- псевдоним располагается в таблице дескрипторов сразу за селектором кода, поэтому получить к нему доступ можно с помощью той же переменной Это открывает возможность создания самоизменяющихся
программ. Например, изменить 10-й по счету байт в коде процедуры можно с помощью такого фрагмента программы:
var
\\Р: Pointer;
\
\
Р :=
:
В защищенном режиме программа не может ссылаться на модуль overlay, использовать директивыи переменные ovrxxxx из модуля system,
т. к. расширитель ДОС автоматически реализует все функции администратора оверлея. Для управления свойством выгружаемое™ кодовых сегментов в среду ВР введена специальная директива компилятора
Идентификаторы, предусмотренные в качестве параметров Flag этой директивы, перечислены в табл. 32.8.
Таблица 32.8. Параметры Flag директивы {$S Flag! Flag] )
Идентификатор
Назначение
moveable
Сегмент может перемещаться в памяти
fixed
Сегмент не может перемещаться
preload
Сегмент загружается в момент старта программы
demanload.
Сегмент загружается только при обращении к нему
discardable
Сегмент можно удалять из памяти
permanent
Сегмент нельзя удалять из памяти
По умолчанию каждый модуль компилируется с такой директивой:
($s MOVEABLE DEMANLOAD' DISCARDABLE)
Это означает, что соответствующий кодовый сегмент может перемещаться в памяти, загружается только когда ему передается управление и он может удаляться из памяти. При необходимости вы можете переопределить умалчиваемые свойства, включив директиву $s в самом начале программы или модуля. Если в директиву вставляются два противоположных по смыслу идентификатора, действует последний из них.
32.4. Создание и использование ресурсов
Как уже говорилось, ресурсы — это двоичные данные, создаваемые специальными редакторами ресурсов для нужд программы и пристыковываемые в конец исполняемого файла. Сразу же оговорюсь, что ресурсы в защищенном режиме и ресурсы библиотеки Turbo Vision — это не одно и то же. Расширитель ДОС использует формат ресурсов Windows, которые обычно хранят элементы графического интерфейса и описание диалоговых окон. Поскольку защищенный режим не поддерживает графику, из всего многообразия ресурсных API-функций в модуль WinAPI включены только средства работы со строковыми ресурсами и ресурсами пользователя. Строковые ресурсы содержат пронумерованные строки. Программа получает строку из ресурса, ссылаясь на ее имя, т. е. примерно так же, как это делается в библиотеке Turbo Vision. Ресурс пользователя содержит произвольные двоичные данные, которые программа может использовать по своему усмотрению.
Создание ресурсов
Создать ресурс любого типа можно с помощью редактора ресурсов Resourse Workshop или одного из компиляторов ресурсов — BRC.EXE или BRCC.EXE. Поскольку редактор ресурсов работает только в среде Windows, a эта операционная система необязательно установлена на вашем ПК, дальше описывается процесс создания ресурсов с помощью компиляторов, которые работают под управлением MS-DOS.
Компилятор ресурсов получает текстовый файл описания ресурсов и производит двоичный ресурсный файл. По умолчанию расширение текстового файла — RC, а ресурсного файла — RES. Текст RC-файла составляется на специальном языке описания ресурсов.
Каждый ресурс в RC-файле содержит заголовок и описание. В заголовке, имеющем формат
[<ИМЯ_РЕСУРСА>] <ТИП_РЕСУРСА>
указывается имя ресурса и его тип. Имя ресурса — обязательный параметр для всех ресурсов, кроме строковых. При формировании имени используются правила создания идентификаторов Паскаля:
О имя содержит латинские буквы, цифры и знак подчеркивания;
□ имя не может начинаться с цифры;
О разница в высоте символов имени игнорируется, т. е. имена MYRES и MyRe з считаются синонимами.
Тип ресурса задается одним из зарезервированных слов: STRINGTABLE — для строкового ресурса и RCDATA для ресурса пользователя. В отличие от имен, эти слова, а также зарезервированные слова BEGIN и END, обрамляющие раздел описания ресурса, должны состоять только из заглавных букв.
Раздел описания открывается словом BEGIN и завершается словом END. Каждая строка в строковом ресурсе описывается следующим образом:
<НОМЕР>"[<СТРОКА>]"
<НОМЕР> представляет собой произвольное целое число без знака в диапазоне от 0 до 65 535. Это число трактуется как уникальный идентификатор строки и не может быть одинаковым у двух разных строк в пределах всего RC-файла (в файле может быть сколько угодно описаний строковых ресурсов). Строка обрамляется двойными кавычками и может быть пустой, т. е. между обрамляющими кавычками может не быть ни одного символа. Если в строку необходимо поместить символ он удваивается. На одной текстовой строке файла можно размещать произвольное количество описаний строк. Перед номером и после него может стоять сколько угодно разделителей — пробелов, символов табуляции и запятых. Если в строке встретилась точка с запятой, остаток строки игнорируется — таким способом можно вставлять
комментарии. Внутри обрамляющих кавычек разделители теряют свое специальное назначение и считаются обычными символами. Пример 32.2 — пример правильного описания строкового ресурса.
• * Ж, 8 ♦ £ • v ’.. ■- •'1' v i *7
? Пример 32.2. Строковый ресурс
I АЛ . .VIAT. Л/Г « eWt к I Жт«/ к f Ж •<А..».'Л• Л A ^v.*A
STRINGTABLE
BEGIN
"Первая строка"
2"Вторая строка"5"Пятая строка «III , 4, , , ,"Четвертая строка..."
END
При выводе строк этого ресурса в цикле от 1 до 5 не экране появится такой текст:
Первая строка
Вторая строка
Четвертая
строка...
Пятая строка
Как видим, разрыв концом строки текста Четвертая строка... привел к тому, что вторая часть текста выводится на следующей строке, но с той же позиции, на которой закончился вывод первой части. Это связано с тем, что в строку вставился лишь символ "Перевод строки" (#ю), но не вставился "Возврат каретки" (#13). Следует заметить, что компилятор ресурсов обрабатывает текст строки по правилам языка Си, в котором разрешены так называемые управляющие символы. Эти символы начинаются обратной косой чертой, за которой может следовать латинская буква, восьмеричное или 16- ричное число в диапазоне 0—255. Восьмеричное число состоит из восьмеричных цифр (цифры от 0 до 7), перед 16-ричным числом указывается латинская бука х или X. Комбинация \<число> определяет внутренний код вставляемого символа, т. е. играет такую же роль, что и константа #<ЧИСЛО> в Паскале. Два следующих управляющих символа вставляют в текст строки символ "Возврат каретки" (#13):
\15 \xD
Комбинация \<БУКВА> для некоторых букв латинского алфавита определяет часто используемые символы (табл. 32.9).
Таблица 32.9. Управляющие символы
Управляющий символ
Значение
Управляющий символ
Значение
\а
Звуковой сигнал
\г
Возврат каретки
\Ь
Забой слева <Backspace>
\t
Табуляция
\п
Перевод строки
Если текст примера 32.2 отредактировать следующим образом:
"Четвертая\п\гстрока"
на экран будет выведено:
Четвертая строка
В связи с тем, что символ \ играет специфичную роль, его нужно удваивать, чтобы компилятор не рассматривал его как служебный символ.
Ресурс
"Четвертая\\строка"
даст на экране
Четвертая\строка
Описание ресурса пользователя в заголовке содержит зарезервированное слово RCDATA, которому должно предшествовать имя ресурса. В раздел описания можно вставлять следующие символы и их комбинации:
десятичные числа типа word, integer;
шестнадцатеричные числа в диапазоне от $оооо до $FFFF; перед шестнадцатеричным числом ставится символ $;
цепочки символов в двойных апострофах по правилам строковых ресурсов, но без замыкающего нуля; если требуется поместить в ресурс ASCHZ-строку, в ее конце нужно добавить \о;
цепочки байтов в одиночных апострофах; байты в такой цепочке задаются в шестнадцатеричном формате и разделяются пробелами.
Пример правильного описания ресурса пользователя:
MyResRCDATA
BEGIN
"Это - ASCIIZ-CTpoKa\0"
$FFFF, 65535; две формы записи одного числа
'123456789 А' ; байты со значениями от 1 до 10 -1 127; для этих чисел будет выделено
по 2 байта
END
Длина каждого ресурса пользователя (а их в одном файле может быть сколько угодно) кратна 256 байтам.
После подготовки текстового файла с описанием ресурсов его следует откомпилировать в RES-файл. Для этого вызывается компилятор BRCC.EXE или компилятор-компоновщик BRC.EXE. Последний не только создает RES-файл, но и пристыковывает его в конец исполняемого файла. Поскольку компоновщик ВР также умеет работать с ресурсами (для этого введена директиваудобнее использовать компилятор
Для его вызова дайте команду ДОС
BRCC MyRes
(предполагается, что путь к каталогу размещения файла BRCC.EXE указан в переменной PATH). В результате такого обращения компилятор откомпилирует файлв файл ресурса
32.4.2. Использование ресурсов
Чтобы компоновщик ВР пристыковал ресурсный файл к исполняемой программе, вставьте в ее текст директивунапример:
{$R MyRes)
Для получения строки из строкового ресурса используется вызов функции Loadstrng. Следующая программа (пример 32.3) выведет на экран содержимое строкового ресурса, описанного в примере 32.2.
:-s-4:4^;’:-y"'"-''"-'--:-"-—-;y-:-;^---—;."---—-"--"------:--'---":---'v--—---—r-^r--;";-;
Пример 32.3. Вывод на экран содержимого строкового ресурса
t::;;...;:t;.\Tra;;;^..:.v^..........^х;;...^...;.^....^".^..^^..^^^^^
Uses
($R MyRes)
var
Buf: array [Byte] of Char;
k : Integer;
begin
for k := 1 to 5 do
begin
ifk, Buf,then
WriteLn(Buf)
end;
end.
Доступ к ресурсам пользователя сложнее и выполняется в такой последовательности:
С помощью FindResource создается дескриптор ресурса.
Еслиресурснайден(FindResource возвращетненоль),онзагружаетсяв
память обращением к LoadResource, которая возвращает дескриптор выделенного ресурсу блока памяти.
С помощьюполучаем физический адрес блока памяти и
работаем с ресурсом.
После использования ресурса удаляем его из памяти обращением к FreeResource.
Следующая программа (пример 32.4) прочитает и распечатает содержимое ресурса, описанного в примере 32.2.
Пример 32.4. Печать содержимого ресурса|
• Глг« Altai a'«•«<<. a..i.'.......a. ill.m..At а Ли 471 a aiTa tt аа nwin>iha «Ин.• «йл».•.I »л«аеек.»...гм» •»•.!» я»
Uses
{$R MyRes}-
type
PMyRes =ATMyRes;
= record
Text: arrayof Char;
Wordl, Word2: Word;
Bytes: arrayof Byte;
Int2: Integer;
end;
var
P: PMyRes;
ResHandle,Word;
• . I
begin
ResHandle
FindResource(hlnstance, 'MyRes', PChar(Longlnt(10))j.;
ifthen
begin
MemHandle :=
P :=
withdo
begin
WriteLn(Text);
WriteLn(Wordl, Word2:10);
for k := 1 to 10 do
Write(Bytes[к]:4);
WriteLn;
WriteLn(Inti, Int2:10)
end;
FreeResource(Handle)
end
end.
\
Небольшой комментарий. При обращении к FindResource третьим параметром требуется указать тип ресурса. Этот параметр определен как pchar, но не пытайтесь здесь поместить строку 'RCDATA', т. к. два слова этого параметра используются специфичным образом: старшее слово должно содержать о, а в младшее необходимо поместить числовой идентификатор типа параметра (для ресурса пользователя он равен io). Именно эту операцию и реализует выражение
PChar(Longlnt(10))
Создание и использование динамических библиотек
Динамически подключаемые библиотеки (далее DLL) представляют собой универсальный механизм интегрирования в вашу программу процедур и функций, написанных другими программистами и, в общем случае, на других, чем Паскаль, языках программирования. DLL не могут использоваться в реальном режиме DOS и поэтому в Турбо Паскале нет средств работы с ними. В ВР могут создаваться программы для защищенного режима и есть соответствующие средства создания и использования DLL.
DLL реализуются в виде исполняемых модулей, содержащих готовые к работе процедуры, функции и/или ресурсы. С точки зрения программиста, есть много общего между DLL и обычными для Турбо Паскаля модулями unit, т. K., в конечном счете, и библиотеки, и модули поставляют подпрограммы, избавляющие программиста от написания собственного кода. Но есть и принципиальные отличия. Главным из них является то, что DLL не в состоянии поставлять в программу переменные, константы и типы, ведь создатели DLL могут использовать нетипизированные языки программирования, например, язык ассемблера. В результате DLL не могут экспортировать в программу столь необходимые сегодня программисту объекты.
Другим важным отличием является способ связывания экспортируемых подпрограмм с основной программой. Модули связываются с программой
на этапе компоновки, т. е. статически. Если загружены две программы, использующие одни и те же модули, в памяти будут два экземпляра одинаковых фрагментов кода. В отличие от этого, DLL подключаются к программе в момент ее исполнения, т. е. динамически. Если, опять же, две программы работают с одной и той же DLL, в памяти будет лишь один экземпляр разделяемого программами кода.
Создание DLL
Для создания DLL в ВР введено зарезервированное слово Library, которым должен начинаться текст библиотеки. За словом Library следует правильный идентификатор, но в отличие от объявления модуля он не обязан совпадать с именем файла: имя DLL определяется именем DLL-файла, а не идентификатором, следующим за Library.
Структура текста DLL повторяет структуру обычной программы с тем исключением, что раздел исполняемых операторов в DLL играет ту же роль, что и установочная часть модуля: операторы этой части исполняются только один раз в момент загрузки библиотеки в память. Каждое очередное обращение с требованием загрузить библиотеку наращивает на единицу ее счетчик ссылок, но не приводит к выполнению операторов исполняемой части.
Сразу за заголовком экспортируемой подпрограммы должно следовать зарезервированное слово Export, которое заставит компилятор выработать специальный код пролога/эпилога. DLL имеют сегменты кода и данных, но у них нет сегмента стека. Специальный пролог/эпилог позволяет DLL- подпрограмме использовать сегмент стека вызвавшей ее программы.
В разделе описаний DLL могут объявляться типы (в том числе и объекты), константы и переменные, но они остаются скрытыми от вызывающей программы и могут применяться только внутри DLL. В разделе описаний помимо стандартных для обычной программы объявлений используется специальный раздел объявления экспортируемых подпрограмм. Этот раздел начинается зарезервированным словом Exports, за которым через запятую перечисляются имена экспортируемых подпрограмм, например:
Library MyLibrary;
FunctionМуFunc(...):...; Export;
begin
end;
Procedure MyProc; Export;
begin
end;
Exports
MyFunc, MyProc;
begin
end.
Раздел Exports помогает компилятору и компоновщику создать специальный заголовок DLL-модуля, в котором перечисляются имена подпрофамм и адреса их точек входа. В DLL может быть несколько списков Exports, но перечисляемые в них подпрограммы должны быть описаны где-то выше по тексту библиотеки.
Помимо имени подпрограммы, в заголовок DLL помещается также ее порядковый номер, точнее присвоенный ей целочисленный индекс. Это позволяет вызывающей программе ссылаться не на имя, а на индекс подпрограммы и тем самым уменьшить затраты времени на установление с ней связи. Индекс присваивается подпрограмме по порядку ее появления в списках Exports: первая подпрофамма в первом списке получает индекс 1, следующая — 2 и т. д. Профаммист может изменить умалчиваемую индексацию и явно указать индекс подпрофаммы, добавив за ее именем в списке Exports слово index и целое число без знака в диапазоне от 1 до 32 767:
Expots
MyFunc index 1, MyProc index 2;
Во избежание возможной путаницы с индексацией советую явно задавать индексы всем экспортируемым подпрофаммам.
Профаммист может определить внешнее имя экспортируемой подпрофам- мы отличным от ее настоящего имени. Для этого в списке Exports добавляется слово name и внешнее имя в апострофах:
Exports
MyFunc index 1 name 'NEWFUNC ;
Внимание
Указываемое в апострофах внешнее имя должно состоять из заглавных букв. Если в приведенном выше примере задать 'NewFunc', вызывающая программа не сможет найти функцию MyFunc ни по имени, ни по индексу! По умолчанию ВР создает внешнее имя совпадающим с настоящим, но преобразует его буквы к заглавным.
Вызывающая программа может ссылаться или на имя экспортируемой подпрограммы, или на ее индекс. При вызове по имени программа просматривает имена в таблице имен в поисках нужного. Так как имена могут состоять из длинных наборов символов и самих имен в таблице может быть много, процесс поиска имени существенно медленнее, чем процесс поиска индекса. Поэтому опытные программисты предпочитают ссылаться не на имя, а на индекс подпрограммы. Тем не менее, нельзя исключить возможность того, что программа будет вызывать DLL-подпрограмму по имени. В этом случае затраты времени на поиск можно заметно сократить, если указать в списке Exports слово resident:
Exports
MyFunc index 1 name 'NEWFUNC resident;
Информация о подпрограмме, экспортируемой с признаком resident, сохраняется в памяти во все время работы с библиотекой.
Замечу, что в отличие от модулей, ВР не компилирует DLL автоматически в режимах МАКЕ или BUILD, т. к. справедливо рассматривает ее как другую программу, никак не связанную в момент КОМПИЛЯЦИИ с основной программой.
В заключение рассмотрим пример создания DLL (пример 32.5), в котором иллюстрируются различные приемы объявления экспортируемых подпрограмм. В состав библиотеки входят 4 процедуры, реализующие действия с комплексными числами.
\ Пример 32.5. Разработка DLL «А А ШтАл.;.. .vA ..«../....". а. . .u...... л....'...... .'•».-J.* AV;.-... .л?.*. »т.« А".. «А А •лГа-.
($N+,E+). {Используем сопроцессор для типа Single)
Library Complex;
type
TComplex = record
Re, Im: Single end;
Procedure CmplxAdd(x, y: TComplex; var z: TComplex); Export; begin z. Im :=+ y. Im;
z. Re : = x. Re + у. Re
end;
Procedure CmplxSub(x, y: TComplex; var z: TComplex); Export; begin
z. Im :=—■ у.Im;
z.Re := x.Re — у .Re end;
Procedure CmplxMul(x, y: TComplex; var z: TComplex); Export;
begin
z.Re : = x.Re * y. Re + x. Im * у. Im;
z. Im := Re * y. Im — X. Im * y. Re end;
Procedure CmplxDiv(x, y: TComplex; var z: TComplex); Export; var
zz: Single;
begin
zz := sqr(y.Re) + sqr(y.Im);
z.Re := (x.Re * y.Re + x. Im * y. Im)/zz;
z.Im := (x.Re * у. Im — x.Im * y.Re)/zz end;
Exports
CmplxAdd index 1 name 'ADD' resident,
CmplxSub index 2,
CmplxMul index 3,
CmplxDiv index 4;
begin end.
Использование DLL
Для использования подпрограмм из DLL необходимо описать их как внешние, добавив за словом External имя библиотеки в апострофах:
Procedure MyProc; Far; External 'MyDLL';
Внимание
Перед External надо обязательно указать Far, в противном случае компилятор потребует поставить точку с запятой после слова External.
Как уже говорилось, подпрограмма вызывается по имени или по индексу. В нашем примере из библиотеки MyDLL вызывается подпрограмма с внеш-
ним именемЕсли нужно сослаться на индекс подпрограммы, за
именем библиотеки указывается слово index и индекс:
Far; External 'MyDLL' index 2;
В этом случае имя, под которым подпрограмма будет известна программе, может не совпадать с ее внешним DLL-именем. Впрочем, программист может и явно переопределить имя подпрограммы, даже если он ссылается на ее внешнее имя:
Procedure MyProc; Far; ExternalName
В этом варианте предполагается, что экспортируется процедура внешним именем
После любого из указанных выше объявлений экспортируемая подпрограмма становится доступна программе и может использоваться в ней как обычная подпрограмма на Паскале независимо от того, на каком языке программирования она была написана.
В следующей программе (пример 32.6) используется описанная ранее биб- лиотекаComplex.
*‘*^1^у^ц^д|шу^^д^у^^^^^1^!му§|у|^у|^^|уд(УДММ?^ь^*^У**>^Ч?'^*|>|^*^****^У^*^У'У^^Дй?й^^|^ЖД^^^^»^>||^^^Д^У'^^~**-,*? Пример 32.6. Иллюстрация работы с библиотекой Complex
($N+,E+)
type
TComplex = record
Re, Single
end;
ProcedureTComplex; var z:Far;
•External
ProcedureTComplex; var z:Far;
Externalindex 3;
Procedurey: TComplex; var z:Far;
ExternalIndex
ProcedureTComplex; var
Externalname
var
TComplex;
begin
комплексные
Re : = 1;
Im :=
Re := 1;
Im := 2;
Add(a,b,c);
WriteLn('Сложение :
Sub(a,b,c);
WriteLn(1 Вычитание: ', c.Re:3:0, c.Im:4:0,'i ') ;
Mui (a, b, c) ;
WriteLn ('Умножение: *-, c.Re:3:0, c.Im:4:0,'i');
CmplxDiv(a,b,c);
WriteLn('Деление : ', c.Re:3:1, c.Im:4:1, 'i');
end.
I
Описанный выше способ связывания DLL с основной программой называется статическим, т. к. он реализуется компилятором и компоновщиком. В результате Windows-заголовок программы для защищенного режима будет содержать список всех DLL и зафузчик расширителя ДОС зафузит библиотеки в память одновременно с загрузкой самой профаммы.
Программа может загружать DLL и самостоятельно (динамически) с помощью трех функций, ВХОДЯЩИХ В МОДУЛЬ WinAPi: LoadLibrary, GetProcAddress и FreeLibrary. Пример 32.7 иллюстрирует технику динамической загрузки DLL Complex.
Г„т;-:т7"уг<"'''"т,""""ЖУУТ™'"Х:м,йф;б^^,^-:у»"""т:;.тт^
Пример 32.7. Динамическая загрузка DLL Complex
Г. S»ift<л.:..‘..'...I,..,,... ..I...,>>-... ,...,.. ...X.i.i.4.UlSi«. .....Л.,
{$N+,E+}
Uses WinAPi;
type
TComplex = record
Re, Im: Single
end;
TComplexProc = Procedure (x, у: TComplex; var z: TComplex);
var
a, b, c: TComplex;
Add, Sub, Mui, CDiv: TComplexProc;
- Handle: Word;
begin
{Исходные комплексные числа: 1+li, l+2i}
a. Re : = 1;
Im := 1;
Re := 1;
Im : = 2;
(Загружаем библиотеку COMPLEX.DLL)
Handle : = LoadLibrary('COMPLEX.DLL');
Handle = 0 then
begin
WriteLn('He найдена библиотека COMPLEX.DLL');
Halt
end;
{Определяем адреса процедур. Три первые вызываем по индексу, четвертую — по имени. При вызове по индексу младшее слово PChar должно содержать индекс, поэтому делаем приведение типов:)
@Add := GetProcAddress(Handle,PChar(Longlnt(1)));
@Sub :=
@Mul := GetProcAddress(Handle,PChar(Longlnt(3)));
@CDiv := GetProcAddress(Handle,'CmplxDiv');
(Используем библиотеку)
Add (a, b, c) ;
WriteLn('Сложение : ', c.Re:3:0, c.Im:4:0,'i');
Sub(a,b,c);
WriteLn('Вычитание: ', c.Re:3:0, c.Im:4:0,'i');
Mui (a,b, c) ;
WriteLn('Умножение: ', c.Re:3:0, c.Im:4:0,'i');
CDiv(a,b,c);
WriteLn('Деление : ', c.Re:3:1, c.Im:4:1,'i');
{После использования удаляем библиотеку из памяти)
•
end.
При вызове DLL-подпрограмм в большинстве случаев бывает необходимо передавать структурированные параметры типа записей, как тип TComplex в предыдущем примере. Поскольку DLL не могут экспортировать типы, приходится объявлять эти типы в вызывающей программе. Если вы часто обращаетесь в своих программах к той или иной DLL, удобно создать интерфейсный модуль (пример 32.8), содержащий объявления как подпрограмм, так и связанных с ними типов.
i Пример 32.8. Интерфейсный модуль для загрузки DLL Complex
ыаейямбйяег.
Unit Cmplx; ($N+,E+)
Interface
type
TComplex =
Re,Single
end;
Procedure Add (x,TComplex; var
z:
Procedure Sub (x,TComplex; var
z:
Procedure(x,TComplex; var
z: TComplex);.
ProcedureTComplex; var
Implementation
z: TComplex);
Procedure Add; External
index 1;
Procedure Sub; External
index
Procedure Mul; External
index 3;
ProcedureExternal
end.
index 4;
Такой интерфейсныйТакой интерфейсныймодуль существенно упрощает разработку основной
Такой интерфейсный
Такой интерфейсный
программы:
{$№,£+}
Uses
var
TComplex;
begin
a. Re : = 1;
a. Im : = 1;
b.Re := 1;
:=
Add(a,b,c);
: ',
Sub(a,b,c);
WriteLn('Вычитание: ', c.Re:3:0, c.Im:4:0,' i');
Mui (a,b, c) ;
',
CDiv(a,b,c) ;
:
end.
.29 Зак.
Глава 33
Адаптация MS-DOS-программ к работе в среде Windows
Операционная система Windows получила повсеместное распространение и фактически стала стандартной ОС для ПК, поэтому рано или поздно вам покажутся тесными рамки MS-DOS и вы захотите попробовать свои силы в написании программ для Windows. Программирование для Windows существенно отличается от программирования для ДОС и его невозможно сколько-нибудь подробно рассмотреть в рамках этой книги. В этом разделе приводятся лишь минимальные сведения о том, как с помощью ВР создать программу, которая бы успешно запускалась из Windows и использовала бы некоторые возможности этой ОС.
Введение в Windows
С позиций программиста Windows представляет собой набор системных функций, образующий так называемый API-интерфейс прикладных программ (API — Application Programming Interface). Чтобы выполнить то или иное действие (например, открыть окно или начертить линию), программа обращается к соответствующей функции API. Функции API образуют ядро Windows.
В состав ядра входит более 800 функций, которые хранятся в многочисленных динамических библиотеках Windows. Наиболее важные из них сосредоточены в трех файлах: KERNEL.EXE (Kernel — ядро), USER.EXE (User — пользователь) и GDI.EXE (Graphics Device Interface — интерфейс графических устройств). Библиотека KERNEL.EXE (на самом деле в зависимости от типа процессора используется KRNL286.EXE или KRNL386.EXE) содержит функции управления памятью, загрузки/удаления программ и обеспечивает непосредственную поддержку выполнения программ. В USER.EXE сосредоточены функции управления окнами (создание окна, изменение его размеров, перемещение, удаление), другими элементами интерфейса, а также
Глава 33. Адаптация MS-DOS-программ к работе в среде Windows 881 функции ввода с клавиатуры и работы с мышью. Наконец GDI.EXE обеспечивает взаимодействие с графическими устройствами (в том числе с экраном, принтерами и плоттерами).
Любая Windows-программа владеет хотя бы одним окном, чаще всего представленным в виде прямоугольной области экрана (в отличие от окон модуля CRT Турбо Паскаля окна Windows могут быть невидимыми). С каждым окном связана некоторая функция, которая называется оконной функцией. В противоположность API-функциям, служащим для связи программы с Windows, оконная функция выполняет обратную задачу и используется для передачи сообщений от Windows к программе. Этой функции Windows посылает сообщения — небольшие информационные пакеты данных. Сообщения возникают как реакция Windows на те или иные события: действия пользователя с клавиатурой или мышью, события от таймера, сигналы от внешних устройств и т. п. И хотя одновременно под управлением Windows могут выполняться несколько программ и, следовательно, существует целый набор окон, Windows безошибочно распознает тип события и направляет сообщение о нем нужной оконной функции.
В момент загрузки программа создает так называемое главное окно, которое служит своеобразным "пропуском" в мир Windows: целочисленный идентификатор окна — дескриптор — используется как параметр обращения к большинству API-функций и, следовательно, не создав окна, программа не сможет воспользоваться возможностями Windows. Для создания окна программа должна подготовить и указать Windows оконную функцию. Если окно создано правильно, Windows возвращает программе дескриптор окна и создает очередь сообщений, которые будут передаваться в оконную функцию. Первоначально очередь пуста, но как только пользователь начнет работать с программой, эта очередь будет заполняться сообщениями Windows о действиях пользователя. Если пользователь закрывает главное окно, Windows прекращает работу программы и выгружает ее из памяти.
Модуль WinCRT
В состав ВР входит модуль WinCRT, с помощью которого создается полноценное Windows-окно с собственной оконной функцией. Все действия по созданию окна выполняются в установочной секции модуля и, следовательно, скрыты от программиста. Таким образом, многие MS-DOS- программы можно практически без переделки перенести в среду Windows: для этого достаточно указать ссылку на winCRT в предложении Uses. Следующая программа (пример 33.1) выводит график и таблицу значений функции у = sin(x).
A.f/t AA.<»«a«« AA'A'.'«li.A'A«A.A...4.A.At.AA A AAA.Aa. AAtAA.'..1.; AA.. A......A.......<..r~..АЗгА*'л a. A.»..•.v.>.Vrt.«.ii«evie*»:A.f/t AA.<»«a«« AA'A'.'«li.A'A«A.A...4.A.At.AA A AAA.Aa. AAtAA.'..1.; AA.. A......A.......<..r~..АЗгА*'л a. A.»..•.v.>.Vrt.«.ii«evie*»:Program WinCRT_Demo;Uses WinCRT;varProgram WinCRT_Demo;Uses WinCRT;var^»•••••••■••••^••••н•^^■•^^•^^^•^•н•, • •*•••••••••••••>•#••»•“№;• v.^•»••l•••v*••••••*•?•••?^•м••••••^•••••/•••••••’•v^тy•l \ Пример 33.1. Использование WinCRT
A.f/t AA.<»«a«« AA'A'.'«li.A'A«A.A...4.A.At.AA A AAA.
Aa. AAtAA.'..1.; AA.. A......A.......<..r~..АЗгА*'л a. A.»..•.v.>.Vrt.«.ii«evie*»:
A.f/t AA.<»«a«« AA'A'.'«li.A'A«A.A...4.A.At.AA A AAA.
Aa. AAtAA.'..1.; AA.. A......A.......<..r~..АЗгА*'л a. A.»..•.v.>.Vrt.«.ii«evie*»:
Program WinCRT_Demo;
Uses WinCRT;
var
Program WinCRT_Demo;
Uses WinCRT;
var
k: Integer;
{Параметр цикла)
const
NX = 50;
(Количество точек по горизонтали/
dX = 2*pi/NX;
{Шаг табуляции}
NY = 10; begin
{Амплитуда графика}
ScreenSize.X := 80;
{Ширина виртуального экрана}
ScreenSize.Y := 60;
(Длина экрана}
AutoTracking := False;
for k0 to NX do
{Запрещаем прокрутку}
begin
{Выводим ось абсцисс:} GotoXY(k+1,NY);
Write('-');
{Выводим точки графика:)
GotoXY(k+1,Round(NY*(l+sin(dX*k))) ) ;
Write('*');
{Формируем таблицу значений:} GotoXY(NX+5,k+1);
Write(dX*k:4:2,sin(dX*k):8:2)
end;
end.
После ссылки на winCRT в распоряжение программы предоставляются некоторые из процедур и функций стандартного модуля CRT, рассчитанные на текстовый режим работы. В частности, программа может использовать стандартные процедуры ввода/вывода ReadLn И WriteLn, фуНКЦИИ KeyPressed И ReadKey, процедуру GotoXY. Для программы создается специальное окно, размеры и положение которого можно менять. В это окно направляется текстовый вывод программы, причем с помощью стандартных для Windows полос управления по бокам окна можно просмотреть вывод, не умещающийся полностью в пределах окна.
Следует учесть, что кодировка символов кириллицы в Windows отличается от широко используемой альтернативной кодировки ДОС, поэтому, если
ваша программа посылает на экран русскоязычные сообщения, в Windows- окне эти сообщения будет невозможно прочитать. Символы кириллицы в Windows соответствуют последним 64 значениям кодов: диапазон от 192 до 223 занимают коды заглавных букв от А до Я (без Ё), а диапазон от 224 до 255 — коды строчных буквы от а до я (без ё). Код буквы Ё — 168, а ё — 184.
Преобразование символов можно выполнить с помощью такой функции:
Function OEM_to_ANSI(3: String): String;
var
k: Integer;
begin
for k := 1 to Length(S) do
caseS[k] of
'A'..'Я':S[k] :=chr (ord (S [k]) ~ord( ’A’) +192) ;
* a ’ . . ’ n ’: Sfk] := chr(ord(S[k])-ord('a')+224);
'P'..'я': S[k] := chr(ord(S[k])-ord(*p')+240)
end;
S
end;
Вставьте в текст примера 33.1 эту функцию, а перед оператором
for k := 0 to NX do
добавьте
WriteLn(OEM_to_ANSI('График функции SIN(X)'));
чтобы в Windows-окне напечатать заголовок графика.
Переменные модуля WinCRT
WindowOrg: TPoint — содержит начало координат виртуального окна. По умолчанию — (О, О).
WindowSize: Tpoint — содержит начальный размер окна.
Screensize: TPoint — содержит размер экрана в символах. Начальное значение: ScreenSize.x = 80, ScreenSize.Y = 25. Если вывод программы содержит более ScreenSize.Y строк, лишние строки будут безвозвратно потеряны, поэтому перед первым выводом на экран следует поместить в это поле подходящее значение (см. пример выше). Точно так же следует настроить ScreenSize.x, если на экран выводятся длинные строки.
П Cursor: TPoint — содержит текущую позицию курсора. Левый верхний угол окна соответствует координатам (0, 0). Это поле доступно толь-
ко для чтения, его значение меняется при обращении к CursorTo или GotoXY.
origin: TPoint — содержит координаты левого верхнего угла окна просмотра относительно границ виртуального экрана. Поле доступно только для чтения, его значение меняется при обращении к процедуре ScrollTo.
О InactiveTitle: pchar — содержит указатель на строку символов, использующуюся в качестве заголовка для неактивного окна (т. е. окна после окончания работы программы).
AutoTracking: Boolean — управляет "прокруткой" содержимого окна: если содержит True, окно автоматически сдвигает свое содержимое при выходе вывода за границы, если False — прокрутка запрещена. В неактивном окне можно использовать управляющие полосы для просмотра вывода даже в том случае, когда AutoTracking = False окно просмотра меняет свое положение в пределах ScreenSize..
checkEOF: Boolean — содержит True, если программа обнаружила конец файла.
checkBreak: Boolean — содержит True, если пользователь нажал комбинацию <Ctrl>+<Break>.
WindowTitle: array [0..79] of Char — содержит заголовок активного окна (т. е. окна в момент работы программы).
Процедуры и функции
Procedure AssignCRT (var F: Text) — связывает содержимое файловой переменной F с виртуальным экраном.
П Procedure ClrEOL — очищает строку от текущего положения курсора до конца строки.
П Procedure ClrScr — очищает виртуальное окно.
О Procedure CursorTo(X, Y: integer) — переводит курсор в заданную точку виртуального экрана. Начало отсчета соответствует точке (О, О).
Procedure DoneWinCRT — уничтожает окно вывода программы. Эта процедура автоматически вызывается при закрытии окна.
П Procedure GotoXY(X,Y: Integer) — переводит Курсор В Нужную точку окна. Начало отсчета соответствует точке (1, 1).
О Procedure InitWinCRT — создает виртуальное окно вывода. Эта процедура автоматически вызывается при первом обращении программы к функциям текстового вывода.
О Function Keypressed: Boolean — возвращает True, если буфер ввода содержит хотя бы один непрочитанный символ.
О FunctionWord — читает ДО Count
символов из клавиатуры в память, на которую указывает Buf, и возвращает количество действительно прочитанных символов.
Function ReadKey: char — возвращает очередной символ из буфера ввода. Если к моменту обращения буфер пуст, приостанавливает работу программы до нажатия пользователем на любую клавишу и возвращает расширенный код этой клавиши.
Procedureinteger) — смещает содержимое окна так,
чтобы его левый верхний угол соответствовал точке x, Y (начало отсчета — 0, 0).
Procedure TrackCursor — смещает содержимое окна так, чтобы в нем стал виден курсор.
П FunctionByte — возвращает текущую абсциссу курсора (начало
отсчета —1).
П FunctionByte — возвращает текущую ординату курсора (начало
отсчета — 1, 1).
П ProcedurePChar; Count: Word)ВЫВОДИТ В ОКНО Count
символов из буфера
П ProcedureChar) — ВЫВОДИТ В текущую ПОЗИЦИЮ окна
СИМВОЛ Ch.
Модификация WinCRT
для использования графики Windows
Хотя стандартный модуль WinCRT и создает нормальное Windows-окно, воспользоваться дескриптором этого окна и, следовательно, получить с его помощью доступ к графическим возможностям Windows невозможно, к. этот параметр "спрятан" в раздел implementation. Поскольку в поставку ВР входит исходный текст модуля, можно модифицировать этот текст — перенести параметрхранящий дескриптор окна, в раздел interface. Следует
учесть, что простая перекомпиляция измененного текста модуля не даст желаемого результата, так как его TPW-файл2 включен в основную библиотеку System и загружается из нее автоматически при ссылке на модуль WinCRT. Наиболее простым выходом из положения является переименование модуля и ссылка на его измененное имя в основной программе. Например:
Unit
($S-)
В файлах с таким расширением хранятся результаты компиляции модулей, расчи- на работу под управлением Windows.
interface
uses WinTypes, WinProcs, WinDos;
{Дескриптор окна}{Дескриптор окна}const
{Дескриптор окна}
{Дескриптор окна}
CrtWindow: HWnd = 0;
implementation
end.
Следующая программа (пример 33.2) создает окно, показанное на рис. 33.1. В этой программе график функции у = sin(x) (см. программу WinCRT_Demo в разд. 33.2) строится с использованием графических средств Windows.
Рис. 33.1. Результат работы программы (пример 33.2)Рис. 33.1. Результат работы программы (пример 33.2)
Рис. 33.1. Результат работы программы (пример 33.2)
Рис. 33.1. Результат работы программы (пример 33.2)
I Пример 33.2. ИллюстрацияграфическихвозможностейWindows
t <<..... i .!.«.. i. .••• ««ТТТеЛ^. •••?.••'.1 .Л^. •••• e<i«»l .. .№«71 .•••«.•«••••<•.••••••!••.••«•«• •••••••••». .••'•••I ••4v««« Л.А1И ••«•••.•.•••« Як.». .•e««.»».»X»»,««4»»......
Uses Win_CRT, WinProcs, WinTypes, Strings; var
k: Integer;{Параметр цикла}
DC: hDC;
R: TRect;
OldFont, Font: hFont;
LogFont: TLogFont;
const
NX = 50;
{Количество точек по горизонтали}
dt = 2*pi/NX;
{Шаг табуляции}
МахХ = 300;
{Горизонтальный размер графика/
MaxY =
{Амплитуда графика}
dx = MaxX div NX;
{Горизонтальный шаг графика)
dd = 20; begin
{Поле по краям графика}
ScreenSize.X := 80;
{Ширина виртуального экрана}
ScreenSize.Y := 60;
. {Длина экрана}
AutoTracking := False; InitWinCrt;
{Запрещаем прокрутку} {Создаем окно}
DC := GetDC(CrtWindow);{Получаем его контекст}
{Выводим светло-серый прямоугольник для фона графика)
R.Left := 0;
R.Top := 0;
R.Right :=
R.Bottom :=
FillRect(DC,R,GetStockObject(LtGray_Brush));
{Вычерчиваем оси}
MoveTo(DC,dd,dd);
LineTo(DC,dd,2* (MaxY+dd));
MoveTo(DC,dd,MaxY+dd);
LineTo(DC,MaxX+2*dd,MaxY+dd);
{Готовим шрифт для надписи}
FillChar(LogFont,SizeOf(LogFont),0);
with LogFont do
begin
{Высота букв}{Наклонный шрифт}{Подчеркнутый шрифт){Жирный шрифт}{Имя шрифта}{Создаем шрифт){Вставляем его вконтекст окна}{Высота букв}{Наклонный шрифт}{Подчеркнутый шрифт){Жирный шрифт}{Имя шрифта}{Создаем шрифт){Вставляем его вконтекст окна}IfHeight := 20;
{Высота букв}
{Наклонный шрифт}
{Подчеркнутый шрифт)
{Жирный шрифт}
{Имя шрифта}
{Создаем шрифт)
{Вставляем его в
контекст окна}
{Высота букв}
{Наклонный шрифт}
{Подчеркнутый шрифт)
{Жирный шрифт}
{Имя шрифта}
{Создаем шрифт)
{Вставляем его в
контекст окна}
Ifltalic := 1;
IfUnderline := 1;
IfWeight := fw_Bold;
StrCopy(IfFaceName,'Arial Cyr') end;
Font := CreateFontlndirect(LogFont); OldFont := Selectobject(DC,Font);
SetBkMode(DC,Transparent);(Отключаемизменение
фона надписи} {Выводим надпись)
DrawText(DC, Трафик синуса’,13,R,dt_Center) ;
Selectobject (DC, OldFont) ;{Восстанавливаем контекст}
DeleteOb j ect (Font);(Уничтожаем шрифт}
(Цикл построения графика и вывода таблицы значений!
for k0 to NX do
begin
(Выводим точки графика:} LineTo(DC,k*dx+dd,Round(MaxY*(1-sin(dt*k)))+dd);
{Формируем таблицу значений:} GotoXY(NX+5,к+1);
Write(dt*k:4:2,sin(dt*k): 8:2)
end;
ReleaseDC (CrtWindow, DC){Освобождаем контекст}
end.
Сначала несколько пояснений. Обычно Windows-окно создается в момент первого обращения программы к процедуре вывода Write/WriteLn. До этого момента окно не создано и его дескриптор недействителен. Поскольку программа вначале выводит серый фоновый прямоугольник и оси графика, ей понадобится дескриптор окна, поэтому она обращается к процедуре InitWinCrt для "досрочного" создания окна. Далее, графический вывод в Windows реализуется с помощью так называемого контекста графического устройства. Программа обращается к API-функции GetDC, чтобы Windows создала контекст окна. Стандартный контекст включает три чертежных инструмента: кисть, перо и шрифт. Кисть предназначена для закрашивания больших поверхностей, перо — для вычерчивания фигур, а шрифт — для вывода надписей.
Для вывода светло-серого прямоугольника программа обращается к API- функции FillRect, передавая ей три параметра: контекст устройства, размеры прямоугольника и светло-серую кисть. Размеры прямоугольника задаются переменной R в виде координат левого верхнего и правого нижнего углов прямоугольника. Координаты указываются в пикселах экрана и отсчитываются от левого верхнего угла рабочей области окна. Чтобы указать светло-серую кисть, вызывается функция Getstockobject с параметром LtGray_Brush. В стандартный контекст входят также черная, белая и темносерая кисти.
Вывод осей графика и самого графика осуществляется с ПОМОЩЬЮ пера и двух API-функций: MoveTo и LineTo. Первая перемещает перо в заданное положение на контексте устройства, вторая вычерчивает линию от текущего положения пера к указанной точке и оставляет перо в этой точке. Стандартный контекст оснащен пером, вычерчивающим черные линии толщиной В 1 пиксел. Если бы программе понадобилось другое перо, она должна была бы предварительно его создать и включить в контекст, а после использования удалить его из контекста и уничтожить.
Именно такие действия осуществляются при выводе заголовка графика. Сначала создается новый шрифт (стандартный шрифт контекста практически не отличается от обычного системного шрифта). Для этого переменная LogFont, содержащая описание шрифта, вначале заполняется нулями, после чего в нее помещаются признаки утолщенного, наклонного и подчеркнутого шрифта, его высота и имя. Описав нужный шрифт, программа обращается к функции CreateFontIndirect, чтобы Windows по этому описанию создала шрифт и вернула его дескриптор, который запоминается в переменной Font. После этого обращением к SelectObject в контекст окна вместо стандартного шрифта вставляется новый, причем дескриптор старого шрифта запоминается в переменной OldFont. Теперь все готово для вывода надписи. После обращения к SetBkMode (этот вызов необходим для отключения прорисовки фона букв) вызывается функция DrawText, которой передается подготовленный контекст, текст надписи, длина текста, охватывающий прямоугольник (переменная R) и признак центрирования надписи по горизонтали. По завершению вывода в контекст вставляется стандартный инструмент, а ненужный более шрифт уничтожается.
После циклического формирования графика и таблицы контекст окна также уничтожается с помощью обращения к функции ReleaseDC.
Полученный таким способом график крайне недолговечен: стоит изменить размеры окна или сделать его прокрутку, как он тут же бесследно исчезнет. Осуществив более глубокую модификацию модуля WinCRT, графику можно придать необходимую "прочность". Я мог бы описать необходимые изменения, но... может быть лучше заняться серьезным изучением Windows?
‘ «I ЧГ :■ '^И»1
л1 fi 143. и, »■
ЧАСТЬ IV
ПРИЛОЖЕНИЯ
Приложение 1
Среда Турбо Паскаля
П1.1. Элементы диалоговой среды
При работе с Турбо Паскалем на экране ПК может формироваться сложная структура прямоугольных участков, рассматриваемых как единое целое и предназначенных для тех или иных действий. Такие участки в тексте книги называются меню, окнами, полями и т. д. На рис. П1.1 показаны эти элементы и даны их названия.
Рис. П1.1. Элементы диалоговой среды Турбо Паскаля
Меню будем называть прямоугольный участок экрана, содержащий кодовые слова и предназначенный для диалогового. выбора продолжения работы. Меню фиксирует некоторое текущее состояние диалоговой среды и предлагает несколько альтернативных путей перехода из этого состояния. Содержащиеся в меню кодовые слова обозначают возможные альтернативы. Условимся называть их в дальнейшем опциями (option — выбор).
Окно в Турбо Паскале предназначено для обмена информацией между программистом и средой. В окно редактора программист помещает текст программы, в окне программы среда показывает результат ее прогона, в справочном окне появляются справочные сообщения, в отладочном окне программист может наблюдать за изменением переменных в процессе отладки программы. Одновременно на экране может присутствовать сразу несколько окон, однако только одно из них активно в каждый момент. Активное окно очерчивается двойной рамкой, неактивные — одинарной.
Диалоговое окно разворачивается на экране по мере надобности. С помощью диалогового окна пользователь уточняет выбранное действие. Внутри диалогового окна может быть несколько полей. В поле ввода программист может подготовить текстовую строку (например, имя файла). В поле выбора среда предлагает несколько возможных имен файлов. В поле переключаемой опции можно установить необходимую настройку среды, с помощью командных полей — передать среде ту или иную команду.
Работа с меню и окнами значительно упрощается, если ваш ПК оснащен устройством ввода типа "мышь". Условимся в дальнейшем вместо длинного "устройство ввода типа мышь" писать просто мышь, что соответствует обиходному названию этого прибора. Подвигав мышь по столу, вы тут же заметите перемещающийся по экрану указатель мыши. С помощью этого указателя можно выбрать нужный элемент диалоговой среды. Для этого подведите к нему указатель и нажмите левую кнопку мыши.
П1.1.1. Работа с окнами
В среде Турбо Паскаля используется несколько окон, которые могут частично или полностью накладываться друг на друга. Для последовательной смены окон служит клавиша <F6>: нажатие на эту клавишу делает активным очередное окно; если это окно было закрыто другими окнами, оно накладывается поверх них. Для смены активности окон можно использовать и мышь: переместите указатель мыши внутрь неактивного окна и нажмите левую кнопку.
В верхней части рамки, очерчивающей активное окно, имеются два небольших поля, необходимых при работе с мышью. Поле [ I ] служит для удаления окна с экрана, поле [ t ] — для "распахивания" окна на весь экран, а если это поле помечено значком t, — для возврата к прежним размерам после распахивания. Эти же действия выполняются и с помощью клавиатуры: <F5> распахивает окно или возвращает окну обычный размер, <Alt>+<F3> закрывает окно (удаляет его с экрана). Закрытое окно удаляется из системы окон Турбо Паскаля и его уже нельзя вызвать с помощью <F6>. Перед закрытием окна редактора, содержащего несохраненный на диске текст, среда спросит, нужно ли его сохранить.
Положение и размеры активного окна можно изменять по своему вкусу. При работе с клавиатурой изменить эти параметры можно с помощью команды <Ctrl>+<F5>. После этой команды изменяются цвет и линии рамки: таким образом среда сигнализирует о переходе к режиму настройки. Теперь клавишами смещения курсора вы можете перемещать окно по экрану и этими же клавишами, нажатыми в сочетании с клавишей <Shift>, можно менять размеры окна. После того как положение и размеры окна установлены нужным образом, нажимается клавиша <Enter>. При работе с мышью для изменения положения активного окна следует подвести указатель к левому верхнему углу рамки окна и нажать левую кнопку — окно будет "схвачено" мышью, о чем свидетельствует изменение цвета и линий рамки. Затем, не отпуская кнопки, перемещайте мышь по столу, при этом окно будет смещаться по экрану. Передвинув окно нужным образом, отпустите кнопку. Для изменения размеров окна используется правый нижний угол рамки, который "захватывают" мышью точно так же, как и при перемещении окна.
В окнах редактора и справочной службы содержится текст, который может не помещаться целиком в окне. Правая и нижняя рамки такого окна содержат указатели размеров, в которых показывается положение демонстрируемого в окне фрагмента относительно полных размеров текста. Эти поля можно использовать для того, чтобы перемещать окно относительно текста с помощью мыши. Чтобы сдвинуть окно вниз на одну строку, подведите указатель мыши к самому нижнему краю вертикального указателя размеров так, чтобы он попал на поле В> и нажмите левую кнопку. Для сдвига окна вниз на страницу установите указатель мыши в любое место вертикального указателя размеров несколько ниже его середины и нажмите левую кнопку. Точно так же поступают при перемещении окна вверх, вправо и влево, используя для этого соответственно верхнюю часть вертикального указателя и правую и левую части горизонтального указателя, если окно смещается на страницу, или поляа ан— при смещении на одну строку или символ.
П1.1.2. Работа с меню
Для перехода из состояния редактирования к выбору из главного меню используется клавиша <F10>, для возврата в редактор — клавиша <Esc>. В активном меню указателем (цветом или оттенком) выделяется очередная опция. Для выбора того или иного продолжения переместите клавишами смещения курсора указатель к нужной опции и нажмите <Enter>. Выбирать можно и другим способом. В кодовом слове опции цветом выделяется одна из букв. Для выбора опции нажмите клавишу с нужной буквой, если выбирается опция из дополнительного меню, или комбинацию <А11>+<буква>, если выбирается опция главного меню. При работе с мышью для выбора из меню нужно переместить указатель мыши к соответствующей опции и нажать левую кнопку.
Выбор опции обычно приводит к развертыванию нового меню или диалогового окна. Если справа от опции стоит многоточие, эта опция связана с развертыванием диалогового окна, если стоит значок вызывается дополнительное меню.
Некоторые часто используемые опции, содержащиеся в дополнительных меню, можно вызвать непосредственно из режима редактирования. Справа от таких опций в меню указывается клавиша или комбинация клавиш, которая позволяет сделать это.
Следует учесть, что детальную информацию на английском языке о том или ином продолжении (опции) можно получить с помощью справочной службы, если клавишами перемещения курсора сместить указатель к этой опции и нажать клавишу <F1>.
П1.1.3. Работа с диалоговым окном
С помощью диалогового окна уточняется выбранное действие. В диалоговом окне имеется несколько полей, в которых группируется информация о возможностях среды и ее настройке.
Сразу после развертывания диалогового окна активизируется то или иное поле, которое выделяется цветом (оттенком). В активных полях ввода, переключаемых опций или выбора файла, кроме того, виден мигающий курсор.
Запомним следующие правила:
□ для перехода от одного поля к другому предназначена клавиша табуляции <Tab> (клавиша располагается в левой верхней части основной зоны клавиатуры и обозначена двумя разнонаправленными стрелками);
П для перехода внутри поля используются клавиши смещения курсора;
П закрыть диалоговое окно можно клавишей <Esc> (в этом случае не происходит никаких действий, связанных с окном) или клавишей <Enter> (в этом случае выполняются все указанные в окне установки или выбирается указанный файл).
При работе с мышью для выбора поля или опции следует сместить к этому полю (опции) указатель мыши и нажать левую кнопку. Для того чтобы закрыть окно и выполнить все связанные с ним установки, нужно указать мышью на соответствующее командное поле и нажать левую кнопку (в большинстве случаев это поле помечается символами ОК). Чтобы закрыть диалоговое окно и не выполнять никаких действий, используется поле [ I ] верхней рамки или командное поле со словом Cancel (Отмена).
Если по смыслу того или иного исполняемого действия необходимо ввести текстовую строку (например, имя файла), то сразу после раскрытия диалогового окна активизируется поле ввода с мигающим курсором. Следует ввести нужный текст и нажать <Enter>. При вводе текста действуют правила
редактирования, принятые в редакторе Турбо Паскаля: ошибочно введенный символ можно стереть клавишами <Backspace> или <Del>, причем этот символ может быть в любом месте строки (используйте клавишу перевода курсора влево, чтобы указать ошибочный символ, или укажите на него мышью и нажмите левую кнопку); ввод текста может происходить в режиме замены (переключается клавишей <Ins>). Если после подготовки текста вы нажмете <Enter>, текст будет введен и диалоговое окно закроется, однако если вы по каким-либо причинам измените свое решение, достаточно нажать <Esc>, чтобы закрыть диалоговое окно без ввода текста. Можно также повторить ранее введенный в аналогичной ситуации текст, например, уже вводившееся имя файла. Для этого вместо ввода ожидаемого текста нажмите клавишу смещения курсора вниз или укажите на поле В справа от поля ввода мышью и нажмите ее левую кнопку. В развернувшемся на экране небольшом окне содержится протокол работы с данной опцией. В протоколе сохраняются текстовые строки, введенные ранее, и можно клавишами смещения курсора подвести цветной указатель-прямоугольник к нужной вам строке протокола и нажать <Enter> или указать на эту строку мышью и дважды подряд нажать на левую кнопку (нажимать на кнопку нужно быстро, без заметной паузы, иначе среда не воспримет это как команду ввода и просто переместит указатель к нужной строке протокола).
Переключаемые опции задают выбор нужной настройки среды из двух или нескольких вариантов. Варианты могут быть связаны с включением или отключением какого-либо параметра среды. Например, можно потребовать от компилятора использовать арифметический сопроцессор или не использовать его. Слева от таких опций в диалоговом окне имеется небольшое поле выбора, выделенное квадратными скобками; включенный параметр отмечается символом X в этом поле: [X]; если поле пустое [ ], данный параметр не задействован. Если переключаемая опция задает выбор из нескольких вариантов, слева от указателя каждого варианта имеется поле выбора, выделенное двумя круглыми скобками, причем выбранный вариант отмечается точкой: (•).
Чтобы изменить состояние переключаемого параметра или выбрать другой его вариант, нужно клавишами смещения курсора подвести указатель к соответствующей опции и нажать клавишу <Пробел> (длинная клавиша в самом низу клавиатуры) или подвести указатель мыши к полю выбора слева от опции и нажать левую кнопку. Можно также нажать клавишу с буквой, которая выделена цветом в нужной опции.
В диалоговом окне обязательно имеется несколько командных полей, которые располагаются в правой или нижней части окна и выделяются цветом. С каждым таким полем связана некоторая команда. Эту команду можно выполнить, если активизировать поле клавишей табуляции и нажать клавишу <Enter> или указать на него мышью и нажать левую кнопку.
дисковые файлы.
□ Change dir. Позволяет изменить текущий каталог пользователя. В поле выбора диалогового окна этой опции (рис. П1.4) приводится дерево каталогов текущего диска.
Перемещаясь по этому дереву, можно указать на нужный каталог, после чего с помощью командного поля CHDIR (CHange DiRectory — изме-
П1.2. Система меню
Все управление средой Турбо Паскаля осуществляется В основном с помощью системы последовательно разворачивающихся меню. Лишь одно из „них — главное меню — постоянно присутствует на экране, остальные разво-
900900Часть IV. Приложения
900
900
нить каталог) сменить текущий каталог. Если выбран указатель DRIVES (Дисководы), можно изменить также текущий диск. Командное поле REVERT (Возвращаться) позволит восстановить прежний текущий каталог, если вы по каким-либо причинам решите отказаться от сделанного вами изменения (эта команда действует до момента закрытия окна).
(Л—(Л—Заме File AsЗаме File AsSave file asSave file asFilesFilesHID.PASBIORITM PAS ВI OS.PAS DATE.PAS HIM.PAS P.PAS Pl PASHID.PASBIORITM PAS ВI OS.PAS DATE.PAS HIM.PAS P.PAS Pl PASП i IП i I1991 2:42pn1991 2:42pnP9.PASРР.PAS PR.PAS PR1HTF.PASRIS.PAS UPSTRNG.PASP9.PASРР.PAS PR.PAS PR1HTF.PASRIS.PAS UPSTRNG.PASd:\meu_book\n.pasd:\meu_book\n.pasВ 10.РАЗВ 10.РАЗ7470Hay 6,7470Hay 6,
(Л—
(Л—
Заме File As
Заме File As
Save file as
Save file as
Files
Files
HID.PAS
BIORITM PAS ВI OS.PAS DATE.PAS HIM.PAS P.PAS Pl PAS
HID.PAS
BIORITM PAS ВI OS.PAS DATE.PAS HIM.PAS P.PAS Pl PAS
П i I
П i I
1991 2:42pn
1991 2:42pn
P9.PAS
РР.PAS PR.PAS PR1HTF.PAS
RIS.PAS UPSTRNG.PAS
P9.PAS
РР.PAS PR.PAS PR1HTF.PAS
RIS.PAS UPSTRNG.PAS
d:\meu_book\n.pas
d:\meu_book\n.pas
В 10.РАЗ
В 10.РАЗ
7470Hay 6,
7470Hay 6,
Рис. П1.3. Диалоговое окно опции File | Save as
pin= Change D irectory
Directory nane
OKOKDirectory treeDirectory treeiiiii i г шин им— n
OK
OK
Directory tree
Directory tree
Рис. П1.4.Диалоговое окно опции File | Change dir
Print. Печатает содержимое активного окна редактора на принтере или выводит его в файл (см. опцию Printer Setup).
PrinterSetup.Настраиваетсредунапечатьтекущегофайла.Полеввода
Filter path должно содержать имя программы-фильтра PRNFLTR.EXE и, возможно, путь к этой программе. Поле ввода Command line содержит выбор принтера/файла и параметров печати. Турбо Паскаль поддерживает три типа принтеров: матричные принтеры Epson, лазерные принтеры HP LaserJet и так называемые PostScript-принтеры, т. е. принтеры, "понимающие" язык описания страниц PostScript. Строки, которые следует поместить в поле Command line для указания нужного типа принтера, приведены в табл. П1.1.
Таблица П1.1. Выбор параметров печати
Строка
Смысл
$NOSWAP /ASCII
Неизвестный тип принтера; в выводной поток помещаются только текстовые символы и коды перевода строки
$NOSWAP /EPSON
Принтер Epson или совместимый с ним; система команд Epson поддерживается большинством матричных принтеров других фирм
$NOSWAP /HP
Принтер HP LaserJet или совместимый с ним; система команд HP LaserJet поддерживается большинством лазерных принтеров других фирм
$NOSWAP /PS
PostScript-принтер, воспринимающий команды на аппаратнонезависимом языке описания страниц PostScript
$NOSWAP /ОххххВывод в файл; имя файла хххх должно следовать сразу за
символами /о без пробелов; если файл не существует, он будет создан в процессе вывода
>---•---
Эти команды могут дополняться следующими параметрами:
/РххххОпределяет количество строк на одной странице (по умолча
нию 55)
/ТххххКоличество символов пробела, заменяющих один символ табу
ляции (по умолчанию 8)
Dos Shell. Обеспечивает временный выход в ДОС. Турбо Паскаль остается резидентным в оперативной памяти и занимает значительную ее часть, поэтому под управлением ДОС в этом состоянии могут выполняться только сравнительно небольшие по объему программы. Чтобы увеличить объем свободной памяти для ДОС, необходимо перед обращением к этой опции сбросить режим отладки клавишами <Ctrl>+<F2>. Для возврата в Турбо Паскаль нужно в ответ на запрос ДОС напечатать слово exit и нажать <Enter>.
О Exit. Завершает работу с Турбо Паскалем. Опция вызывается непосредственно из редактора клавишами <Alt>+<X>.
П1.2.3. Меню опции Edit
Undo. В активном окне редактора восстанавливает только что уничтоженную командой <Ctii>+<Y> или измененную строку. Турбо Паскаль создает специальный буфер изменений для каждой страницы редактора. Последовательное использование опции Undo может отменить все сделанные вами изменения текста. Опция может вызываться непосредственно из окна редактора клавишами <Alt>+<Backspace>.
П Redo. Отменяет действие предыдущей команды Undo.
о Cut. Удаляет выделенный блок из окна редактора и переносит его в буфер обмена (команды редактора для работы с блоками см. в главе Т). Опция вызывается непосредственно из редактора командой <Shift>+<Del>.
П Сору. Копирует выделенный блок из окна редактора в буфер обмена. Оп-ция вызывается непосредственно из редактора командой <Ctrl>+<Ins>.
П Paste. Копирует содержимое буфера обмена в окно редактора. Содержимое буфера остается без изменений и может использоваться повторно. Опция вызывается непосредственно из редактора командой <Shift>+<Ins>.
П Clear. Удаляет из окна редактора выделенный блок, но не помещает его в буфер. Удаленный фрагмент безвозвратно теряется. Опция вызывается непосредственно из редактора командой <Ctrl>+<Del>.
Show Clipboard. Показывает содержимое буфера обмена.
П1.2.4. Меню опции Search
П Find. Обеспечивает поиск нужного фрагмента текста в активном окне редактора. В момент обращения к этой опции в поле выбора диалогового окна (рис. П1.5) содержится слово, на которое указывал курсор в активном окне редактора. Вы можете ввести новое слово или текстовую строку, положение которой в редактируемом файле вам необходимо найти, или выбрать эту строку из протокола. После нажатия на <Enter> (или выбора командного поля ОК) редактор отыщет этот фрагмент в тексте и установит курсор на его начало.
-diFind
Text to find ■^■■■l■■^■^■■■^■i П
OptionsOptions[ 1 Case sensitive[ 1 Whole words only[ I Regular expression[ 1 Case sensitive[ 1 Whole words only[ I Regular expressionDirectionDirection(•) Forward( ) Backward(•) Forward( ) Backward
Options
Options
[ 1 Case sensitive
[ 1 Whole words only
[ I Regular expression
[ 1 Case sensitive
[ 1 Whole words only
[ I Regular expression
Direction
Direction
(•) Forward
( ) Backward
(•) Forward
( ) Backward
ScopeScope(•) Global< ) Selected text(•) Global< ) Selected textOriginOrigin( ) Fron cursor(•) Entire scope( ) Fron cursor(•) Entire scope
Scope
Scope
(•) Global
< ) Selected text
(•) Global
< ) Selected text
Origin
Origin
( ) Fron cursor
(•) Entire scope
( ) Fron cursor
(•) Entire scope
Рис. П1.5. Диалоговое окно опции Search | Find
Поиск управляется следующими переключаемыми опциями:
Case sensitive — учитывать величину букв (т. е. прописные буквы считать отличающимися от строчных; опция определена только для латинских букв);
Wholewordsonly— искать посовпадениюцелыхслов(еслитекст будет
обнаружен внутри более длинного слова, поиск продолжится дальше);
Regular expression — искать по выражению-описателю текста (см. ниже);
Forward — направление поиска вниз по тексту;
Backward — направление поиска вверх по тексту;
Global — искать во всем тексте;
Selected text — искать только в выделенном блоке;
From cursor — начать поиск от текущего положения курсора;
Entire scope — искать от начала текста.
В поле ввода можно ввести выражение-описатель текста, которое формируется из следующих специальных символов:
A — в начале строки в выражении-описателе означает начало текстовой строки в искомом тексте;
$ — в конце строки в выражении-описателе показывает конец текстовой строки;
. — на этом месте может стоять любой символ;
* — после любого символа означает любое количество (в том числе
ноль) этих символов, которые могут стоять вместо него; например, bo* означает b, bo, boo, bot, be;
+ — после символа означает один или больше (не ноль) этих символов, которые могут стоять вместо него; например, bo+ означает Ьо, bot, boo, но не b или be;
[ ] — означает один из символов, который может стоять внутри ско
бок, но не любой другой символ; например, [bot] означает b, о или t;
[^] — символ A в начале строки, заключенной в квадратные скобки, означает отрицание; например [Abot] — это любые символы, кроме b, о или t;
[-] — символ - между двумя символами, обрамленными квадратными скобками, определяет диапазон; например, [Ь-о] означает любые символы от b до о включительно;
\ — перед специальным символом означает сам символ; например, V означает сам символа не начало строки.
Replace. Отыскивает в окне редактора нужный текстовый фрагмент и заменяет его на новый. Диалоговое окно этой опции похоже на предыдущее со следующими отличиями:
в поле ввода Text to find нужно ввести искомый текст или выражение- описатель, в поле New text — тот текст, который будет вставляться вместо искомого;
переключаемая опция Prompt on replace указывает на необходимость запросить подтверждение у программиста, прежде чем производить замену найденного фрагмента;
командное поле Change all используется для поиска и замены всех обнаруженных вхождений фрагмента текста (поле ОК или нажатие на <Enter> означает только однократный поиск-замену).
Search again. Повторяет поиск или поиск и замену фрагмента текста для ранее установленных параметров.
Go to line number. Осуществляет позиционирование курсора в окне редактора на строку с указанным номером.
О Show last compiler error. Показывает строку текста программы, в которой была обнаружена синтаксическая ошибка при последнем прогоне компилятора.
Find error. Отыскивает в тексте программы строку, вызвавшую ошибку периода исполнения программы. Как правило, при работе в среде Турбо Паскаля ошибка периода прогона программы (например, деление на ноль) вызывает автоматическое прекращение прогона, в окне редактора появляется текст программы и курсор показывает то место, в котором возникла эта ошибка. Если программа компилировалась без привлечения средств отладки (см. ниже опцию Options | Compiler) или исполнялась вне среды Турбо Паскаля, нужно записать или запомнить два шестнадцатеричных числа, которые появляются на экране в сообщении об ошибке и указывают адрес ошибочной ситуации (задаются в формате SSSS:0000, где ssss — сегмент, а оооо — смещение), затем загрузить Турбо Паскаль (если программа исполнялась вне среды) и вызвать эту опцию. После ввода адреса ошибки среда начнет поиск ошибочного оператора.
Find procedure. Позволяет в режиме отладки отыскать в тексте программы нужную процедуру или функцию.
П1.2.5. Менюопции Run
О Run. Осуществляет компиляцию, компоновку и исполнение (прогон) программы из файла редактора. Компиляция проходит в режиме Make (см. ниже опцию Compile | Make). Если программа уже откомпилирована к этому моменту, то среда сразу начнет ее прогон. Опция вызывается непосредственно из редактора командой <Ctrl>+<F9>.
Go to cursor. Начинает или продолжает режим отладки исполняемой программы под управлением встроенного отладчика. Вначале осуществляются все действия по компиляции и компоновке программы, затем программа начинает работать обычным образом (экран переходит в режим воспроизведения окна программы) и останавливается перед выполнением первого оператора из той строки, на которую указывает курсор.
В этот момент экран возвращается в режим воспроизведения окна редактора, а строка с курсором выделяется цветным прямоугольником. Можно перевести курсор к новой строке и вновь выбрать эту опцию — программа остановится перед выполнением нового оператора и т. д. В этом режиме доступны все средства встроенного отладчика. Для прекращения отладки нажмите клавиши <Ctrl>+<F2>. Опция вызывается непосредственно из редактора клавишей <F4>.
Trace into. Начинает или продолжает режим отладки исполняемой программы под управлением встроенного отладчика. Если к моменту обращения к этой опции режим отладки не был запущен, он запускается точно так, как если бы была вызвана опция Go to cursor, однако программа останавливается перед первым исполняемым оператором, т. е. указатель будет указывать на слово begin, открывающее раздел операторов основной программы. Если режим отладки уже был запущен, вызов этой опции приведет к выполнению всех действий, запрограммированных в текущей строке, и указатель сместится к следующей строке программы.
. Если текущая строка содержит обращение к процедуре или функции, управление будет передано внутрь этой процедуры (функции) и программа остановится перед исполнением ее первого оператора. Таким образом, с помощью этой опции можно по шагам прослеживать исполнение всех нестандартных процедур (функций). Опция вызывается непосредственно из редактора клавишей <F7>.
Step over. Так же, как и предыдущая опция, начинает или продолжает пошаговое прослеживание работы программы, но не прослеживается работа вызываемых процедур и функций. Опция вызывается непосредственно из редактора клавишей <F8>.
П Program reset. Сбрасывает все ранее задействованные отладочные средства и прекращает отладку программы. Удаляет исполнявшуюся программу из памяти и закрывает все открытые в ней в этот момент файлы. Опция вызывается непосредственно из редактора командой <Ctrl>+<F2>.
П Parameters. Позволяет задать текстовую строку параметров, которые ДОС передает вызываемой программе. Эта строка передается программе, находящейся в окне редактора, при ее прогоне.
П1.2.6. Меню опции Compile
О Compile. Компилирует программу или модуль, который загружен в данный момент в активное окно редактора. Если в этой программе (модуле) содержатся обращения к нестандартным модулям пользователя, последние уже должны быть откомпилированы и храниться на диске в виде TPU-файлов. Опция вызывается непосредственно из редактора командой <Alt>+<F9>.
Make. Создает программу, которая, возможно, содержит включаемые файлы и/или обращения к нестандартным модулям. Прежде всего компилируется начальный файл, если, разумеется, он определен опцией Compile | Primary file (см. ниже). Если начальный файл не задан, компилируется файл из активного окна редактора. Если в процессе компиляции встретилось объявление нестандартного модуля, среда проверяет, были ли сделаны в соответствующем PAS-файле с текстом программы этого модуля какие-либо изменения с момента последней его компиляции и получения TPU-файла; если изменения были, TPU-файл создается вновь, а если изменения коснулись его интерфейсной части, будут перекомпилированы также все другие объявленные в программе модули, в которых содержатся обращения к измененному модулю. Однако, если PAS-файл с текстом измененного модуля не будет найден, система воспользуется существующим TPU-файлом без контроля его "свежести". Отметим, что этот контроль осуществляется по дате и времени создания PAS-файла и соответствующего ему TPU-файла. Если системная дата установлена неправильно, среда Турбо Паскаля может ошибочно откомпилировать PAS-файл, для которого существует адекватный ему TPU-файл.
Опция существенно упрощает процесс разработки многофайловых программ, т. к. всегда компилируется только тот минимум файлов, которых коснулись сделанные в программе изменения. Опция вызывается непосредственно из редактора клавишей <F9>.
Build. Эта опция полностью подобна опции Make за одним исключением: для всех TPU-файлов отыскивается соответствующий PAS-файл и осуществляется его перекомпиляция независимо от того, были ли сделаны в нем изменения или нет. После компиляции в этом режиме вы можете быть уверены в том, что в полученной программе учтены все изменения.
Destination. Эта опция управляет выходом компилятора: если справа от нее стоит кодовое слово Memory (Память), выходной файл компилятора будет сохранен в оперативной памяти и может затем сразу же запускаться из Турбо Паскаля без его загрузки с диска; если справа стоит кодовое слово Disk (Диск), файл с кодом программы будет сохранен на диске в виде файла с расширением ЕХЕ. Если объявлен начальный файл, его имя будет присвоено имени вновь создаваемого ЕХЕ-файла, в противном случае ЕХЕ-файл получит имя файла из того окна редактора, которое содержит текст основной программы. Независимо от значения этого параметра TPU-файлы, создаваемые в режимах Run | Run, Compile | Make и Compile | Build, будут помещены на диск.
Primary file. Задает имя начального файла. Если это имя задано, то вне зависимости от того, какая часть программы загружена в данный момент в окна редактора, ее компиляция в режимах Run, Make и Build будет начинаться с этого файла. Чаще всего начальный файл содержит текст ос-
тгттгтBreakpoints listBreakpoints listBIO.PASВ10.PASBIO.PASВ10.PASLine It ConditionLine It ConditionIB74IB74PassPassИ8И8UKUKDeleteDeleteU lewU lewJ lean a 11J lean a 11
тгт
тгт
Breakpoints list
Breakpoints list
BIO.PAS
В10.PAS
BIO.PAS
В10.PAS
Line It Condition
Line It Condition
IB
74
IB
74
Pass
Pass
И
8
И
8
UK
UK
Delete
Delete
U lew
U lew
J lean a 11
J lean a 11
новной части программы. В этом случае при загрузке в окно (окна) редактора включаемого файла или файла-модуля компилятор сумеет правильно построить программу. Если начальный файл не указан, то компиляция в режимах Run, Make и Build возможна только в том случае, когда в активном окне редактора находится основная программа.
Clear primary file. Очищает имя начального файла, заданное опцией Primary File.
Information. Показывает статистику программы.
П1.2.7. Меню опции Debug
О Breakpoints. Эта опция позволяет просмотреть все контрольные точки и при необходимости удалить, переместить любую контрольную точку или задать условия ее работы. В диалоговом окне опции (рис. П1.6) приводится список всех контрольных точек с указанием имени файла (колонка Breakpoints list), номера строки в этом файле (колонка Line #), с которой связана контрольная точка, условия, при котором срабатывает останов программы в этой точке (колонка Condition), и количества проходов (колонка Pass), в течение которых останов не происходит. Цветным указателем выделяется текущая контрольная точка.
Breakpoints
Рис. П1.6. Диалоговое окно опции Debug | Breakpoints
В качестве условия, управляющего работой контрольной точки, можно указать любое допустимое условное выражение, которое будет вычисляться в ходе исполнения программы; контрольная точка будет игнорироваться до тех пор, пока значение этого выражения не окажется равным True. Вычисление выражения и останов будут происходить только тогда, когда с момента запуска программы будет выполнено заданное количество обращений к строке с контрольной точкой (колонка Pass).
С помощью командного поля Edit можно отредактировать текущую точку, т. е. установить новые ее параметры (файл, номер строки, условие и количество проходов). Задав новый файл и/или номер строки, можно пе-
реместить точку на новое место. Командное поле Delete используется для удаления текущей точки, а поле Clear all — для удаления всех контрольных точек. С помощью поля View можно загрузить в окно редактора и установить его содержимое так, чтобы увидеть соответствующую контрольную точку.
О Call stack. Делает активным окно программного стека. В этом окне отображаются все вызовы процедур и функций. Внизу стека находится program, т. е. имя вашей программы, в вершине стека — текущая процедура (функция). Каждое новое обращение к процедуре (функции) отображается в этом окне в виде имени подпрограммы и списка параметров вызова. Эта опция вызывается из редактора командой <Ctrl>+<F3>.
О Register. Делает активным окно регистров. В этом окне отображается текущее состояние всех регистров микропроцессора ПК.
Watch. Делает активным окно отладки.
Output. Делает активным окно программы.
User screen. Делает активным окно программы и распахивает его на весь экран. Вызывается из редактора комбинацией клавиш <Alt>+<F5>.
Evaluate/Modify. Эта опция дает возможность в процессе отладки просмотреть содержимое любой переменной или найти значение любого выражения. При необходимости можно с ее помощью установить новое значение любой переменной. При обращении к ней на экране разворачивается диалоговое окно, содержащее три поля: Expression (Выражение), Result (Результат) и New value (Новое значение) — p^ic. П1.7.
pLU —1 Evaluate anil Modify 11'— i
Expression
Evaluate
ResultЕЖЕШВ^Н
о ЕИП^ВВо ЕИП^ВВ
о ЕИП^ВВ
о ЕИП^ВВ
Рис. П1.7. Диалоговое окно опции Debug | Evaluate | Modify
В первом поле следует ввести имя любой переменной или некоторое выражение. Сразу после того, как вы нажмете клавишу <Enter>, в поле Result появится соответствующее значение или сообщение unknown identifier (Неопределенный идентификатор), если такая переменная не определена в вашей программе. К моменту вызова опции программа должна находиться в режиме отладки, в противном случае это сообщение будет даваться для любых переменных и выражений с их участием.
Если вы запросили значение переменной, вы можете перевести курсор в нижнее поле New value и установить новое значение переменной — это значение будет немедленно передано в программу.
При обращении к опции среда анализирует ближайшее окружение курсора в активном окне редактора и, если это возможно, выделяет идентификатор или константу, на которую указывает курсор. Выделенное автоматически переносится в поле Expression и предлагается в виде вычисляемого выражения. Таким образом, если перед вызовом этой опции установить курсор на интересующий вас идентификатор, останется лишь нажать клавишу <Enter>, чтобы туг же получить его значение в поле Result. Если предлагаемый идентификатор вас не устраивает, можно его отредактировать или ввести новый. Для ввода нового нажмите на любую алфавитно-цифровую клавишу, и предлагаемый в окне идентификатор исчезнет, заменившись вновь введенным символом. Для перехода к редактированию предлагаемого идентификатора следует сразу же после появления окна нажать <Ноте> или <End>, затем перевести курсор к нужному месту идентификатора и отредактировать его с использованием клавиш <Ins>, <Del> и <Backspace>. Наконец, если сразу после вызова опции нажать клавишу <End>, а затем — клавишу перевода курсора вправо, появившийся в поле Expression идентификатор дополнится символом, расположенным справа от него в тексте программы. Теперь при каждом нажатии на клавишу перевода курсора вправо очередной символ из текста программы будет копироваться в поле Expression. Описанная возможность существенно облегчает ввод длинных выражений и составных идентификаторов.
Вы можете ввести (и вычислить) выражения с участием констант и переменных из вашей программы, а также некоторых стандартных функций. Выражения составляются по правилам формирования выражений Турбо Паскаля, причем тип выражения может быть любым стандартным или определенным в программе типом. Фактически единственным ограничением на выражения является то, что в них нельзя использовать вызовы нестандартных функций (можно использовать только следующие предварительно определенные функции и константы: ABS, ADDR, CHR, DSEG, HI, IORESULT, LENGTH, LO, MAXAVAIL, MEMAVAIL, 0D6, OFS, ORD, PRED, PTR, ROUND, SEG, SIZEOF, SPTR, SSEG, succ, SWAP, TRUNC, а также массивы MEM, ME MW и MEML).
Формат выводимого в поле Result результата по умолчанию совпадает со стандартным для Турбо Паскаля, но вы можете управлять этим форматом с помощью специальных ключей. Ключ помещается в конце выражения (переменной) и отделяется от него запятой. Например, запрос
MemL[$40:0] ,h
выдаст содержимое четырех байтов оперативной памяти в шестнадцатеричном формате.
Допускаются следующие ключи:
с — формат CHAR (Символьный);
s — формат STRING (Строковый);
D — формат целых десятичных чисел;
$, н или х —шестнадцатеричный формат;
Fn — формат REAL (Вещественный); число n определяет количество значащих цифр в выводимом результате;
м — формат копии памяти: выводится содержимое переменной побайтно, начиная с младшего байта, в шестнадцатеричном формате; если ключ указан для выражения, он игнорируется;
р — формат POINTER (Указатель); результат выводится в виде PTR(SEG,OFS) , Например, Ptr($3EA1,$20) ;
R — формат RECORD (Запись); выводится в круглых скобках список полей с указанием их значений, например: (X: l;Y:10;Z:5).
Перед ключом формата можно указать целое число, которое трактуется как коэффициент повторения. Если, например, в поле Expression содержится
UserList[0],4D
то в качестве результата будет выдано в формате целых десятичных чисел значение четырех последовательных элементов массива UserList, начиная с нулевого элемента, т. е. UserList[0], UserList [1] и т. д. Коэффициент повторения относится только к переменным (по отношению к выражениям он игнорируется) и его можно указывать без ключа формата. Если, например,
var
UserList : array [0;.20] of integer;
то обращения
UserList[0],4D
UserList[0],4
дадут идентичные результаты.
Выражения в поле Expression можно задавать многократно, допускаются также ранее введенные выражения из протокола опции. Выход из диалога — с помощью клавиши <Esc> или полей Cancel и [ I ] — при работе с мышью. Опцию можно вызвать непосредственно из редактора командой <Ctrl>+<F4>. Отметим, что эта опция может использоваться как встроенный в Турбо Паскаль калькулятор.
Add watch. С помощью этой опции можно указать отладчику те переменные и/или выражения, за изменением значений которых вы хотели бы наблюдать при отладке программы. Указанные переменные и выражения вместе с их текущими значениями будут постоянно содержаться в окне наблюдения, доступ к которому возможен с помощью клавиши <F6>. Если вы сделаете активным это окно, вы сможете перемещаться в нем, вызывая при необходимости "прокрутку" его содержимого. Таким образом можно наблюдать за произвольным количеством переменных и выражений.
Опцию можно вызвать непосредственно из редактора командой <Ctrl>+<F7>. При этом справедливо все сказанное выше относительно вызова опции Debug | Evaluate | Modify, т. е. выделение идентификатора, его редактирование и дополнение, использование коэффициента повторения и ключей формата. Сразу после добавления отслеживаемого выражения активизируется окно наблюдения.
Add breakpoint. С помощью этой опции меню Debug вы можете установить в текущей строке контрольную точку. Текущая строка — это строка с курсором в окне редактора. Если для нее установлена контрольная точка, строка выделяется цветом (яркостью). В программе можно установить произвольное количество контрольных точек. После запуска профаммы с установленными контрольными точками (точкой) отладчик прекратит исполнение профаммы перед выполнением того оператора, который содержится в первой (по логике работы профаммы) контрольной точке. При этом на экране появится окно редактора с контрольной точкой, и среда перейдет к режиму отладки профаммы. Если контрольная точка задана для строки, не содержащей исполняемого оператора (например, для строки со словом begin), профамма остановится перед первым после этой строки исполняемым оператором. Останов в контрольной точке можно сделать условным. Однажды установленная контрольная точка действует на каждое очередное обращение к соответствующей строке программы.
В диалоговом окне опции поле Condition задает условие останова. Это может быть произвольное логическое выражение с использованием любых переменных, констант, вызовов функций. Если к моменту исполнения оператора с контрольной точкой это выражение имеет значение True, произойдет останов прогона и среда перейдет к режиму отладки. Поле Pass count указывает количество обращений к оператору с контрольной точкой, после которого произойдет останов.
С помощью комбинации клавиш <Ctrl>+<F8> контрольную точку можно установить/снять непосредственно из режима редактирования.
30 Зак 941
П 1.2.8.Меню опции Tools
□ Messages. Активизирует окно сообщений. Окно сообщений содержит вывод инструментальных программ типа GREP и позволяет использовать эти сообщения для поиска нужных фрагментов в текстах программ. Для поиска фрагмента подведите цветной указатель окна Messages к нужному сообщению и нажмите <Пробел> или дважды щелкните по этому сообщению мышью. Среда отыщет и покажет файл с нужным фрагментом текста программы.
Go to next. Ищет фрагмент, заданный следующим сообщением в окне Messages. Закрывает окно Messages, открывает нужный файл и позиционирует курсор на строку, соответствующую следующему по отношению к текущему (т. е. выделенному цветом) сообщению в окне. Опция вызывается непосредственно из окна редактора клавишами <Alt>+<F8>.
Go to previous. Ищет фрагмент, заданный предыдущим сообщением в окне Messages. Опция вызывается непосредственно из окна редактора клавишами <Alt>+<F7>.
□ Grep. Инициализирует работу утилиты GREP. В строке Enter program arguments диалогового окна опции необходимо перечислить аргументы вызова GREP: имена процедур, функций, переменных, которые необходимо отыскать в текстовых файлах, а также имена этих файлов. По умолчанию параметром вызова GREP указывается *.PAS, что означает поиск по всем PAS-файлам текущего каталога. Если к моменту вызова опции текстовый курсор стоял на некотором имени процедуры, функции или переменной, это имя появится в качестве аргумента вызова перед *.PAS. После нажатия <Enter> GREP начнет поиск имени во всех файлах. В окне Messages появятся сообщения GREP с указанием имени файла, номера строки и фрагмента текста программы, где было найдено нужное имя. Опция вызывается непосредственно из окна редактора клавишами <Shift>+<F2>. .
П1.2.9. Меню опции Options
Compiler. Эта опция задает несколько параметров, с помощью которых вы можете управлять генерацией машинного кода программы. Вид диалогового окна этой опции показан на рис. П1.8.
Опция Force far calls определяет генерацию машинного кода, рассчитанного на дальнюю модель памяти. В соответствии с архитектурой центрального процессора ПК могут использоваться две модели вызова процедур и функций: ближняя (NEAR) и дальняя (FAR). Ближняя модель обеспечивает адресацию в пределах текущего сегмента, дальняя предназначена для организации межсегментных связей. Если опция установлена
в активное состояние, все вызовы процедур и функций будут использовать дальнюю (межсегментную) модель, в противном случае — ближнюю (внутрисегментную) модель. Ближняя модель дает более экономный код программы и исполняется быстрее, однако при организации оверлея и вызове из программы других программ с помощью процедуры EXEC нужно использовать дальнюю модель.
OKOKConditional definesConditional defines
OK
OK
Conditional defines
Conditional defines
Рис. П1.8. Диалоговое окно опции Options | Compiler
При активном состоянии опции Overlays allowed компилятор генерирует дополнительный код при компиляции оверлейных модулей. Этот код позволяет передавать строки и множества в качестве фактических параметров при обращении из одного оверлейного модуля в другой. Отметим, что Турбо Паскаль считает модуль оверлейным только в том случае, когда он откомпилирован с активной опцией Overlays allowed.
Опция Word align data определяет способ выравнивания переменных и констант в памяти: если опция активна, каждая переменная и константа начинается в байте с четным адресом, т. е. выравнивается на начало машинного слова, если неактивна, переменные и константы располагаются в памяти сплошной цепочкой. Выравнивание по словам увеличивает скорость выполнения программ ценой несколько неэкономного расходования памяти.
Активное состояние опции 286 instructions предписывает компилятору создавать код программы с полным набором команд микропроцессора Intel 80286. В неактивном состоянии опции компилятор порождает код, соответствующий набору команд микропроцессора Intel 8088 и представляющий собой подмножество команд микропроцессора Intel 80286. В целях переносимости программ имеет смысл устанавливать неактивное состояние этой опции, т. к. в процессе счета программа не проверяет
фактическое наличие микропроцессора Intel 80286 и не может эмулировать его систему команд.
В активном состоянии опции Range checking генерируется дополнительный код для проверки возможного выхода значений переменных за границы диапазона. Программа, откомпилированная с активной опцией, занимает несколько больший объем памяти и исполняется медленнее, зато дает возможность контролировать выход индексов за пределы, определенные в описании массивов.
Опция Stack checking аналогична опции Range checking и контролирует возможное переполнение программного стека.
Опция I/O checking используется для включения/отключения генерации программных кодов, контролирующих правильность операций ввода/ вывода.
Установка в неактивное состояние опции Strict var-strings позволяет отказаться от проверки на совпадение длины формального и фактического параметра-строки при обращении к процедуре или функции. Если установлено активное состояние этой опции, компилятор вставляет в программу команды для сравнения длины строк.
В активном состоянии опции Complete boolean eval все логические выражения вычисляются в программе полностью, в неактивном состоянии вычисление прекращается в тот момент, когда становится ясен окончательный результат. Допустим, имеется такой фрагмент программы:
BooleanBooleanFunction МуFunс(var x : integer) begin
Boolean
Boolean
x := х+1;
MyFunc := х>10
end;
x : = 0;
if False and MyFunc(х) then x := 10;
После его компиляции с неактивной опцией Complete boolean eval исполнение этого фрагмента даст х=о, т. к. не произойдет обращения к функции MYFUNC: выражение False and MYFUNC всегда имеет значение False вне зависимости от того, что является вторым операндом операции and. Если же к моменту компиляции программы было установлено активное состояние этой опции, вычисление логического выражения продолжится до конца, состоится вызов функции MYFUNC и переменная х по-
лучит значение 1. Разумеется, и в том и в другом случае не будет исполняться оператор х := 10.
Активное состояние опции Extended syntax дает возможность использовать в программе расширенный синтаксис Турбо Паскаля, который разрешает вызывать определенные программистом функции не только в выражениях, но и в виде отдельного оператора, подобно вызову процедуры. Отметим, что эта опция не распространяется на стандартные функции.
Опция 8087/80287 ориентирует компилятор на работу с арифметическим сопроцессором. При неактивном состоянии все операции с вещественными данными реализуются программно и в программе допустимым является только один вещественный тип REAL. Если опция установлена в активное состояние, компилятор будет создавать код, содержащий обращения к числовому сопроцессору, причем программе становятся доступны также типы SINGLE, DOUBLE, EXTENDED и COMP (см. главу 4).
Опция Emulation указывает компилятору, надо ли создавать такой код программы, который будет одинаково пригоден при работе на ПК с арифметическим сопроцессором или без него. Программа сама определит наличие сопроцессора и, если он имеется, будет использовать все его возможности; если же сопроцессора нет, его работа будет эмулироваться программно. В этом случае программе становятся доступны все вещественные типы. Активное состояние этой опции увеличивает размеры программы за счет подключения процедур эмуляции, но делает ее независящей от аппаратных особенностей ПК. Отметим, что опция Emulation игнорируется, если неактивна опция 8087/80287.
Активное состояние опции Debug information устанавливает режим генерации отладочной информации в процессе компиляции программы. Отладочная информация представляет собой специальные таблицы, позволяющие установить однозначную связь между операторами исходного текста программы и теми кодами, которые порождает компилятор. Только после компиляции с активной опцией Debug information становится возможной автоматическая локализация ошибки периода исполнения, а также пошаговая отладка программы. Активное состояние опции увеличивает размер TPU-файлов и объем оперативной памяти, занимаемой программой, если она работает под управлением среды Турбо Паскаля, но не влияет на размер той же программы, запускаемой вне среды под управлением ДОС. Иными словами, дополнительные таблицы отладки загружаются в память только средой Турбо Паскаля, a ДОС игнорирует эту информацию.
Опция Local symbols аналогична опции Debug information и относится к именам локальных и глобальных переменных: если опция установлена в активное состояние, среда получит возможность доступа на этапе отлад-
ки к переменным по их именам.
В поле Conditional defines вы можете задать условия, которые используются в операторах условной компиляции (см. разд. П1.3).
□ Memory sizes. В диалоговом окне опции Options | Memory sizes существуют три поля ввода. С их помощью можно регулировать размеры памяти, которую занимает работающая программа:
Stack size — размер программного стека; по умолчанию 16 384 байт, максимум — 65 535 байт;
Low heap limit — минимальный размер кучи; по умолчанию 0;
High heap limit — максимальный размер кучи; по умолчанию 655 360 байт; этот параметр не может быть меньше параметра Low heap limit.
Для оценки необходимых программе объемов памяти следует учесть, что все локальные переменные при каждом обращении к процедуре (функции) размещаются в стеке, а при выходе из нее стек освобождается. Таким образом, требуемый размер стека определяется количеством вложенных вызовов процедур (функций) и суммарным количеством их локальных переменных. Величина кучи определяется реальными потребностями программы в динамической памяти. Если установлен максимально возможный размер кучи 655 360 байт, то такая программа после загрузки займет всю доступную оперативную память, а это исключит возможность запуска из нее других программ.
□ Linker. В диалоговом окне этой опции имеются две группы переключаемых опций, с помощью которых регулируется режим работы компоновщика Турбо Паскаля: опции группы Map file управляют выходным документом компоновщика, опции группы Link buffer — использованием памяти. Выходной документ компоновщика (карта распределения памяти) бывает полезен при отладке программы с помощью внешнего отладчика. Опция Off запрещает формирование -карты. Опция Segments формирует сегментную карту с указанием адреса запуска программы и сообщениями об ошибках периода компоновки программы. Опция Public дает такую же карту, как и опция Segments, и дополнительно приводит список внешних символов в алфавитном порядке. Наконец, опция Detailed дает полную карту распределения памяти. Опция Memory предписывает компоновщику использовать оперативную память для размещения своих таблиц и временного хранения компонуемой программы, при активной опции Disk компоновщик для этих целей использует пространство диска. Если активна опция Memory, компоновщик будет работать значительно быстрее, однако при разработке крупных программ ему может не хватить оперативной памяти и он не скомпонует программу.
Вообще, следует помнить о том, что даже довольно большой объем оперативной памяти ПК (640 Кбайт) может оказаться недостаточным для разработки с помощью среды Турбо Паскаля крупных программных про-
ектов: ведь сам Турбо Паскаль занимает в памяти 304 Кбайт. Если обнаружена нехватка памяти, среда дает сообщение
Out of memory
(Не хватает памяти)
и устанавливает курсор в конец программы. В этом случае следует, прежде всего, попытаться сэкономить память за счет установки в активное состояние опции Disk. Еще примерно 44 Кбайт памяти можно сэкономить за счет отказа от автоматической загрузки системной библиотеки TURBO.TPL (см. ниже опцию Options | Environment | Startup). Наконец, может оказаться необходимым отказ от услуг самой среды Турбо Паскаля на этапе прогона программы. Для этого нужно установить опцию Compile | Destination в состояние Disk, создать программу с помощью опций Make или Build, выйти из среды и запустить программу. В этом случае программа получает в свое распоряжение всю память ПК, но вы лишаетесь возможности отлаживать ее средствами встроенного отладчика. В некоторых случаях за счет оверлейной структуры программы (см. главу 11) ее размеры удается уменьшить настолько, что даже крупная программа помещается в памяти вместе со средой. Если, несмотря на все меры экономии, памяти все-таки не хватает, можно полностью отказаться от услуг среды и использовать автономный компилятор-компо-' новщик ТРС.ЕХЕ.
О Debugger. Эта опция определяет используемый отладчик и режим обновления экрана дисплея в процессе отладки. Если активна опция Integrated, к программе будет добавлена информация, необходимая для работы встроенного отладчика. Только в этом состоянии опции можно использовать контрольные точки и пошаговую отладку. При активизации опции Standalone к ЕХЕ-файлу программы будут добавлены соответствующие таблицы, которые позволят вести отладку программы вне среды Турбо Паскаля с помощью внешнего отладчика TD.EXE. Три других опции сообщают среде, в каких случаях следует переключать экран с воспроизведения окна редактора на окно программы. В режиме Smart среда будет переключать экран по мере надобности •— только если в очередном операторе программы было обращение к экрану для вывода или к клавиатуре для ввода. Переключение на окно программы будет также и тогда, когда отладчик "перескакивает" через вызов процедуры (функции) по
клавише <F8>, но в этой процедуре (функции) есть обращение к экрану. Если установлен режим Always, переключение будет происходить перед исполнением любого оператора программы. Наконец, в режиме None среда никогда не переключает экран, даже если он требуется для вывода данных, т. е. вывод программы будет накладываться на текст программы. Испорченный в результате такого прогона текст в окне редактора можно обновить с помощью опции Window | Refresh display.
Directories. Четыре поля ввода в диалоговом окне опции Options | Directories позволяют определить четыре группы функциональных каталогов Турбо Паскаля.
EXE & TPU directories указывает тот каталог, в который будут помещаться готовые к работе программы в виде ЕХЕ-файлов и результат компиляции модулей в виде TPU-файлов. Если каталог не указан, эти файлы будут помещаться в текущий каталог — именно такое состояние этой опции соответствует стандартной настройке среды. Не рекомендуется устанавливать в этой опции каталог, содержащий файлы системы Турбо Паскаль.
Include directories — здесь следует перечислить те каталоги, в которых Турбо Паскаль будет искать включаемые файлы, т. е. файлы, задаваемые директивой компилятору {$ I <имя файла>}. При указании нескольких каталогов, они перечисляются через точку с запятой. Отметим,чтопоискв этихкаталогахидеттольковтомслучае,если
включаемый файл не найден в текущем каталоге.
Unit directories — задает каталоги, в которых среда ищет TPU-файлы, если они не обнаружены в текущем каталоге. В этой опции обычно указывается каталог, содержащий файл GRAPH.TPU (если в программе используются графические средства Турбо Паскаля), а также каталог, указанный в поле EXE & TPU directories. При перечислении нескольких каталогов они разделяются точкой с запятой.
Если в своей программе вы используете внешние процедуры и функции (см. главу 11), они должны быть представлены в виде OBJ-файлов. Поле Object directories задает один или несколько каталогов, в которых Турбо Паскаль будет искать эти файлы, если их нет в текущем каталоге.
□ Environment. При вызове этой опции разворачивается еще одно дополни-тельное меню, содержащее пять опций. Эти опции описываются ниже.
•Environment|Preferences.Диалоговоеокноэтойопциипоказанона
рис. П1.9.
О Опции группы Screen sizes определяют размер текстового экрана: если активна опция 25 lines, на экране будет 25 строк, если активна опция 43/50 lines, на экране будет 43 или 50 строк в зависимости от того, оснащен ли ваш ПК дисплеем с адаптером EGA или VGA.
Опции Source Tracking определяют способ использования текущего окна редактора в процессе отладки. Если активна опция New window, прослеживаемая программа будет загружаться в новое окно редактора (если, разумеется, она еще не загружена в одно из ранее открытых окон), если активна опция Current window — в текущее окно.
Рис.Диалоговое окно
опции Options | Environment | Preferences
Группа опций Auto save регулирует запись на диск текущей программы и информации о ней. Опция Editor files предписывает автоматически сохранять на диске содержимое всех окон редактора перед прогоном программы, если только текст в окне изменялся после последней записи на диск. Чрезвычайно полезная опция, которую я настоятельно рекомендую всегда устанавливать в активное состояние — автосохранение измененного текста программы избавит васнеприятностей при работе с плохо отлаженной
программой или на ПК с ненадежной (сбойной) памятью. Разумеется, дополнительное обращение к диску затягивает переход к прогону/отладке, однако вы по достоинству оцените эту услугу среды после первого же "зависания" программы. Опция Environment задает режим автоматического сохранения текущей настройки среды Турбо Паскаля в файле конфигурацииНастройка
среды будет сохранена автоматически при временном или окончательном выходе из Турбо Паскаля, если она изменилась с момента последней записи в этот файл. Активное состояние опции Desktop требует от среды автоматически сохранять на диске в файле информацию об открытых окнах, их размерах, контрольных точках и т. п. Использование этой опции позволит при очередной загрузке Турбо Паскаля автоматически получить точную копию того состояния среды, в котором вы покинули ее в последний раз. Следует учесть, что опция Desktop игнорируется, если не был создан конфигурационный файл TURBO.TP.
О Группа опций Desktop file уточняет режим создания файла TURBO.DSK: если выбрана опция None, файл не будет создаваться, даже если опция Desktop активна; опция Current directory определяет размещение этого файла в текущем каталоге, а опция Config file directory — в том же каталоге, где размещается конфигурационный файл TURBO.TP.
Environment | Editor. Диалоговое окно этой опции показано на рис. ШЛО.
-in-inEditor optionsEditor options
-in
-in
Editor options
Editor options
Editor options
OK■Cancel■HelpOK■Cancel■HelpI ] Create backup f i les 1X1 Insert node [X] flutoindent node [ ] Use tab characters [ ] Optinal fill [X] Backspace unindents [1 Cursor through tabs
OK■Cancel■Help
OK■Cancel■Help
Tab size EH
Рис. П1.1О.Диалоговое окноопции Options | Environment | Editor
Активное состояние опции Create backup files заставит среду при записи файла с текстом программы проверить, существует ли уже одноименный файл; если существует, он будет переименован в файл с тем же именем и расширением ВАК, что позволит иметь на диске предыдущую версию только что сохраненной информации. Опция Insert mode указывает на основной режим работы редактора: если она активна, редактор работает в режиме вставки, неактивна — в режиме замены. Опция определяет лишь начальный режим работы сразу после загрузки Турбо Паскаля, т. к. в процессе работы с редактором вы всегда можете переключить режим клавишей <Ins>. Активизация опции Autoindent mode облегчит вам выделение отступами условных и составных операторов, т. к. в этом случае каждая новая строка будет начинаться с таким же отступом от левого края экрана, что и предыдущая строка. Опция Use tab characters определяет использование символов табуляции: если она активна, нажатие на клавишу табуляции вставит в текст специальный символ табуляции, если неактивна, в текст будет вставлено нужное число пробелов. Дополнительно к этому активная опция Optimal fill заставит редактор вставлять минимально возможное число символов табуляции и пробелов при нестандартной длине табу- ляционного интервала (не 8 символов). Активная опция Backspace unindents связывает с клавишей <Backspace> функции уничтожения предыдущего символа с учетом автоотступа: если слева от курсора нет ни одного значащего символа, нажатие клавиши <Backspace> сдвинет
курсор к позиции предыдущего автоотступа. Обычно эта опция устанавливается в активное состояние одновременно с опцией Autoindent mode. Опция Cursor through tabs определяет перемещение курсора по строке при нажатии клавиши смещения курсора влево или вправо: если она активна, курсор смещается скачком всякий раз, когда в строке встречается символ табуляции; при неактивной опции он всегда смещается только на одну позицию. В поле Tab size вы можете задать длину табуляционного интервала.
Environment | Mouse.
Группа опций Right mouse button определяет способ использования правой кнопки мыши в сочетании с нажатой и удерживаемой клавишей <Ctrl>. Полностью все опции перечислены в табл. П1.2
Таблица П1.2. Опции группы Right mouse button
Опция
Функция правой кнопки
Nothing
Нажатие на кнопку игнорируется
Topic search
Эквивалент действию опции Help I Topic search
Go to cursor
Эквивалент действию опции Run I Go to cursor
Breakpoint
Включает/отключает контрольную точку
Evaluate
Эквивалент действию опции Debug | Evaluate
Add watch
Эквивалент действию опции Debug | Add watches
Например, если активной сделана опция Go to cursor, то при работе с мышью нажатие на ее правую кнопку при нажатой и удерживаемой клавише <Ctrl> будет эквивалентно вызову опции Run | Go to cursor или нажатию на клавишу <F4>.
При обращении к справочной службе в тексте могут встретиться ссылки на другие разделы справочных сообщений. Эти ссылки выделяются в справочном сообщении цветом (в стандартной настройке это — яркий желтый цвет). Соответствующие справки можно вызвать с помощью мыши: нужно установить указатель на ссылку и дважды подряд с небольшим промежутком времени "(доли секунды) нажать левую кнопку легкими отрывистыми ударами. Этот прием требует определенной сноровки, т. к. слишком большая пауза между нажатиями воспринимается средой как два нажатия на левую кнопку, а не как команда вызова соответствующей справки. Поле Mouse double click в диалоговом окне опции Environment | Mouse предназначено для регулирования промежутка времени, в течение которого два нажатия воспринимаются как одна команда: для увеличения интервала установите указатель мыши ближе к правой границе поля и нажмите левую кнопку.
Переключаемая опция Reverse mouse buttons используется для реверсирования функции кнопок мыши: при ее активизации нажатие на левую кнопку воспринимается средой как нажатие на правую кнопку и наоборот. Эта опция может быть полезна в том случае, если вам удобнее работать с мышью левой рукой.
Environment | Startup. На рис. п 1.11 показан вид диалогового окна этой опции.
С помощью опции Dual monitor support можно сообщить среде, что ваш ПК оснащен двумя дисплеями. После активизации этой опции на дополнительный дисплей будет выводиться окно прогона программы.
гпStartup options
[ 1 Dualnonitor support [X1 Graphics screen saue [XI ЕГ.П/UGA palette saue[ ] CGA snou checking[ 1 LCD color set[X] Use expanded nenory(XI load TIIRRO . TPL[ 1 Dualnonitor support [X1 Graphics screen saue [XI ЕГ.П/UGA palette saue[ ] CGA snou checking[ 1 LCD color set[X] Use expanded nenory(XI load TIIRRO . TPLWindow heap size teeEditor heap size едаOuerlay heap size КПДWindow heap size teeEditor heap size едаOuerlay heap size КПД
[ 1 Dualnonitor support [X1 Graphics screen saue [XI ЕГ.П/UGA palette saue
[ ] CGA snou checking
[ 1 LCD color set
[X] Use expanded nenory
(XI load TIIRRO . TPL
[ 1 Dualnonitor support [X1 Graphics screen saue [XI ЕГ.П/UGA palette saue
[ ] CGA snou checking
[ 1 LCD color set
[X] Use expanded nenory
(XI load TIIRRO . TPL
Window heap size tee
Editor heap size еда
Ouerlay heap size КПД
Window heap size tee
Editor heap size еда
Ouerlay heap size КПД
Swap file directory
OK ■ CancelOK ■ Cancel■ Help■ Help
OK ■ Cancel
OK ■ Cancel
■ Help
■ Help
Рис. П1.11.Диалоговое окно опции Options | Enviroment | Startup
Опция Graphics screen save обеспечивает сохранение в оперативной памяти ПК копии графического экрана. Эту опцию следует активизировать, если вы разрабатываете (и отлаживаете в среде Турбо Паскаля) программу, использующую графические средства ПК. При активизации этой опции вы всегда сможете увидеть в окне прогона программы те графические изображения, которые она формирует.
Опция EGA/VGA palette save позволяет сохранить в оперативной памяти цветовую палитру экрана, поэтому изменения этой палитры в отлаживаемой программе не будут влиять на вид окон среды Турбо Паскаль.
Отметим, что обе предыдущие опции используют часть оперативной памяти для сохранения графического экрана и цветовой палитры, поэтому их следует отключать, если ваша профамма критична к объему доступной памяти и/или не нуждается в графических средствах ПК и не меняет цветовую палитру.
Опция CGA snow checking может быть неактивна, если ваш ПК оснащен адаптерами типа EGA или VGA. Но в случае адаптеров типа CGA неактивное состояние этой опции будет создавать помехи на экране в виде "снега" при смене на нем изображения (активное состояние этой
опции несколько замедляет темп обновления информации на экране, но зато гарантирует отсутствие "снега" на дисплеях любого типа).
Опцию LCD color set следует активизировать только в том случае, когда ПК оснащен жидкокристаллическим дисплеем (обычно такие дисплеи устанавливаются на переносимых ПК).
На машинах класса IBM PC/XT может устанавливаться отображаемая память типа EMS (на ПК с процессорами 80386 и выше эта память может эмулироваться). Активизация опции Use expanded memory ука-_ жет среде на возможность использования этой памяти для размещения оверлейных модулей, сохранения копий графического экрана и некоторых других функций (см. ниже).
При стандартной настройке среды сразу после загрузки Турбо Паскаля в оперативную память считывается системная библиотека SYSTEM.TPU из библиотечного файла TURBO.TPL. Вы можете отказаться от этого (и таким образом сэкономить для программы около 44 Кбайт), если сделаете неактивной опцию Load TURBO.TPL. Следует учесть, что в этом случае системная библиотека должна быть выделена в виде отдельного файла SYSTEM.TPU в каталоге, путь к которому указан в поле Unit directories опции Options | Environment | Directories. Извлечение модуля SYSTEM.TPU из библиотеки TURBO.TPL осуществляется с помощью утилиты TPUMOVER.EXE, входящей в комплект поставки системы Турбо Паскаль. Для этого служит команда ДОС вида:
TPUMOVER TURBO.TPL *SYSTEM.TPU
(перед именами утилиты, библиотеки и системного модуля можно указывать необходимые пути; символ * перед именем системного модуля определяет операцию извлечения модуля). Если программа работает и с другими библиотеками, входящими в TURBO.TPL (CRT, DOS, PRINTER, OVERLAY — см. главу 10), их также нужно выделить в отдельные TPU-файлы.
В поле Window heap size задается размер оперативной памяти (в килобайтах), который выделяется для хранения неактивных окон среды, в поле Editor heap size — для хранения содержимого окон редактора, а в поле Overlay heap size — для хранения оверлейных модулей. Если активизирована опция Use expanded memory, эта память выделяется из дополнительной памяти ПК, если неактивна — из основной. Соответствующей установкой значений в этих полях можно регулировать размеры буферной памяти в следующих пределах (табл. П1.3).
Таблица П1.3. Допустимые размеры буферной памяти
Поле
Минимум
Максимум
Window heap size
24 Кбайт
64 Кбайт
Editor heap size
28 Кбайт
128 Кбайт
Overlay heap size
64 Кбайт
256 Кбайт
В поле Swap file directory можно указать "быстрый" диск, с которым среда будет производить динамический обмен данными (свопинг) в процессе своей работы. В качестве "быстрого" диска обычно указывается виртуальный диск, т. е. участок оперативной памяти, работа с которым подобна операциям с механическим диском (виртуальный диск организуется средствами ДОС). Если в этом поле ничего не указано, для свопинга используется текущий каталог.
Environment | Colors. С помощью диалогового окна этой опции вы можете установить нужную цветовую палитру отдельных элементов среды Турбо Паскаля. В колонке Group указаны следующие элементы среды:
О Desktop — вид экрана;
О Watches — окно отладки;
О Menus — меню;
О Call stack — окно стека;
О Dialogs — диалоговые окна;
О Register — окно регистров;
О Editor — окна редактора;
О Output — окно программы;
О Help — окно помощи;
О Compiler — окно компилятора.
В колонке Item детализируются эти элементы (например, цвет рамки окна, цвет основных символов, цвет выделения и т. п.). В поле Foreground задается цвет символов, а в поле Background — цвет фона.
□ Options.
Options | Open. Здесь вы можете указать имя конфигурационного файла, из которого среда должна получить информацию о своей настройке.
Options | Save. Сохраняет текущую настройку среды в конфигурационном файле.
Options | Save as. С помощью этой опции из меню Options можно указать каталог и файл, в котором среда будет сохранять свою настройку (по умолчанию это файл TURBO.TP).
П1.2.10. Меню опции Window
Tile. Располагает окна так, чтобы каждое было видно на экране, и все они имели бы приблизительно одинаковые размеры.
Cascade. Располагает на экране окна редактора таким образом, чтобы были видны рамки каждого из них. Используется для организации более удобной работы с помощью мыши.
Close all. Закрывает все открытые окна.
Refresh display. Удаляет следы вывода программы, работавшей в режиме отладки с установленной опцией Options | Debugger | Display swapping | None.
Size | Move. Эта опция обеспечивает перемещение окна по экрану и/или изменение его размеров. Вызывается из редактора командой <Ctrl>+<F5>.
Zoom. Распахивает активное окно на весь экран или возвращает ему прежний вид. Вызов из редактора клавишей <F5>.
Next. Активизирует очередное окно. Вызывается из редактора клавишей <F6>.
Previous. Активизирует предыдущее активное окно. Вызывается из редактора командой <Shift>+<F6>.
П Close. Закрывает активное окно. Вызывается из редактора командой <Alt>+<F3>.
П List. Выводит на экран список всех открытых окон среды. Вызывается из редактора командой <Alt>+<0>.
П1.2.11. Меню опции Help
Contents. Выводит на экран содержание справочной службы.
П Index. Выводит на экран алфавитный список всех ссылок справочной службы. Вызывается из редактора нажатием комбинации клавиш <Shift>+<Fl>.
Topic search. Осуществляет поиск в окрестности курсора зарезервированного слова или имени стандартной процедуры (функции) и дает соответствующую справку. Вызывается из редактора нажатием комбинации клавиш <Ctrl>+<Fl>.
Previous topic. Выводит на экран предыдущее справочное сообщение. Вызывается из редактора нажатием комбинации клавиш <Alt>+<Fi>:
□ Help on Help. Дает справку о том, как пользоваться справочной службой. Отметим, что в сообщениях справочной службы все перекрестные ссылки выделяются цветом. Вы можете подвести к любой из них указатель мыши и двойным нажатием на ее левую кнопку вызвать на экран соответствующее справочное сообщение (или сместить к ней указатель с помощью клавиш перевода курсора и нажать клавишу <Enter>).
Files. С помощью этой опции вы можете установить нужные файлы справочной службы.
Compiler rirectives. Показывает справку о директивах компилятора.
О Reserved words. Показывает справку о зарезервированных словах.
Standard units. Показывает справку о стандартных модулях.
Turbo Pascal language. Показывает справку о языке Турбо Паскаль.
Error messages. Показывает справку о сообщениях об ошибках.
П About. Выводит информацию об авторских правах и версии Турбо Паскаля.
П1.3. Директивы компилятора
В меню Options | Compiler включены опции, с помощью которых можно управлять работой компилятора. В ряде случаев бывает необходимо временно отменить действие той или иной опции при трансляции некоторого фрагмента программы. Особенно часто, например,. такая необходимость возникает при обращении к диску: если программа пытается прочитать несуществующий файл или записать данные на защищенный диск, возникнет ошибка периода исполнения и программа аварийно закончит свою работу. В то же время, если отключить опцию I/O checking, этого не произойдет, программа сможет проанализировать последствия обращения к диску и предпринять альтернативные действия.
В Турбо Паскале можно использовать директивы компилятора, которые в виде особым образом оформленных комментариев вставляются в текст программы и модифицируют те или иные возможности компилятора в процессе компиляции. Директивы могут быть переключающими, условными и параметрическими. Переключающие директивы воздействуют на те опции, которые включены в диалоговое окно Options | Compiler; условные директивы определяют условия, при которых компилируются те или иные фрагменты программы; параметрические директивы задают параметры, которые должен учитывать компилятор.
Все директивы оформляются в виде особых комментариев: они обрамляются фигурными скобками, а за открывающей скобкой должен без пробелов следовать знак доллара (десятичный код 36). Как только в процессе разбора исходного текста программы компилятор встретит такого рода последова-
тельность символов, он воспримет их как директиву и нужным образом изменит свою работу.
директива содержитобозначающую опцию, и знаки
или -. Знак + означает установку опции в активное состояние, знак -- в пассивное состояние. Например, директиваозначает временное от
ключение контроля ошибок ввода/вывода, директива {$R—— включение контроля границ диапазона. В одной директиве можно перечислить несколько опций, например:
{SA+,В-,D+,Е+, F+ , I+,L+,N+,O-,R+,S+,V+)
Следует учесть, что директивы компилятора действуют от момента своего появления в тексте до конца текущего модуля, т. е. локализуются в теле модуля, в то время как опции, установленные в самой среде, распространяются на все модули и основную программу. В случае конфликта между директивами и опциямиотдается директивам. Таким образом, пра
вильно расставленные директивы обеспечивают нужную компиляцию программы независимо от настройкиособенно полезны в случае,
когда компиляция осуществляется автономным компилятором ТР.ЕХЕ.
Некоторые директивы компилятора могут действовать только на часть текста программы, такие директивы называютсяв отличие от этого
глобальные директивы располагаются в самом начале текста программы (модуля) и действуют сразу на всю программу (модуль) в целом.
Ниже приводится список всех директив компилятора. В скобках дается действие директивы для знака -. Знаком * отмечены локальные директивы.
П (ЗА- * — выравнивать данные на границу слова (байта);
!$в+р — вычислять логические выражения полностью (до получения результата);
П— разрешить (запретить) работу со встроенным отладчиком;
($Е+) — включить (отключить) режим программной эмуляции сопроцессора;
П — использовать дальнюю (ближнюю) модель вызова;
П рс;—★ — использовать (не использовать) полный набор команд микропроцессора Intel 80286 (микропроцессора Intel 8088);
П— включить (отключить) контроль операций ввода/вывода;
П — включить (не включать) локальные символы в информацию для отладчика;
П {$м+у — использовать арифметический сопроцессор (реализовать операции с плавающей запятой программно);
($ои — разрешить (не разрешать) создание оверлейной структуры;
включить (отключить) контроль границ диапазона;
П — включить (отключить) контроль возможного переполнения стека;
П — включить (отключить) контроль длины строк при обращении к процедуре или функции;
П — использовать (не использовать) расширенный синтаксис.
К условным директивам относятся следующие локальные директивы компилятора:
П— установить условный символ;
П— проверить установку условного символа;
если символ установлен, будет компилироваться вся следующая за директивой часть программы вплоть до директивилив про
тивном случае этот фрагмент программы будет пропущен компилятором;
П— ПроверитьСИМВОла; обратна директивет. е. действует только в том случае, когда
условный символ не установлен;
П— определяет начало альтернативного фрагмента программы;
этот фрагмент будет компилироваться в том случае, если условный символ, проверенный предыдущей по тексту программы директивой илине установлен; альтернативная часть вместе с
директивойможет опускаться;
ограничивает область действия директив
ИЛИ
Установить условие — это значит с помощью опции Options ||
Conditional Defines среды Турбо Паскаля или с помощью директивы компилятораввести некоторое слово (условный символ), которое затем
будет управлять компиляцией какого-либо фрагмента программы. Если, например, использовать в качестве условного символа слово Debug, то можно написать такой фрагмент программы:
($IFDEF Debug)
WriteLn ('Отладка: x=', x) ;
($ENDIF)
Теперь, если установлен (задан в среде или введен с помощью директивы УСЛОВНЫЙ СИМВОЛ Debug, В
оператор writeLn, если этот условный символ не задан, компилятор пропустит этот оператор.
Отметим, что условные символы никак не связаны с множеством идентификаторов самой программы и недоступны ей на этапе исполнения.
Для предыдущего примера нельзя написать
WriteLn(Debug);
если, разумеется, в программе не определена переменная или константа с этим именем.
Одновременно в программе может быть установлено сколько угодно условных символов. Для тестирования нескольких условий используется вложение условных директив компилятора, например:
($IFDEF Vari}
{5IFDEF Var2}
a := n;
(SELSE}
a := 0;
{$ENDIF}
{SENDIF}
В директивах {$IFDEF} или {$IFNDEF} программист может использовать следующие стандартные условные символы, которые устанавливаются в среде автоматически:
□ VER70 — указывает версию 7.0 Турбо Паскаля (для других версий используются другие условные символы: VER40, VERSO, VER55, VER60);
□ MS DOS — указывает на то, что программа будет исполняться под управлением операционной системы MS-DOS (PC DOS); версии Турбо Паскаля для других операционных систем не будут устанавливать этот символ;
□ CPUS 6 — указывает на то, что программа будет исполняться на ПК с микропроцессором, принадлежащим семейству Intel 80x86;
□ CPU87 — указывает на то, что в составе аппаратных средств ПК обнаружен (к моменту компиляции программы!) арифметический сопроцессор семейства Intel 80x87.
К параметрическим относятся директивы:
П ($M <стек>,<нижн>,<верх>} — установить требуемые размеры памяти (глобальная директива). Здесь:
<стек> — размер стека;
<нижн> — нижняя граница динамической памяти;
<верх> — верхняя граница динамической памяти;
П {$1 <имя файла>} — включение PAS-файла. <имя файла> — имя включаемого файла;
П {$L <имя файла>) — включение OBJ-файла. <имя файла> — имя включаемого файла.
В директиве ($М) все размеры задаются в байтах, следуют друг за другом в указанной последовательности и отделяются запятыми. Между буквой м и первой цифрой размера стека должен быть хотя бы один пробел, между последней цифрой верхней границы динамической памяти и закрывающей фигурной скобкой не должно быть никаких символов, например:
($М 16384,0,655360}
Включаемый файл, определенный в директиве {$!}, должен содержать исходный текст фрагмента программы. Компилятор использует эту директиву как указание обратиться к дисковому файлу за очередной строкой программы. После того как весь фрагмент программы из файла будет прочитан, компилятор продолжит чтение строк из файла редактора. Если в имени файла опущено расширение, подразумевается стандартное расширение PAS. Диск и каталог, в котором находится файл, задаются опцией Options | Environment | Directories.
Директива {$L} предназначена для указания компилятору файла, в котором содержится результат трансляции ассемблерной процедуры или функции, объявленной в программе как внешняя (EXTERNAL, см. главу 11). Если в имени файла опущено расширение, используется стандартное расширение OBJ. Диск и каталог, в котором находится файл, задаются опцией Options | Environment | Directories.
П1.4. Редактор
Основные приемы работы с редактором описаны в главе 1. Ниже приводится полное описание команд редактора. Все команды редактора можно разделить на команды перемещения курсора, команды удаления/вставки, команды работы с блоками и прочие команды.
При их описании приняты следующие обозначения клавиш управления курсором:
П ВЛ — курсор влево;□ ВП — курсор вправо;
□ ВВ — курсор вверх;П ВН — курсор вниз.
П1.4.1. Команды перемещения курсора
П <Ctrl>+<S> или ВЛ — на символ влево;
П <Ctrl>+<D> или ВП — на символ вправо;
П <Ctrl>+<A> или <С1г1>+ВЛ — на слово влево;
П <Ctrl>+<F> или <С1г1>+ВП — на слово вправо;
П <Ctrl>+<E> или ВВ — на строку вверх;
П <Ctrl>+<X> или ВН — на строку вниз;
П <Ctrl>+<W> — прокрутка вниз на строку вместе с курсором;
П <Ctrl>+<Z> — прокрутка вверх вместе с курсором;
П <Ctrl>+<R> или•<PgUp> — на страницу вверх;
П <Ctrl>+<C> или <PgDn> — на страницу вниз;
<Ctrl>+<Q>,<S> или <Home> — в начало строки;
П <Ctrl>+<Q>,<D> или <End> — в конец строки;
П <Ctrl>+<Q>,<E> или <Ctrl>+<Home> — в начало экрана;
П <Ctrl>+<Q>,<X> или <Ctrl>+<End> — в конец экрана;
<Ctrl>+<Q>,<R> или <Ctrl>+<PgUp> — в начало файла;
П <Ctrl>+<Q>,<C> или <Ctrl>+<PgDn> — в конец файла;
П <Ctrl>+<Q>,<B> — в начало блока;
П <Ctrl>+<Q>,<K> — в конец блока;
П <Ctrl>+<Q>,<P> — на последнюю позицию (используется после поиска или поиска/замены);
<Ctrl>+<Q>,<W> — на последнюю ошибку.
П1.4.2. Команды удаления/вставки
П <Ctrl>+<V> или <Ins> — включить/отключить режим вставки;
О <Ctrl>+<N> — вставить строку;
П <Ctrl>+<Y> — удалить строку;
П <Ctrl>+<H> или <Backspace> — стереть символ слева от курсора;
О <Ctrl>+<G> или <Del> — стереть символ над курсором;
О <Ctrl>+<T> — стереть слово справа от курсора;
О <Ctrl>+<Q>,<Y> — стереть остаток строки справа от курсора.
П1.4.3. Команды работы с блоками
При подготовке текстов программ часто возникает необходимость перенести фрагмент текста в другое место или удалить его. Для такого рода операций удобны блоки — фрагменты текста, рассматриваемые как единое целое. Длина блока может быть достаточно большой (до 64 Кбайт), он может занимать несколько экранных страниц. В каждый момент в среде может быть объявлен только один блок в одном окне редактора. Обмен блоками между окнами возможен только через буфер редактора (см. опцию Edit в главном меню).
П <Ctrl>+<K>,<B> — пометить начало блока;
П <Ctrl>+<K>,<K> — пометить конец блока;
— пометить в качестве блока слово слева от курсора;
П <Ctrl>+<K>, <P> — напечатать блок;
П— копировать блок, начиная с позиции курсора;
О <Ctrl>+<K>,<V> — переместить блок;
<Ctrl>+<K>,<H> — убрать выделение блока цветом; повторное использование этой команды вновь выделит блок;
П <Ctrl>+<K>,<Y> — удалить блок;
П <Ctrl>+<K>,<R> — читать блок из дискового файла;
П <Ctrl>+<K>,<W> — записать блок на диск;
П— сместить блок вправо;
П— сместить блок влево.
Прочие команды
— искать по образцу;
П— продолжить поиск;
П— искать по образцу и заменять;
П— прекратить дальнейшее выполнение поиска или поиска и
замены;
П <Ct!l>+<K>,</?> — установить маркер; п — О,..., 9 (см. ниже);
П— искать
П— искать ошибку;
П— искать правую парную скобку (см. ниже);
П— искать левую парную скобку (см. ниже);
П <Ctrl>+<Q>,<L> — восстановить испорченную строку (см. ниже);
О <CtrI>+<Q>,<T> или <Ctrl>+<O>,<T> — включить/отключить табуляцию;
П <Ctrl>+<O>,<F> — переключить заполнение табуляции;
Пили— включить/отключить автоотступ;
П— вставить настройку компилятора в начало файла (см.
ниже).
Назначение большинства команд понятно без каких-либо комментариев, но некоторые нуждаются в
ПУстанавливает в текущую позицию курсора маркер с
номером п. Маркер на экране невидим и никак не влияет на исполнение программы. Команда используется совместно с командой
(искать маркер с номером ri) для ускорения поиска нужных фрагментов текста при разработке крупных программ. Обратите внимание — комбинации клавиш <Ctrl>+<K>,<»> набираются следующим образом: вначале нажимается <Ctrl>, затем, не отпуская ее, ■*^— <К>; после этого все клавиши отпускаются и нажимается клавиша с цифрой п. Точно так же набирается команда <Ctri>+<Q>,</7>.
<Ctrl>+<Q>,<]> и <Ctrl>+<Q>,<[>. Эти команды используются для поиска ближайшей парной скобки. Команды позволяют отыскивать пары скобок: (И) , ( и }, [и] . Подведите курсор так, чтобы он указывал на одну из скобок, и дайте соответствующую команду — редактор отыщет нужную парную скобку.
<Ctrl>+<0>,<0>. Эта команда заставит редактор поместить в самое начало файла строки, содержащие текущую настройку среды в виде директив компилятора, например:
$A + / В - , D+ / E+ # F+ z G+, 14-, L+, N+ # O—,R+ # S4, V+ , X+ }
($M 16384,0,655360}
($DEFINE single}
П1.4.5. Команды,
передаваемые среде из редактора
Некоторые наиболее часто используемые команды можно передать среде непосредственно из режима редактирования. Все они уже описаны выше, когда рассматривалась система меню. Тем не менее я повторю их здесь, чтобы вам было легче ими пользоваться.
<F1> — получить справку;
<F2> — записать файл из окна редактора на диск;
<F3> — прочитать файл с диска в окно редактора;
<F4> — исполнить до курсора (выполнить опцию Run | Go to cursor);
О <F5> — распахнуть окно на весь экран или вернуть ему прежние размеры;
<F6> — активизировать следующее окно;
<F7> — проследить процедуру (выполнить опцию Run | Trace into);
<F8> — пропустить процедуру (выполнить опцию Run | Step over);
<F9> — компилировать программу (выполнить опцию Compile | Make);
П <F10> — перейти в главное меню;
П <Ctrl>+<Fl> — получить контекстную справку;
П <Ctrl>+<F2> — сбросить режим отладки;
<Ctii>+<F3> — активизировать окно программного стека;
П <Ctrl>+<F4> — вычислить выражение или показать/изменить переменную (выполнить опцию Debug | Evaluate | Modify);
П <Ctrl>+<F5> — перейти к режиму установки ^положения и размеров окна;
П <Ctrl>+<F7> — добавить выражение в окно отладки (выполнить опцию Debug | Watches | Add watch);
П <Ctrl>+<F8> — переключить контрольную точку;
П <Ctrl>+<F9> — выполнить компиляцию и прогон программы;
П <Ctrl>+<Del> — очистить буфер редактора;
П <Ctrl>+<Ins> — копировать блок в буфер редактора;
П <Alt>+<C> — вызвать меню компиляции Compile;
П <Alt>+<D> — вызвать меню отладки Debug;
П <Alt>+<E> — вызвать меню редактора Edit;
П <Alt>+<F> — вызвать меню файловой службы File;
П <Alt>+<H> — вызвать меню справочной службы Help;
П <Alt>+<O> — вызвать меню установок Options;
П <Alt>+<R> — вызвать меню прогона программы Run;
П <Alt>+<S> — вызвать меню службы поиска Search;
П <Alt>+<W> — вызвать меню окон Window;
О <Alt>+<X> — выйти из Турбо Паскаля;
П <Alt>+<0> — получить список открытых окон;
П <Alt>+<Fl> — получить последнюю справку;
П <Alt>+<F3> — закрыть активное окно;
П <Alt>+<F5> — показать окно программы;
П <Alt>+<F9> — выполнить опцию Compile | Compile;
П <Shift>+<Fl> — получить список ссылок справочной службы;
<Shift>+<F6> — активизировать предыдущее активное окно;
П <Shift>+<Del> — перенести блок из окна редактора в буфер;
П <Shift>+<Ins> — копировать буфер в окно редактора.
П1.5. Вызов Турбо Паскаля
Полный формат команды ДОС, осуществляющей вызов Турбо Паскаля, имеет вид:
[PATH]turbo [Options] [FileName]
Здесь:
PATH — путь к системному каталогу (см. главу 1 и разд. 5.1.1);
Optoins — список опций;
О FileName — ИМЯ PAS-файла.
В квадратных скобках указаны необязательные параметры команды.
Параметр FileName, если он указан, задает имя текстового файла, который будет автоматически загружен средой в активное окно редактора. Если в имени файла не указано расширение, среда добавляет стандартное расширение PAS.
С помощью параметров Options среде можно передать указание изменить нужным образом свою настройку. Каждый параметр задается в формате /z, где z — буква, определяющая настройку среды (см. ниже), знаки + (включить настройку) или - (отключить настройку); несколько параметров отделяются друг от друга пробелами.
В списке Options можно использовать следующие управляющие параметры:
/с<имя> — загрузить нужный файл конфигурации (эквивалентно опции Options | Open среды); <имя> — имя конфигурационного файла, например: /CMy.tp;
/D+ — использовать дополнительный монитор (эквивалентно опции Options | Environment | Startup | Dual monitor support);
/Е<размер> — установить нужный размер памяти для экранного буфера (эквивалентно опции Options | Environment | Startup | Editor heap size); <размер> — устанавливаемый размер (Кбайт); например /Е15 — установить размер экранного буфера в 15 Кбайт;
/G+ — сохранять копию графического экрана (эквивалентно опции Options | Environment | Startup | Graphics screen save);
/L+ — ПК оснащен жидкокристаллическим дисплеем (эквивалентно опции Options | Environment | Startup | LCD color set);
/N+ — ПК оснащен адаптером типа CGA (эквивалентно опции Options | Environment | Startup | Cga snow checking);
/о<размер> — установить нужный размер памяти для хранения оверлейных модулей системы Турбо Паскаля (эквивалентно опции Options | Environment | Startup | Overlay heap size);
/Р+ — сохранять цветовую палитру экрана (эквивалентно опции Options | Environment | Startup | EGA/VGA palette save);
/3<путь> — определяет путь к "быстрому" диску (эквивалентно опции Options | Environment | Startup | Swap file directory);
/т+ — загружать в память библиотеку SYSTEM.TPU из файла TURBO.TPL (эквивалентно опции Options | Environment | Startup | Load TURBO.TPL);
/W<pa3Mep> — установить нужный размер памяти для хранения окон ТурбоПаскаля(эквивалентноопцииOptions|Environment|Startup|
Window heap size);
/х+ — использовать EMS-память (эквивалентно опции Options | Environment | Startup | Use expanded memory).
бб00 01 0203О405 0607 0809 Ю 111213141500 01 0203О405 0607 0809 Ю 1112131415►0 вр*раЧ|1ЙQaЧвги2вяЬг•!!#3сSс5*11S4Dтdt§У.5ЕиеU*-^6Fиfи•!'7Gи9U0Тс8НXЬX01)9IчiУS■♦*JZjZв*-+К[k{?1—,<L\11е•-=М]m}пА>НT1*Т/70оо►0 вр*раЧ|1ЙQaЧвги2вяЬг•!!#3сSс5*11S4Dтdt§У.5ЕиеU*-^6Fиfи•!'7Gи9U0Тс8НXЬX01)9IчiУS■♦*JZjZв*-+К[k{?1—,<L\11е•-=М]m}пА>НT1*Т/70ооаа00 01O2 озO4O5СЮ 07 0809 1О111213 141500 01O2 озO4O5СЮ 07 0809 1О111213 1415
б
б
00 01 02
03
О4
05 06
07 08
09 Ю 11
12
13
14
15
00 01 02
03
О4
05 06
07 08
09 Ю 11
12
13
14
15
►
0 в
р
*
р
а
Ч
|
1
Й
Q
a
Ч
в
г
и
2
в
я
Ь
г
•
!!
#
3
с
S
с
5
*
11
S
4
D
т
d
t
§
У.
5
Е
и
е
U
*
-
^
6
F
и
f
и
•
!
'
7
G
и
9
U
0
Т
с
8
Н
X
Ь
X
0
1
)
9
I
ч
i
У
S
■♦
*
J
Z
j
Z
в
*-
+
К
[
k
{
?
1—
,
<
L
\
1
1
е
•
-
=
М
]
m
}
п
А
>
Н
T1
*
Т
/
7
0
о
о
►
0 в
р
*
р
а
Ч
|
1
Й
Q
a
Ч
в
г
и
2
в
я
Ь
г
•
!!
#
3
с
S
с
5
*
11
S
4
D
т
d
t
§
У.
5
Е
и
е
U
*
-
^
6
F
и
f
и
•
!
'
7
G
и
9
U
0
Т
с
8
Н
X
Ь
X
0
1
)
9
I
ч
i
У
S
■♦
*
J
Z
j
Z
в
*-
+
К
[
k
{
?
1—
,
<
L
\
1
1
е
•
-
=
М
]
m
}
п
А
>
Н
T1
*
Т
/
7
0
о
о
а
а
00 01
O2 оз
O4
O5
СЮ 07 08
09 1О
11
12
13 14
15
00 01
O2 оз
O4
O5
СЮ 07 08
09 1О
11
12
13 14
15
Приложение 2
Варианты кодировки знакогенераторов ПК
Стандартный знакогенератор ПК IBM PC имеет кодировку символов, изображенную на рис. Символы с кодами от 0 до 127, образующие первую половину символов знакогенератора (рис. а), построены по стандарту ASCII и одинаковы на всех IBM-совместимых ПК. Вторая половина символов (коды могут отличаться на ПК разного типа. В стандартном знакогенераторе фирмы IBM символы псевдографики занимают три смежных колонки (коды рис. П2.1, б).
§ШNCOVОФN
HсоVU3СО01н
□ □□□□□он
Рис.Стандартная кодировка фирмы IBM:
а — для кодовб — для кодов
Колонки с кодами от 128 до 175 и от 224 до 239 используются для размещения некоторых символов национального алфавита различных европейских языков, а последняя колонка (коды — для размещения специальных знаков. С учетом этого расположения символов разрабатывается подавляющее большинство программ зарубежного происхождения.
Стандартный отечественный знакогенератор строится по рекомендациям Международного консультационного комитета по телеграфии и телефонии (МККТТ). Расположение символов во второй половине таблицы этого знакогенератора (рис. резко отличается от принятого фирмой IBM, что затрудняет использование зарубежного программного обеспечения на отечественных ПК. В связи с этим, стандартный (так называемый ГОСТовский) вариант кодировки часто заменяется альтернативным (рис. П2.2. б), главное достоинство которого — расположение символов псевдографики на тех же местах, что и в знакогенераторе IBM. Недостаток такого знакогенератора заключается в том, что символы кириллицы не образуют непрерывный массив. Вариант, показанный на рис. 112.2. б, в настоящее время получил наибольшее распространение на отечественных ПК. Именно на него рассчитаны практически все программы отечественного производства. Он стал фактическим стандартом для зарубежных фирм, изготовляющих ПК для экспорта в нашу страну.
00 N?§й88 (ЧяоNXПгЙрарЁ00ЛИ1Бс6сёО1тлJВтвISогцLГУгУхозI=-дфДфSО4IIII1ЕXеXX051иТЖцжЦ-*06п1iЭч3ч0711J!±Ишиш108LIItЙийЦТО9ГftiКъкътДОЛ1лылы+11J!мьмьN'12t1•нЭнЭН13\\110юою■14п+•пяпя1500 N?§й88 (ЧяоNXПгЙрарЁ00ЛИ1Бс6сёО1тлJВтвISогцLГУгУхозI=-дфДфSО4IIII1ЕXеXX051иТЖцжЦ-*06п1iЭч3ч0711J!±Ишиш108LIItЙийЦТО9ГftiКъкътДОЛ1лылы+11J!мьмьN'12t1•нЭнЭН13\\110юою■14п+•пяпя15030304040505ОБОБО7О712121313080908090001020001021415141510111011ООО1 ог озООО1 ог оз04040505060607070808О9О9ДО11ДО11121213131414151500 5 СS00 5 СSft Б В г Д Е К 3 и Й к п пft Б В г Д Е К 3 и Й к п пр с т У ф X ц ч ш щ ъ ы ьр с т У ф X ц ч ш щ ъ ы ьдедеDODO±±11[[04040102 оз0102 оза 6вга 6вгp сp сIIн 0 пн 0 пЭ ю яЭ ю ян3и йкн3и йклмлмнопнопии1=1=rпrпtJtJУфXЦУфXЦгJtгJtшш0506050607070808О9О9IIIIIIIIъыьъыьл Jл J\\i\\iЭ ю яЭ ю я••1111ДО11ДО111212131314141515
00 N
?
§
й
8
8 (Ч
я
о
N
X
П
г
Й
р
а
р
Ё
00
Л
И
1
Б
с
6
с
ё
О1
т
л
J
В
т
в
I
S
ог
ц
L
Г
У
г
У
х
оз
I
=
-
д
ф
Д
ф
S
О4
II
II
1
Е
X
е
X
X
05
1
и
Т
Ж
ц
ж
Ц
-*
06
п
1
i
Э
ч
3
ч
07
11
J!
±
И
ш
и
ш
1
08
L
II
t
Й
и
й
Ц
Т
О9
Г
ft
i
К
ъ
к
ъ
т
ДО
Л
1
л
ы
л
ы
+
11
J
!
м
ь
м
ь
N'
12
t
1
•
н
Э
н
Э
Н
13
\\
1
1
0
ю
о
ю
■
14
п
+
•
п
я
п
я
15
00 N
?
§
й
8
8 (Ч
я
о
N
X
П
г
Й
р
а
р
Ё
00
Л
И
1
Б
с
6
с
ё
О1
т
л
J
В
т
в
I
S
ог
ц
L
Г
У
г
У
х
оз
I
=
-
д
ф
Д
ф
S
О4
II
II
1
Е
X
е
X
X
05
1
и
Т
Ж
ц
ж
Ц
-*
06
п
1
i
Э
ч
3
ч
07
11
J!
±
И
ш
и
ш
1
08
L
II
t
Й
и
й
Ц
Т
О9
Г
ft
i
К
ъ
к
ъ
т
ДО
Л
1
л
ы
л
ы
+
11
J
!
м
ь
м
ь
N'
12
t
1
•
н
Э
н
Э
Н
13
\\
1
1
0
ю
о
ю
■
14
п
+
•
п
я
п
я
15
03
03
04
04
05
05
ОБ
ОБ
О7
О7
12
12
13
13
08
09
08
09
00
01
02
00
01
02
14
15
14
15
10
11
10
11
ОО
О1 ог оз
ОО
О1 ог оз
04
04
05
05
06
06
07
07
08
08
О9
О9
ДО
11
ДО
11
12
12
13
13
14
14
15
15
00 5 С
S
00 5 С
S
ft Б В г Д Е К 3 и Й к п п
ft Б В г Д Е К 3 и Й к п п
р с т У ф X ц ч ш щ ъ ы ь
р с т У ф X ц ч ш щ ъ ы ь
д
е
д
е
DO
DO
±
±
1
1
[
[
04
04
01
02 оз
01
02 оз
а 6
в
г
а 6
в
г
p с
p с
I
I
н 0 п
н 0 п
Э ю я
Э ю я
н
3
и й
к
н
3
и й
к
л
м
л
м
н
о
п
н
о
п
и
и
1=
1=
r
п
r
п
t
J
t
J
У
ф
X
Ц
У
ф
X
Ц
г
J
t
г
J
t
ш
ш
05
06
05
06
07
07
08
08
О9
О9
II
II
II
II
ъ
ы
ь
ъ
ы
ь
л J
л J
\\
i
\\
i
Э ю я
Э ю я
•
•
11
11
ДО
11
ДО
11
12
12
13
13
14
14
15
15
Рис.Варианты кодировки для кодов
а — по рекомендациям МККТТ; б —популярный
Приложение 3
Сообщения и коды ошибок
П3.1. Сообщения об ошибках периода компиляции
Среда Турбо Паскаля дает исчерпывающую информацию о характере и месте обнаруженной компилятором ошибки. При обнаружении ошибки среда автоматически загружает в окно редактора исходный файл и помещает курсор около того места, где в исходном тексте обнаружена ошибка. При этом в верхней строке редактора появляется диагностическое сообщение. После нажатия на любую клавишу (кроме <F1>) верхняя строка восстанавливает свой первоначальный вид и среда переходит к режиму редактирования. Если после появления сообщения об ошибке нажать <F1>, на экране появится окно справочной службы с детальной информацией об ошибке и рекомендациями по ее устранению. Некоторые ошибки в исходном тексте обнаруживаются не сразу, а в ходе продолжающегося контекстного разбора. Например, несоответствие типов в операторе присваивания не может быть обнаружено до тех пор, пока не будет вычислено целиком выражение в правой части этого оператора. В таких случаях ищите ошибку слева от курсора или в предыдущей строке текста.
Ниже приводятся сообщения об ошибках в том виде, в каком они появляются в верхней строке редактора, а также перевод сообщений справочной службы.
1 Out- of memory
О (Выход за границы памяти)
Компилятору не хватает памяти. Имеется ряд возможных решений этой проблемы:
если в опции Compile | Destination установлено значение Memory, замените эту опцию на Disk;
еслив опцииOptions|Linker |Linkbufferустановленозначение
Memory, замените эту опцию на Disk;
если у вас активны резидентные обслуживающие программы, такие как Sidekick, Norton, удалите их из памяти;
если вы используете интегрированную среду TURBO.EXE, то попробуйте воспользоваться компилятором ТРС.ЕХЕ — он занимает меньше памяти.
Если ни одна из рекомендаций не помогает, то, возможно, ваша программа просто слишком велика, чтобы компилировать ее в таком объеме памяти. В этом случае вы должны разбить ее на два или более модулей.
2 Identifier expected
(Не указан идентификатор)
В этом месте должен находиться идентификатор. Возможно, вы пытаетесь использовать в качестве идентификатора зарезервированное слово.
3 Unknown identifier
(Неизвестный идентификатор)
Этот идентификатор не был описан.
4 Duplicate identifier (Двойной идентификатор)
Попытка дважды описать один и тот же идентификатор.
5 Syntax error
(Синтаксическая ошибка)
В исходном тексте найден недопустимый символ. Возможно, вы забыли заключить в апострофы строковую константу.
О 6 Error in real constant
(Ошибка в вещественной константе)
Синтаксис констант вещественного типа описан в главе 4.
7 Error in integer constant (Ошибка в целой константе)
Синтаксис констант целого типа описан в главе 4. Учтите, что после целых действительных чисел, превышающих диапазон представления целых чисел (—2 147 483 648 — +2 147 483 647), должны ставиться точка и ноль, например, 12 345 678 912.0.
8 String constant exceeds line (Строковая константа превышает допустимые размеры)
Вероятно, вы забыли поставить апостроф в конце строковой константы.
О 9 Too many nested files
(Слишком много вложенных файлов)
Компилятор допускает не более пяти уровней вложенности включаемых файлов (т. е. в исходном файле есть ссылка на включаемый файл, в котором есть ссылка на еще один включаемый файл и т. д.).
О 10 Unexpected end of file
(He найден конец файла)
Вы могли получить это сообщение об ошибке по одной из следующих причин:
ваш исходный файл закончился перед последним оператором end основного раздела операторов; вероятно, в вашей программе неодинаковое количество операторов begin и end;
включаемый файл заканчивается в середине раздела операторов; каждый раздел операторов должен целиком помещаться в одном файле;
вы не закончили комментарий.
О 11 Line too long
(Слишком длинная строка)
Максимальная длина строки, обрабатываемой компилятором, равна 126 символам (обратите внимание: редактор среды может обрабатывать строки практически неограниченной длины).
О 12 Type identifier expected
(Здесь нужен идентификатор типа)
Не указан тип идентификатора.
О 13 Too many open files
(Слишком много открытых файлов)
Появление этой ошибки означает, что конфигурационный файл CONFIG.SYS операционной системы не включает параметр FILES=XX или этот параметр указывает слишком мало файлов. Увеличьте число файлов до нужного значения, например, до 80.
14 Invalid file name
(Неверное имя файла)
Имя файла неверно или указан несуществующий путь.
15 File not found
(Файл не найден)
Файл не был найден в просмотренных каталогах.
16 Disk full
(Диск заполнен)
Удалите некоторые файлы или воспользуйтесь новым диском.
17 Invalid compiler directive
(Неправильная директива компилятора)
Неверная буква в директиве компилятора, один из параметров директивы компилятора неверный, или вы пользуетесь глобальной директивой компилятора, когда компиляция тела программы уже началась.
18 Too many files
(Слишком много файлов)
В компиляции программы или программного модуля участвуют слишком много файлов. Попытайтесь не использовать так много файлов, например, объединяя включаемые файлы или делая короче имена файлов.
19 Undefined type in pointer definition (Неопределенный тип В объявлении указателя)
Попытка объявить типизированный указатель, связанный с ранее не объявленным типом данных.
20 Variable identifier expected
(Отсутствует идентификатор переменной)
На этом месте должен быть идентификатор переменной.
21 Error in type
(Ошибка в объявлении типа)
Объявление типа не может начинаться с этого символа.
22 Structure too large
(Слишком большая структура)
Максимально допустимый размер любого структурного типа составляет 65 520 байт.
23 Set base type of range
(Базовый тип множества нарушает границы)
Базовый тип множества должен представлять собой тип-диапазон с границами в пределах от 0 до 255 или перечисляемый тип с не более чем 256 значениями.
24 File components may not be files (Компонентами файла не могут быть файлы)
Конструкции типа file of file (файл файлов), ИЛИ file of object (файл объектов) не допускаются. Нельзя объявлять любые структурные типы, которые используют в качестве компонентов объекты или файлы.
25 Invalid string length
(Неверная длина строки)
Объявляемая длина строки должна находиться в диапазоне от 1 до 255.
26 Type mismatch
(Несоответствие типов)
Это сообщение может быть вызвано следующими причинами:
несовместимые типы переменной и выражения в операторе присваивания;
несовместимые типы фактического и формального параметров в обращении к процедуре или функции;
тип выражения не совместим с типом индекса при индексировании массива;
несовместимые типы операндов в выражении.
27 Invalid subrange base type
(Неправильный базовый тип для типа-диапазона)
Допустимыми базовыми типами являются все порядковые типы.
О 28 Lower bound greater than upper bound
(Нижняя граница больше верхней)
Описание типа-диапазона содержит неправильные границы.
29 Ordinal type expected
(Нужен порядковый тип)
Вещественные, строковые, структурные, процедурные типы и указатели в данном месте программы не допускаются.
30 Integer constant "expected
(Нужна целая константа)
О 31 Constant expected
(Нужна константа)
п 32 Integer or real constant expected
(Нужна целая или вещественная константа)
33 Type identifier expected
(Нужен идентификатор типа)
34 Invalid function result type
(Неправильный тип результата функции)
Правильными типами результата функции являются все простые типы, строковые типы и указатели.
35 Label identifier expected
(Нужен идентификатор метки)
Метка не обозначена с помощью идентификатора, как это требуется из контекста программы.
36 BEGIN expected
(.Нужен BEGIN)
37 END expected
(Нужен END)
38 Integer expression expected
(Нужно выражение типа INTEGER)
39 Ordinal expression expected
(Нужно выражение перечисляемого типа)
40 Boolean expression expected
(Нужно выражение типа BOOLEAN)
31 Зак 941
41 Operand types do not match operator
(Типы операндов не соответствуют операции)
Данная операция не может быть применена к указанным операндам, например,
П 42 Error in expression
(Ошибка в выражении)
Данный символ не может участвовать в выражении указанным образом. Возможно, вы забыли указать операцию между двумя операндами.
П 43 Illegal assignmentч.
(Неверноеприсваивание)
Файлам и нетипизированным переменным нельзя присваивать значения. Идентификатору функции можно присвоить значение только внутри раздела операторов данной функции.
П 44 Field identifier expected
(Нужен идентификатор поля)
Попытка использовать запись целиком в том месте, где требуется ссылка на какое-либо поле записи.
45 Object file too large
(Объектный файл слишком большой)
Турбо Паскаль не может компоноватьбольше 64 Кбайт.
П 46external
(Неопределенная внешняя процедура)
Внешняя процедура или функция не имеет соответствующего определения PUBLIC в объектном файле. Убедитесь, что вы указали все объектные файлы в директивахи проверьте написание
идентификаторов процедуры или функции в файле ASM.
47 Invalid object file record
(Неправильная запись объектного файла)
Файл OBJ содержит неверную объектную запись. Убедитесь, что данный файл является действительно файлом OBJ.
П 48 Code segment too large
(Сегмент кода слишком большой)
Максимальный размер кода программы или программного модуля равняется 65 520 байт. Разбейте вашу программу или модуль на два или более модулей.
П 49 Data segment too large
(Сегмент данных слишком велик)
Максимальный размер сегмента данных программы равен 65 520 байт, включая данные, используемые программными модулями. Если вам нужно большее количество глобальных данных, опишите большие структуры
с помощью указателей и выделяйте для них память динамически с помощью процедуры NEW.
□ 50 DO expected
(Нужен оператор DO)
П 51 Invalid PUBLIC definition
(Неверное PUBLIC-определение)
Возможные причины сообщения:
данный идентификатор получил тип PUBLIC с помощью соответствующей директивы языка ассемблера, но не соответствует описанию EXTERNAL в программе или программном модуле Паскаля;
две или более директивы PUBLIC языка ассемблера определяют один и тот же идентификатор;
файлы OBJ определяют символы PUBLIC, не находящиеся в сегменте CODE.
52 Invalid EXTRN definition
(Неправильное EXTRN-определение)
Возможные причины сообщения:
программа на ассемблере ссылается с помощью директивы EXTRN на идентификатор, который не описан в программе на Паскале и не был описан в интерфейсных секциях используемых программных модулей;
ассемблерная программа ссылается на идентификатор, обозначающий абсолютную переменную (т. е. определенную словом ABSOLUTE);
ассемблерная программа ссылается на идентификатор процедуры или функции типа INLINE.
П 53 Too many EXTRN definition
(Слишком много EXTRN-определений)
Турбо Паскаль не может обрабатывать файлы OBJ при наличии более чем 256 определений EXTRN.
54 OF expected
(Требуется OF)
55 INTERFACE expected
(Требуется интерфейсная секция)
О 56 Invalid relocatable reference
(Неправильная перемещаемая ссылка)
Возможные причины сообщения:
файл OBJ содержит данные и перемещаемые ссылки в сегментах, отличных от CODE. Например, вы пытаетесь описать инициализированные переменные в сегменте DATA;
файл OBJ содержит ссылки с размерами в байтах на перемещаемые символы. Такая ошибка происходит в случае, если вы используете операторы HIGH и LOW с перемещаемыми символами или если вы ссылаетесь в директивах DB на перемещаемые символы;
операнд ссылается на перемещаемый символ, который не был определен в сегменте CODE или в сегменте DATA;
операндссылаетсянапроцедуруEXTRNилифункцию EXTRNсосдви
гом, например, CALL sortProc+8.
57 THEN expected
(Требуется THEN)
О 58 ТО or DOWNTO expected
(Требуется ТО или DOWNTO)
59 Undefined forward
(Неопределенное опережающее описание)
Возможные причины сообщения:
была описана процедура или функция в интерфейсной секции программного модуля, но ее определение отсутствует в секции реализации;
процедуры или функции были описаны с помощью опережающего описания, но их определение не найдено.
□ 60 Too many procedures
(Слишком много процедур)
Турбо Паскаль допускает не более 512 процедур или функций в одном модуле. Если вы компилируете программу, то поместите некоторые процедуры или функции в модули. Если вы компилируете модуль, то разбейте его на два или несколько модулей.,
О 61 Invalid typecast
(Неверное преобразование типа)
Возможные причины сообщения:
вы пытаетесь разместить в памяти, занимаемой некоторой переменной, значение выражения другого типа в случае, когда размер размещаемого значения не равен размеру переменной;
вы пытаетесь осуществить преобразование типа выражения, когда разрешается только ссылка на переменную, процедуру или функцию.
П 62 Division by zero
(Деление на ноль)
Предшествующая операция пытается выполнить деление на ноль.
О 63 Invalid file type
(Неверный файловый тип)
Данный файловый тип не обслуживается процедурой обработки файлов. Например, процедура readin используется для типизированного файла или процедура seek — для текстового файла.
64 Cannot Read or Write variables of this type
(Нет возможности считать или записать переменные данного типа)
Нарушены следующие ограничения:
процедуры read и readln могут считывать переменные символьного, целого, действительного и строкового типов;
процедуры write и writein могут выводить переменные символьного, целого, действительного, булевского и строкового типов.
П 65 Pointer variable expected
(Нужно использовать переменную-указатель)
Предыдущая переменная должна быть указателем.
П 66 String variable expected
(Нужна строковая переменная)
Предшествующая переменная должна иметь строковый тип.
□ 67 String expression expected
(Нужно выражение строкового типа)
Предшествующее выражение должно иметь строковый тип.
П 68 Circular unit reference (Перекрестная ссылка модулей)
Два модуля ссылаются друг на друга:
Unit A;Unit В;
Uses В;Uses A;
П 69 Unit name mismatch
(Несоответствие имен программных модулей)
Имя программного модуля, найденное в файле TPU, не соответствует имени, указанному в предложении uses.
О 70 Unit version mismatch
(Несоответствие версий модулей)
Один или несколько программных модулей, используемых данной программой, были изменены после их компиляции. Воспользуйтесь опцией Compile | Make или Compile | Build в интегрированной среде или опциями /м или /в в компиляторе ТРС, что позволит автоматически скомпилировать программные модули, нуждающиеся в перекомпиляции.
71 Duplicate unit name •
(Повторное имя программного модуля)
Вы уже указали этот программный модуль в операторе uses.
П 72 Unit file format error
(Ошибка формата файла модуля)
Файл TPU не соответствует стандарту Турбо Паскаля.
73 IMPLEMENTATION expected
(Отсутствует исполняемая часть модуля)
74 Constant and case types do not match
(Типы констант и тип выражения оператора CASE не соответствуют
друг другу)
Тип константы оператора. case не совместим с выражением в операторе варианта.
75 Record variable expected
(Нужна переменная типа запись)
Предшествующая переменная должна иметь тип запись.
76 Constant out of range
(Константа нарушает границы)
Возможные причины сообщения:
вы пытаетесь указать индекс массива, выходящий за его границы;
вы пытаетесь присвоить переменной значение, выходящее за границы, допустимые для типа этой переменной;
вы пытаетесь передать в качестве фактического параметра процедуре или функции константу, выходящую за границы, допустимые для типа соответствующего формального параметра.
77 File variable expected
(Нужна файловая переменная)
Предшествующая переменная должна иметь файловый тип.
78 Pointer expression expected
(Нужно выражение типа указатель)
Предшествующее выражение должно иметь тип указателя.
79 Integer or real expression expected
(Нужно выражение вещественного или целого типа)
Предшествующее выражение должно иметь тип REAL или INTEGER.
80 Label not within current block
(Метка не находится внутри текущего блока)
Оператор GOTO не может ссылаться на метку, находящуюся вне текущего блока.
81 Label already defined
(Метка уже определена)
Данная метка уже помечает оператор.
О 82 Undefined label in processing statement part
(Неопределенная метка в предшествующем разделе операторов)
Данная метка была описана, и на нее осуществлялась ссылка в предшествующем разделе операторов, но она не указана в тексте программы.
83 Invalid @ argument
(Неправильный аргумент операции @)
Правильнымиаргументамиявляютсяидентификаторыпеременных, процедур и функций.
84 Unit expected
(Нужно кодовое слово UNIT)
85expected
(Нужно указать";")
О 86expected .
(Нужно указать":")
87expected
(Нужно указать", ")
88expected
(Нужно указать м (")
89expected
(Нужно указать")")
90expected
(Нужно указать " = ")
91expected
(Нужно указать":=")
92or expected
(Нужно указатьили "(.")
□93or expected
(Нужно указать"]" или".)")
О94expected
(Нужноуказать".")
О 95expected
(Нужно указать”. . ”)
О 96 Too many variables
(Слишком много переменных)
Нарушены следующие ограничения:
общий размер глобальных переменных, описанных в программе или программном модуле, не может превышать 64 Кбайт;
размер локальных переменных, описанных в процедуре или функции, не может превышать 64 Кбайт.
П 97 Invalid FOR control variable
(Неправильный параметр цикла оператора FOR)
Параметр цикла оператора for должен быть переменной порядкового типа, определенной в разделе описаний текущей подпрограммы.
П 98 Integer variable expected
(Нужна переменная целого типа)
Предшествующая переменная должна иметь целый тип.
О 99 File and procedure types are not allowed here
(Здесь не могут использоваться файлы или процедурные типы)
Типизированная константа не может иметь файловый или процедурный тип.
100 String length mismatch (Несоответствие длины строки)
Длина строковой константы не соответствует количеству элементов символьного массива.
О 101 Invalid ordering of fields (Неверный порядок полей)
Поля в константе типа запись должны записываться в порядке их описания.
102 String constant expected
(Нужна константа строкового типа)
103 Integer or real variable expected
(Нужна переменная типа INTEGER или REAL)
Предшествующая переменная должна иметь целый или вещественный тип.
О 104 Ordinal variable expected
(Нужна переменная порядкового типа)
Предшествующая переменная должна иметь порядковый тип.
105 INLINE error
(Ошибка в операторе INLINE)
Оператор < не допускается в сочетании с перемещаемыми ссылками на переменные. Такие ссылки всегда имеют размер в слово.
106 Character expression expected
(Предшествующее выражение должно иметь символьный тип)
О 107 Too many relocation items
(Слишком много перемещаемых элементов)
Размер таблицы перемещения файла ЕХЕ превышает 64 Кбайт, что является верхним пределом в Турбо Паскале. Если вы обнаружили эту ошибку, то это значит, что программа просто слишком велика для обработки компоновщиком Турбо Паскаля. Возможно также, что она слишком велика для выполнения в MS-DOS. В таком случае нужно выделить в программе основной раздел, который выполнял бы обращение к двум или более вспомогательным разделам с помощью процедуры EXEC из модуля DOS (см. главу 77/
108 Overflow in arithmetic operator
(Переполнение при выполнении арифметического оператора)
Результат предыдущей арифметической операции не лежит в диапазоне -2 146 483 648 — +2 147 483 647. Исправьте операцию или используйте
вещественные типы вместо целочисленных.
109 No enclosing FOR, WHILE or REPEAT statement
(Нет операторов, заканчивающих операторы FOR, WHILE или REPEAT)
Процедуры break и continue не могут вызываться вне тела оператора цикла.
110 Debug information table overflow
(Переполнение информационной таблицы отладки)
Возможно, программа содержит более 65 536 имен или 65 536 строк. Отключите генерацию таблиц отладки директивой компилятора /sd-J или исправьте один или более модулей.
111
Ошибка с этим кодом не описана в версии 7.0 системы Турбо Паскаль.
CASE constant out of range
(Константа CASE нарушает допустимые границы)
Целочисленные константы оператора case должны находиться в диапазоне от -32 768 до 32 767.
Error in statement (Ошибка в операторе)
Данный символ не может быть первым символом в операторе.
Cannot call an interrupt procedure (Невозможно вызвать процедуру прерывания)
Вы не можете непосредственно вызвать процедуру обработки прерывания.
115
Ошибка с этим кодом не описана в версии 7.0 системы Турбо Паскаль.
О 116 Must be in 8087 mode to compile this
(Для компиляции необходим режим 8087)
Данная программа может быть скомпилирована только в режиме В состоянииоперации с типами SINGLE, DOUBLE, EXTENDED и
не допускаются.
П 11.7 Targetnot found
(Указанный адрес не найден)
Команда Compile | Find error в среде Турбо Паскаля (или поиск с помощью опции /F в командной строке компилятора ТРС.ЕХЕ) не обнаружила оператор, соответствующий заданному адресу.
П 118 Include files are not allowed here (Здесь не допускаются включаемые файлы)
Раздел операторов должен целиком размещаться в одном файле.
П 119 No inherited methods are accessible here
(В этом месте программы нет унаследованных методов)
Вы используете зарезервированное слово INHERITED вне метода объекта или в методе, который не унаследован от родительского объекта.
120
Ошибка с этим кодом не описана в версии 7.0 системы Турбо Паскаль.
О 121 Invalid qualifier
(Неверный
Возможные причины сообщения:
вы пытаетесь индексировать переменную, которая не является массивом;
вы пытаетесь указать поля в переменной, которая не является записью;
вы используете в качестве указателя переменную, которая не является указателем.
122 Invalid variable reference
(Недействительная ссылка на переменную)
Предыдущая конструкция удовлетворяет синтаксису ссылки на переменную, но она не указывает адрес памяти. Возможно, вы вызываете функцию-указатель, но забываете сослаться на результат с помощью знака
123 Too many symbols
(Слишком много символов)
Программа или программный модуль содержат более 64 Кбайт символов. Если вы компилируете программу с директивойто попробуйте от
ключить эту директиву или разбейте программу на несколько модулей.
О 124 Statement part too large
(Слишком большой раздел операторов)
Турбо Паскаль ограничивает размер раздела операторов до величины примерно 24 Кбайт. Если вы обнаружили эту ошибку, поместите части раздела операторов в одну или несколько процедур и вообще сделайте вашу программу более структурированной.
125
Ошибка с этим кодом не описана в версии 7.0 системы Турбо Паскаль.
О 126 Files must be var parameters (Файлы должны передаваться как
Вы пытаетесь передать процедуре или функции параметр-значение файлового типа. Параметры файлового типа должны быть параметрами- переменными.
О 127 Too many conditional symbols (Слишком много условных символов)
Недостаточно памяти для определения условных символов (слов, управляющих командами условной компиляции). Попытайтесь удалить некоторые символы или уменьшить их
128 Misplaced conditional directive (Пропущена условная директива)
Компилятор обнаружил директивуилибез соответствующих директивИЛИ
О 129directive missing
(Пропущена директива ENDIF)
Исходный файл закончился внутри конструкции условной компиляции.
В исходном файле должно быть равное количество директиви
{$ENDIF}.
130 Error in initial conditional defines (Ошибка в условных
Исходные условные символы, указанные в опции Options | Compiler | Conditional defines, являются недействительными. Турбо Паскаль требует нуля или более идентификаторов, разделенных пробелами, запятыми или точками с запятой.
131 Header does not match
(Заголовок не соответствует предыдущему определению)
Возможные причины сообщения:
заголовок процедуры или функции, указанный в интерфейсной секции, не соответствует заголовку в исполняемой части;
заголовок процедуры или функции, указанный с помощью опережающего описания FORWARD, не соответствует заголовку найденной далее одноименной процедуры или функции.
132 Critical disk error (Критическая ошибка диска)
Во время компиляции произошла критическая ошибка диска (например, дисковод находится в состоянии "не готов").
133 Cannot evaluate this expression (Нельзя вычислить данное выражение)
В выражении-константе или в отладочном выражении вы пытаетесь использовать неподдерживаемые средства, например, в описании константы пытаетесь использовать функцию sin или вызвать в отладочном выражении определенную пользователем функцию.
134 Expression incorrectly terminated (Некорректное завершение выражения)
Контекстуально в данном месте программы должен быть конец выражения или оператора.
П 135 Invalid format specifier
(Неверный спецификатор формата)
Используется неверный спецификатор формата или числовой аргумент спецификатора формата выходит за допустимые границы.
О 136 Invalid indirect reference
(Недопустимая косвенная ссылка)
Оператор пытается осуществить недопустимую косвенную ссылку. Например, вы используете абсолютную переменную, базовая переменная которой в текущем модуле неизвестна, или используете программу типа INLINE, в которой делается ссылка на переменную, неопределенную в текущем модуле.
П 137 Structured variable are not allowed here
(Здесь нельзя использовать переменную структурного типа)
Делается попытка выполнить над переменной структурного типа неподдерживаемую операцию. Например, вы пытаетесь перемножить две записи.
П 138 Cannot evaluate without System unit
(Нельзя вычислить выражение без модуля SYSTEM)
Чтобы отладчик смог вычислить выражение, в файле TURBO.TPL должен содержаться модуль SYSTEM.
139 Cannot access this symbol
(Нет доступа к данному символу)
Как только вы скомпилируете программу, все множество ее символов становится доступным. Однако к отдельным символам (например, к ременным) нельзя получитьпока вы не запустите программу.
140 Invalidoperation
(Недопустимая операция с плавающей запятой)
При выполнении операции с плавающей запятой произошло переполнение или деление на ноль.
П 141 Cannotoverlay to memory
(Нельзя выполнить компиляцию оверлейных модулей в
Программа, использующая оверлейные модули, должна компилироваться на диск.
П 142 Procedure or function variable expected (Должна использоваться переменная процедурного типа)
В этом контексте оператор получения адреса @ может использоваться только с переменной процедурного типа.
П 143 Invalid procedure or function reference (Недопустимая ссылка на процедуру или функцию)
Возможные причины сообщения:
вы пытаетесь вызвать процедуру в выражении;
процедура или функция, использующаяся в качестве параметра вызова другой процедуры или функции, должна компилироваться в состояниии не может описываться с помощью зарезервированных
СЛОВ INLINE ИЛИ INTERRUPT.
Cannot overlay this unit
(Этот модуль не может использоваться в качестве оверлейного)
Попытка использовать в качестве оверлейного модуль, который не был скомпилирован с директивой
Toonested scopes
(Слишком много вложений)
В программе не может быть больше 512 вложений с не более чем 128 вложениями в каждом модуле. Вложениями считаются:
каждый модуль в предложении uses;
каждая вложеннаяв типе RECORD;
•
каждый вложенный оператор with.
146 File access denied
(Отказано в доступе к файлу)
Возможные причины:
вы пытаетесь использовать файл с атрибутом "только для чтения" в качестве выводного файла;
вы используете имя каталога вместо имени выводного файла.
О 147 Object type expected
(Здесь должен быть тип OBJECT)
Этот идентификатор должен принадлежать к типу OBJECT.
148 Local object types are not allowed
(Нельзя объявлять локальные объекты)
Нельзя объявить объект в процедуре (функции).
О 149 VIRTUAL expected (Пропущено слово VIRTUAL)
150 Method identifier expected
(Пропущен идентификатор инкапсулированного правила)
О 151 Virtual constructor are not allowed
(Конструктор небыть виртуальным)
□ 153 Destructor identifier expected
(Пропущен идентификатор деструктора)
154 Fail only allowed within constructor
(Обращение к стандартной процедуре FAIL может содержаться только в
155 Invalid combination of opcode and operands (Недопустимая комбинация кода команды и
Код ассемблерной команды не может иметь такие операнды. Причины ошибки:
указано слишком много или слишком мало операндов для данной команды, например, INCили MOV AX;
количество операндов правильное, но их типы или порядок следования не соответствуют данной команде, например, DECMOV
ИЛИ MOV
156 Memory reference expected
(Отсутствует адрес)
Операнд ассемблерной команды не содержит адрес. Вероятно, вы забыли выделить квадратными скобками индексный регистр, например, MOV вместо MOV АХ,
О 157 Cannot add or subtract relocatable symbols (Нельзя складывать или вычитать перемещаемые символы)
В ассемблерных выражениях обоими операндами в операциях сложения или вычитания могут быть только ссылки на константу. Идентификаторы переменных, процедур, функций и меток являются перемещаемыми символами и не могут использоваться в качестве двух операндов одновременно в таких операциях. Если var — идентификатор переменной, а const — константы, то предложения MOV AX,и MOVE AX,
VAR+CONST будут правильными, ввремя как выражение MOV
VAR+VAR недопустимо.
•
158 Invalid register combination (Недопустимая комбинация
Допустимыми комбинациями индексных регистров являются
[DI] , [вх+si],иДругие комбинации
(например, [АХ], [ВР+ВХ],недопустимы. Заметим, что локаль
ные переменные размещаются в стеке и доступ к ним организуется через регистр ВР. Ассемблер автоматически добавляет [ВР] в ссылках на такие переменные, поэтому в операндах типагде LOCAL — локаль
ная переменная, образуется недопустимая ссылка LOCAL [ВР+ВХ] .
159 286/287 instructions are not enabled
(Недоступен набор команд микропроцессоров 286/287)
Используйте директивуно учтите, что такую программу можно
исполнять только на ПК, оснащенных микропроцессором Intel 80286/80287 или более поздним.
О Invalid symbol reference
(Недопустимая ссылка на символ)
К указанному символу нельзя обратиться в ассемблерной программе. Это может быть следствием таких причин:
вы пытаетесь использовать ссылки на стандартные процедуры (функции) или специальные массивы MEM,MEML, PORT и в
ассемблерных операндах;
вы обращаетесь к строковой, вещественной константе или константе типа SET в операнде ассемблерной команды;
в ассемблерном операнде вы обращаетесь к процедуре или функции, написанной в режиме INLINE;
вы пытаетесь получить с помощью операции @Result доступ к результату, возвращаемому функцией;
вы пытаетесь использовать короткую форму команды JMP для передачи управления в непомеченный оператор программы.
161 Code generation error
(Ошибка генерации кода)
Ошибка возникает, в частности, при компиляции ассемблерных фрагментов, содержащих команды LOOP, LOOPE, LOOPNE, JCXZ, если команда ссылается на недоступную метку.
162 ASM expected
(ОтсутствуетзарезервированноесловоASM)
П3.2. Ошибки, возникающие во время выполнения программ
Некоторые ошибки, обнаруженные во время выполнения программы, приводят к появлению на экране сообщения вида
Runtime error nnn at xxxx:yyyy
(Ошибка периода исполнения nnn по адресу хххх:уууу)
где:
П nnn — номер ошибки;
П xxxx:yyyy — адрес (сегмент и смещение).
После этого сообщения программа завершает свою работу.
Ошибки периода исполнения делятся на четыре категории: ошибки, обнаруживаемые DOS (коды ошибок с 1 до 99), ошибки ввода/вывода (с 100 по 149), критические ошибки (с 150 по 199) и фатальные ошибки (коды ошибок с 200 до 255).
П3.2.1. Ошибки, обнаруживаемые ДОС
1 Invalid function number (Неверный номер функции)
Вы пытаетесь вызвать несуществующую функцию ДОС.
П 2 File not found
(Не найден файл)
Ошибка генерируется процедурами RESET, APPEND, RENAME или ERASE в случае, если имя, присвоенное файловой переменной, указывает несуществующий файл.
3 Path not found
(Путь не найден)
Ошибка генерируется процедурами:
RESET, REWRITE, APPEND ИЛИ ERASE в Случае, если ИМЯ, присвоенное файловой переменной, является недействительным или указывает на несуществующий подкаталог;
CHDIR, MKDIR или RMDIR в случае, если путь является недействительным или указывает несуществующий подкаталог.
4 Too many open files
(Слишком много открытых файлов)
Ошибка генерируется процедурами RESET, REWRITE или APPEND в случае, если профамма имеет слишком много открытых файлов. ДОС не позволяет использовать более 15 открытых файлов для каждого процесса. Если ошибка возникла при наличии менее 15 открытых файлов, то она может указывать на то, что файл CONFIG.SYS не содержит параметра FILES=XXX или что этот параметр задает слишком мало файлов. Увеличьте параметр FILES=XXX до какого-либо подходящего значения, например, до 80.
5 File access defined
(Отказано в доступе к файлу)
Данная ошибка генерируется процедурами:
RESET или APPEND в случае, когда имя, присвоенное файловой переменной, указывает каталог или файл, доступный только для чтения, в то время как параметр FILEMODE файловой переменной содержит указание на запись данных;
REWRITE в случае, когда каталог заполнен, или если имя, присвоенное файловой переменной, задает каталог или существующий файл, доступный только для чтения;
RENAME в случае, если имя, присвоенное файловой переменной, указывает каталог или если новое имя указывает существующий файл;
ERASE в случае, если имя, присвоенное файловой переменной, указывает каталог или файл, доступный только для чтения;
MKDIR в случае, если файл с тем же именем уже существует в порождающем каталоге, если нет места в порождающем каталоге или если путь к каталогу содержит имя логического устройства;
RMDIR в случае, если каталог не является пустым, если путь не определяет каталог или если путь задает корневой каталог;
READ или BLOCKREAD в случае типизированного или нетипизированного файла, если файл не открыт для чтения;
WRITE или BLOCKWRITE для типизированного или нетипизированного файла в случае, если этот файл не открыт для записи.
6 Invalid file handle файловый канал)
Данная ошибка генерируется в случае, когда системному вызову ДОС передается недопустимый файловый канал. Эта ошибка не должна возникать в правильно работающей программе. Ее появление является свидетельством того, что файловая переменная каким-либо образом испорчена.
О 12 Invalid file access code
(Недействительный код доступа к файлам)
Ошибка генерируется процедурами RESET или APPEND, если значение параметрав файловой переменной не является допустимым.
15 Invalid drive number
(Недопустимый номер дисковода)
Ошибка генерируется процедурой GETDIR в случае, если номер дисковода не является допустимым.
П Cannot remove current directory
(Нельзя удалить текущий каталог)
Ошибка генерируется процедуройв случае, если путь указывает те
кущий каталог.
П 17 Cannot rename across drives
(Нельзя при переименовании указывать разные дисководы)
Генерируется процедурой RENAME в случае, если оба файла не находятся на одном и том же диске.
Ошибки
Если один из операторов компилировался с директивойто ошибка
ввода/вывода приводит к прекращению выполнения программы. В состояниипрограмма продолжает выполняться, а ошибка возвращается
функцией
П 100 Disk read error
(Ошибка чтения с диска)
Генерируется процедурой READ в типизированном файле в случае, если вы пытаетесь осуществить считывание из полностью прочитанного файла.
П 101 Disk write error
(Ошибка записи на диск)
Ошибка генерируется процедурами CLOSE, WRITE,FLUSH в слу
чае, если диск заполнен.
П 102 File not assigned
(Файлу не присвоено имя)
Ошибка генерируется процедурами RESET, REWRITE, APPEND, RENAME и ERASE в случае, если файловой переменной не было присвоено имя файла с помощью обращения к процедуре ASSIGN.
П 103 File not open (Файл не открыт)
Ошибка генерируется процедурами CLOSE, READ, WRITE, SEEK, EOF, FILEРОЗ, FILESIZE, FLUSH, BLOCKREAD, BLOCKWRITE В Случае, если фаЙЛ не открыт.
П 104 File not open for input (Файл не открыт для ввода)
Ошибка генерируется процедурами READ, READLN, EOF, EOLN, SEEKEOF или SEEKEOLN в текстовом файле в случае, если файл не открыт для ввода.
П 105 File not open for output (Файл не открыт для вывода)
Ошибка генерируется процедурами WRITE или WRITELN в текстовом файле в случае, если файл не открыт для вывода.
106 Invalid numeric format (Неверный числовой формат)
Генерируется процедурами READ или READLN в случае, если числовое значение, считанное из текстового файла, не соответствует правильному числовому формату.
П3.2.3. Критические ошибки
150 Disk is write protected (Диск защищен от записи)
О 151 Unknown unit (Неизвестный модуль)
П 152 Drive not ready (Дисковод находится "в состоянии "не готов")
П 153 Unknown command (Неопознанная команда)
154 CRC error in data (Ошибка в исходных данных)
П 155 Bad drive requiest structure length
(При обращении к диску указана неверная длина структуры)
О 156 Disk seek error .
(Ошибка при операции установки головок на диске)
П 157 Unknown media type (Неизвестный тип носителя)
П 158 Sector not found (Сектор не найден)
159 Printer out of paper
(Кончилась бумага на принтере)
160 Device write fault
(Ошибка при записи на устройство)
161 Device read fault
(Ошибка при чтении с устройства)
О 162 Hardware failure (Сбой аппаратуры)
ПЗ.2.4. Фатальные ошибки
Этивсегда приводят к немедленной остановке программы.
О 200 Division by zero (Деление на ноль)
201 Range check error
(Ошибка при проверке границ)
Ошибка генерируется операторами, скомпилированными в состоянии при возникновении одной из следующих ситуаций:
индексное выражение массива находилось вне допустимого диапазона;
была осуществлена попытка присвоить переменной значение, находящееся вне диапазона переменной;
была осуществлена попытка передать значение, находящееся вне допустимого диапазона, в качестве параметра процедуре или функции.
202 Stack overflow error
(Переполнение стека)
Эта ошибка генерируется на входе в процедуру или функцию, скомпилированную в режиме {$sч-}, в случае, если нет достаточной области для размещения локальных переменных подпрограммы. Увеличьте размер стека, используя директиву компилятора
203 Heap overflow error (Переполнение кучи)
Эта ошибка генерируется процедурами NEW илив случае, если в
куче нет свободной памяти требуемого размера.
204 Invalid pointer operation
(Недействительная операция с указателем)
Эта ошибка генерируется процедурами DISPOSE илив случае,
когда указатель имеет значение NIL или указывает на адрес, лежащий за пределами динамически распределяемой области памяти.
О 205 Floating point overflow
(Переполнение при операции с плавающей запятой)
В результате выполнения операции с плавающей запятой получено слишком большое вещественное число.
О 206 Floating point underflow
(Исчезновение порядка при операции с плавающей запятой)
Эта ошибка генерируется только в том случае, если используется сопроцессор 8087/80287/80387 с управляющим словом, которое демаскирует ошибку исчезновения порядка. По умолчанию исчезновение порядка приводит к возвращению результата, равного нулю.
207 Invalid floating point operation
(Недопустимая операция с плавающей запятой)
Возможные причины сообщения:
аргумент функций TRUNC или ROUND не может быть преобразован в целое число, находящееся внутри диапазона типа LONGINT (от -2 147 483 648 до +2 147 483 647);
отрицательный аргумент функции SQRT (извлечение квадратного корня);
аргумент функции LN (логарифм) равен нулю или имеет отрицательное значение;
произошло переполнение стека сопроцессора.
П 208 Overlay manager not installed
(He установлена подсистема управления оверлеем)
Ваша программа вызывает оверлейную процедуру или функцию, а подсистема управления оверлеем не инициализирована. Вероятнее всего, в программе отсутствует обращение к процедуре OVRINIT или обращение к этой процедуре завершилось с ошибкой. Нужно иметь в виду, что если в каком-либо из оверлейных модулей содержится раздел инициализации, то в программе необходимо создать дополнительный или использовать имеющийся неоверлейный модуль, вызывающий процедуру OVRINIT в своем разделе инициализации, и указать этот модуль в предложении USES перед любым из оверлейных модулей.
П 209 Overlay file read error
(Ошибка чтения оверлейного файла)
Ошибка чтения произошла, когда подсистема управления оверлеем пыталась считать оверлейный модуль из оверлейного файла.
210 Object not initialized
(Не инициализирован объект),
Вы обращаетесь к виртуальному правилу применительно к неинициализированному объекту (до вызова конструктора).
П 211 Call to abstract method
(Вызов абстрактного правила)
Эта ошибка генерируется правилом ABSTRACT модуля OBJECT при работе в среде Turbo Vision в случае обращения к абстрактному правилу, т. е. к
виртуальному правилу, которое разработано специально для его замены в объектах-потомках.
212 Stream registration error
(Ошибка в регистрируемом потоке)
Эта ошибка генерируется правилом REGISTERTYPE модуля OBJECT при работе в среде Turbo Vision в случае возникновения одной из следующих ошибок:
запись регистрируемого потока не'содержит сегмента данных;;
поле OBJTYPE записи регистрируемого потока нулевое;
указанный тип уже был зарегистрирован;
существует другой тип с таким же полем OBJTYPE.
213 Collection index out of range
(Набираемый индекс выходит из границ диапазона)
Индекс, передаваемый объекту TCOLLECTION при работе в среде Turbo
Vision, выходит за границы диапазона.
214 Collection overflow error
(Переполнение коллекции)
Эта ошибка генерируется объеК1 ом TCOLLECTION при работе в среде Turbo Vision в случае, если делается попытка добавить элемент к коллекции, которую нельзя расширять.
Приложение 4
Стандартные библиотечные модули
П4.1. Модуль System
Модуль System является основной библиотекой Турбо Паскаля. Он реализует подпрограммы для всех встроенных возможностей, таких как ввод/вывод, обработка строк, эмуляция арифметического сопроцессора, управление оверлеями и динамическое распределение памяти. Модуль SYSTEM используется автоматически любым модулем или программой и никогда не указывается в предложении USES.
П4.1.1. Константы
const
ErrorAddr :
Pointer
= NIL; {Адрес ошибки времени выполнения}
ExitProc
Pointer
=
NIL; (Процедура выхода)
ExitCode
Integer
=
0;{Выходной код}
FileMode
Byte
=
2•(Режим открытия файла}
FreeList
Pointer
=
NIL; (Список свободных блоков кучи}
HeapEnd:
Pointer
=
NIL; (Конец кучи}
HeapError
Pointer
=
NIL; {Функция обработки ошибок кучи}
HeapOrg
Pointer
=
NIL; { На чало кучи)
HeapPtr
Pointer
=
NIL; (Указатель кучи)
InOutRes:
Integer
=
0;(Буфер результата ввода/вывода }
OvrCodeList :
Word
=
0;{Список оверлейных сегментов кода}
OvrDebugPtr :
Pointer
=
NIL; (Адрес отладчика оверлеев}
OvrDosHandle:
Word
=
0;{Обработчик DOS оверлеев}
OvrEmsHandle:
Word
=
0;{Обработчик EMS оверлеев}
OvrHeapEnd ;
Word
=
0;(Конец буфера оверлеев}
OvrHeapOrg :
Word
=
0; (Начало буфера оверлеев)
OvrHeapPrt :
Word
=
0; (Указатель буфера оверлеев}
OvrHeapSize
Word
= 0;
размер буфера
OvrLoadList
Word
= 0;
загруженных
RandSeed
: Longlnt
= 0;
значение генератора случайных
PrefixSeg
Word
= 0;
. (Префикс программного сегмента}
Seg0040
Word
=$0040}
SegAOOO
Word
=
SegBOOO
Word
=
SegBSOO
Word
=
Selectorlnc
Word
=селекторов)
StackLimit
Word
= 0;
на нижнюю границу
Test8086
Byte
= 0;
наличия процессора 80x86}
Test8087
Byte
= 0;
{Признак наличия сопроцессора)
OvrCodeList,
OvrHeapSize,
OvrHeapOrg,OvrHeapPtr,
OvrHeapEnd,OvrDosHandle Ииспользуются MO-
дулем Overlay для реализации администратора оверлеев. Буфер оверлеев размещается между сегментом стека и кучей, причем OvrHeapOrg и OvrHeapEnd содержат адреса начала и конца сегмента. Размер буфера оверлеев по умолчанию соответствует размеру максимального оверлея в программе; если в программе нет оверлеев, то размер оверлейного буфера равен нулю.
HeapOrg, HeapPtr, HeapEnd, FreeList, HeapError ИСПОЛЬЗУЮТСЯ ТОрОМ кучи для реализации распределения динамической памяти.
Переменные ExitProc, Exitcode и ErrorAddr реализуют процедуры выхода.
PrefixSeg содержит сегментную часть адреса префикса программного сегмента (PSP).
содержит смещение от нижней границы сегмента стека, соответствующее наименьшему допустимому значению регистраОбычно
stackLimit содержит ноль; если программа откомпилирована с опциями компилятор будет устанавливать его равным 224 для резервирования рабочего пространства на нижней границе сегмента стека, используемого для эмуляции сопроцессора.
используется встроенными программами ввода/вывода для запоминания значения, возвращаемого при последующем вызове стандартной ФУНКЦИИ
RandSeed содержит опорное число генератора случайных чисел. При присваивании конкретного значения этой переменной функция Random будет генерировать строго определенную последовательность псевдослучайных чисел. Процедура Randomize заносит в переменную RandSeed текущее
темное время и тем самым обеспечивает новую псевдослучайную последовательность при очередном прогоне программы.
FileMode позволяет изменить режим доступа, с которым открываются типизированные и нетипизированные файлы стандартной процедурой RESET. Текстовые файлы этой процедурой открываются только для чтения.
seg0040 хранит сегмент участка памяти, в котором MS-DOS размещает свои переменные.
segAOOO содержит сегмент начала видеопамяти для графического режима работы, а SegBOOO и SegBSOO — сегменты видеопамяти для текстового режима.
Selectorlnc используется как шаг наращивания т. н. селекторов — указателей, обеспечивающих линейную модель памяти.
Test8086 хранит результат теста автообнаружения процессора Intel 80x86, а Test8087 — сопроцессора.
П4.1.2. Переменные
var
Input: Text;
Output: Text;
SavelntOO: Pointer;
/Стандартный файл ввода)
{Стандартный файл вывода} {Сохранение вектора $00)
Savelnt75: Pointer;
(Сохранение вектора
$75}
input и output — стандартные файлы, необходимые в каждой реализации языка Паскаль. По умолчанию они ссылаются на стандартные входные и выходные файлы DOS: input — на клавиатуру, output — на экран.
В момент загрузки среда Турбо Паскаля изменяет значения 18 векторов прерывания (векторы $оо, $02, $1N, $21, $23, $24, $35, $36, $37, $38, $39, $ЗА, $зв, $зс, $3D, $ЗЕ, $3F и $75). В переменных SavelntXX сохраняются старые значения этих векторов (хх — номер вектора: SavelntO о — для вектора $00, SavelntlB — для вектора $LB и т. д.).
П4.1.3. Стандартные процедуры и функции
Процедуры управления
Procedure Break
Обеспечивает немедленный выход из операторов повторения.
Procedure Continue
Завершает очередной итерационный цикл операторов повторения.
Procedure Exit
Позволяет немедленно выйти из текущей подпрограммы. При вызове из тела основной программы завершает ее работу.
Procedure Halt[(ExitCode: Word)]
Останавливает выполнение программы и возвращает управление в операционную систему. Необязательный параметр Exitcode определяет код завершения программы.
П Procedure RunError[(ErrorCode: Byte)]
Останавливает выполнение программы и генерирует ошибку периода выполнения программы. Необязательный параметр ErrorCode определяет код ошибки.
Процедуры динамического распределения памяти
Procedure Dispose(var P: Pointer [,Destructor])
Уничтожает динамическую переменную, связанную с указателем р. Необязательный параметр Destructor определяет метод-деструктор для динамического объекта.
Procedure FreeMem(var P: Pointer; Size: Word)
Уничтожает динамическую переменную р размером size байт.
Procedure GetMem(var P: Pointer; Size: Word)
Создает новую динамическую переменную Р заданного размера size.
П Procedure Mark(var P: Pointer)
Записывает в указателе р текущее состояние кучи.
Procedure New(var P: Pointer [,Constructor])
Создает новую динамическую переменную, связанную с указателем р. Необязательный параметр constructor определяет метод-конструктор для динамического объекта.
П Procedure Release(var P: Pointer)
Возвращает кучу в состояние, которое было сохранено в указателе р процедурой Mark.
Функции динамического распределения памяти
Function MaxAvail: Longlnt
•
Возвращает размер наибольшего непрерывного свободного блока кучи, соответствующий размеру наибольшей динамической переменной, которая может быть помещена в кучу.
О Function MemAvail: Longlnt
Возвращает количество имеющихся в куче свободных байтов памяти.
Функции преобразования
Процедуры Pack и unpack, определенные в стандартном Паскале, в Турбо Паскале не реализованы.
Function Chr(X: Byte): Char
Возвращает символ с заданным порядковым номером х.
О Function Ord(X): Longlnt
Возвращает порядковый номер, соответствующий значению х порядкового типа.
Function Round(R: Real): Longlnt
Округляет значение R вещественного типа до ближайшего целого.
О Function Trunc(R: Real): Longlnt
Усекает значение вещественного типа до значения типа Longlnt путем отбрасывания дробной части.
Арифметические функции
При компиляции в режиме использования сопроцессора или его эмуляции арифметические функции возвращают значение типа EXTENDED, в противном случае — типа REAL.
FunctionAbs(R: Real): Real
Возвращает абсолютное значение аргумента.
Function ArcTan(R: Real): Real
Возвращает арктангенс аргумента.
Function Cos(R: Real): Real
Возвращает косинус аргумента.
Function Exp(R: Real): Real
Возвращает экспоненту аргумента.
Function Frac(R: Real): Real
Возвращает дробную часть аргумента.
Function Int(R: Real): Real
Возвращает целую часть аргумента.
Function Ln(R: Real) : Real
Возвращает натуральный логарифм аргумента.
Function Pi: Real
Возвращает значение числая= 3', 1415926535897932385.
Function Sin(R: Real): Real
Возвращает синус аргумента.
О Function Sqr(R: Real): Real
Возвращает аргумент в квадрате.
О Function Sqrt(R: Real): Real
Возвращает квадратный корень аргумента.
Процедуры порядкового типа
Procedure Dec(var X [; DX: Longlnt])
Уменьшает значение переменной х на величину DX, а если параметр DX не задан — на 1.
Procedure Inc(var X [; DX: Longlnt])
Увеличивает значение переменной х на величину DX, а если параметр DX не задан — на 1.
Функции порядкового типа
О Function Odd(X): Boolean
Проверяет, является ли аргумент нечетным числом.
Function Pred(X)
Возвращает предшествующее значение аргумента. Тип результата совпадает с типом аргумента.
О Function Succ(X)
Возвращает последующее значение аргумента. Тип результата совпадает с типом аргумента.
Строковые процедуры
О Procedure Delete(var S: String; Index, Count: Integer)
Удаляет Count символов из строки s, начиная с позиции index.
Procedure Insert(SubS: String; var S: String; Index: Integer)
Вставляет подстроку subs в строку s, начиная с позиции Index.
Procedure Str(X [: Width [: Decimals]]; var S: String)
Преобразует численное значение х в его строковое представление s.
Procedure Val'(S: String; var X; var'Code: Integer)
Преобразует строковое значение s в его численное представление х. Параметр code содержит признак ошибки преобразования (о — нет ошибки).
Строковые функции
Function Concat(SI [, S2, . . . , SN] ) : String
Выполняет конкатенацию последовательности строк.
О Function Copy(S: String; Index, Count: Integer): String
Возвращает подстроку из строки s, начиная с позиции index и длиной Count СИМВОЛОВ.
О Function Length(S: String): Byte
Возвращает текущую длину строки s.
Function Pos(SubS, S: String): Byte
Возвращает позицию, начиная с которой в строке s располагается подстрока subs (о — s не содержит subs).
Функции для работы с указателями и адресами
О Function Addr(X): Pointer
Возвращает адрес заданного объекта х.
Function Assigned: (var P) : Boolean
Проверяет, хранит ли указатель р значение, отличное от NIL, и возвращает TRUE в этом случае.
Function CSeg: Word
Возвращает текущее значение регистра cs.
Function DSeg: Word
Возвращает текущее значение регистра DS.
О Function Ofs (X): Word
Возвращает смещение заданного объекта.
Function Ptr(Seg, Ofs: Word): Pointer
Преобразует сегмент Seg и смещение of s в значение типа "указатель".
Function Seg(X): Word
Возвращает сегмент для заданного объекта х.
О Function SPtr: Word
Возвращает текущее значение регистра SP.
О Function SSeg: Word
, • .
Возвращает текущее значение регистра ss.
Процедуры разного назначения
□
Procedure Exclude
Исключает элемент
(var S: set of T;
т из множества s.
I:
T)
□
Procedure FillChar
(var X; Count: Word;
Value)
Заполняет заданное количество count последовательных байтов переменной х указанным значением value (выражение любого порядкового типа).
О Procedure Include (var S: set of T; I: T) .
Включает элемент т во множество s.
Procedure Move(var X, Y; Count: Word)
Копирует заданное количество последовательных байтов из источника х в переменную Y.
Procedure Randomize
Инициализирует случайным значением (текущим системным временем) встроенный генератор псевдослучайных чисел.
Функции разного назначения
Function Hi(X: Word): Byte
Возвращает старший байт аргумента х.
О Function High(X)1
Возвращает максимальное значение порядкового типа.
О Function Lo(X: Word): Byte
Возвращает младший байт аргумента х.
Function Low(X)
Возвращает минимальное значение порядкового типа.
Function ParamCount: Word
Возвращает число параметров, переданных программе в командной строке (строке вызова).
Function ParamStr(N: Byte): String
Возвращает N-Й параметр командной строки.
Function Random)(Range: Word)]
Возвращает псевдослучайное число. Если параметр Range опущен, функция возвращает вещественное число в диапазоне от о до 1, если указан — целое число в диапазоне от о до Range - 1.
Function SizeOf(X): Word
Возвращает число байтов, занимаемых аргументом.
Function Swap(X)
Производит перестановку старших и младших байтов двухбайтного аргумента х. Тип функции соответствует типу аргумента.
Function UpCase(C: char): Char
Преобразует латинскую букву в заглавную.
Процедуры ввода/вывода
Procedure Assign(var F; Name: String)
Связывает внешний файл Name с файловой переменной F.
О Procedure ChDir(S: String)
Устанавливает текущий каталог.
О Procedure Close(var F)
Закрывает открытый файл.
Procedure Erase(var F)
Удаляет внешний файл».
Procedure GetDir(D: Byte; var S: String)
Возвращает каталог по умолчанию s на заданном диске D:.
Procedure MkDir(S: String)
Создает подкаталог S.
Procedure Rename(var F)
Переименовывает внешний файл.
Procedure Reset(var F)
Открывает существующий файл для чтения или изменения.
О Procedure Rewrite(var F)
Создает и открывает новый файл.
Procedure RmDir(S: String)
Удаляет пустой подкаталог.
Procedure Seek(var F; N: Longlnt)
Устанавливает текущую позицию файла на указанный элемент (не используется с текстовыми файлами).
О Procedure Truncate(var F)
Усекает размер файла до текущей позиции в файле (не используется с текстовыми файлами).
Функции ввода/вывода
О Function EOF(var F): Boolean
Возвращает для файла F признак конца файла.
О Function FilePos(var F) : Longlnt
Возвращает текущую позицию в файле (не используется с текстовыми файлами).
Function FileSize(var F) : Longlnt
Возвращает текущий размер файла (не используется с текстовыми файлами).
Function lOResult: Integer
Возвращает целое значение, являющееся состоянием последней выполненной операции ввода/вывода.
Процедуры для текстовых файлов
О Procedure Append(var F: Text)
Открывает существующий файл для расширения.
П Procedure Flush(var F: Text)
Выталкивает буфер файла вывода.
П Procedure Read([var F: Text;] VI [, V2,...,VN])
Считывает одно или более значений из текстового файла в одну или более переменных.
П Procedure Readin
Выполняет те же действия, что и Read, а потом делает пропуск до начала следующей строки файла.
Procedure SetTextBuf(var F: Text; var Buf [; Size: Word])
Назначает буфер ввода/вывода для текстового файла. Параметр size определяет длину буфера в байтах (если size опущен, длина буфера равна 128 байт).
П Procedure Write ( [var F: Text;] VI [, V2,..., VN])
Записывает в текстовый файл одно или более значений.
П Procedure WriteLn
Выполняет те же действия, что и write, а затем добавляет к файлу маркер конца строки.
Функции для текстовых файлов
□ Function Eoin(var F: Text): Boolean
Возвращает признак конца строки.
Function SeekEo,f [ (var F: Text)]: Boolean
Возвращает признак конца файла. Предварительно пропускает все пробелы, символы табуляции и признаки конца строк.
Function SeekEoln[(var F: Text)]: Boolean
Возвращает признак конца строки. В отличие от Eoin, предварительно пропускает все пробелы и символы табуляции.
Процедуры для нетипизированных файлов
Procedure BlockRead(var F: File; var Buf;
Count: Word [; var Result: Word])
Считывает в переменную Buf count записей из файла F. Необязательный параметр Result содержит истинное количество считанных записей.
О Procedure BlockWrite(var F: File; var Buf;
Count: Word [;var Result: Word])
Передает Count записей из переменной Buf в файл F. Необязательный параметр Result содержит истинное количество переданных записей.
П4.2. Модуль Dos
Модуль DOS реализует ряд очень полезных профамм операционной системы и обработки файлов. Ни одна из профамм модуля DOS не определена в стандартом Паскале и поэтому они размещены в собственном модуле.
П4.2.1. Константы
Константы регистра флагов
Следующие константы используются для проверки отдельных битов флага в регистре Flags после вызова Intr или msdos:
const
FCarry= $0001; FAuxiliary = $0010;FSign= $0080;
FParity = $0004; FZero= $0040;FOverFlow ' = $0800;
Константы режима доступа к файлу
const
fmClosed =режима файл закрыт)
fmlnput =(Маска режима открыт для чтения}
fmOutput =режима открыт для записи}
fmlnOut =открыт для чтения и записи}
Константы атрибутов файла
const
Readonly =
$01;
{Маска
только для чтения}
Hidden=
$02;
{Маска
скрытого файла}
SysFile =
$04;
{Маска
системного файла}
=
$08;
{Маска
заголовка тома}
Directory^
$10;
{Ма ска
ка талога}
Archive =
$20;
(Маска
архивного файла}
=
$3F;
{Маска
любого файла)
П4.2.2.Типы
type
{Типизированные и нетипизированные файлы}
FileRec = record
HandleWord;
Mode: Word;
RecSizeWord;
Privatearray[1..6] of Byte;
UserData : array[1..16]ofByte;
Namearray[0..79]ofChar;
end;
{Текстовые файлы)
TextBuf = array [0..127] of Char;
TextRec = record
Handle
: Word;
Mode
: Word;
BufSize
: Word;
Private
: Word;
BufPos
: Word;
BufEnd
: Word;
BufPtr
: ATextBuf ;
OpenFunc
: Pointer;
InOutFunc : Pointer;
FlushFunc : Pointer;
CloseFunc : Pointer;
UserData
array [1..16] of Byte;
Name
array [0..79] of Char;
32 Зак. 941
Buffer
: TextBuf;
end;
Registers = record
case Integer of
0:(AX,BX,CX,DX, BP,SI,DI, DS, ES, Flags: Word) ;
1:(AL,AH,BL,BH, CL,CH,DL, DH: Byte);
end;
DataTime = record
Year, Month, Day, Hour, Min, Sec: Integer;
end;
SearchRec = record
File: array [1..21] of Byte;
Attr: Byte;
Time: Longlnt;
Size: Longlnt;
Name: String [12] ;
end;
DirStr =
String
[67];
{Диск и каталог}
=
String
[8] ;
{Имя файла}
ExtStr =
String
[4];
(Расширение файла /
=
String
[127];
{Командная строка)
=
String
[79];
(Полный маршрут поиска файла}
П4.2.3.Переменные
var
DosError : Integer;
Значение, запомненное в DosError, представляет собой код ошибки операционной системы:
□ 0 — нет ошибки;
2 — файл не найден;
з — путь не найден;
5 — доступ запрещен;
6 — неверный обработчик;
О 8 — 10 — неправильная среда;
И — неправильный формат;
18 — больше нет файлов.
П4.2.4. Процедуры и функции
Процедуры даты и времени
Procedure GetDate(var Year, Month, Day, DayOfWeek: Word)
Возвращает текущую дату.
Procedure GetFTime(var F; var Time: Longlnt)
Возвращает дату и время последнего обновления файла.
Procedure GetTime(var Hour, Minute, Second, SeclOO: Word)
Возвращает текущее время.
Procedure PackTime(var Pack: DataTime; var Time: Longlnt)
Преобразует запись Pack в 4-байтовое упакованное значение даты и времени типа Longlnt, используемое процедурой SetFTime. Поля записи DateTime не проверяются на диапазон.
Procedure SetData(Year, Month, Day: Word)
Устанавливает текущую дату.
Procedure SetFTime(var F; Time: Longlnt)
Устанавливает время и дату последнего обновления файла.
О Procedure SetTime(Hour, Minute, Second, SeclOO: Word)
Устанавливает текущее время.
Procedure UnpackTime(Time: Longlnt; var Pack: DataTime)
Преобразует 4-байтовое упакованное значение даты и времени, возвращаемое GetFTime, FindFirst или FindNext, в распакованную запись типа DateTime.
Процедуры обслуживания прерываний
П Procedure GetlntVec(IntNo: Byte; var Vec: Pointer)
Возвращает адрес, хранящийся в указанном векторе прерывания.
Procedure Intr (IntNo: Byte; var R: Registers)
Выполняет указанное программное прерывание.
Procedure MSDos(var R: Registers)
Выполняет функцию операционной системы.
Procedure SetlntVec(IntNo: Byte; P: Pointer)
Устанавливает адрес для указанного вектора прерывания.
Функции статуса диска
Function DiskFree(Disk: Word): Longlnt
Возвращает число свободных байтов на указанном диске.
Function DiskSize(Disk: Word): Longlnt
Возвращает полный объем указанного диска в байтах.
•
Процедуры обработки файлов
Procedure FindFirst(Path: String; Attr: Byte;
var Search: SearchRec)
Ищет в указанном или текущем каталоге первый файл, соответствующий заданному имени файла и набору атрибутов.
О Procedure FindNext(var Search: SearchRec)
Возвращает следующий файл, соответствующий имени и атрибутам, указанным в предыдущем вызове FindFrst.
Procedure GetFAttr(var F; var Attr: Byte)
Возвращает атрибуты файла.
О Procedure SetFAttr(var F; Attr: Byte)
Устанавливает атрибуты файла.
О Procedure FSplit(Path: PathStr; var Dir: DirStr;var Name:
NameStr; var Ext: ExtStr)
Разбивает имя файла Path на 3 составные части (каталог, имя файла, расширение).
Функции обработки файла
Function FExpand(Name: PathStr): PathStr
Берет имя файла Name и возвращает полное имя файла (устройство, каталог, имя и расширение).
Function FSearch(Name: PathStr; DirList: String): PathStr
Ищет файл Name в списке DirList каталогов. Элементы списка разделяются точкой с запятой.
Процедуры обработки процессов
Procedure Exec(Name: PathStr; CmdLine: String)
Выполняет заданную программу Name с указанной командной строкой CmdLine.
О Procedure Keep(Code: Word)
Завершает программу и оставляет ее резидентной в памяти.
О Procedure SwapVectors
Обменивает сохраненные векторы прерываний из переменных SavelntXX с текущими векторами.
Функции управления программой
Function DosExitCode: Word
Возвращает код завершения подпроцесса.
Функции управления средой
Function EnvCount: Integer
Возвращает число переменных окружения, содержащихся в среде DOS.
Function EnvStr(Index: Integer): String
Возвращает указанную переменную окружения DOS.
О FunctionGetEnv(EnvVar: String): String
.
Возвращает значение указанной переменной окружения DOS.
Дополнительные процедуры
О Procedure GetCBreak(var CBreak: Boolean)
Возвращает состояние проверки <Ctrl>+<Break>.
Procedure SetCBreak(CBreak: Boolean)
Устанавливает состояние проверки <Ctrl>+<Break> в DOS.
Procedure GetVerify(var Verify: Boolean)
Возвращает состояние флага верификации в DOS.
П Procedure SetVerify(Verify: Boolean)
Устанавливает состояние флага верификации в DOS.
Дополнительные функции
Function DosVersion: Word
Возвращает номер версии DOS.
П4.3. Модуль Crt
Модуль Crt содержит подпрограммы управления текстовым выводом на экран дисплея, звуковым генератором и чтения клавиатуры.
В режиме текстового вывода используются следующие координаты экрана: левый верхний угол экрана имеет координаты (1, 1); горизонтальная координата возрастает слева направо, вертикальная — сверху вниз. Если на экране определено окно, все координаты определяются относительно границ окна. Исключением являются координаты процедуры window установки границ окна, которые всегда задаются относительно границ экрана.
Для чтения клавиатуры существуют две функции — KeyPressed и ReadKey. Функция KeyPressed определяет факт нажатия на любую клавишу и не при-*
останавливает дальнейшее исполнение программы. Функция KeyPressed читает расширенный код нажатой клавиши, Если к моменту обращения к функции не была нажата ни одна клавиша, программа приостанавливает свою работу, ожидая действий пользователя.
Управление звуковым генератором строится по схеме Sound — Delay — NoSound. Процедура SQund включает звуковой генератор и заставляет его непрерывно генерировать звук нужного тона. Процедура Delay приостанавливает работу программы на заданное количество миллисекунд реального времени. Процедура NoSound отключает звуковой генератор.
П4.3.1. Константы
Константы режима работы
const
BW40
0;
{Черно-белый, 40 символов, 25 строк)
BW80
2;
{Черно-белый, 80 х 25)
Mono
7;
{ Монохромный, 80 х 25}
CO40
1;
{Цветной, 40 х 25}
С080
3;
{Цветной, 80 х 25)
=
256;
(Для EGA/VGA режим 43 или 50 строк}
=
CO40;
{Для совместимости с версией 3.0)
=
COSO;
(Для совместимости с версией 3.0)
Константы цветов
const
Black
=0;
(Черный)
Blue
=1;
{Синий}
Green
=2;
(Зеленый}
Cyan
=
(Голубой}
Red
=4;
{Красный}
Magenta
=5;
(Фиолетовый}
Brown6;
(Коричневый)
LightGray = 7;
{Светло-серый)
DarkGray= 8;
(Темно-серый)
LightBlue= 9;
(Ярко-синий)
LightGreen = 10;
(Ярко-зеленый)
LightCyan= 11;
{Ярко-голубой)
LightRed = 12;
(Розовый)
LightMagenta= 13;
(Малиновый)
Yellow= 14;
(Желтый)
White- 15;
(Белый)
Blink128;
(Мерцание символа)
П4.3.2.Переменные
var
CheckBreakBoolean; {Разрешает/запрещает контроль Ctrl-Break)
CheckEofBoolean ; (Разрешает/запрещает контроль Ctrl-Z}
CheckSnowBoolean(Разрешает/запрещает контроль "снега "}
DirectVideo Boolean; (Разрешает/запрещаетпрямой доступ к видеопамяти}
LastMode: Word;(Хранит последний текстовый режим)
TextAttr Byte ;{Хранит текущий байт атрибутов)
WindMin: Word;(Координаты левого верхнего угла текущего окна)
WindMax: Word;(Координаты правого нижнего угла}
П4.3.3. Процедуры и функции
Функции
Function KeyPressed: Boolean
Возвращает True, если на клавиатуре была нажата клавиша, и False в противном случае. Не задерживает исполнение программы.
Function ReadKey: char
Читает символ с клавиатуры без эхоповтора на экране. Приостанавливает исполнение программы до нажатия на любую клавишу, кроме <Shift>, <Ctrl>, <Alt>, <Caps Lock>, <Num Lock>, <ScrollLock>.
Function WhereX: Byte
Возвращает горизонтальную координату текущей позиции курсора относительно текущего окна.
Function WhereY: Byte
Возвращает вертикальную координату текущей позиции курсора относительно текущего окна.
Процедуры
Procedure AssignCrt(var F: Text)
Связывает с файловой переменной устройство CON (клавиатуру для ввода и экран для вывода).
П Procedure ClrEol
Удаляет все символы от текущей позиции курсора до конца строки без перемещения курсора.
П Procedure ClrScr
Очищает экран (окно) и помещает курсор в верхний левый угол.
П Procedure Delay(D: Word)
Приостанавливает работу программы на указанное число D миллисекунд.
П Procedure DelLine
Удаляет строку, на которой находится курсор, и перемещает все строки ниже этой строки на строку вверх. Нижняя строка очищается.
П Procedure GotoXY(X, Y: Byte)
Перемещает курсор в нужное место экрана (окна).,
Procedure HighVideo
Устанавливает высокую яркость символов.
Procedure InsLine
Вставляет пустую строку в позицию курсора.
Procedure LowVideo
Устанавливает низкую яркость символов.
О Procedure NormVideo
Устанавливает нормальную яркость символов.
П Procedure NoSound
Выключает звуковой генератор.
П Procedure Sound(F: Word)
Включает звуковой генератор. F — частота звука (Гц).
П Procedure TextBackground (Color: Byte)
Устанавливает цвет фона.
Procedure TextColor(Color: Byte)
Устанавливает цвет символов.
Procedure TextMode(Mode: Word)
Устанавливает нужный текстовый режим.
Procedure Window(XI, Yl, X2, Y2: Byte)
Определяет текстовое окно на экране, xi, yi — координаты левого верхнего угла, Х2, Y2 — правого нижнего угла.
П4.4.Модуль GRAPH
Модуль GRAPH представляет собой мощную библиотеку графических подпрограмм универсального назначения, рассчитанную на работу с наиболее распространенными графическими адаптерами IBM-совместимых ПК. Подпрограммы модуля Graph обеспечивают различные режимы работы многорежимных адаптеров, полностью используют их цветовые возможности и разрешающую способность.
При исполнении графических программ требуется автономный драйвер графического адаптера (BGI-файл). Если программа использует штриховые шрифты, то, кроме того, нужен один или несколько шрифтовых файлов (CHR-файлы). При необходимости драйвер и шрифты могут быть включены в тело программы еще на этапе компиляции.
В табл. П4.1 перечислены графические адаптеры (и полностью совместимые с ними), поддерживаемые графическими драйверами.
Таблица П4.1. Поддерживаемые графические адаптеры
CGA
Hercules
EGA
3270 PC
MCGA
AT&T 400
VGA
IBM-8514
В табл. П4.2 указаны драйверы для поддержки соответствующих аппаратных средств.
Таблица П4.2. Назначение графических драйверов
CGA.BGI
Драйвер для CGA, MCGA
EGAVGA.BGI
Драйвер для EGA, VGA
HERC.BGI
Драйвер для монохромного Hercules
ATT.BGI
Драйвер для AT&T 6300 (400 строк)
Таблица П4.2 (окончание)
PC3270.BGIДрайвер для IBM 3270 PC
IBM8514.BGIДрайвер для IBM 8514
Во время выполнения программы процедура InitGraph автоматически распознает графический адаптер, установленный на ПК, загружает и инициализирует соответствующий графический драйвер, переводит адаптер в графический режим и возвращает управление вызывающей программе. Процедура CloseGraph выгружает драйвер из памяти и восстанавливает текстовый режим работы адаптера.
Подпрограммы модуля Graph позволяют адресоваться к любому элементу (пикселу) растрового графического экрана и управлять светимостью этого элемента. Для указания пиксела используется следующая система координат: верхний левый угол графического экрана имеет координаты (0, 0); горизонтальная координата X увеличивается слева направо, вертикальная координата Y увеличивается сверху вниз. Например, в режиме 640x480 (адаптер VGA) правый нижний угол экрана имеет координаты (639, 479), а центр экрана — координаты (319, 239).
Некоторые графические подпрограммы используют понятие текущего указателя. Указатель содержит координаты того пиксела, начиная с которого будет строиться изображение подпрограммами LineTo, LineRel, OutText и др. В этом смысле указатель подобен текстовому курсору, но, в отличие от него, не имеет видимого изображения на экране.
Для вывода текстовых сообщений на графический экран модуль GRAPH предоставляет один матричный и 4 штриховых шрифта. Каждый символ матричного шрифта на экране реализуется в виде матрицы из 8x8 пикселов. Штриховые шрифты для каждого символа определяют набор штрихов (векторов), с помощью которых на экране создается (вычерчивается) соответствующий символ. Штриховые шрифты позволяют изменять размеры текстовых надписей в широких пределах без существенного ухудшения качества изображения символов. Однако стандартные штриховые шрифты не содержат символов кириллицы.
В модуле GRAPH имеется несколько процедур для рисования элементарных графических фигур — точек, линий, окружностей, дуг и т. п. При необходимости замкнутые фигуры могут быть закрашены различными цветами и стилями (образцами закраски).
Процедура setviewPort создает на экране графическое окно. Если окно определено, весь дальнейший графический вывод осуществляется относительно координат этого окна и отсекается его границами.
В модуле имеются средства сохранения и выдачи изображений, работы с несколькими фафическими страницами, установки необходимых цветов.
П4.4.1. Константы
Константы задания драйверов и режимов работы
const
Detect
■
0;
CGA
MCGA-
EGA=
EGA64=
—
IBM8514=
—
ATT400=
VGA
=
CurrentDriver—
1;
2;
3;
4;
5;
6;
7;
8;
9;
10;
-128; {Передается в Ge tModeRange)
CGACO=
0;
(320x200; палитра 0; 1 страница)
CGAC1
1;
(320x200; палитра 1; 1 страница)
CGAC2—
О.
(320x200; палитра 2 ; 1 страница}
CGAC3=
3;
(320x200; палитра 3 ; 1 страница)
=
MCGACO=
4;
0;
(640x200; 1 страница}
(320x200; палитра 0; 1 страница}
=
1;
(320x200; палитра 1; 1 страница}
=
2;
(320x200; палитра 2; 1 страница/
MCGAC3=
3;
(320x200; палитра 3 ; 1 страница }
=
=
=
4*
5;
0;
(640x200; 1 страница}
(640x480; 1 страница)
(640x200; 16 цветов; 4 страницы)
=
1;
{640x350; 16 цветов; 2 страницы)
=
0;
(640x200; 16 цветов; 1 страница)
EGA64Hi =
1;
(640x350; 4 цвета; 1 страница)
EGAMonoHi=
3;
{640x350; 64К: 1 страница; 256К: 2 страницы)
HercMonoHi=
ATT400CO =
=0;
0;
(720x348; 2 страницы)
(320x200; палитра 0; 1 страница)
ATT400C1 =
1;
(320x200; палитра 1; 1 страница)
АТТ400С2 = 2;
(320x200; палитра 2; 1
страница}
АТТ400СЗ = 3;
(320x200; палитра 3; 1
страница}
ATT400Med= 4;
(640x200; 1 страница}
ATT400Hi = 5;
(640x400; 1 страница}
VGALo= 0;
{640x200; 16 цветов; 4
страницы}
VGAMed =
(640x350; 16 цветов; 2
страницы}
VGAHi=
(640x480; 16 цветов; 1
страница}
РС3270Н1 = 0;
(720x350; 1 страница}
IBM8514LO 0;
(640x480; 256 цветов}
IBM8514HI= 1;
(1024x768; 256 цветов}
.
Значения ошибок, возвращаемые функцией GraphResult const
grOk= О;{Нет ошибок)
grNoInitGraph = -1; (Графика не инициализирована) grNotDetected = -2;{Графическое устройство не обнаружено)
grFileNotFound = -3;{Файл драйвера устройства не найден)
grInvalidDriver= -4;(Неправильный файл драйвера устройства}
grNoLoadMem
= -5;
(Нет памяти для загрузки драйвера)
grNoScanMem
= -6;
{Нет памяти для просмотра областей)
grNoFloodMem
=
(Нет памяти для закраски областей}
grFontNotFound =шрифта не найден)
grNoFontMem =памяти для загрузки шрифта}
grlnvalidMode=-10{Недопустимый графическийрежим)
grError=-11(Общая ошибка)
grIOerror=-12{Ошибка графического ввода/вывода}
grlnvalidFont=-13{Неверный файл шрифта}
grInvalidFontNum= -14(Неверный номер шрифта}
Константы цвета
const
Black
=
(Черный}
Blue
=
(Синий}
Green
= 2;
{Зеленый}
Cyan
= 3;
{Голубой}
Red
— 4 •
(Красный}
Magenta
—5;
(Фиолетовый}
Brown
= 6;
(Коричневый}
LightGray =
7;
{Светло-серый}
DarkGray
8;
(Темно - серый /
LightBlue
9;
(Ярко-синий)
=
10;
{Ярко-зеленый j
LightCyan=
11;
(Ярко-голубой)
LightRed •=
12;
{Розовый)
LightMagenta=
13;
(Малиновый)
Yellow
14;
(Желтый)
White
15;
(Белый)
Следующие константы цветов могут быть использованы с SetRGBPalette для выбора цветов на фафическом адаптере IBM 8514:
const
EG/XBlack
= 0;
(Темные цвета)
EGABlue
= 1;
EGAGreen
=
EGACyan
= 3;
EGARed
= 4;
EGAMagenta
= 5•
EGALightGray
=
EGAB г own
=20;
EGADarkGray
= 56;
{Светлые цвета}
EGALightBlue
=57;
EGALightGreen
=
EGALightCyan
= 59;
EGALightRed
= 60;
EGALightMagenta
= 61;
EGAYellow
= 62;
EGAWhite
= 63;
Константы типов и толщины линий
const
SolidLn.-б;(Сплошная)
DottedLn=I;-{Точечная}
CenterLn=2;(Штрихпунктирная)
DashedLn=3;(Пунктирная)
UserBitLn =4;(Тип определяется пользователемj
NormWidth = 1; (Нормальная толщина)
ThickWidth= 3;(Тройная толщина}\
Константы управления шрифтом
const
DefaultFont
0;
(Матричныйшрифт 8x8}
TriplexFont=
1;
{Шрифт триплекс; файл TRIP.CHR)
SmallFont=
2;
(Мелкий шрифт ; файл LITT.CHR)
SansSerifFont=
3;
(Прямой шрифт; файл SANS.CHR)
GothicFont =
4;
(Готическийшрифт; файл GOTH.CHR)
HorizDir
0; -
- (Горизонтальное направление)
VertDir=
1;
{Вертикальное направление)
UserCharSize =
0;
{Размер символа, определяемый пользова телем)
LeftText=
0;
(Указатель слева от текста)
CenterText=
1;
(Указатель по центру текста)
RightText=
2;
(Указатель справа от текста)
=
0;
(Указатель снизу от1 текста)
CenterText=
1;
(Указатель по центру текста)
TopText
2;
(Указатель сверху от текста}
Константы отсечения
const
ClipOn = True; {Отсекать изображение)
ClipOff= False;{ Игнорирова ть окно (не отсекать изображение)}
Константы для процедуры Bar3D
const
TopOn = Тrue;(Рисовать вершину)
TopOff= False;{Не рисовать вершину)
Константы шаблона штриховки
const
EmptyFill
= 0;
(Нет штриховки)
SolidFill
= 1;
{Сплошнаяштриховка)
LineFill
= 2;
/штриховка)
LtSlashFill
= 3;
//// штриховка)
SlashFill
= 4;
{/// штриховка (толстыми линиями)}
BkSlashFill
= 5;
{\ \ \ штриховка (толстыми линиями))
LtBkSlashFill
= 6;
{\ \ \ штриховка)
HatchFill
= 7;
{+++ штриховка)
XHatchFill =
{xxx штриховка)
InterleaveFill=9;
{Штриховка в клетку}
WideDotFill =10;
{Штриховка редкими точками}
CloseDotFill=11;
{Штриховка частыми точками}
UserFill=12;
(Штриховка задается пользователем}
Битовые константы
const
=
0;
{MOV}
XORPut =
1;
{XOR}
=
2;
{OR}
=
3;
{AND}
NotPut =
4;
{NOT}
Константа палитры
const
MaxColors = 15;{Максимальное количество цветов}
.
П4.4.2. Типы
type
PaletteType = record{Используется в GetPalette}
Size : Byte;
Colors : array [О..MaxColors] of Shortlnt;
end;
LineSettingsType = record{Используется в GetLineSettings/
LineStyle : Word;
Pattern: Word;
Thickness : Word;
end;
TextSettingsType = record{Используется в GetTextSettings/
Font : Word;
Direction : Word;
CharSize : Word;
Horiz : Word;
Vert : Word;
end;
FillSettingsType = record{ Используется в GetFillSettings}
Pattern : Word;
Color: Word;
end;
FillPatternType = array [1..8] of Byte;
PointType = record{Для задания координат многоугольников}
Y: Integer;
end;
ViemPortType = record{Используется в GetViewSettings)
У1, X2, Y2: Integer;
Clip: Boolean;
end;
ArcCoordsType = record{ Используется в GetArcCoords)
Y:Integer;
Xstart, Ystart: Integer;
Xend, Yend : Integer;
end;
П4.4.3. Переменные
var
GraphGetMemPrt : Pointer;{Pa определение кучи)
GraphFreeMemPrt: Pointer; {Освобождение кучи)
Эти переменные указывают на программы управления кучей модуля Graph. Если ваша программа использует собственный алгоритм управления памятью, присвойте адреса ваших программ распределения и освобождения памяти переменным GraphGetMemPrt И GraphFreeMemPrt.
П4.4.4. Процедуры
Procedure Arc(X, Y: Integer; StAngle, EndAngle, Radius: Word)
Рисует дугу радиусом Radius от начального угла StAngle к конечному EndAngle, используя х, Y как координаты центра.
Procedure Bar(Xl, Yl, X2, Y2: Integer)
Рисует полосу заданного размера текущим стилем и цветом.
Procedure ВагЗО(XI, Yl, X2, Y2: Integer;
Depth: Word; Top: Boolean)
Рисует трехмерную полосу текущим стилем и цветом.
Procedure Circle(X, Y: Integer; R: Word)
Рисует окружность радиуса R, используя х, Y как координаты центра.
Procedure ClearDevice
Очищает экран.
Procedure ClearViewPort
Очищает окно.
Procedure CloseGraph
Закрывает графический режим.
Procedure DetectGraph(var Driver, Mode: Integer)
Возвращает тип Driver установленного драйвера и текущий режим Mode его работы.
Procedure DrawPoly(NumPoints: Word; var PolyPoints)
Рисует многоугольник из NumPoints вершин с координатами в PolyPoints текущим цветом и типом линии.
Procedure Ellipse(X, Y: Integer; StAngle, EndAngle, XR, YR: Word)
Рисует эллиптическую дугу от начального угла StAngle к конечному углу EndAngle, используя х, Y как координаты центра и XR, YR — как горизонтальный и вертикальный радиусы.
О Procedure FillEllipse(X, Y: Integer; XR, YR: Word)
Рисует заштрихованный эллипс, используя х, Y как центр и XR, YR как горизонтальный и вертикальный радиусы.
О Procedure FillPoly(NumPoints: Word; var PolyPoints)
Рисует и штрихует многоугольник, содержащий NPoints вершин с координатами В PolyPoints.
О Procedure FloodFill(X, Y: Integer; Border: Word)
Штрихует замкнутую область, содержащую внутреннюю точку с координатами х, Y и ограниченную линией с цветом Border. Используется текущий образец штриховки и цвет.
CD Procedure GetArcCoords(var ArcCoo: ArcCoordsType)
Возвращает координаты центра, начала и конца дуги.
Li Procedure GetAspectRatio (var XAsp, YAsp: Word)
Возвращает два числа, позволяющие оценить отношение сторон графического экрана (XASP/YASP).
Procedure GetDefaultPalette(var Palette: PaletteType)
Возвращает текущую палитру в записи PaletteType.
ПFillPatt:
Возвращает текущий образец штриховки.
ОFillSettingsType)
Возвращает текущий образец и цвет штриховки.
П ProcedureYl, X2, Y2 : Integer; var BitMap)
Сохраняет в переменной BitMap битовый образ указанной части экрана.
О Procedure
Возвращает текущий стиль, шаблон и толщину линии.
П ProcedureInteger;
varHiLode: Integer)
Для графического драйвера GraphDriver возвращает диапазон возможных режимов работы.
П ProcedurePalette: PaletteType)
Возвращает текущую палитру и ее размер.
О ProcedureTextSettingsType)
Возвращает текущий шрифт, направление, размер и выравнивание текста, установленные процедурами setTextstyle и
П ProcedureViewport:
Возвращает координаты и признак отсечки текущего окна.
О Procedure
Устанавливает стандартные параметры графического режима.
П ProcedureDriver,Integer; Path: String)
Инициализирует графический режим. Переменные Driver и Mode должны содержать тип графического драйвера и его режим работы. Для автоматического определения этих параметров по результатам тестирования аппаратуры можно указать Driver = о. Параметр Path определяет маршрут поиска файла графического драйвера.
П ProcedureYl,Integer)
Рисует линию от точкидо точки
П Procedure LineRel(DX, DY: Integer)
Рисует линию от текущего указателя к точке, заданной приращением координат.
П Procedure LineTo(X, Y: Integer)
Рисует линию от текущего указателя к точке х, Y.
П ProcedureDY)
Смещает текущий указатель к точке, заданной приращением координат.
Procedure MoveTo(X, Y: Integer)
Смещает текущий указатель к точке х, Y.
Procedure OutText(Textstring: String)
Выводит текстовую строку на экран.
Procedure OutTextXY(X, Y: Integer; TextString: String)
Выводит текст в заданное место экрана.
Procedure PieSlice(X, Y: Integer;
StAngle, EndAngle, Radius: Word)
Рисует и штрихует сектор окружности радиусом Radius с центром в х, Y от начального угла StAngle к конечному углу EndAngle.
Procedure Putlmage(X, Y: Integer; var BitMap; BitBlt: Word)
Выводит битовый образ на экран.
G Procedure PutPixel(X, Y: Integer; Color: Word)
Выводит точку цветом color с координатами х, Y.
Procedure Rectangle(XI, Yl, X2, Y2: Integer)
Рисует прямоугольник, используя текущий цвет и тип линии.
Procedure RestoreCRTMode
Восстанавливает текстовый режим работы экрана.
П Procedure Sector(X, Y: Integer; StAngle, EndAngle, XR, YR: Word)
Рисует и штрихует сектор эллипса радиусами XR, YR с центром в х, Y от начального угла StAngle к конечному углу EndAngle.
D Procedure SetActivePage(Page: Word)
Устанавливает активную страницу для графического вывода.
О Procedure SetAllPalette(var Palette)
Изменяет все цвета палитры.
П Procedure SetAspectRatio(XAsp, YAsp: Word)
Изменяет масштабный коэффициент отношения сторон графического экрана.
П Procedure SetBkColor(Color: Word)
Устанавливает цвет фона.
П Procedure SetColor(Color: Word)
Устанавливает основной цвет, которым будет осуществляться рисование.
П Procedure SetFillPattern(Pattern: FillPatternType; Color: Word)
Устанавливает произвольный образец штриховки.
П Procedure SetFillStyle(Pattern, Color: Word)
Устанавливает образец штриховки и цвет.
П Procedure SetGraphBufSize(Size: Word)
Позволяет изменить размер буфера для функций штриховки.
П Procedure SetGraphMode(Mode: Integer)
Устанавливает новый графический режим и очищает экран.
П Procedure SetLineStyle(LineStyle, Pattern, Thickness: Word) Устанавливает толщину и стиль линии.
П Procedure SetPalette(ColorNum, Color: Word)
Заменяет цвет палитры с номером ColorNum на цвет color.
П Procedure SetRGBPallete(ColorNum, Red, Green, Blue: Integer) Позволяет модифицировать палитру для IBM 8514 и VGA.
П Procedure SetTextJustify(Horiz, Vert: Word)
Устанавливает выравнивание текста, используемое в процедурах OutText И OutTextXY.
Procedure SetTextStyle(Font, Direction, CharSize: Word) Устанавливает текущий шрифт, стиль и размер текста.
П Procedure SetUserCharSize(MultX, DivX, MultY, DivY: Word) Изменяет пропорции шрифта.
П Procedure SetViewPort(XI, Yl, X2, Y2: Integer; ClipOn: Boolean) Устанавливает текущее окно для графического вывода.
П Procedure SetVisualPage(PageNo: Word)
Устанавливает номер видимой графической страницы.
П Procedure SetWriteMode(WriteMode: Integer)
Устанавливает режим вывода (копирование или XOR) для линий, рисуемых процедурами DrawPoly, Line, LineRel, LineTo, Rectangle.
П4.4.5.Функции
П Function GetBkColor: Word
Возвращает текущий фоновый цвет.
П Function GetColor: Word
Возвращает текущий цвет.
Function GetDriverName: String
Возвращает строку с именем текущего драйвера.
О Function GetGraphMode: Integer
Возвращает текущий графический режим.
Function GetMaxColor: Word
Возвращает максимальный цвет, который можно задать в SetColor.
Function GetMaxMode: Integer
Возвращает номер максимального режима текущего загруженного драйвера.
D Function GetMaxX: Integer
Возвращает максимальную горизонтальную координату графического экрана.
П Function GetMaxY: Integer
Возвращает максимальную вертикальную координату фафического экрана.
Function GetModeName(ModeNum: Word): String
Возвращает строку с именем указанного графического режима.
Function GetPaletteSize: Integer
Возвращает размер таблицы палитры.
П Function GetPixel(X, Y: Integer): Word
Возвращает цвет пиксела с координатами х, Y.
П Function GetX: Integer
Возвращает координату X текущего указателя.
П Function GetY: Integer
Возвращает координату Y текущего указателя.
П Function GraphErrorMsg(ErrorCode: Integer): String
Возвращает строку сообщения об ошибке для заданного кода ErrorCode.
П Function GraphResult: Integer
Возвращает код ошибки ДЛЯ последней фафической операции.
П Function Imagesize(XI, Yl, X2, Y2: Integer)
Возвращает число байтов, требуемое для сохранения прямоугольной области экрана.
П Function InstallUserDriver(Name: String; AutoDetectPtr: Pointer): Integer
Устанавливает пользовательский драйвер устройства в таблицу драйверов устройств.
О Function InstallUserFont(FontFileName: String): Integer
Устанавливает новый шрифт, который не встроен в BGI-систему.
О Function RegisterBGIdriver(Driver: Pointer): Integer
Регистрирует драйвер для графической системы.
Function RegisterBGIfont(Font: Pointer): Integer
Регистрирует шрифт для графической системы.
Function TextHeight(TextStr: String): Word
Возвращает высоту строки в пикселах.
Function Textwidth(TextStr: String): Word
Возвращает ширину строки в пикселах.
Приложение 5
Тексты программ
П5.1. Программа определения дня недели •
(Эта программа вводит дату в формате дд ММ ГГГГ и выводит на экран
соответствующий этой дате день недели. Описание программы см.
разд.2. 7.1.}
var
IsCorrectDate: Boolean; (Признак правильной даты}
d,m, у: Integer; {Вводимаядата — день, месяц и год}
I,
1
Procedure InputDate(var d,m, у : Integer; var correctly : Boolean);
(Вводит в переменные d, m и у очередную дату и проверяет ее.
Если дата правильная, устанавливает correctly=true, иначе correctly=false }
begin {InputDate}
Write('Введите дату в формате ДЦ ММ ГГГГ: ');
ReadLn(d,m,у) ;
correctly := (d>=l) and (d<=31) and (m>=l)
' and (m<=12) and (y>=1582) and (y<=4903)
end;{InputDate}
Procedure WriteDay(d,m,у :
const
Days_of_week: array [0. .6] of String [11] =
('воскресенье','понедельник','вторник',
'среда',’четверг','пятница’,'суббота');
var
с, w : Integer;
begin
if m < 3 then
begin(Месяц январь или февраль}
m := m + 10;
У := У ~1
end
else
m := m — 2;{Остальные месяцы)
с := у div 100;{Вычисляем столетие}
у := у mod 100;год в
w := abs (trunc (2.6*m-0.2) +d+y div 4+y+c div 4-2*c) mod 7 ;
WriteLn(Days_of_week[w])
end;
/;
begin
repeat
InputDate (d,m,y, IsCoprectDate);
if IsCorrectDate then
WriteDay(drm,y)
until not IsCorrectDate
end.
П5.2. Определение биоритмов
{Программа для определения физической, эмоциональной и интеллектуальной активности человека. Вводится дата рождения и текущая дата. Программа вычисляет и выводит на экран общее количество дней, часов, минут и секунд, разделяющих обе даты, а также прогнозирует на месяц вперед даты, соответствующие максимуму и минимуму биоритмов.
Описание программы см. разд. 2. 7.2.}
const
Size_of_Month: array [1..12] of Byte =
(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) ;
var
dO, d,(Днирождения и текущий)
mO, m, (Месяцырождения и текущий)
уо, 'у, (Годы рождения и текущий)
dmin,(Наименее благоприятный день)
dmax,(Наиболее благоприятный день)
days: Integer; (Количество дней от рождения)
Procedure:
дату рождения идату.дат и их
непротиворечивость (текущая дата должна быть позже даты var correctly: Boolean;правильного
I1
1 Procedure InpDateString; varу:
приглашениевводит дату в формате ММ ГГГГ и
проверяет ее const 1800;правильный
=правильный
begin repeat Write(text); ReadLn(d,m,у); correctly :=(у>=YMIN)and(Y<=YMAX)and(m>=1)
and (m <= 12} and (d > 0) if correctly then if(m=2)and(d = 29)and(ymod4 =0)
then неэто 29 февраля високосного
else correctly := d <= if not correctly then в until correctly end; {InpDate} Г^ begin repeat Введите дату рождения в формате ДЦ ММdO,
Введите текущую дату: ', d, m, непротиворечивость correctly := у > if not correctly and (у = yO) then
begin
correctly := m > mO;
if not correctly and (m = mO) then correctly := d >= dO end
until correctly
end; {InputDates}
{
Procedure Get_numbers_of_days (dO,mO,yO, d,m, y: Integer; var days: Integer)
{Определение полного количества дней, прошедших от одной даты до другой) { I
Procedure Variant2;
{Подсчет количества дней в месяцах, разделяющих обе даты} var
mm : Integer;
begin {Variant2}
mm := mO;
while mm < m do
begin
days := days + Size_of_Month[mm] ;
if (mm = 2) and (yO mod 4 = 0) then inc (days); inc (mm)
end
end; {Variant2}
{;
Procedure Variants;
{Подсчет количества дней в месяцах и годах, разделяющих обе даты) var
mm, yy : Integer;
begin {Variants)
mmmO + 1;
while mm <= 12 do{Учитываем остаток года рождения:)
begin
days := days+Size_of_Month[mm] ;
if (mm = 2) and (yO mod 4 = 0) then
inc (days);
inc (mm) end;
уу := уО + 1;
while уу < у doразницу
beginI
days := days + if yy modthen
inc(days); inc(yy) end; mm while mm <doначало текущего
begin
days :=days+ if (y modand (mm= 2) then
inc(days);
inc(mm)
end end;
--} begin if (y = yO) and (m =thenотличаются только
days := d — dO elseотличаются не только
begin days := d +- dO;
количество в текущем месяце и количество дней до конца месяца if (yO modand (mO = 2) then
високосный год) if у = yO then Variant2в месяцах одного и того же
else отличаются годами) end end; ()
ProcedureInteger; days:
{Поиск критических дней)
const
TF = 2*3.1416/23.6884; {Период физической активности)
ТЕ = 2*3.1416/28.4261; (Период эмоциональной активности)
TI = 2*3.1416/33.1638;{Период интеллектуальной активности}
INTERVAL = 30;{Интервал прогноза)
var
min,(Накапливает минимум биоритмов)
max,(Накапливает максимум биоритмов)
х : Real;{Текущее значение биоритмов)
iInteger;
begin (FindMaxMin}
max := sin(days*TF)+sin(days*TE)+sin(days*TI);
min := max;(Начальное значение минимума и максимума
равно значению биоритмов для текущего дня) drain :=
dmax : = days ;
for i := 0 to INTERVAL do
begin
x := sin( (days+i)*TF) + sin((days+i)*TE) + sin((days+i)*TI) ;
if x > max then begin
max := x;
dmax := days + i
end
else if x < min then
begin
minx;
dmin := days + i end
end;
end;{FindMaxMin/ 1
/)
Procedure WriteDates (drain,dmax, days :
(Определение и вывод дат критических дней. Вывод дополнительной информации о количестве прожитых дней, часов, минут и секунд }
/ 1
J /
Procedure WriteDate (text: String; dd: Integer);
{Определение даты для дня DD от момента рождения. В глобальных переменных d, m и у имеется текущая дата, в переменной DAYS — количество дней, прошедших от момента рождения до текущей даты. Выводится сообщение TEXT и найденная дата в формате ДЦ-МЕС-ГГГГ} const
Names_of_Monthes : array [1..12] of String [3] = ('янв', 'фев', 'мар', 'апр', 'мая', 'июн', 'июл', 'авг', 'сен', 'окт' , 'ноя', 'дек') ; var dO,mO, уО, ddd : Integer; begin {WriteDate} dO := d; mO := m; yO := y; ddd := days; while dddodd do begin inc(dO);{Наращива ем число}
if(yOmod4<>0)and(dO> Size_of_Month[mO] )or
(yO mod 4=0) and (dO=30) then begin(Корректируеммесяц}
dO := 1; inc(mO) ; if mO = 13 then{Корректируем год)
begin mO := 1; inc(уО) end end; inc (ddd) end;
WriteLn (text, dO, ' - ' , Names_of_Monthes [mO] , '-2 ^)j end; (WriteDate) (j
var
LongDays: Longlnt; ("Длинная"целая переменная для часов, минут и секунд
begin {WriteDates} LongDays := days; WriteLn( 'Прошло: ',LongDays, ' дней, 1 , longDays*24,
' часов, ', LongDays*24*60, ' минут, ',LongDays*24*60*60, ' секунд'); WriteDate ('Наименее благоприятный день: ’,dmin);
WriteDate ( 'Наиболее благоприятный день: ',dmax)
end; (WriteDates} /,
begin (Главная программа} InputDates (dO,mO, yO, d,m,y) ;
Get_numbers_of_days (dO,mO,yO, d,m,y, days) ; FindMaxMin(drain,dmax, days); WriteDates (drain,dmax, days) end.
П5.3. Игра "Ним"
Описание программы см. в разд. 2.7.3.
Uses CRT;{Подключение библиотеки дополнительных
процедур и функций для управления экраном} const
MAXROW = 14; {Максимальное количество рядов}
MAXCOL =количество фишек в ряду}
type
ColType = array [ 1 . .MAXROW] of Integer;
var
exit:Boolean;{Признак окончания работы}
change:Boolean;{Признак изменения условий игры)
nrow:Integer;{Количество рядов}
ncol:ColType;(Максимальное количество фишек порядам}
col:ColType;{Текущее количество фишек по рядам}
{;
Procedure ShowField;
(Отображает на экране текущее состояние игрового поля}
const
FISH = #220;{Символ-указатель фишки}
ХО = 4;(Левая колонка номеров рядов;
XI= 72;{Правая колонка количества фишек)
X= 20;(Левый край игрового поля)
var
i,j : Integer;
begin (ShowField)
for i := 1 to nrow do
begin
GotoXY(Х0,i+4);
write (i);{Номер ряда )
GotoXY(XI,i+4);
write(col[i]:2); (Количество фишек в ряду)
for j := 1 to ncol[i] do(Вывод ряда фишек:}
begin
GotoXY(X+2*j,i+4) ; if j<=col[i] then write(FISH) else write ('. ') end end end; (ShowField)
Procedure Prepare;
{ Подготовка данных и формирование экрана }
const
HeaderO = ’И Г Р А Н И M';
Headerl = 'Выможете взять любое число фишек из любого ряда.';
Header2 = 'Выигрывает тот, кто возьмет последнюю фишку.';
Headsг3 =
Header4 = 'Кол-во фишек';
var
i : Integer;
begin (Prepare)
ClrScr;(Очищаем экран )
(Выводим заголовок:)
GotoXY((80-Length(HeaderO)) div 2,1);
write(HeaderO);
GotoXY((80-Length(Headerl)) div 2,2) ;
write(Headerl);
GotoXY((80-Length(Header?)) div 2,3) ;
writein(Header2);
write(Header3);.
GotoXY(80-Length(Header4), 4) ;
write (Header 4).;
{Подготовить начальную раскладку:}
for i := 1 to nrow do col[i] :=ncol[i]
end;{Prepare}
(;
Procedure GetPlayerMove;
{ Получить, проконтролировать и отобразить ход игрока }
const
TEXTl =Ваш ход в формате РЯД КОЛИЧ '+
'(например, 2 3 — взять из 2 ряда 3 фишки)1;
ТЕХТ2 =’или введите0 0длявыходаиз игры;-10длянастройкиигры*;
ТЕХТЗ = ’Ваш ход:1 ;
Y = 20;(номер строки для вывода сообщений)
var
correctly : Boolean; (признак правильности сделанного хода)
х1,х2: Integer;{вводимыйход}
/)
Procedure GetChange;
{ Ввести новую настройку игры (количество рядов и количество фишек в каждом ряду}
const
tl-'НАСТРОЙКА ИГРЫ';.
t2 = '(ввод количества рядов и количества фишек в каждом ряду)';
var
correctly : Boolean;
i: Integer;
begin (GetChange}
. clrscr;
GotoXY((80-Length(tl)) div 2,1);
write(tl);
GotoXY((80-Length(t2) ) div 2,2);
write (t2);
repeat
GotoXY(1,3);
write('Введите количество рядов (максимум ',MAXROW,'):') ;
GotoXY(WhereX-6,WhereY) ;
I
readln (mow) ;
correctly := (nrow<=MAXROW) and (nrow>l);
if not correctly then
write(#7)
until correctly;
for i 1 to nrow do
repeat
GotoXY(1,1+3);
write (' ряд * ,.i, *, количество фишек (максимум ' , MAXCOL, ' ) :1);
GotoXY(WhereX-6,WhereY);
readln(ncol[i] );
correctly := (ncol[i]<=MAXCOL) and (ncol[i]>0);
if not correctly then , write(#7) until correctly end; f GetChange)
begin {GetPlayerMove} ShowField;{Показать начальное состояние поля }
{ Сообщить игроку правила ввода хода: }
GotoXY((80-Length(TEXT1)) div 2,Y) ; write(TEXT1);
GotoXY((80-Length(TEXT2)) div 2,Y+1); write(TEXT2); repeat { Пригласить игрока ввести ход: ) GotoXY(l,Y+2);
Write(TEXT3); {вывести приглашение и стереть предыдущий ход) GotoXY(WhereX-16,Y+2); {курсор влево на 16 позиций} ReadLn(xl,x2);{ввести очередной ход}
exit:= xl=0;{контроль команды выхода)
change := x1=-1;{контроль команды изменения}
if not (exit or change) then begin correctly := (xl>0) and (xl<=nrow) and (x2<=col[xl]) and (х2>0); if correctly then
33 Зак 94]
begin{ход правильный: }
col[xl] :=раскладку фишек)
ShowField(показать поле)
end else write(#7) {ход неправильный: дать звуковой сигнал } end else correctly := true {случай EXIT или CHANGE) until correctly; if change then GetChange end; (GetPlayerMove)
Procedure SetOwnerMove;
{ Найти и отобразить очередной ход программы )
{.'
Function CheckField : Integer;
{ Проверка состояния игры. Возвращает 0, если нет ни одной фишки (победа игрока), 1 — есть один ряд (победа машины) и количество непустых рядов в остальных случаях )
var
i, j : Integer;
begin (CheckField}
j := 0;
for i := 1 to nrow do if col[i]>0 then inc(j);
CheckField := j
end; {CheckField)
Procedure CheckPlay;
{ Контроль окончания игры } var
i : Integer;
begin {CheckPlay}
GotoXY(l,25) ;
write('Введите 1, если хотите сыграть еще раз, 0 — выход: ’ ) 5 readln(i);
if i=l then change := true else exit := true
end; {CheckPlay)
^^ ^^. ^^. ^^. ^^^^ ^^. Procedure PlayerVictory; { Поздравить игрока с победой и усложнить игру } const tl =С ОТЛИЧНОЙ ПОБЕДОЙ!';
var i : Integer; begin GotoXY((80-Length(tl)) div 2,24); writeln(tl,#7); for i := 1 to nrow do л if ncol[i]<MAXROW then inc(ncol[i]) ; CheckPlay end;{PlayerVictory}
Procedure OwnVictory;
{ Победа машины }
const
tl = 'ВЫ ПРОИГРАЛИ: СЛЕДУЮЩИМ ХОДОМ Я БЕРУ ВЕСЬ РЯД var
i : Integer;
begin {OwnVictory}
i := 1;
while col[i1=0 do inc(i);
GotoXY((80-Length(tl)) div 2,24);
write (tl,i, #7) ;
delay (2000);(задержка на 2 секунды)
col[i] := 0;
ShowField;
CheckPlay
end; {OwnVictory}
/)
Procedure ChooseMove; -
{ Выбор очередного хода }
const
BIT = 6;{количество двоичных разрядов}
type
BitType = array [1..BIT] of Integer;
var
ncbit : array [1..MAXROW] of BitType;
i,j,k : Integer;
nbit : BitType;
Procedure BitForm(n : Integer; var b : BitType);
{ Формирует двоичное представление b целого числа n I var
i : Integer;
begin {BitForm}
for i := BIT downto 1 do
begin
if odd(n) then b[i] := 1 else b[i] := 0;
n n shr 1
end
end; {BitForm},
()
begin (ChooseMove/
{Найти двоичное представление количества фишек во всех рядах:} for i := 1 to nrow do BitForm(col[i],ncbit [i]);
{Найти сумму разрядов по модулю 2:)
for i := 1 to BIT do
begin
nbit[i] := 0;
for j := 1 to nrow do nbit[i] :=xor ncbit[j,i]
end;
{Найти i = старший ненулевой разряд суммы)
i .•— 1 /•
while nbit[i]=0 do inc(i);
if i>BIT then
{Опасный вариант}
begin j := 1;
while col[j]=0 do inc(j); {найтиненулевой ряд} k := 1{взять из него 1 фишку)
end
else
{Безопа сный вариант } ■
begin
j := 1;
while ncbit [j,i]=O do inc(j);(найти нужный ряд}
for i := i to BIT do
if nbit[i]=l then
ncbit [ j , i ] : = ord ( ncbit [ j , i ] =0 ) ; {инверсюразрядов ) k := 0;
for i := 1 to BIT do
begin
if ncbit [j,i]=l then inc(k) ;
if i<BIT then k := k shl 1
end;
k col[j] — k
end;
GotoXY(l,23);
write ('Мой ход:') ;
GotoXY(WhereX-8,WhereY);
delay(1000);*
write (j , ' ', k);
col[j] := col[j]-k
end; {ChooseMove}
begin (SetOwnerMove}
case CheckField of{проверить количество непустых рядов}
0 :ряды пусты - победа игрока}
: OwnVictory; (один непустой ряд — победа машины}
else
ChooseMove; (выбрать очередной ход}
end;{case}
end;{SetOwnerMove}
(/
begin {Главная программа}
nrow := 3;{ Подготовить игру }
ncol [ 1 ] : = 3 ;{ на поле из трех }
ncol[2] := 4;{ рядов фишек }
ncol[3] := 5;
repeat{ Цикл изменения условий игры }
Prepare;{ Подготовить экран }
\ repeat{ Игровой цикл }
Ge t P1ayerMove;{ Получить ход пользователя }
if not (exit or change) then
SetOwnerMove { Определить собственный ход } until exit or change until exit end. ,
П5.4. Программа Notebook
Описание профаммы см. в главе 15.
Program Notebook;
{Программа обслуживает файлы данных "записной книжки”.
Описание программы см. в гл.15}
Uses App, Objects, Menus, Drivers, Views, StdDlg, DOS, Memory, Dialogs; type
{Объект TWorkWin создает рамочное окно с полосами скроллинга для управления встроенным в него объектом Tin terior}
PWorkWin =ATWorkWin;
TWorkWin = object (TWindow)
Constructor Init(Bounds: TRect); end;
(Объект TDlgWin создает диалоговое окно для выбора режима работы) PDlgWin =ATDlgWin;
TDlgWin = object (TDialog)
Procedure HandleEvent(var Event: TEvent); Virtual; end;
/Следующий объект обслуживает внутреннюю часть рамочного окна TWorkWin. Он создает скроллируемое окно с записями из архивного файла и с помощью диалогового окна TDlgWin управляет работой с этими записями)
PInterior =ATInterior;
TInterior = object (TScroller)
PS: PStringCollection;
Location: Word;
Constructor Init(var Bounds: TRect; HS,VS: PScrollBar);
Procedure Draw; Virtual;
Procedure ReadFile;
Destructor Done; Virtual;
Procedure HandleEvent(var Event: TEvent); Virtual;
end;
(Объект-программа TNotebook поддерживает работу с меню и строкой стату- ca)
TNotebook = object (TApplication)
Procedure InitStatusLine; Virtual;
Procedure InitMenuBar; Virtual;
Procedure HandleEvent(var Event: TEvent); Virtual;
Procedure FileSave;
Procedure ChangeDir;
Procedure DOSCall;
Procedure FileOpen;
Procedure Work;
end;
const
{Команды для обработчиков событий:}
cmChDir
= 202;
(Сменить каталог}
cmWork
= 203;
{Обработать данные}
cmDOS
=
(Временно выйти в DOS)
cmCan
= 205;
{Команда завершения работы}
cmDelete
= 206;
{Уничтожить текущую- запись)
cmSearch
= 207;
(Искать нужную запись}
cmEdit
= 208;
(Редактировать запись) ■
cmAdd
= 209;
(Добавить запись)
{Множество временно недоступных команд:}
WinComl: TCommandSet =
WinCom2: TCommandSet = [cmOpen];
LName =LPhone= 11;LMdr = 40;LName =LPhone= 11;LMdr = 40;(Длина поля Name}
LName =
LPhone= 11;
LMdr = 40;
LName =
LPhone= 11;
LMdr = 40;
(Длина поля Phone) (Длина поля Addr)
LLine = LName+LPhone+LAddr; (Длина строки}
type
DataType = record(Тип данных в файле)
Name : String [LName];(Имя)
Phone: String [LPhone]; (Телефон)
Addr : String [LAddr]{Адрес}
end;
var
DataFile: file of DataType;(Файловая переменная}
OpFileF :открытого файла)
Реализация объекта TWorkWin
1 >
Constructor TWorkWin.Init(Bounds: TRect);
( Создание окна данных}
var
HS,VS: PScrollBar; {Полосы-указатели}
Interior: PInterior; (Указатель на управляемое текстовое окно) begin
TWindow.Init(Bounds, ' ', 0);(Создаемновое окно с рамкой)
GetClipRect(Bounds);(Получаемв BOUNDS координаты
' минимальной перерисовываемой части окна}
Bounds.Grow(-1,-1);(Устанавливаемразмеры окна с текстом)
(Включаем стандартные по размеру и положению полосы-указатели:)
VS := StandardScrollBar(sbVertical+sbHandleKeyBoard);
HS := StandardScrollBar(sbHorizontal+sbHandleKeyBoard);
{Создаем текстовое окно:}
Interior : = New(PInterior,Init(Bounds, HS, VS) ) ;
Insert(Interior){Включаем его в основное окно)
end;(TWorkWin.Init}
{.I
Procedure TDlgWin.HandleEvent;
begin
Inherited HandleEvent(Event);
if Event.What=evCommand then
EndModal(Event.Command)
end;
!>
Procedure
файл
var
PF:окно выбора
Word;
s:
begin
экземпляр динамического
нужныйфай
ла)
помощью следующего оператора окно выводится на экран и результат работы пользователя с ним помещается в переменную
Control :=
результат
case Control of
StdDlg.cmFileOpen, cmOk:
beginуказал имя
содержит имя файла)
Assign(DataFile,s);
{$!-)
Reset(DataFile);
if<> 0 then
Rewrite(DataFile);
OpFileF :=
{$!+}
if OpFileF then
begin
DisableCommands(WinCom2);
;
Workк
end
end;
end;
Done)
end;
-I
Procedure
{Закрывает файл данных}
begin
Close(DataFile);
OpFileF := False;
(Разрешаем открыть файл)(Запрещаемработу и сохранение)(Разрешаем открыть файл)(Запрещаемработу и сохранение)EnableCommands (WinCom2);
(Разрешаем открыть файл)
(Запрещаемработу и сохранение)
(Разрешаем открыть файл)
(Запрещаемработу и сохранение)
DisableCommands (WinComl) end; {TNotebook. FileSave}
{ I
Procedure TNotebook.ChangeDir;
(Изменяет текущий каталог)
var
PD: PChDirDialog;
Control: Word;
(Диалоговое окно смены каталога/диска)
begin
New(PD, Init (cdNormal,0) ) ;(Создаемдиалоговое окно)
Control := DeskTopA. ExecView(PD) ; (Используемокно)
ChDir (PDA.DirInputA . DataA) ;(Устанавливаемновый каталог)
Dispose (PD, Done)
(Удаляем окно из кучи)
end; (TNotebook.ChangeDir)
(-;
Procedure TNotebook.DOSCall;
(Временный выход в DOS)
const
txt = 'Для возврата введите EXIT в ответ 1 + 'наприглашениеDOS ... 1 ?
begin
DoneEvents;
(Закрыть обработчик событий)
DoneVideo;
(Закрыть монитор экрана)
DoneMemory;
{Закрыть монитор памяти)
SetMemTop(HeapPtr) ;
{Освободить кучу)
WriteLn (txt) ;
{Сообщить о выходе)
SwapVectors;
(Установить стандартные векторы)
(Передать управление командному процессору DOS: ) Exec(GetEnv( ' COMSPEC '),");
(Вернуться из DOS:)
SwapVectors;
(Восстановить векторы)
SetMemTop (HeapEnd) ;
{Восстановить кучу)
InitMemoг у;{Открыть монитор памяти}
InitVideo;(Открыть монитор экрана)
InitEvents;(Открыть обработчик событий)
InitSysError;(Открыть обработчик ошибок} •
Redraw(Восстановить вид экрана)
end;(DOSCall}
()
Constructor TInterior. Init;
{Создает окно скрроллера}
begin
TScroller. Init (Bounds, Hs, VS) ;
ReadFile;
GrowMode := gfGrowHiX+gfGrowHlY;
SetLimit(LLine, PSA.Count)
end;
Destructor TInterior. Done;
begin
Dispose (PS,Done) ;
Inherited Done
end;
Procedure TInterior. ReadFile;
(Читает содержимое файла данных в массив LINES} var
Integer;
String;
Data: DataType;
text;
begin
PS := New(PStringCollection, Init(100,10) ) ;
seek(DataFile,0);
while not (EOF(DataFile) or LowMemory) do
begin
Read(DataFile, data);
with data do
•
begin
s Name;
while Length(s) < LName do s := s+* ’; s := s+Phone;
while Length(s) < LName+LPhone do sч •.= ss-f ';•
s := s+Addr end;
if so’' then PSA.Insert(NewStr(S)) end; Location0;
end; (ReadFile} {1
Procedure TInterior.Draw;
(Выводит данные в окно просмотра}
var
п,{Текущая строка экрана}
k: Integer;{Текущая строка массива}
В: TDrawBuffer; Color: Byte;
р: PString;
begin if Delta.Y>Location then
Location := Delta.Y;
if Location>Delta.Y+pred(Size.Y) then Location := Delta.Y+pred(Size.Y);
for n := 0 to pred(Size.Y) do
{Size. Y — количество строк окна}
begin k := Delta.Y+n; if k=Location then Color := GetColor(2) else Color := GetColor(1); MoveChar(B,' ',Color,Size.X); if k < pred(PSA.Count) then begin p := PSA.At(k);
MoveStr (В,Copy (рл,Delta.X+l, Size.X) , Color) ;
end;
WriteLine(0,N,Size.X,1,B)
end
end;/ Tin teri or. Dr a w}
(/
Function Control: Word;
{Получает команду из основного диалогового окна]
const
X = 1;
L 12;
DX= 13;
But: array [ 0.. 4 ] of String [13] =(Надписи на кнопках:}
( '~1~ Выход ','~2~ Убрать *,•~3~ Искать ','~4~ Изменить ','-5~ Добавить ');
Txt: array [0..3] of String [52] = (
{Справ о чный текст: )
'Убрать — удалить запись, выделенную цветом*,
'Искать — искать запись, начинающуюся нужными буквами',
'Изменить — изменить поле (поля) выделенной записи',
'Добавить — добавить новую запись');
var
R: TRect;
D: PDlgWin;
k: Integer;
begin
R.Assign(7,6,74,15);
D := New (PDlgWin, Init (R, ' Выберите продолжение: 1));
with DA do
begin
for k := 0 to 3 do(Вставляем поясняющий текст}
begin
R. Assign (1,1+k, 65, 2+k) ;
Insert (New (PStaticText,Init (R,#3+Txt [k] ) ) )
end;
for k0 to 4 do{Вставляем кнопки:)
begin
R. Assign (X+k*DX,6, X+k*DX+L, 8) ;
Insert (New(PButton, Init(R,But[k],cmCan+k,bfNormal)) ) end;
SelectNext (False) ;(Активизируемпервую кнопку}
end;
Control := DeskTopA.ExecView(D) ;{Выполняемдиалог}
end; {Control} (/.
Procedure TInterior .HandleEvent;
Procedure Deleteltem;
{Удаляетуказанный в Location элемент данных} var
D: Integer; PStr: PString;
s: String;
Data: DataType; begin
PStr := PSA.At (Location); {Получаемтекущую запись} s : = copy(PStrA,l,LName);
seek(DataFile,0) ;
D := -1;ID — номер записи в файле)
repeat{Цикл поиска по совпадению поля Name:}
inc (D) ;
read(DataFile,Data) ;
with Data do while Length (Name) < LName do Name := Name+' '
until Data.Name=s;
seek(DataFile,pred(Filesize (DataFile)) ) ;
read (DataFile,Data) ;{Читаемпоследнюю запись}
seek(DataFile,D) ;
write (DataFile,Data) ;{Помещаемее на место удаляемой}
seek(DataFile,pred(FileSize(DataFile)) );
truncate (DataFile) ;(Удаляемпоследнюю запись)
with PSA do D := IndexOf (At (Location) ) ;
PS A.At Free(D);{Удаляем строку из коллекции)
Draw{Обновляем окно}
end; {Deleteltem}
Procedure Additem(Edit: Boolean);
(Добавляет новый или редактирует старый элемент данных}
const
У = 1;
dy= 2;
L = LName+LPhone+LAddr;
var
Data: DataType;
R: TRect;
InWin: PDialog;
BName, BPhone,BAddr: PInputLine;
Control: Word;
OldCount: Word;
String;
p: PString;
begin
Seek(DataFile,FileSize(DataFile)) ;(Добавляемзаписи в конец файла}
repeat(Цикл ввода записей}
if Edit then (Готовим заголовок}
s := 'Редактирование:'
I else
begin
Str(FileSize(DataFile)+1, s) ;
while Length(s) ,< 3 do s := ' 0 * +s;
s := 'Вводится запись N '+s end;
FillChar(Data,SizeOf(Data) , ’ ’); (Заполняем поля пробелами)
R. Assign(15,5, 65,16);
InWin := New(PDialog, Init(R, s) ) ; (Соз да ем окно}
with InWinA do
begin{Формируем окно:}
R.Assign(2,y+l,2+LName,y+2);
BName :=
Insert (BName) ;{Поле имени)
R.Assign (2, y, 2+LName, y+1) ;
Insert(New(PLabel,
Init(R, 'Имя',ВКате)));
R.As s ign(2,y+dy+1,2+LPhone,y+dy+2); BPhone := New(PInputLine, Init(R,LPhone));.
Insert (BPhone) ;(Поле телефона)
R.Assign(2,y+dy,2+LPhone,y+dy+1);
Insert(New(PLabel, Init(R, 'Телефон',BPhone)));
R.Assign(2,y+2*dy+l,2+LAddr,y+2*dy+2);
BAddr : = New(PInputLine, Init (R, LAddr) ) ; Insert (BAddr);{Поле адреса)
R.Assign(2,y+2*dy,2+LAddr,y+2*dy+l);
Insert(New(PLabel, Init(R, 'Адрес',BAddr))); {Вставляем две командные кнопки:} R.Assign(2,y+3*dy+l,12,y+3*dy+3); Insert(New(PButton, Init(R, 'Ввести',cmOK,bfDefault)));
R.Assign(2+20,y+3*dy+l,12+20,y+3*dy+3); Insert(New(PButton, Init(R, 'Выход',cmCancel,bfNormal)));
SelectNext(False)(Активизируемпервую кнопку)
end;{Конец форгфирования окна)
if Edit then with Data do begin(Готовим начальный текст:/
p := PSЛ.At(Location);(Чита ем данные из записи}
s := р"; Name := сору(s,1,LName);
Phone:= copy(s,succ(LName),LPhone);
Addr := copy(s,succ(LName+LPhone),LAddr); InWinA.SetData(Data)(Вставляемтекст в поля ввода}
end;
Control := DeskTopA .ExecView (InWin); (Выполняемдиалог)
if Control=cmOk then with Data do
begin if Edit then Deleteltem;(Удаляем старую запись}
Name := BNameA.DataA;
Phone:= BPhoneA.DataA;
Addr := BAddrA.DataA;
s [0] :=
FillChar (s[l],L,', ') ;
move(Name[1],s[1],Length(Name));■
move(Phone[ 1 ], s [succ(LName)],Length(Phone));
move(Addr[ 1 ], s [succ(LName+LPhone)] , Length(Addr));
OldCount := PSA.Count; {Прежнее количество записей}
PSA.Insert(NewStr(s)); '{Добавляемв коллекцию) {Проверяем добавление} if OldCount <> PSA.Count then
Write (DataFile, Data){Да — добавляем в файл)
end
until Edit or (Control=cmCancel);
Draw
end; {Additem}
Procedure Searchitem;t
{Ищет нужный элемент) Function UpString(s: String): String;
строку в верхний•i
var
k: Integer;«
begin
for k 1 to Length(s) do if s[k] in [ 'a'.. 'z'] then s[k] := chr(ord('A')+ord(s[k])-ord('a'))
else if s[k] in [■'a'. . 'n'J then
s[k] := chr(ord('A')+ord(s[k])-ord('a')) i
else if s[k] in ['p'..'я'] then
s[k? :=)
UpString :'= s end; {UpString}
var
InWin: PDialog;
R: TRect;
s: String;
p: PInputLine;
k: Word;
begin(Searchitem}
R.Assign(15,8, 65,16);
InWin :=
Init(R,'Поиск записи:'));
with InWinA do
begin
R.Assign(2,2,47,3);
p :=NewfPInputLine, Init(R,50));
Insert(p);
R.Assign(1,1,40,2) ;
Insert(New(PLabel, Init(R, 'Введите образец для поиска:',p)));
R.Assign(10,5,20,7);
Insert(New(PButton, Init(R,'Ввести',cmOk,bfDefault)));
R.Assign(25,5,35,7);
Insert(New(PButton, Init(R, 'Выход',cmCancel,bfNormal))); SelectNext(False)
end;
if DeskTopA.ExecView(InWin) = cmCancel then exit;• ,
s := pA.DataA;
Location := 0;
while (UpString(s) >= UpString(PString(PSA.At(Location))A)) and (Location < pred(PSA.Count) ) do
inc(Location);
if (Location < Delta.Y) or (Location > Delta.Y+pred(Size.Y)) then ScrollTo(Delta.X,Location)
else
Draw
end; {Searchitem}
{/
var
R: TPoint;
label Cis;
begin
TScroller.HandleEvent(Event);
case Event.What of
evCommand:
case Event.Command of
cmClose:
begin
Cis:
case Control of{Получить команду из основного диалогового окна}
cmCan, cmCancel: EndModal(cmCancel) ;
cmEdit :
cmDelete: Deleteltem;
cmSearch: Searchitem;
crnAdd :
end
end;
cmZoom: exit;
end;
evMouseDown:{Peaкция на щелчок мышью}
begin
MakeLocal (MouseWhere, R) ;{Получаемв R локальные
координаты указателя мыши} Location := Delta.Y+R.Y;
Draw
end;
evKeyDown:{Реакция на клавиши + -}
case Event . KeyCode of
kbEsc: goto Cls;
kbGrayMinus: if Location > Delta.Y then
begin
dec(Location);
Draw
end;'
kbGrayPlus: if Location < Delta.Y+pred(Size. Y) then
begin
inc(Location);
Draw
end;
end
end
end; {Tinterior.HandleEvent}
(;
Procedure TNotebook.Work;
{Работа с данными}
var
R: TRect;
i
PW: PWorkWin;
Control: Word;
begin
R.Assign(0,0,80,23);
PW : =New(PWorkWin, Init(R));
Control := DeskTopA.ExecView(PW);
Dispose (PW, Done)-
end;
Procedure TNotebook.HandleEvent(var Event: ТЕvent);
(Обработчик событий программы}
begin{TNotebook.HandleEvent}
TApplication.HandleEvent(Event);(Обработка стандартных
команд cmQuit и cmMenu} if Event.What =then
' case Event.Command of
{Обработка новых команд:}
cmOpen: FileOpen;{Открыть файл}
cmSave: FileSave;(Закрыть файл}
cmChangeDir :
cmDOSShell :выход в DOS}
cmWork : Work;{Обработать данные}
else
exit{He обрабатывать другие команды)
end;
ClearEvent(Event){Очистить событие после обработки}
end;{TNotebook.HandleEvent}
Procedure TNotebook.InitMenuBar;
(Создание верхнего меню} var
R: TRect;
begin
GetExtent(R);
R.B.Y := succ(R.A.Y);{R — координаты строки меню}
MenuBar :=Init(R,
NewMenu ({ Созда ем меню}
{Первый элемент нового меню представляет собой подменю (меню второго уровня). Создаем его} NewSubMenu('~Е~/Файл', hcNoContext, /Описываем элемент главного меню} NewMenu({Создаем подменю)
Newl tern ({Первый элемент}
' ~1~/ Открыть',' F3',kbF3,cmOpen,hcNoContext, *
Newl tem ({Второй элемент}
'~2~/ Закрыть','F2',kbF2,cmSave,hcNoContext, , Newl tem ((Третий элемент}
' ~3~/ Сменить диск', ' ',0,cmChangeDir,hcNoContext, NewLine({Строка -разделитель}
Newltem('~4~/ Вызов DOS', ' ', О,cmDOSShell,hcNoContext, Newltem('~5~/ Конец работы','Alt-X', kbAltX,cmQuit,hcNoContext,
NIL)))))) (Нет других элементов подменю) • ), {Создаем второй элемент главного меню)
Newltem('~W~/ Работа','',kbF4,cmWork,hcNoContext, NIL) (Нет других элементов главного меню} )))) end;(TNotebook.InitMenuBar}
{--}
Procedure TNotebook.InitStatusLine;
{Формирует строку статуса}
var
R: TRect; (Границыстроки статуса} begin
GetExtent(R); (Получаемв R координаты всего экрана} R.A.Y := StatusLine := Init(R,{Создаемстроку статуса)
NewStatusDef(0, $FFFF,(Устанавливаем максимальный диапазон кон
текстной справочной службы)
NewStatusKey('~Alt-X~ Выход', kbAltX, cmQuit, NewStatusKey('~F2~ Закрыть', kbF2, cmSaveFile,
NewStatusKey ( '~F3~Открыть’, kbF3, cmOpenFile, NewStatusKey ( ' ~F4~ Работа*, kbF4, cmWork, NewStatusKey ( *~Р10~Меню', kbFlO, cmMenu, NIL) ) ) ) ) ,(Нетдругих клавиш)
NIL){Нет других определений}
));
DisableCommands (WinComl) (Запрещаемнедоступные команды) end; (TNotebook.InitStatusLine} ()
var Nbook: TNotebook; begin Nbook.Init; Nbook.Run; Nbook.Done end.
Приложение 6
Описание редактора векторных шрифтов
В этом приложении приводится описание программы, предназначенной для редактирования существующих или создания новых векторных BGI- шрифтов (файл FONTEDIT.PAS, находящийся на сопровождающей книгу дискете). Такие шрифты используются для вывода текстовых сообщений стандартными процедурами OutText и OutTextXY, ' а также процедурой OytString из модуля F_GrText в графическом режиме работы экрана. Описание формата BGI-шрифтов приведено в разд. 24.2. Программа рассчитана на работу с мышью и экраном типа EGA/VGA.
П6.1. Интерфейс с пользователем
Для запуска программы следует дать команду
fontedit [FName]
В квадратных скобках указан необязательный параметр, определяющий имя уже существующего CHR-файла.
В момент запуска программа проверяет параметры обращения и, если указано имя FName, настраивается на редактирование шрифта из этого файла. Например, обращение
fontedit trip
заставит программу загрузить BGI-шрифт из файла TRIP.CHR с целью редактирования стандартного шрифта TriplexFont (добавления новых символов или изменения старых). Вы можете задать имя с предшествующим ему маршрутом поиска CHR-файла на диске (если файл имеет стандартное расширение CHR, это расширение можно не указывать — см. пример выше).
Если параметр обращения отсутствует, программа запрашивает имя файла. В этот момент вы должны ввести имя, если хотите редактировать шрифт, или нажать клавишу <Enter>, если хотите создать новый.
После правильной загрузки данных из CHR-файла экран приобретет вид, показанный на рис. П6.1.
Из таблицыВ таблицу ОчиститьОбразецСохранить Конец
Ао*«««ааоя<?9глк ► <: я и § ml t 1 _,*_._ „*„
I " # Ш&.’ ( ) * +, -. / 0123456789:; <=>? ЭА С DEFGHI J KLMNO PQBSTUVWXYZ1 \ ] ~ _ * a be de f g hi j к 1 mn о p q r s t u v w x у z j1 } *0 АБВГДЕЖЗИЙКЛМНОП P C T V ФХ Ц Ч ШЩЪЫЬ 3 ЮЯ а б в г де ЖЗ ИЙК Л MHO п 81Й1 ^ . =„ .^^-“j 1 I L -ь T I- _ 4.1= ihl^ji-i^jlx Ji^^u-t F m4|.4J r ■■! !■ pc ту <pxцчшщъыьЭ ЮЯ = ±S£ г J *° . . 4" n Й »
Рис. П6.1. Вид экрана редактора шрифтов
Верхняя строка содержит опции меню, левую часть экрана занимает поле редактора, а правую часть — таблица всех символов из редактируемого BGI- шрифта.
Для создания нового или изменения существующего символа используется поле редактора. Это поле расчерчено точками, определяющими локальные координаты символа — любой векторный элемент представляет собой вектор, соединяющий две произвольные точки (два узла) поля редактора. Каждое изменение поля редактора автоматически отображается в уменьшенном виде (точнее, с масштабными коэффициентами 1, 1) в небольшом окне повторения ниже поля редактора.
Таблица символов предназначена для выбора очередного символа перед его загрузкой в поле редактора, а также для указания того места в ASCII- таблице, куда будет помещен вновь созданный или отредактированный в этом поле символ. Ниже таблицы создается небольшое окно, в котором показывается ASCII-код выбираемого элемента. Так как стандартные векторные шрифты не содержат символов с ASCII-кодами от 0 до 31 и символа #255, соответствующие позиции в таблице заполняются матричными символами шрифта DefaultFont и изображаются пониженной яркостью. При желании вы можете создать векторный символ для любого ASCII-кода, в том числе и для этих позиций таблицы.
Команды меню имеют следующий смысл:
Из таблицы — активизирует выбор из таблицы символа, который требуется загрузить в поле редактора;
В таблицу — выбирает то место в таблице ASCII-кодов, в которое будет помещен вновь созданный или отредактированный символ;
Очистить — очищает поле редактора;
Образец — активизирует выбор из таблицы символа, который будет служить образцом для вновь разрабатываемого; образец выводится в рабочем поле пониженной яркостью и никак не влияет на новый символ;
Сохранить — сохраняет текущее состояние шрифта в файле;
Конец — завершает работу программы и возвращает управление DOS; если к этому моменту в ASCII-таблице были сделаны несохраненные на диске изменения, шрифт автоматически сохраняется в файле.
Работа программы управляется мышью, две кнопки которой отождествляются со следующими действиями:
левая кнопка — выбирает продолжение из меню, начинает или заканчивает формирование очередного векторного элемента в поле редактора, выбирает текущий элемент в таблице символов;,
правая кнопка — отменяет формирование нового векторного элемента, удаляет (стирает) векторный элемент, отменяет выбор из таблицы символов, перемещает в новое положение правую границу символа в поле редактора.
Для редактирования (изменения) существующего векторного символа выберите в меню команду Из таблицы, сместите мышью курсор таблицы (белый прямоугольник) к нужному символу и нажмите левую кнопку — поле редактора очистится, и в него будет помещена совокупность векторов, определяющих начертание нужного символа. Замечу, что все последующие изменения символа в поле редактора никак не связываются с редактируемым символом до тех пор, пока вновь созданный символ не будет помещен на его место. Более того, из поля редактора можно переносить новый символ на любое место — таким способом, например, можно быстро заполнить ту часть русского алфавита, которая по начертанию совпадает с латиницей.
Если вы хотите создать новый символ, не имеющий сходства с ранее созданными, очистите поле редактора с помощью команды Очистить. Возможно, вы захотите посмотреть на детали реализации того или иного ранее созданного символа, но не хотите, чтобы его векторы "мешали" вновь создаваемому — в этом случае используйте команду Образец (например, можно загрузить как образец латинскую букву Р при создании символа Ф).
Для добавления нового вектора подведите указатель мыши к узлу, от которого он должен начинаться, и нажмите левую кнопку — указатель исчезнет,
что сигнализирует о "захвате" узла. Указатель появится вновь только после отпускания кнопки, но будет уже представлять собой небольшой прямоугольник, показывающий границы чувствительности мыши. При перемещении такого указателя по рабочему полю за ним будет тянуться "резиновая нить". В этом состоянии подведите указатель ко второму узлу так, чтобы этот узел попал в прямоугольник указателя, и вновь нажмите левую кнопку, указатель приобретет нормальный вид, а в поле редактора добавится новый вектор. Если вы передумаете и не захотите добавить вектор, нажмите правую кнопку — "резиновая нить" исчезнет без каких-либо последствий.
Для удаления какого-либо вектора подведите к нему указатель и нажмите правую кнопку. Если мышь позиционирована достаточно точно, вектор будет стерт, а стандартная маска указателя заменится на небольшой прямоугольник. Если теперь, не отпуская кнопки, перемещать указатель в рабочем поле, будут стираться все векторы, на которые покажет этот указатель. После отпускания кнопки указатель восстановит стандартный вид, и мышь перестанет стирать векторы.
После того как вы отредактируете символ нужным образом, выберите в меню команду В таблицу и поместите символ на нужное место.
На некотором расстоянии справа от символа в поле редактора формируется вертикальная линия, показывающая текущую ширину символа. Положение этой линии автоматически изменяется, если вновь созданный векторный элемент выходит за нее. Вы можете установить положение линии вручную, для этого подведите к ней указатель и нажмите правую кнопку, указатель изменит свой вид на небольшой прямоугольник. В таком состоянии перемещайте мышь, удерживая нажатой ее правую кнопку; линия будет отслеживать горизонтальное положение указателя. Перед тем как поместить отредактированный символ в таблицу, вы должны убедиться в том, что эта линия задает правильный межсимвольный промежуток.
П6.2. Описание программы
Работа программы проходит в три этапа и реализуется процедурами I.nit, Run И Done.
На этапе выполнения процедуры Init осуществляются следующие действия.
Вводится имя редактируемого или вновь создаваемого CHR-файла, содержащего нужный векторный шрифт (процедура Nameinput).
Если файл уже существует, программа проверяет его структуру и считывает данные (процедура ReadData), в противном случае — запрашивает параметры вновь создаваемого файла и готовит его описание (процедура NewFile).
Переводит экран в графический режим, инициализирует мышь и готовит для нее графические указатели, расчерчивает поле редактирования, выводит таблицу символов и готовит меню для работы с мышью (процедура Buildscreen).
Основная работа реализуется процедурой Run, тело которой состоит из бесконечного цикла while True do begin... end. В ходе этого цикла непрерывно сканируется состояние мыши, и в зависимости от результатов сканирования выполняются следующие действия:
если мышью выбран один из элементов меню (т. е. указатель подведен к соответствующей надписи и нажата левая кнопка мыши), вызывается одна ИЗ процедур GetChar, SetChar, ClearWE, Pattern, SaveFont ИЛИ Exit В зависимости от сделанного выбора;
О если нажата левая кнопка мыши, а указатель при этом был в поле редактора, вызывается процедура MouselnNed, проверяющая выбор того или иного узла поля редактора; если узел выбран, он становится исходной точкой и от него вслед за указателем мыши будет тянуться "резиновая нить"; если "резиновая нить" уже была сформирована предыдущим обращением к MouseinNed, обращение к этой процедуре вызовет формирование нового векторного элемента редактируемого символа;
если нажата правая кнопка и указатель располагался в поле редактора, вызывается процедура MouselnLine, проверяющая выбор того или иного вектора и осуществляющая его стирание, если вектор выбран;
П если не нажата ни одна кнопка, но мышь изменила свое положение, будет вызвана процедура MouseMove, реализующая "резиновую нить".
Завершающий этап работы программы реализуется процедурой Done. В ходе ее выполнения вызывается процедура SaveFont для сохранения в файле сделанных изменений, и восстанавливается текстовый режим работы экрана.
В программе используются процедуры и функции из модулей F_GrText (для формирования меню и вывода номера элемента ASCII-таблицы), F_Mouse (для обслуживания мыши и контроля выбора элемента меню) и F_Anti (для автоматической защиты программы от нападения вирусов).
Приложение 7
Описание содержимого дискеты
Дискета содержит тексты программ и модулей, описываемых в третьей части книги.
Папка 866 содержит Р AS-файлы в кодировке MS-DOS (OEM, кодовая страница 866), папка 1241 содержит те же файлы, но в кодировке Windows (ANSI, кодовая страница 1251).
Ниже перечисляются все расположенные на дискете файлы. В скобках указываются номера разделов книги, в которых описываются или упоминаются программные средства.
DISKETT.PAS — содержит программу для создания дискет, работающих с модулем F_prot (см. разд. 30.2.5).•
F_ANTLPAS — содержит модуль F_Anti, осуществляющий контроль возможного заражения исполняемого ЕХЕ-файла компьютерным вирусом (см. разд. 30.4).
F_DISK.PAS — содержит модуль F_Disk для гибкой работы с дисками (см. разд. 26.3).
FEMS.PAS— модуль F_EMS, который содержит объекты и подпрограммы для работы с EMS-памятью (см. разд. 28.3).
F_GRTEXT.PAS — модуль F_GrText для поддержки текстового вво- да/вывода в графическом режиме (см. разд. 24.3).
О F_MOUSE.PAS — модуль F_Mouse для работы с устройством ввода типа мышь (см. разд. 27.2).
FPROT.PAS— модуль F_Prot для защиты программ от нелегального копирования (см. разд. 30.4).
F_TEXT.PAS — модуль F_Text для поддержки многостраничного вывода в текстовом режиме. Этот модуль не описан в книге, но используется в других модулях.
О FAG.ASM — антивирусный фаг (см. разд. 29.4).
FONTEDIT.PAS — редактор векторных шрифтов формата CHR (см. приложение 6).
SETFAG. PAS — программа для установки антивирусного фага (см. разд. 29.4.1).
Список литературы
Абель П. Язык Ассемблера для IBM PC и программирования: Пер. с англ. Ю. В. Сальникова. — М.: Высш. шк., 1992. — 447 с.
Аваков с.м. Системные функции DOS 4.O. / Библиотека информационной технологии: Вып. 4. / Под ред. Г. Р. Громова. — М.: ИнфоАрт, 1992. - С. 3-98.
Белецкий Я. Турбо Паскаль с графикой для персональных компьютеров: Пер. с польск. Д. И. Юренкова. — М.: Машиностроение, 1991. — 320 с.
Береза Д. А. Графические шрифты фирмы Borland. // Мир ПК. — 1992. — № 8. - С. 109-114.
Блашкин И. И., Буров А. А. Новые возможности Turbo-Pascal 6.O. — СПб.: Изд-во "Макет", 1992. - 64 с.
БорзенкоА.Путешествиепопамяти.//КомпьютерПресс.— 1992.—
№ 7. - С. 7-14.
Бородич Ю. С. и др. Паскаль для персональных компьютеров: Справ. Пособие / Ю. С. Бородич, А. Н. Вальвачев, А. И. Кузьмич. — Минск: Выш. шк.: БФ ГИТМП "НИКА", 1991. - 365 с.
Васильев П. П. Турбо Паскаль — мой друг. — М.: Компьютер, ЮНИТИ, 1995. - 96 с.
Данкан Р. Профессиональная работа в MS-DOS: Пер. с англ. М.: Мир, 1993. - 509 с.
Джордейн Р. Справочник программиста персональных компьютеров типа IBM PC, XT и AT: Пер. с англ. / Предисл. Н. В. Гайского. — М.: Финансы и статистика, 1991. — 544 с.
Зуев Е. А. Язык программирования Turbo Pascal 6.0. — М.: Унитех, 1992. - 298 с.
Касперский Е. Компьютерные вирусы в MS-DOS. — М.: Эдель, 1992. — 176 с.
Мизрохи. Turbo Pascal и объектно-ориентированное программирование. — М.: Финансы и статистика, 1992. — 185 с.
Моисеенков И. Суета вокруг Роберта или Моррис-сын и все, все, все... // Компьютер Пресс. — 1991. - № 8. — С. 45-62
1036.Список литературы
Мурашко И. В., Авалян В. Э. Библия MS-DOS версии 5.0 в 2-х книгах. Кн. 1. — М.: НПО "Гермес", 1992. — 220 с. -
Мурашко И. В., Авалян В. Э. Библия MS-DOS версии 5.0 в 2-х книгах. Кн. 2. - М.: НПО "Гермес", 1992. - 251 с.
Основский Ю. Н. Компьютерные вирусы. Классификация, методы борьбы. -М.: Центр МИФИ СП "Диалог", 1990. — 41 с.
Правиков Д. И. Можно ли защититься от нелегального копирования? / Библиотека информационной технологии: Вып. 4. / Под ред. Г. Р. Громова. - М.: ИнфоАрт, 1992. - С. 99-107.
Расторгуев С. П., Дмитриевский Н. Н. Искусство защиты и "раздевания" программ. — М.: Совмаркет, 1991. — 94 с.
Скэнлон Л. Персональные ЭВМ IBM PC и XT. Программирование на языке ассемблера: Пер. с англ. — 2-е изд., стереотип. — М.: Радио и связь, 1991. — 336 с.
Спесивцев А. В. и др. Защита информации в персональных ЭВМ. — М.: Радио и связь, МП "Веста", 1992. — 192 с.
Справочник по процедурам и функциям Borland Pascal with Objects 7.0. — Киев: "Диалектика", 1993. — 272 с.
Фаронов В. В. Программирование на персональных ЭВМ в среде Турбо Паскаль. — М.: Изд-во МГТУ, 1990. — 580 с.1
Фаронов В. В. Турбо Паскаль (в 3-х книгах). Кн. 1. Основы Турбо Паскаля. - м.. Учебно-инженерный центр "МВТУ-ФЕСТО ДИДАКТИК", 1992. - 304 с.
Фаронов В. В. Турбо Паскаль (в 3-х книгах). Кн. 2. Библиотека Turbo Vision. — М.: Учебно-инженерный центр "МВТУ-ФЕСТО ДИДАКТИК", 1993. - 412 с.
Фаронов В. В. Турбо Паскаль (в 3-х книгах). Кн. 3. Практика программирования. Часть 1. — М.: Учебно-инженерный центр "МВТУ-ФЕСТО ДИДАКТИК", 1993. - 256 с.
Фаронов В. В. Турбо Паскаль (в 3-х книгах). Кн. 3. Практика программирования. Часть 2. — М.: Учебно-инженерный центр "МВТУ-ФЕСТО ДИДАКТИК", 1993. - 304 с.
Фаронов В. В. Практика Windows-программирования. — М.: Информпе- чать, 1996. — 247 с.
Фаронов В. В. Паскаль и Windows. — М.: Учебно-инженерный центр "МВТУ-ФЕСТО ДИДАКТИК", 1995. - 539 с.
Фролов А. В., Фролов Г. В. Аппаратное обеспечение IBM PC: В 2-х ч. Ч. 1. - М.: "ДИАЛОГ-МИФИ", 1992. - 208 с.
Список литературы1037
Фролов А. В., Фролов Г. В. Аппаратное обеспечение IBM PC: В 2-х ч. Ч. 2. -М: "ДИАЛОГ-МИФИ", 1992. - 208 с.
Фролов А. В., Фролов Г. В. Операционная система MS-DOS: В 3-х кн. Кн. 3. - М..: "ДИАЛОГ-МИФИ", 1991. ■^—• 224 с.
Фролов А. В., Фролов Г. В. Защищенный режим процессоров Intel 80286, 80386, 80486. Практическое руководство по использованию защищенного режима. — М.: "ДИАЛОГ-МИФИ", 1993. — 240 с.
Фролов А. В., Фролов Г. В. MS-DOS для программиста. Часть 2. — М.: "ДИАЛОГ-МИФИ", 1995. - 256 с.
Федоров А. Особенности программирования на Borland Pascal. — Киев: Диалектика, 1994. — 144 с.
Хершель Р. Турбо Паскаль / 2-е изд., перераб. — Вологда: МП "МИК", 1991. - 342 с.
Хижняк. Пишем вирус и... антивирус. / Под общ. ред. И. М. Овсянниковой. — М.: ИНТО, 1991. — 90 с.
Шерстюк Ф. Н. Вирусы и антивирусы на компьютере IBM PC. / Библиотека информационной технологии: Вып.2. / Под ред. Г. Р. Громова. — М.: ИнфоАрт, 1991. - С. 119-138.
POWER TOOLS PLUS. Процедуры поддержки для Turbo Pascal 4.0: Справочное руководство пользователя. Техническая документация. — 318 с.
।
Магазин-салон
НОВАЯ ТЕХНИЧЕСКАЯ КНИГА”
190005, Санкт-Петербург, Измайловский пр., 29
В магазине представлена литература покомпьютерным технологиямрадиотехнике и электроникефизике и математике
экономикемедицинеи др.
Низкие цены
Прямые поставки от издательствЕжедневное пополнение ассортиментаПодарки и скидки покупателям.
Магазин работает с 10.00 до 20.00без обеденного перерывавыходной день - воскресенье
Тел.: (812)251-41-10, e-mail: trade@techkniga.com
ккДискета содДискета содВ ПОДЛИННИКЕВ ПОДЛИННИКЕИНТЕРНЕТ®ИНТЕРНЕТ®ISBN 5-94157-295-6ISBN 5-94157-295-69785941 5729539785941 572953
к
к
Дискета сод
Дискета сод
В ПОДЛИННИКЕ
В ПОДЛИННИКЕ
ИНТЕРНЕТ®
ИНТЕРНЕТ®
ISBN 5-94157-295-6
ISBN 5-94157-295-6
9785941 572953
9785941 572953
Валерий Васильевич Фаронов - один из известнейших российских специалистов в области программирования на Турбо Паскале и Delphi, разработчик систем управления базами данных и консультант по их эксплуатации. Свою первую книгу по Турбо Паскалю написал еще в 1988году, а ныне является автором более двадцати книг, разошедшихся суммарным тиражом свыше полумиллиона экземпляров. Многие книги Фаронова служат эталоном учебных пособий по продуктам фирмы Borland и получили высокую оценку как представительства фирмы, так и многочисленной армии российских разработчиков ПО,
программ и модулей приведенных
Валерий Фаронов
Turbo Pascal
В книге содержится исчерпывающее руководство по программированию в среде Турбо Паскаль. Детально описаны язык Турбо Паскаль и основные приемы программирования, работа со стандартными библиотеками CRT и Graph, а также объектно-ориентированной библиотекой Turbo Vision.
Рассмотрены особенности операционной системы MS-DOS и их учет при программировании в среде Турбо Паскаля, поддержка основных аппаратных средств компьютера, а также возможности защиты разрабатываемых программ от вирусов и нелегального копирования.
Книга предназначена для начинающих и опытных программистов, студентов и преподавателей, специализирующихся в области информатики и вычислительной техники.
Доступность и простота изложения
Готовые тексты программных модулей
Рекомендации профессионала
БХВ-Петербург
198005, Санкт-Петербург,
Измайловский пр., 29
E-mail: mail@bhv.ru
Internet: www.bhv.ru
тел.: (812) 251-42-44
факс: (812)251-12-95
1 •/•<. •••?5vlv«»A<'Al?;v5Zr#^R'f№y^^^H^^^^^»^^wLWfS^?#wv«R^»«^^;rflf»»8^TW№w?iV«m)^75Rsn№t^S^f^CTI^^^^^^^^^^^^^^^^^^^^^^^^W^^^^^W^^W
Program Average;
{Программа создает массив из N случайных целых чисел, равномерно распределенных в диапазоне от 0 до MAX_VALUE-1, подсчитывает среднее арифметическое этих чисел, а также минимальное и максимальное из них. }
const
N = 1000;элементов
MAXJZALUE =значений случайных чисел}
var