должна придерживаться следующих правил, чтобы правильно
выполняться в этом окружении:
- программа должна быть построена в крохотной или малой
моделях памяти;
- программа не должна использовать функций ДОС от 0 до 12;
- при изменении текущего дискового каталога программа должна
восстанавливать его при возврате в прерванную программу;
- программа не должна использовать операции с плавающей
запятой;
- программа не должна завершаться или выходить в ДОС.
Вас может заинтересовать, почему надо избегать использования
операций с плавающей запятой. Дело в том, что подпрограммы
с плавающей запятой Турбо Си используют ряд векторов прерываний,
которые присоединяются во время выполнения стартового кода. Эти
вектора не восстанавливаются до завершения программы и передачи
управления при этом в стартовый код. В стартовом коде не
поддерживается область сохранения для этих векторов (среди
которых имеется и вектор немаскируемого прерывания 2); таким
образом, при завершении работы TSR-программы и удалении ее из
памяти эти векторы не будут восстанавливаться.
В этолй главе используются программа exec.c из главы 10 и
все примеры оконных программ практически готовых стать
резидентными.
Действия трех программных модулей.
-----------------------------------------------------------------
Три программных модуля для TSR-программ - это popup.c,
resident.c и ваша утилита на Турбо Си. Popup.c (листинг 12.3) и
resident.c (листинг 12.4) содержат сам TSR-драйвер. Popup.c - это
модуль, который надо изменить соответственно требованиям вашей
утилиты, а resident.c - модуль, остающийся неизменным для всех
TSR-программ. В дальнейшем обсуждении мы будем переключаться
между двумя этими модулями, так как они оба созданы для поддержки
создания вами TSR-программ.
Popup.c содержит кроме любого установочного кода,
необходимого вам, еще и несколько переменных, которые надо
инициализировать значениями, описывающими вашу программу.
Размер TSR-программы.
-----------------------------------------------------------------
Беззнаковая переменная sizeprogram специфицирует размер
программы в параграфах по 16 байт. Вы уже прочитали в главе 11,
как определять это значение. До тех пор, пока ваша программа не
начнет действовать, увеличивайте это значение. Программа в
крохотной модели не может быть больше, чем 64К (4096 параграфов),
а в малой модели - больше 128К (8192 параграфов).
Присвоение "горячего ключа".
-----------------------------------------------------------------
Значение клавиши "горячего ключа" TSR-программы определяется
значениями беззнаковых переменных scancode и keymask. При нажатии
клавиши обработчик прерывания 09 читает входной порт клавиатуры,
в котором находится скан-код клавиши (ее порядковый номер).
Каждая клавиша имеет свой скан-код, которые изображены на рисунке
12.1. Следовало бы присваивать такие клавиши, которые не
совпадали бы с клавишами других программ, в том числе и
нерезидентных. Стоит избегать функциональных клавиш, комбинаций с
Alt- и Ctrl-, так как многие программы их используют. Наилучшим
выбором является редкая и оригинальная комбинация клавиш, такая,
как Alt-точка.
ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД¬
¦ЪДДВДД¬ЪДДВДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДД¬ЪДДВДДВДДВДД¬¦
¦¦59¦60¦¦41¦2¦ 3¦ 4¦ 5¦ 6¦ 7¦ 8¦ 9¦10¦11¦12¦13¦43¦14¦¦1 ¦69¦70¦84¦¦
¦ГДД†ДДґГДДБВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДДґГДД†ДД†ДД†ДДґ¦
¦¦61¦62¦¦15 ¦16¦17¦18¦19¦20¦21¦22¦23¦24¦25¦26¦27¦ ¦¦71¦72¦73¦55¦¦
¦ГДД†ДДґГДДДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДЩ 28¦ГДД†ДД†ДД†ДДґ¦
¦¦63¦64¦¦ 29 ¦30¦31¦32¦33¦34¦35¦36¦37¦38¦39¦40¦ ¦¦75¦76¦77¦74¦¦
¦ГДД†ДДґГДДДДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБДДДДДґГДД†ДД†ДД†ДДґ¦
¦¦65¦66¦¦ 42 ¦44¦45¦46¦47¦48¦49¦50¦51¦52¦53¦ 54 ¦¦79¦80¦81¦ ¦¦
¦ГДД†ДДґГДДДДВБДВБДДБДДБДДБДДБДДБДДБДДБДДБДД†ДВДДДДДґГДДБДД†ДДґ78¦¦
¦¦67¦68¦¦ 56 ¦ ¦ 57 ¦ ¦ 58 ¦¦ 82 ¦83¦ ¦¦
¦АДДБДДЩАДДДДЩ АДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ АДДДДДЩАДДДДДБДДБДДЩ¦
¦ ¦
¦ Клавиатура IBM AT ¦
¦ ¦
¦ ¦
¦ЪДДВДД¬ЪДДВДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДВДДДДД¬ЪДДДДДВДДДДД¬¦
¦¦59¦60¦¦ 1¦2¦ 3¦ 4¦ 5¦ 6¦ 7¦ 8¦ 9¦10¦11¦12¦13¦ 14 ¦¦ 69 ¦ 70 ¦¦
¦ГДД†ДДґГДДБВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДВБДВДДДґГДДВДД†ДДВДДґ¦
¦¦61¦62¦¦15 ¦16¦17¦18¦19¦20¦21¦22¦23¦24¦25¦26¦27¦ ¦¦71¦72¦73¦74¦¦
¦ГДД†ДДґГДДДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБ¬28¦ГДД†ДД†ДД†ДДґ¦
¦¦63¦64¦¦ 29 ¦30¦31¦32¦33¦34¦35¦36¦37¦38¦39¦40¦41¦ ¦¦75¦76¦77¦ ¦¦
¦ГДД†ДДґГДДДДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБВДБДД†ДДґГДД†ДД†ДДґ ¦¦
¦¦65¦66¦¦ 42 ¦44¦45¦46¦47¦48¦49¦50¦51¦52¦53¦ 54 ¦55¦¦79¦80¦81¦78¦¦
¦ГДД†ДДґГДДДДВБДВБДДБДДБДДБДДБДДБДДБДДБДДБВВБДДДВ†ДДББВДБДДБДДґ ¦¦
¦¦67¦68¦¦ 56 ¦ ¦ 57 ¦¦ 58 ¦¦ 82 ¦ 83 ¦ ¦¦
¦АДДБДДЩАДДДДЩ АДДДДДДДДДДДДДДДДДДДДДДДДДЩАДДДДЩАДДДДБДДДДДДДБДДЩ¦
¦ ¦
¦ Клавиатура IBM PC ¦
АДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Рис.12.1. Скан-коды клавиатуры.
Обнаружение комбинации клавиш облегчается при использовании
маски статуса по адресу 0:417, содержащей биты для клавиш Ctrl,
Alt, Shift, Ins, Caps Lock и Scroll Lock. Эта маска показывает,
какая из этих клавиш в текущий момент нажата. На рис.12.2
показано положение битов в маске.
ЪДДДДДДВДДДДДДДВДДДДДДДВДДДДДДДДВДДДДДДДВДДДДДДДВДДДДДДДВДДДДДДД¬
¦ INS ¦ CAPS ¦ NUM ¦ SCROLL ¦ ALT ¦ CTRL ¦ LEFT ¦ RIGHT ¦
¦ ¦ LOCK ¦ LOCK ¦ LOCK ¦ ¦ ¦ SHIFT ¦ SHIFT ¦
АДДДДДДБДДДДДДДБДДДДДДДБДДДДДДДДБДДДДДДДБДДДДДДДБДДДДДДДБДДДДДДДЩ
Рис.12.2.
Если соответствующий бит в маске установлен в 1, то клавиша
нажата. По скан-коду нажатой клавиши и значению бита в маске
обработчик прерываний от клавиатуры может определить, нажата ли
клавиша "горячего ключа". Чтобы специфицировать эти значения, вам
надо присвоить значения переменным scancode и keymask в
popup.c. Как показано в листинге, scancode равен 52, и keymask
равен 8, что соответствует Alt-точка.
Сигнатура TSR-программы.
-----------------------------------------------------------------
Программа popup.c также обеспечивает сигнатуру, которая
используется для определения, не резидентна ли уже TSR-программа.
Символьный массив с именем signature - это строка,
заканчивающаяся \0.
При первом старте popup.c вызывает функцию resident. Она
находится в resident.c, ее задачей является определение,
резидентна ли уже программа, и если нет, определить и назначить
коммуникационный вектор. Это делается сканированием
пользовательских векторов прерываний от 0х60 до 0х67. Если какой-
либо вектор содержит значение, то сегментная половина этого
значения комбинируется со смещением сигнатуры в программе. Это
смещение является указателем на сигнатуру, то есть смещением
относительно регистра сегмента данных. Сегментная часть вектора
суммируется с разницей между значениями регистров сегмента кода и
сегмента данных.
Помните, что значение сегментного регистра, взятое из
вектора, должно быть значением сегмента кода первоначально
стартовавшей TSR-программы. Сигнатура имеется как в области
данных TSR-программы, так и в области данных копии TSR-программы,
которая пытается стать резидентной. Нам нужен сегментный адрес
данных TSR-программы, который не сохраняется в коммуникационном
векторе. Это значение должно быть вычислено.
Программа, просматривающая векторы, является второй копией
TSR-программы (в предположении, что TSR-программа уже загружена),
поэтому разность значений регистров кода и данных у нее должна
быть такой же, как и у уже резидентной программы. Пользуясь этим
алгоритмом, вы сравниваете значение по соответствующим смещением,
и если они одинаковы, то программа уже загружена, и функция
resident вернет найденный вектор в функцию main в popup.с. Если
сигнатуры не равны, сканирование продолжается, пока не будут
проверены все векторы. Если определяется, что TSR-программа еще
не резидентна, то первый подходящий вектор становится
коммуникационным. В функцию main возвращается 0, что означает,
что программа стала резидентной.
Коммуникационные прерывания.
----------------------------------------------------------------
Если функция main обнаруживает, что копия программы уже
резидентна, она проверяет параметры командной строки.
Используемая техника позволяет передавать при запуске программы
параметры для ее резидентной копии. Напомним, что в памяти
имеется в этот момент две копии TSR-программы - резидентная и
только что загруженная в нерезидентную область. Нерезидентная
версия может связываться с резидентной копией через
коммуникационный вектор. В типичной TSR-программе используются
три параметра, но вы можете добавить еще, если
необходимо. Стандартные три позволяют пользователю удалять
программу из памяти, приостанавливать и возобновлять ее
выполнение. Если один из этих параметров присутствует в командной
строке (это определяется при помощи args,argv), то генерируется
программное прерывание, с установлением регистра ax в
соответствующее опции значение. Прерывание генерируется функцией
main нерезидентной копии TSR-программы.
Посмотрим далее на листинг popup.c. Функция прерывания с
именем ifunc является обработчиком коммуникационного прерывания.
Когда функция main нерезидентной копии TSR-программы инициирует
прерывание, вызывается обработчик из резидентной копии. Он
проверяет значение регистра ax и производит соответствующие
действия. При этом вызывается одна из трех функций: terminate,
restart или wait в resident.c. Вы можете вставить сюда и другую
логику, чтобы управлять состоянием резидентной программы.
Функции wait и restart в resident.c просто устанавливают и
очищают флаг, означающий для обработчика клавиатуры необходимость
реакции на нажатие клавиши "горячего ключа".
Функция terminate в resident.c должна определять, может ли
быть завершена TSR-программа, и затем, если да, производить все
действия, аналогичные действиям ДОС при завершении нерезидентной
программы. Этот процесс будет обсуждаться после того, как вы
поймете, как программа становится резидентной.
Подготовка к резидентности.
-----------------------------------------------------------------
Если функция resident обнаруживает, что программа еще не в
памяти, то popup.c подготавливает себя к переходу в резидентные.
ваша программа захочет открыть файлы или сделать еще что-нибудь
"по хозяйству" при ее вызове. Popup.c, взятая для примера,
вызывает load_help для установки функций помощи из главы 7,
устанавливает путь ДОС для использования программой notepad из
главы 9, и выдает сообщение. Потом она вызывает resinit, функцию
из resident.c, которая делает все остальное, чтобы программа
стала резидентной.
Первоначально, resinit сохраняет значение регистра сегмента
стека для переключения контекста стеков при вызове TSR-программы.
Затем читается флаг занятости ДОС с помощью функции ДОС 0х34.
Функция getdta получает адрес дискового буфера для TSR-программы.