   Программы обработки прерываний и резидентные программы.

                           А.Обзор.

     Перед программистами  ПК часто встают две сложные задачи:
написание программ обработки прерываний (ISR interrupt service
routine) и   резидентных  программ  (TSR  Terminate  bat  Stay
Resident). Хотя  Object  Professional  не может гарантировать,
что ваши программы не будут использовать слишком много места в
стеке,  однако  данная  юнита  предоставляет  вам средства для
быстрого написания таких программ.

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

     Большим удобством  в  Turbo  Pascal  является  "interrupt
procedure", которая самостоятельно сохраняет и восстанавливает
все регистры  центрального процессора,  инициализирует регистр
DS (регистр сегмента данных) и устанавливает размер стека  та-
ким  образом,  что вы можете обращаться к этим регистрам как к
параметрам.  Этого уже достаточно для написания простых  прог-
рамм обработки прерываний без использования ассемблера, но все
же этого недостаточно.  Так что вторая задача OPINT состоит  в
том, чтобы помочь вам в написании действительно программ обра-
ботки прерываний,  в особенности больших и  требующих  больших
объемов в стеке.  Она содержит процедуры для выделения и осво-
бождения дополнительного стека, для переключения между стеками
в вашей программе, для быстрого возврата из прерывания, не об-
ращая внимания на уровень  вложенности  в  подпрограммах,  для
сцепления с предыдущей программой обработки прерывания и т.д.

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

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

     {$S-, I-, R-}
     {$M 2048,0,655360}
     program HelloWorld;
     uses Dos, OpInt, OpTsr;
     const
        HotKey = $080F;         { Код Alt-Tab}
        WaitForDos = True;      { Не прерывать выполнения }
                                { ф-ций DOS }
        {$F+}
        procedure PopupEntryPoint(var Regs:Registers);
        begin
           WriteLn('Hello World');
        end;  {$F-}
     begin
        { определить всплывающую процедуру }
        if DefinePop(HotKey,PopupEntryPoint,Ptr(SSeg,SPtr),
                     WaitForDos) then begin
           WriteLn('Popup загружена, для активизации нажмите
                    <Alt>-<Tab>');
           { сделать ее активной }
           PopupsOn;
           { оставить резидентной }
           StayResident(ParagraphsToKeep,0);
        end;
        { если мы попали в эту точку, значит ошибка }
        WriteLn('Не могу остаться резидентной');
     end.

     Вы вряд ли захотите показать данную программу друзьям, но
что можно ожидать от примерно 30 строк исходного кода?

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

     Несколько лет   назад,   когда   мы   представили   Turbo
Professional, юнита OPTSR (тогда она называлась TPTSR) предло-
жила основную технологию.  Состояние дел  изменялось  довольно
быстро,  и в настоящий момент даже 30К считается для резидент-
ной программы слишком много.  Появляются  подкачиваемые  рези-
дентные программы.  В  отличие от обычных резидентных программ
подкачиваемые резидентные программы требуют  только  несколько
килобайт памяти, когда они не используются, не зависимо от то-
го, какого они размера в действительности.  Если коротко, дело
в следующем.  Перед загрузкой в память такая программа записы-
вает большую часть кода и областей памяти на диск или в допол-
нительную память и затем возвращает максимально возможный объ-
ем памяти DOS. После оставления программы резидентной, в памя-
ти остается только небольшое ядро,  которое содержит программу
обработки прерываний,  отслеживающую состояние системы и обра-
батывающую запросы  на  активизацию  программы.  При появлении
запроса на активизацию это "ядро" производит загрузку с  диска
или  из  дополнительной  памяти  "подкачиваемой области" на их
место и вызывает соответствующую процедуру из этой области.

     Теоретически это очень просто,  однако  на  практике  это
очень сложная задача.  Мы делаем за вас всю эту муторную рабо-
ту, соединяя технологию резидентных программ из OPTST с техно-
логией подкачки OPEXEC. В результате, написание таких подкачи-
ваемых резидентных программ с использованием юниты OPSWAP ста-
новится не намного более сложным,  чем написание обычных рези-
дентных программ с использованием OPTSR. Ниже приведена подка-
чиваемая версия программы "Hello World", которая теперь состо-
ит из двух частей: маленькой основной программы:

     {$S-, I-, R-}
     {$M 2048, 0, 655360}
     program HelloWorld;
     uses HWmain, OpSwap;
     begin
        HelloWorldMain;
     end.

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

     {S-, I-, R-}
     unit HWmain;
     interface

     uses
        OpInLine, OpSwap1;
        procedure HelloWorldMain;

     implementation

     const
        HotKey = $080F;
        Swap1 = 'Hello1.SWP';
        Swap2 = 'Hello2.SWP';

     {$F+}

     procedure HelloWorldMain;
     begin
        { отключить сообщения подкачки при работе с EMS}
        SetSwapMsgOn(not WillSwapUseEms(ParagraphsToKeep) );
        { определить "всплывающую" процедуру }
        if DefinePop(HotKey,PopupEntryPoint,Ptr(SSeg,SPtr),
                     WaitForDos) then begin
           WriteLn('Popup загружена, для активизации нажмите
                    <Alt>-<Tab>');
           { сделать ее активной }
           PopupsOn;
           { оставить резидентной }
           StayResSwap(ParagraphsToKeep,0,Swap1,Swap2,True);
        end;
        { если мы попали в эту точку, значит ошибка }
        WriteLn('Не могу остаться резидентной');
     end;

     end.

     Если вы внимательно посмотрите на текст данной программы,
то вы заметите, что отличия в основном носят косметический ха-
рактер. Основное  отличие  вы  не  увидите,  глядя на исходный
текст. Оно состоит в том, что исходная программа HELLO занима-
ла в памяти 12К, а программа с подкачкой только 6К. Это конеч-
но небольшое отличие,  однако только потому,  что и сам пример
очень мал. Лучше взглянуть на демонстрационный пример DESKPOP,
который также занимает только 6К,  но если написать его с  по-
мощью OPTST, то он будет требовать около 400К!

     Юнита OP8087 предназначена для резидентных программ,  ко-
торые используют арифметический сопроцессор для  вычислений  с
плавающей точкой.  Процедура  Save8087 позволяет вам сохранять
содержимое регистров сопроцессора при активизации  резидентной
программы,  а  Restore8087 позволяет вам восстановить исходное
состояние сопроцессора при выходе  из  резидентной  программы.
OP8087 содержит также две подпрограммы, которые управляют тем,
как сопроцессор обрабатывает ошибки. Error8087 возвращает ста-
тус сопроцессора,  позволяя вам определить,  была ли ошибка во
время  выполнения  предыдущей   последовательности   операций.
OP8087 поддерживает также эмуляцию сопроцессора Turbo Pascal.


     B. OPINT: Работа с программами обработки прерываний.

     Как было указано в начале главы,  OPINT решает три основ-
ные задачи,  важнейшей из которых является управление програм-
мами обработки прерываний.

     OPINT отслеживает для вас ваши программы обработки преры-
ваний. Обычно для захвата  вектора  прерывания  в  TurboPascal
требовалось выполнить    следующее:   1)   Вызвать   процедуру
GetIntVec из юниты DOS,  2) сохранить текущий вектор,  3) выз-
вать SetIntVec  для  указания  вашего  вектора прерывания и 4)
восстановить старый вектор при завершении программы. Шаг 4 до-
вольно сложен,  поскольку программа может завершаться по множ-
феству причин и во многих точках. Во многих случаях OPINT поз-
воляет свести эти четыре шага к:

     const MyIsrHandler = 16;
     ...
     if InitVector(IntNumber,MyIsrHandler,@MyIsr) then {};

     OPINT сохраняет  содержимое вектора,  устанавливает новый
вектор и восстанавливает исходный вектор при завершении  прог-
раммы. OPINT  может отслеживать до 20 (MaxISRs) программ обра-
ботки прерываний,  что намного больше,  чем  необходимо  любой
программе,  и каждому должен быть присвоен уникальный дескрип-
тор. Дескриптор - это просто номер, используемый для идентифи-
кации программы обработки прерываний, и в большинстве программ
вы можете использовать любые числа от 1 до 20,  но  необходимо
помнить, что в Object Professional две другие юниты используют
OPINT и используют определенные дескрипторы.  OPTSR использует
дескрипторы  от  1 до 11,  а OPMACRO использует дескриптор 12.
Поскольку в дальнейшем число используемых  дескрипторов  может
возрасти,  то мы рекомендуем использовать вам дескрипторы с 16
и выше.

     Вторая основная функция OPINT  состоит  в  предоставлении
возможности по  облегчению написания программ обработки преры-
ваний на TurboPascal. Все эти процедуры зависят от двух вещей:
"interrupt procedure"  Turbo  Pascal  и  от переменных и типов
данных, объявленных в OPINT,  IntRegisters.  Если  вы  изучили
описание  процедур  обработки  прерывания Turbo Pascal,  то вы
знаете,  что он позволяет вам рассматривать регистры централь-
ного процессора как параметры процедуры обработки прерывания:

     procedure MyIsr(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP:
                     Word);
        interrupt;
     begin
     end;

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

     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
     end;

     Этим вы решаете две задачи.  Первая:  вы можете ссылаться
теперь и на половинные регистры (AH,AL,BH,BL и т.д.),  исполь-
зуя Regs.AH,  Regs.AL и т.д. Вторая: теперь вы можете рассмат-
ривать  все  или некоторые из них как группу и посылать их как
один параметр процедуре.

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

     procedure MyIsrHelper(var Regs : IntRegisters);
     begin
       {... обработка ...}
        IntReturn(Regs);
     end;

     ChainInt позволяет  вам  завершить работу вашей процедуры
обработки прерывания и передать управление старому вектору:

     ChainInt(Regs, IsrArray[MyIsrHandle].OrogAddr);

     SwapStackAndCallNear и SwapStackAndCall позволяют вам пе-
реключаться на альтернативный стек и вызывать другие процедуры
из программы обработки прерывания.  Когда вызванная  процедура
возвращает управление,    восстанавливается   исходный   стек.
EmulateInt позволяет вам вызвать старый  вектор,  не  завершая
программу обработки прерывания. Например:

     EmulateInt(Regs, IsrArray[MyIsrHandle].OrogAddr);

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

     Примечание: Как указано в  руководстве  по  Turbo  Pascal
(раздел "Inside   Turbo   Pascal",   под   "Writing  Interrupt
Procedures"), процедуры ввода/вывода  и  процедуры  управления
памятью Turbo Pascal не реентерантны и не могут использоваться
в программах обработки прерываний.  Функции DOS также не реен-
терантны  и  поэтому не могут быть использованы.  И последнее,
многие процедуры  Object  Professional  также  нереентерантны.
Чтобы убедиться  в  этом,  можно посмотреть на исходные тексты
процедуры - любые процедуры,  изменяющие состояние  глобальных
переменных не реентерантны.

                          Константы.

     MaxISRs = 20;

     Максимальное число программ обработки прерываний, которые
может поддерживать OPINT.

                            Типы.

     Dummy5 = array[1..5] of Word;
     IntRegisters =
        record
        case Byte of
           1: (BP,ES,DS,DI,SI,DX,CX,BX,AX,IP,CS,Flags:Word);
           2: (Dummy:Dummy5;DL,DH,CL,CH,BL,BH,AL,AH:Byte);
        end;

     Структура, отслеживающая содержимое стека в процедуре об-
работки прерывания.

     IsrRecord =
       record
         IntNum : Bute;
         OrigAddr, NewAddr : Pointer;
         Captured : Boolean;
       end;

     Структура, содержащая информацию об определенной програм-
ме обработки  прерывания.  IntNum  - это номер обрабатываемого
вектора. OldAdr - это  адрес  предыдущей  программы  обработки
прерывания. NewAdr  -  это  адрес  текущей программы обработки
прерывания. Captured предназначено для внутреннего использова-
ния и показывает, был восстановлен вектор или нет.

                         Переменные.

     IsrArray : array[1..MaxISRs] of IsrRecord;

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

AllocateStack
──────────────────────────────────────────────────────────────
Объявление

     function AllocateStack(var P:Pointer; SizeInBytes:Word):
                            Boolean;

Функция

     Выделяет стек размером SizeInBytes байт.

Описание

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

     Важное замечание:  Для освобождения стека,  выделенного с
помощью AllocateStack, вы должны использовать DeallocateStack.

Пример

     if not AllocateStack(MyStack, MyStackSize) then
        Halt();

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

Смотрите также

     DeallocateStack

ChainInt
──────────────────────────────────────────────────────────────
Объявление

     procedure ChainInt(var Regs:IntRegisters;JumpAddr:Pointer);

Функция

     Восстанавливает стек, регистры из Regs и производит пере-
ход на адрес JumpAddr.

Описание

     Эта встроенная  макрокоманда  позволяет  вам восстановить
регистры центрального процессора,  помещенные в стек при входе
в программу обработки прерывания,  и передает управление прог-
рамме обработки  прерывания,  которая обслуживала вектор перед
тем,  как  вы  его  перехватили.  В  отличие  от  макрокоманды
JumpToOldIsr из OPINLINE, ChainInt предназначена для использо-
вания в программах обработки прерываний из Turbo Pascal,  а не
из встроенного кода.

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

Пример

     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
        { ... обработка ... }
        ChainInt(Regs,IsrArray[MyIsrHandle].OrogAddr);
     end;
     ...
     if not InitVector($09,MyIsrHandle,@MyIsr) then {error};

     Допустимо. ChainInt восстановит регистры и  передаст  уп-
равление старой программе обработки прерывания.

     ChainInt(Regs,IsrArray[MyIsrHandle].OrigAddr^);

     Неверно. '^' после OrigAddr ненужно.

Смотрите также

     EmulateInt                   JumpToOldAddr(OPINLINE)

DeallocateStack
──────────────────────────────────────────────────────────────
Объявление

     procedure DeallocateStack(var P:Pointer);

Функция

     Освобождает стек.

Описание

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

     Важное замечание:  Не  используйте  DeallocateStack   для
освобождения стека,   который   не   был   выделен  с  помощью
AllocateStack.

Пример

     if not AllocateStack(MyStackPtr, MyStackSize) then {error}
     ...
     DeallocateStack(MyStackPtr);

Смотрите также

     AllocateStack

EmulateInt
──────────────────────────────────────────────────────────────
Объявление

     procedure EmulateInt(var Regs : IntRegisters;
                          IntAddr : Pointer);

Функция

     Эмулирует прерывание.

Описание

     Эта процедура  моделирует  прерывание,  заполняя регистры
центрального процессора содержимым Regs,  запрещает прерывания,
помещает в стек регистр флагов и делает дальний вызов IntAddr.
Как  и макрокоманда из OPINLINE CallOldIsr,  она позволяет вам
вызвать программу обработки  прерывания,  которая  обслуживала
данный вектор до того,  как вы его захватили. Однако в отличие
CallOldIsr, EmulateInt расчитана на программы обработки преры-
ваний, написанные на паскале, а не на встроенном коде.

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

     Второе назначение  EmulateInt  заключается в вызове подп-
рограмм через командный указатель.  Описание этой  возможности
приведено в документации по OPTSR.

Пример

     if not InitVector($09,MyIsrHandle,@MyIsr) then {error};
     ...
     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
        { ... обработка ... }
        EmulateInt(Regs,IsrArray[MyIsrHandle].OrigAddr);
     end;

     Допустимо.

     EmulateInt(Regs,IsrArray[MyIsrHandle].OrigAddr^);

     Неверно. '^' после OrigAddr ненужно.

Смотрите также

     ChainInt                   CallOldAddr(OPINLINE)

InitVector
──────────────────────────────────────────────────────────────
Объявление

     function InitVector(IntNumber, Handle : Byte;
                         UserRoutine : Pointer) : Boolean;

Функция

     Устанавливает программу обработки прерывания.

Описание

     Основная подпрограмма из OPINT.  InitVector позволяет вам
изменить вектор прерывания с номером IntNumber таким  образом,
чтобы он указывал на программу обработки прерывания,  написан-
ную вами (ее адрес в UserRoutine). Handle - это число, которое
вы  используете для идентификации программы обработки прерыва-
ния. (Смотрите обсуждение дескрипторов в начале главы в разде-
ле "Обзор").  Если вы вызовите InitVector, она уже позаботится
обо всех необходимых вещах сама. При завершении вашей програм-
мы старый  вектор будет восстановлен автоматически,  даже если
произошла ошибка времени выполнения.  Если нужно  восстановить
вектор до  завершения  программы,  вы можете сделать это с по-
мощью RestoreVector или RestoreAllVectors.  InitVector возвра-
щает False,  только  если параметр Handle больше,  чем MaxISRs
или если он уже занят.

Пример

     if not InitVector($09,MyIsrHandle,@MyIsr) then
        { дескриптор уже используется };

     Здесь InitVector используется для установки программы об-
работки прерывания 09 MyIsr.

     RestoreVector(MyIsrHandle);
     Code := ExecDos('',True,NoExecDosProc);
     if not InitVector($09,MyIsrHandle,@MyIsr) then
        { что то случилось }

     В данном  примере  RestoreVector  восстанавливает  старый
вектор прерывания 09 перед выходом в процессор команд  DOS,  а
затем InitVector вызывается для повторного захвата вектора.

Смотрите также

     RestoreVector           RestoreAllVectors

InterruptrOff
──────────────────────────────────────────────────────────────
Объявление

     procedure InterruptsOff;

Функция

     Выключает прерывания.

Описание

     Эта макрокоманда отключает прерывания.

InterruptrOn
──────────────────────────────────────────────────────────────
Объявление

     procedure InterruptsOn;

Функция

     Включает прерывания.

Описание

     Эта макрокоманда включает прерывания.

Пример

     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
        InterruptsOff;
        ...
        InterruptsOn;
     end;

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

IntReturn
──────────────────────────────────────────────────────────────
Объявление

     procedure IntReturn(var IntRegs : IntRegisters);

Функция

     Производит возврат из прерывания.

Описание

     Эта процедура позволяет вам осуществить возврат из преры-
вания из любой точки программы,  позволяя  текущей  процедуре,
которой в   качестве   изменяемого   параметра   был   передан
IntRegisters, считать себя частью программы обработки прерыва-
ния. Аналогично, ChainInt IntReturn не работает с копией пере-
менной IntRegisters.

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

Пример

     procedure NonIsr(var Regs : IntRegisters);
     begin
        { ... обработка ...}
        if NeedToReturnFast then
           IntReturn(Regs);
        { ... обработка ... }
     end;

     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
        if Regs.AX = MagicValue then
           NonIsr(Regs);
        else begin
        ...
        end;
     end;

     MyIsr вызывает NonIsr, посылая в качестве параметра Regs,
и NonIsr использует IntReturn для возврата из прерывания.  За-
метим, что параметр Regs для NonIsr должен быть изменяемым па-
раметром (var).

Смотрите также

     ChainInt                SwapStackAndCallNear
     SwapStackAndCall

RestoreAllVectors
──────────────────────────────────────────────────────────────
Объявление

     procedure RestoreAllVectores;

Функция

     Восстанавливает все захваченные вектора прерываний.

Описание

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

Пример

     if not InitVector($09,MyIsrHandle,@MyIsr) then
                                              {error}
     if not InitVector($09,MyOtherIsrHandle,@MyOtherIsr) then
                                              {error}
     ...
     RestoreAllVectors;

Смотрите также

     RestoreVector

RestoreVector
──────────────────────────────────────────────────────────────
Объявление

     procedure RestoreVector(Handle : Byte);

Функция

     Восстанавливает исходное значение вектора прерывания.

Описание

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

Пример

     if not InitVector($09,MyIsrHandle,@MyIsr) then
           { дескриптор уже используется }
     RestoreVector(MyIsrHandle);
     Code := ExecDos('',True,NoExecDosProc);
     if not InitVector($09,MyIsrHandle,@MyIsr) then
        { что-то случилось }

     Восстанавливает старый вектор прерывания 09 перед выходом
в оболочку DOS.

Смотрите также

     InitVector              RestoreAllVectors

SendEOI
──────────────────────────────────────────────────────────────
Объявление

     procedure SendEOI;

Функция

     Посылает сигнал End Of Interrupt (конец прерывания) прог-
раммируемому контроллеру  прерываний  (PIC).  Может  использо-
ваться только в программах обработки прерывания  оборудования,
типа 08 (часы) и 09 (клавиатура).

Примеры

     procedure Int9(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
        B : Byte;
     begin
        {получить скан-код нажатой или отпущенной клавиши}
        B := Port[$60]; {если клавиша отпускается, то стар-}
                        {ший бит установлен }
        ...
        if not NeedToEatKeyStroke then
           ChainInt(Regs,OldInt9)
        else begin
           {восстановить исходное состояние клавиатуры }
           B := Port[$61];
           Port[$61] := B or $80;
           Port[$61] := B;
           { послать сигнал об окончании прерывания }
           SendEOI;
        end;
     end;

     Реальная программа  обработки  прерываний  от  клавиатуры
(09), которая при некоторых условиях "проглатывает" нажатия на
клавиши.

SwapStackAndCall
──────────────────────────────────────────────────────────────
Объявление

     procedure SwapStackAndCall(Routine,SP:Pointer;
                                var Regs:IntRegisters);

Функция

     Переключается на стек с указателем SP и вызывает Routine,
передавая в  качестве  параметра  Regs.

Описание

     Данная процедура позволяет вам переключиться на альтерна-
тивный стек (с указателем SP) и вызвать  процедуру,  передавая
ей  в  качестве  параметра Regs.  Вызываемая процедура Routine
должна иметь следующую форму:

     {$F+}
     procedure MyProc(var Regs:IntRegisters);
     begin
        ...
     end;

     Очень важное замечание: MyProc должна быть дальней проце-
дурой (FAR).  Если это не так, то нужно использовать процедуру
SwapStackAndCallNear.

Пример

     {$F+}
     procedure NonIsr(var Regs : IntRegisters);
     begin
        { ... обработка ...}
        if NeedToReturnFast then
           IntReturn(Regs);
        { ... обработка ... }
     end;
     {$F-}

     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
        if Regs.AX = MagicValue then
           SwapStackAndCall(@NonIsr,MyStackPtr,Regs);
        else begin
        ...
        end;
     end;

     Допустимо. SwapStackAndCall переключается на стек, указы-
ваемый MyStackPtr,  и вызывает NonIsr, передавая ей в качестве
параметра Regs. Заметим, что NonIsr должна быть откомпилирова-
на в модели FAR.

           SwapStackAndCall(@NonIsr,MyStackPtr^,Regs);

     Неверно. '^' после MyStackPtr не нужна.

Смотрите также

     SwapStackAndCallNear

SwapStackAndCallNear
──────────────────────────────────────────────────────────────
Объявление

     procedure SwapStackAndCallNear(Routine : Word ,SP:Pointer;
                                    var Regs:IntRegisters);

Функция

     Переключается на стек с указателем SP и вызывает Routine,
передавая в  качестве  параметра  Regs.

Описание

     Данная процедура позволяет вам переключиться на альтерна-
тивный стек  (с указателем SP) и вызвать процедуру,  передавая
ей в качестве параметра  Regs.  Вызываемая  процедура  Routine
должна иметь следующую форму:


     {$F-}
     procedure MyProc(var Regs:IntRegisters);
     begin
        ...
     end;

     Очень важное замечание: MyProc должна быть ближней проце-
дурой (NEAR). Если это не так, то нужно использовать процедуру
SwapStackAndCall.

Пример

     {$F-}
     procedure NonIsr(var Regs : IntRegisters);
     begin
        { ... обработка ...}
        if NeedToReturnFast then
           IntReturn(Regs);
        { ... обработка ... }
     end;

     procedure MyIsr(BP:Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
     begin
        if Regs.AX = MagicValue then
           SwapStackAndCallNear(Ofs(NonIsr),MyStackPtr,Regs);
        else begin
        ...
        end;
     end;

     Допустимо. SwapStackAndCall переключается на стек, указы-
ваемый MyStackPtr,  и вызывает NonIsr, передавая ей в качестве
параметра Regs. Заметим, что NonIsr должна быть откомпилирова-
на в модели NEAR.

           SwapStackAndCallNear(Ofs(NonIsr),MyStackPtr^,Regs);

     Неверно. '^' после MyStackPtr не нужна.

Смотрите также

     SwapStackAndCall



        C. OPTSR: Управление резидентными программами.

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

     TSR (Terminate and Stay Resident) - резидентной,  - назы-
вается программа,  которая практически сразу же после загрузки
возвращает управление операционной системе (DOS), хотя и оста-
ется в памяти.  Каждая резидентная программа содержит по край-
ней  мере  одну  "всплывающую"  процедуру,  которая вызывается
диспетчером при  возникновении  определенных  условий.  Работа
диспетчера может быть разбита на шесть частей: 1) отслеживание
возникновения определенных событий, 2) при возникновении соот-
ветствующего события определить необходимую "всплывающую" про-
цедуру, 3) основываясь на информации о "всплывающей" процедуре
определить нужно  ли ее вызывать,  4) перед вызовом "всплываю-
щей" процедуры сохранить текущее состояние системы  и  принять
все  возможные меры для предотвращения влияния на него "всплы-
вающей"  процедуры,  5)  вызвать  процедуру,  6)  восстановить
состояние системы.   (Замечание:  последовательность  событий,
описанная в данном и последующих параграфах, приводится только
для облегчения понимания данного сложного процесса).

     Рассмотрим на основе простого примера, приведенного в на-
чале главы "HelloWorld",  все эти принципы в действии. Диспет-
чер "всплывающих" процедур отслеживает клавиатуру,  ожидая од-
новременного нажатия  клавиш Tab и Alt.  При появлении данного
события диспетчер просматривает свои таблицы и определяет, что
должна быть вызвана процедура PopupEntryPoint,  и что ей необ-
ходим доступ к функциям DOS (WaitForDos = True). Затем диспет-
чер проверяет  наличие  доступа к данной процедуре,  и если он
есть, переходит к созданию среды для запуска  процедуры.  И  в
завершении происходит вызов PopupEntryPoint.  После завершения
выполнения процедуры управление возвращается диспетчеру, кото-
рый восстанавливает состояние системы и возвращает центральный
процессор  к  обработке задачи,  которой он занимался в момент
нажатия <Tab>-<Alt>, и продолжает отслеживать клавиатуру.

     Рассмотрим каждую из этих частей более детально.

             Число и типы "всплывающих" процедур.
     
     По умолчанию   OPTSR   позволяет   вам  определить  до  8
(MaxPopups) "всплывающих" процедур в одной  программе,  каждая
из  которых  должна  быть  отнесена к одной из двух категорий.
Тип, который вы будете использовать чаще,  - это процедура,  с
которой связаны некие "горячие" клавиши (HotKey) - о них  чуть
позже.  Другой тип "всплывающих" процедур вызывается при появ-
лении некоторого события.  Вместо этого вы можете вызвать ее в
любой  момент  с  помощью  процедуры SetPopTicker,  передав ей
дескриптор, связанный с данной процедурой  (смотрите  ниже)  и
счетчик тиков (TimeOut). Этим вы сообщаете диспетчеру, что не-
обходимо ожидать удобного  случая  для  запуска  "всплывающей"
процедуры.  Если  в  течении  TimeOut  тиков  такого случая не
представилось,  то процедура не будет вызвана.  Хороший пример
такой  "всплывающей"  процедуры  содержится в файле PTIME.PAS,
которая используется для вывода  экранных  часов,  для  выдачи
сигналов  в определенные моменты времени и для выполнения мак-
рокоманд из SMACS в определенные моменты времени.

     "Горячие" клавиши (HotKey).
     Более удобными "всплывающими"  процедурами  являются  те,
которые вызываются при нажатии определенной комбинации клавиш,
которая и называется HotKey.

     В Object Professional HotKey  называется  в  действитель-
ности одна  клавиша,  которая  нажимается с комбинацией клавиш
регистров. В качестве клавиш регистров рассматриваются клавиши
"LeftShift", "RightShift",  "Ctrl"  и  "Alt".  Для определения
HotKey вы должны сконструировать значение из битов, определяе-
мых следующим образом:

     Номер бита     Значение     Описание
     ───────────────────────────────────────────────────
     15-12          $0000        Не используются. Должны
                                 быть равны 0.
     11             $0800        Alt
     10             $0400        Ctrl
     9              $0200        Left Shift
     8              $0100        Right Shift
     7-0            $00xx        Скан-код клавиши
     ───────────────────────────────────────────────────

     Например, мы хотим, чтобы "всплывающая" процедура активи-
зировалась при одновременном нажатии на <Alt>, <Right Shift> и
<Z>.  Скан-код  <Z>  равен  $2C (Смотрите таблицу скан кодов в
Приложении С справочного  руководства  программиста  по  Turbo
Pascal, или в короткий справочник в DESKPOP).  Это даст значе-
ние для младшего байта,  для битов 7-0. Если мы прибавим к по-
лученному значению значения для <Alt> и <Right Shift>,  мы по-
лучим $0800+$0200+$02C = $0A2C (Между  прочим,  калькулятор  в
DESCPOP поможет вам выполнить 16-ричные вычисления).

     Больше о HotKey.
     О "горячих" клавишах необходимо знать еще две вещи.  Пер-
вое, о чем не было сказано в предыдущем разделе, что вы не мо-
жете определить "всплывающую" процедуру, которая будет активи-
зироваться по нажатию только одной клавиши. Обязательно должна
быть указана хотя бы одна клавиша регистра.  Однако в качестве
основной клавиши можно рассматривать и клавишу регистра.  Нап-
ример,   для   указания   в   качестве    HotKey    комбинации
<Ctrl><LeftShift> вы  можете указать и $042A,  и $021D,  в за-
висимости от того,  какая из двух клавиш  должна  быть  нажата
первой.  И  второе,  вы можете указать для одной "всплывающей"
процедуры больше чем одну комбинацию HotKey. Смотрите описание
AddHotKey ниже в данной главе.

             Дескрипторы "всплывающих" процедур.
     
     При вызове  DefinePopProc  для  определения "всплывающей"
процедуры,  которая будет вызываться в результате обращения  к
SetPopTicker,  она запрашивает у вас дескриптор.  В отличии от
OPINT, которая позволяет вам указать свой собственных дескрип-
тор, OPTSR связывает его сама. От вас требуется только опреде-
лить глобальную байтовую переменную,  скажем MyPopupHandle,  и
передать ее как параметр.  Если имеются свободные дескрипторы,
то DefinePopProc присвоит его данной переменной и вернет  зна-
чение  True.  Когда настанет время для вызова вашей "всплываю-
щей" процедуры, вы должны сказать что-то вроде этого:

     SetPopTicker(MyPopupHandle,18);

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

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

     {$F+}
     procedur PopupShell(var Regs : Registers);
     begin
        {... тело процедуры ...}
     end;

     И директива {$F+}, и объявление параметра Registers необ-
ходимы. Если вы в  них  что-нибудь  напутаете,  то  компилятор
выдаст ошибку при обращении к DefinePop или DefinePopProc.

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

                            Стеки.
     
     Каждая "всплывающая"    процедура   должна   иметь   свой
собственный стек. Это означает, что параметр SteckPtr, переда-
ваемый DefinePop/DefinePopProc для каждой "всплывающей" проце-
дуры, должен быть уникальным. Большинство резидентных программ
состоят из одной "всплывающей" процедуры, и поэтому в качестве
параметра вы  можете  использовать  "Ptr(SSeg,SPtr)".  Однако,
программа, содержащая  несколько  "всплывающих" процедур (типа
SMACS.PAS),  должна выделять место для  дополнительного  стека
либо  объявляя массив как глобальную переменную,  либо выделяя
стек при помощи процедуры AllocateStack из OPINT,  которая вы-
деляет массив в "куче".  В любом случае параметр StackPtr дол-
жен указывать на последний элемент массива,  а не  на  первый,
как обычно.  (Заметим,  что AllocateStack возвращает указатель
на последний элемент массива.)

                        Ожидание DOS.
     
     Большинству "всплывающих"  процедур  необходим  доступ  к
функциям DOS.  Если вы в этом не уверены, то должны подразуме-
вать, что  ваша  "всплывающая"  процедура нуждается в функциях
DOS. Однако,  если вы уверены,  что такой доступ вам не  нужен
(например, если ваша процедура должна делать что-либо простое,
типа вывода текста с  использованием  процедуры  FastWrite  из
OPCRT), вы можете указать False в параметре DosWait, передава-
емом DefinePop.  Этим вы позволите вашей процедуре "всплывать"
в те моменты,  в которые в противном случае она не  смогла  бы
этого сделать.

     Между прочим,  функции DOS выполняют множество задач: все
дисковые операции ввода/вывода требуют участия  DOS,  и  кроме
этого множество других операций,  также выполняется с участием
DOS,  и это может оказаться для  вас  сюрпризом.  Подпрограммы
GetDate и SetDate из юниты DOS, например, также используют вы-
зовы DOS.  Поэтому,  если вы сомневаетесь,  то лучше считайте,
что используется DOS.

     Детальное описание вы найдете в разделе "Безопасная рабо-
та".

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

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

     StayRes(ParagraphsToKeep,0);

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

     StayRes(ParagraphsToKeep+250,0);

     Пример "всплывающей"  процедуры,  выделяющей память после
того, как стала резидентной, вы найдете в SMACS.PAS.

     Важное замечание:  Не нужно  определять  очень  маленькое
значение, определяющее  максимальный  размер  "кучи" для рези-
дентных программ (посредством директивы  $M).  Наоборот,  если
вашей резидентной  программе может потребоваться выделение па-
мяти, вы  должны  указать  очень   большое   число.   Параметр
ParasToKeep, передаваемый StayRes,  не может превысить  макси-
мально доступное значение для "кучи" (т.е.  ParasToKeep не мо-
жет превышать значения MaxParagraphsToKeep).

                Запуск резидентной программы.
     
     Если вы не знали этого раньше, мы должны вам сказать, что
резидентные программы  должны  запускаться из командной строки
DOS. Таким образом,  если вы привыкли разрабатывать свои прог-
раммы в  интегрированной  среде  разработки  TurboPascal  (IDE
Integrated Development Environment) и сразу же  запускать  их,
то вам придется в данном случае изменить свои привычки. Знайте
также,  что большинство отладчиков нельзя использовать для от-
ладки резидентных программ,  включая Turbo Debugger 1.0 и 1.5,
и встроенный отладчик TurboPascal. Мы использовали для отладки
отладчик Periscope, который и рекомендуем вам.

     Ошибки ввода/вывода и критические ошибки DOS.
     Перед тем, как вызвать "всплывающую" процедуру, требующую
доступа к функциям DOS,  диспетчер устанавливает программу об-
работки критических ошибок DOS, тех, что вызывают появления на
экране строки "Abort,  Retry,  Ignore". Это сообщение не сулит
ничего хорошего обычным программам, а для резидентных программ
такие ошибки  просто гибельны.  Программа обработки таких оши-
бок, устанавливаемая диспетчером,  просто сообщает, что ошибку
нужно проигнорировать (под DOS версии 2.x), или что вызов сле-
дует завершить по ошибке (под DOS версии 3.x). При этом управ-
ление возвращается вашей программе, однако под DOS 2.x ошибоч-
ное состояние сохраняется и вам предстоит иметь  с  ним  дело.
Как?  В  начале вашей программы добавьте директиву {$I-},  чем
укажете библиотеке времени выполнения,  что  появление  ошибок
ввода/вывода не должно рассматриваться как сигнал к завершению
процесса.  После каждой операции ввода/вывода,  добавьте вызов
IoResult. Если эта процедура будет возвращать что-либо, отлич-
ное от 0,  это значит что произошла  ошибка  ввода/вывода  или
произошла критическая ошибка DOS.

                  Ошибки времени выполнения.
     
     Обычно ошибки времени выполнения можно рассматривать  как
смертельные. OPTSR содержит программу обработки ошибок,  кото-
рая пытается сохранить систему при возникновении ошибки време-
ни выполнения,  однако это не всегда удается. Хорошей новостью
может быть то, что если ваша программа использует альтернатив-
ный стек в сегменте данных или в  "куче",  то  обычно  удается
сохранить  систему,  поэтому при разработке программ хорошо бы
его использовать. (Завершенная резидентная программа не должна
генерировать ошибок времени выполнения,  и если она генерирует
такие ошибки, то она не может считаться завершенной). Заметим,
что эта программа обработки ошибок не выводит сообщений,  даже
если она успешно сохранила систему. Программа обработки ошибок
Turbo, которая печатает сообщения, никогда не достигается. По-
этому, если вы хотите получить сообщение от программы обработ-
ки ошибок, вы должны написать свою собственную программу обра-
ботки ошибок,  пользуясь шагами,  описанными в руководстве  по
Turbo Pascal.

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

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

     StayRes(ParagraphsToKeep+25,0);

     Этим вы  оставляете  400  (16*25)  байт   дополнительного
пространства в  "куче".  Обсуждение выделения памяти для рези-
дентных программ вы найдете в предыдущем  разделе  "Оставление
резидентной".

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

                Использование стека и "кучи".
     
     Одной из сложных задач,  которую OPTSR не может решить за
вас, является  определение размеров стека и "кучи",  требуемых
вашей "всплывающей" процедуре.  Наше решение состоит в следую-
щем. На стадии разработки выделяйте больше стека и "кучи", чем
по вашему мнению необходимо,  и не беспокойтесь об использова-
нии   памяти.  Когда  ваша  программа  в  основном  закончена,
воспользуйтесь юнитой TPSTACK из директории  BONUS  для  того,
чтобы вычислить необходимый размер стека.  TPSTACK разработана
для использования с нерезидентными программами, но при необхо-
димости может быть доработана для работы с резидентными. Смот-
рите документацию по TPSTACK.

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

                 Резидентные программы и EMS.
     
     Аналогичные требования предъявляются к резидентным  прог-
раммам, использующим в процессе активизации память EMS.  OPEMS
содержит процедуры, которые нужны вам для сохранения и восста-
новления состояния системы EMS, так что вы можете ее использо-
вать. Смотрите описание SaveEmsContext и RestoreEmsContext,  в
документации по OPEMS.

               Резидентные программы и оверлеи.
     
     Написание резидентной программы, использующей систему уп-
равлениями оверлеями фирмы Borland, также потребует от вас до-
полнительных усилий. Ниже приведена программа (базирующаяся на
OVRDEMO.PAS), которая иллюстрирует основные шаги:

     {$V-, S-,I-,R-,F+,O+}
     {$M 4096,0,650000}
     program DvrDemo;
     uses
        Overlay, Dos, OpDos, OpTsr, OpCrt, OvrDemo1, OvrDemo2;
     {$O OvrDemo1}
     {$O OvrDemo2}

     const
        TabKey = $080F; {ALT-TAB}
        Count : Word = 0;
     var
        OvrName : string[64]

        procedure PopProc(var Regs : Registers);
           {...Эта процедура вызывается при нажатии Alt-Tab...}
        begin
           if not ReleaseEms then
              if not ExistFile(OvrName) then
                 { ошибка. Не могу вызвать оверлей }
           {увеличить счетчик}
           Inc(Count);
           WriteLn("Внутри "всплывающей" процедуры. Счетчик =',
                   Count,'.');
           {вызвать процедуры из оверлеев}
           Write1;
           Write2;
           {отключить программу после десятого вызова}
           if Count >= 10 then
              if DisableTsr then
                 WriteLn('Отключение OVRDEMO...');
        end;
     begin
        { определение местонахождения оверлейных файлов }
        if NotExistOnPath('OVRDEMO.OVR',OvrName) then begin
           WriteLn('Не могу найти OVRDEMO.OVR');
           Halt;
        end;
        { открыть оверлейный файл }
        OvrInit(OvrName);
        if OvrResult <> 0 then begin
           WriteLn("Ошибка оверлея : ',OvrResult);
           Halt;
        end;
        { переместить оверлей, если возможно, в EMS }
        OvrInitEms;
        case OvrResult of
           ovrIOError     : WriteLn('Ошибка чтения/записи овер
                                   лейного файла.);
           ovrNoEMSDriver : WriteLn('Не установлен драйвер EMS);
           ovrNoEMSMemory : WriteLn('Не хватает памяти EMS');
        end;
        { если запрос EMS удовлетворен, то установить флаг }
        { ReleaseEms }
        ReleaseEms := (OvrResult = 0);
        { Alt-Tab активизируют "всплывающую" процедуру }
        if not DefinePop(TabKey,PopProc,Ptr(SSeg,SPtr),,True) then
           WriteLn('Не могу создать "всплывающую" процедуру');
        else begin
           WriteLn('Процедура загружена, для активизации
                    нажмите Alt-Tab');
           PopupsOn;
           StayRes(ParagraphsToKeep,0);
           { ошибка при попадании в эту точку }
           WriteLn('Не могу остаться резидентной');
        end;
     end.

     Хотя данная  программа  имеет весьма слабую проверку воз-
никновенижя ошибок, она демонстрирует две важные вещи. Первое,
вы должны быть совершенно уверены, что "всплывающая" процедура
имеет доступ к оверлейному файлу.  Лучшим  решением  в  данном
случае является получение полного маршрута доступа к файлу при
загрузке программы и использование ExistFile для проверки  су-
ществования файла  при  активизации  "всплывающей"  процедуры.
Если оверлейный файл не найден, вы должны вывести сообщение об
ошибке и  выйти из "всплывающей" процедуры без вызова оверлей-
ных процедур.  Заметим,  что при  использовании  для  хранения
оверлеев памяти EMS,  нет необходимости в проверке существова-
ния оверлейных файлов.

     Второе, если вы используете OvrInitEms для сохранения со-
держимого оверлейного файла в памяти EMS, вы должны установить
переменную ReleaseEms из OPTSR. Этим вы укажете системе управ-
ления оверлеями, что при завершении резидентной программы необ-
ходимо освободить  память  EMS.  Заметим,  что   необходимости
восстанавливать контекст  отображения EMS внутри вашей "всплы-
вающей" процедуры не нужно,  т.к. система управления оверлеями
делает это самостоятельно.

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

                           Модули.
     
     Что, если  вы  случайно попытаетесь загрузить ту же рези-
дентную программу второй раз?  Многие программы не могут поме-
шать  этому,  и  результат  в  этом  случае  не приведет вас в
восторг.  OPTSR предоставляет вам простое решение данной проб-
лемы созданием  того,  что мы называет стандартным интерфейсом
резидентной программы. Стандартный интерфейс резидентной прог-
раммы  предоставляет возможности не только определить то,  что
программа уже загружена, но и средства взаимодействия с други-
ми резидентными программами (или модулями), использующими этот
интерфейс. Короче, для предотвращения повторной загрузки вашей
резидентной программы вы должны:  Присвоить каждой резидентной
программе уникальное имя,  строку из 5-10 символов. Перед тем,
как сделать   программу   резидентной,   вы   должны   вызвать
ModuleInstalled для проверки того, что программа еще не загру-
жена:

     const ModuleName : string[5] = 'MYTSR';
     ...
     if ModuleInstalled(ModuleName) then begin
        WriteLn('Уже загружена.');
        Halt;
     end;

     Для того, чтобы это сработало, вы в первый раз должны пе-
ред оставлением программы резидентной инсталлировать модуль:

     InstallModule(ModuleName,nil);

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

     Внешний интерфейс.
     Как другие программы могут обратиться к вашей  программе?
Вы указали во втором параметре InstallModule 'nil'. Второй па-
раметр InstallModule - CmdEntryRoutine - это адрес  процедуры,
называемой  также  внешним интерфейсом,  которую может вызвать
другая программа для выполнения  определенной  функции.  Любая
программа  может обратиться к этой процедуре через данный ука-
затель:

     ExtIfcPtr := ModulePtrByName('MYTSR');
     if ExtIfcPtr = nil then
        { MYTSR не загружена }
     else
        ExtIfcPtr := ExtIfcPtr^.CmdEntryPtr;

     Ваша процедура внешнего интерфейса должна иметь следующую
форму:

     procedure MyExternalInterface(BP : Word); interrupt;
     var Regs : IntRegisters absolute BP;
     begin
        with Regs do begin
          { ... обработка вызова функции ...}
        end;
     end;

     Почему процедура обработки прерывания?  По трем причинам.
Вы можете просто осуществить ее  вызов  посредством  процедуры
EmulateInt из  OPINT.  Второе,  вы можете передавать параметры
через регистры центрального процессора так же, как вы делаете,
используя Intr и MsDos.  Например, пусть MyExternInterface вы-
полняет определенную работу, если AH = 1. Вы можете вызвать ее
следующим образом:

     var Regs : InitRegisters;
     ...
     Regs.AH := 1;
     EmulateInt(Regs,ExtIfcPtr);

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

     Возможности данной  функции неограничены.  Примером прог-
раммы, содержащей внешний интерфейс,  может служить SMACS.PAS,
а программы, использующей этот интерфейс, PTIME.PAS.

               Выгрузка резидентной программы.
     
     Одной из наиболее красивых возможностей резидентных прог-
рамм (включая  и  коммерческие  продукты) является возможность
выгрузки ее.  И OPTSR предоставляет вам  эту  возможность.  Из
"всплывающей" процедуры  просто вызовите DisableTSR.  Если она
возвращает True,  это означает, что резидентная программа выг-
ружена из памяти,  а также то,  что вы вышли из  "всплывающей"
процедуры.  Если она возвращает False,  это означает,  что это
делать очень опасно, и она не может этого сделать. Определение
"безопасности" вы найдете в документации по SafeToDisable.

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

     procedure ExternalInterface(BP : Word); interrupt;
     var
        Regs : IntRegisters absolute BP;
        SavePsp : Word;
     begin
        { Если AH содержит, 'U' выгрузить резидент }
        if Char(Regs.AH) = 'U' then begin
           { сохранить текущий ПСП и переключиться на ваш }
           SavePSP := GetPSP;
           SetPSP(PrefixSeg);
           { попытка выгрузить программу - код возврата в AL }
           AL := Org(DisableTSR);
           { переключиться на старый ПСП }
           SetPSP(SavePsp);
        end;
     end;

     procedure UnloadFromCommandLine;
     var
        Regs : IntRegisters;
        P : IfcPtr;
     begin
        P := ModulePtrByName(OurModuleName);
        if (P <> nil) and (P^.CmdEntryPtr <> nil) then begin
           { восстановить старые вектора прерываний }
           RestoreAllVectors;
           {Вызвать функцию }
           Regs.AH := Org('U');
           EmulateInt(Regs,P^.CmdEntryPtr);
           if Regs.AL = 1 then
             { программа выгружена }
           else
             { не могу выгрузить программу }
        end;
     end;

     Некоторые вещи в данных двух процедурах требуют  дополни-
тельных комментариев.

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

     Второе, вы заметили,  что в ExternalInterface имеется об-
ращение к встроенным макрокомандам  GetPSP  и  SetPSP.  GetPSP
возвращает текущий  Префикс  Сегмента  Программы  (PSP Program
Segment Prefix), а SetPSP его изменяет. Каждая программа имеет
свой собственный PSP, а внутри DOS имеется скрытая переменная,
содержащая PSP текущей программы. GetPSP вызывает недокументи-
рованную функцию  DOS,  возвращающую  PSP  текущей  программы.
SetPSP вызывает другую документированную функцию,  которая из-
меняет его. О необходимости сохранения, изменения и восстанов-
ления PSP можно написать небольшую книгу,  поэтому мы  опустим
подробности. Вы   должны  запомнить,  что  если  вы  вызываете
DisableTSR из процедуры внешнего интерфейса вы  должны  сохра-
нить, изменить  и восстановить PSP при помощи GetPSP и SetPSP.
(Если коротко, то DisableTSR запрашивает у DOS текущий PSP для
того,  чтобы освободить память, и DOS не сможет этого сделать,
если PSP будет неверным).

     Третье, заметьте,  что  RestoreAllVectors  вызывается   в
UnloadFromCommandLine перед неявным вызовом процедуры внешнего
интерфейса посредством EmulateInt. Это очень важно. Код иници-
ализации OPTSR  захватывает  некоторые  вектора,  и они должны
быть освобождены перед тем, как копия (Б) вашей программы зап-
росит копию (А) о выгрузке ее из памяти. Если вы этого не сде-
лаете,  DisableTSR будет считать,  что опасно производить выг-
рузку программы и завершится по ошибке.

     Все остальное об этих двух подпрограммах вы можете узнать
самостоятельно, рассмотрев их тексты,  за исключением, возмож-
но, того,  почему мы загружаем в Regs.AH 'U".  Это  совершенно
необязательно. Если внешний интерфейс выполняет только функцию
выгрузки программы из памяти,  то можно вообще не использовать
его  в  процедурах  UnloadFromCommandLine и ExternalInterface.
Если же внешний интерфейс выполняет еще какие-либо задачи,  то
для  определения  выполняемой  функции вы должны с помощью ре-
гистров передать информацию туда и обратно.  Как уже было ска-
зано SMACS содержит пример программы, использующей внешний ин-
терфейс, и вы можете перед тем,  как  писать  свою  программу,
изучить этот пример.

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

                      Работа с SideKick.
     
     SideKick может вызвать появление всех проблем,  о которых
мы упоминали, однако OPTSR поможет вам исключить их.

     Первое, во время инициализации  OPTSR  проверяет  наличие
SideKick в памяти.  Если он есть, то она ставит на резидентную
копию заплатку, которая позволит другим резидентным программам
загружаться после SideKick.  Единственное,  чем данное решение
плохо,  это то,  что после этого SideKick не сможет определить
свое наличие в памяти при попытке повторной загрузки.  Если вы
хотите, чтобы ваша программа была защищена  от  таких  возмож-
ностей,    вы    должны    отменить    определение   директивы
'ThwartSideKick' в OPDEFINE.INC и перекомпилировать OPTSR.

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

     OPTSR может также обнаружить наличие SideKickPlus, однако
в этом  случае  он оставляет флаг SideKickLoaded равным False,
поскольку с данной программой  обычно  проблем  не  возникает.
Обычно после  SideKickPlus  загружаются  резидентные программы
без проблем.

                   Выполнение без проблем.
     
     Перед вызовом "всплывающей" процедуры,  которой необходим
доступ к функциям DOS, диспетчер выполняет некоторые предосто-
рожности, чтобы обеспечить наиболее надежную среду для "всплы-
вающей" процедуры:  1) сохраняет текущий уровень DOS  BREAK  и
отключает его, чтобы нажатие <CtrlBreak> не уничтожило "всплы-
вающую" процедуру.   2)    Прерывания    $1B    (переключаемое
<CtrlBreak>) и  $23 (переключаемое <CtrlC> в обычной ситуации)
устанавливает таким образом, чтобы оба они указывали на коман-
ду IRET.  3)  Текущую DTA (Область связи с диском) и PSP (Пре-
фикс сегмента программы) сохраняет и устанавливает те, которые
были текущими в момент загрузки резидента.  4) Устанавливается
программа обработки критических ошибок DOS, чтобы вы могли об-
работать такие  ошибки.  При  возврате  управления  диспетчеру
система  восстанавливается в состояние,  в котором она находи-
лась перед тем, как были совершены данные действия.

     Технические замечания для хаккеров.
     OPTSR всегда  отслеживает  прерывания  $05  (PrtSc),  $08
(Таймер), $09  (клавиатура),  $13  (BIOS чтение/запись диска),
$16 (BIOS клавиатура),  $25 (чтение абсолютного сектора  DOS),
$26 (запись абсолютного сектора DOS), и $28 (многозадачность).
Прерывание $09 используется для отслеживания нажатия "горячих"
клавиш, когда  устанавливается таймер для "всплывающей" проце-
дуры (на 2 секунды).  Вектора $08 и  $28  вместе  представляют
диспетчер. Если счетчик не 0,  то они определяют, можно ли за-
пустить "всплывающую"  процедуру,  и,  если  можно  запускают,
соответсвующую прикладную  задачу.  Все  остальные  прерывания
представляют  из  себя фильтры,  которые установлены для того,
чтобы быть уверенным что "всплывающая" процедура не будет  за-
пущена  при их обработке.  Если процедура запускается в "блоке
совместимости" OS/2,  то фильтруются три дополнительных преры-
вания: $10 (BIOS VIDEO), $14 (BIOS Communications) и $17 (BIOS
Printer).  Этого достаточно для понимания того,  как  произво-
дится  управление  "всплывающими" процедурами.  Дополнительную
информацию вы можете получить изучив OPTSR.ASM.

                            Типы.

     IfcPtr = ^IfcRecord;

     Указатель на IfcRecord.

     IfcRecord = record
        NamePtr : ^String;
        Version : Word;
        CmdEntryPoint : Pointer;
        PrevIfc,NextIfc : IfcPtr;
     end;

     Запись, содержащая  информацию  об  определенном  модуле.
NamePtr указывает на имя модуля.  Version это номер версии ин-
терфейса. Если CmdEntryPoint не равно nil, то ее можно вызвать
из другой программы.  PrevIfc и NextIfc  -  это  указатели  на
предшествующий и последующий элементы связного списка модулей.

     PopupProc = procedure(var Regs : Registers);

     Форма "всплывающей" процедуры OPTSR.

                          Константы.

     Int5Handle = 1;
     Int8Handle = 2;
     Int9Handle = 3;
     Int10Handle = 4;
     Int13Handle = 5;
     Int14Handle = 6;
     Int16Handle = 7;
     Int17Handle = 8;
     Int25Handle = 9;
     Int26Handle = 10;
     Int28Handle = 11;

     Это дескрипторы векторов, используемые OPTSR. (Дескриптор
12 используется OPMACRO).

     MaxPopups = 8;

     Максимальное число "всплывающих" процедур.

     SideKickLoaded : Boolean = False;

     Устанавливается инициализирующим   кодом,  если  загружен
SideKick.

                         Переменные.

     PopTickerPtr : ^Word;

     Указатель на скрытую в OPTSR переменную,  которая опреде-
ляет, что  в настоящий момент есть запрос на запуск "всплываю-
щей" процедуры.  Значение по этому адресу  сообщает  программе
управления "всплывающими" процедурами, как много времени оста-
лось для  запуска  такой  процедуры (попытки делаются 18 раз в
секунду).  Эта переменная предназначена только для внутреннего
использования.

     PopupInUse : array[1..MaxPopups] of Boolean;

     Эти флаги используются для определения активных процедур.
Предназначены для внутреннего использования.

     PopupToCallPtr : ^Byte;

     Указатель на  скрытую переменную,  которая содержит деск-
риптор процедуры,  ожидающей  активизации   в   случае,   если
PopTickerPtr^ не равен 0. Предназначен для внутреннего исполь-
зования.

     ReleaseEms : Boolean = False;

     Эта переменная должна быть установлена в True, если рези-
дентная программа использует оверлейные файлы и сохраняет их в
EMS.

     ThisIfc : IfcRecord;

     Запись, содержащая информацию об определенном модуле.

AddHotKey
─────────────────────────────────────────────────────────────
Объявление

     function AddHotKey(PrimaryHotKey,
                        SecondaryHotKey : Word) : Boolean;

Функция

     Добавляет дополнительную "горячую" клавишу к указанной.

Описание

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

     AddHotKey возвращает значение False,  если она  не  может
найти    "всплывающую"    процедуру,    с    которой   связаны
PrimaryHotKey,  или если часть скан-кода SecondaryHotKey конф-
ликтует с существующей "горячей" клавишей.  Например, конфликт
возникнет,  если вы  попытаетесь  добавить  <Ctrl><Tab>,  если
<Alt><Tab> - это PrimaryHotKey или такие "горячие" клавиши уже
связаны с другой "всплывающей" процедурой.

Пример

     HotKey1 = $0829;  { <Alt><-> }
     hotKey2 = $040F;  { <Ctrl><Tab> }
     ...
     if DefinePop(HotKey1,PopupEntryPoint,Ptr(SSeg,SPtr),True)
        then
        if not AddHotKey(HotKey1,HotKey2) then
     { была определена другая "горячая" клавиша, исполь- }
     { зующая символ Tab }

     Правильно. Нажатие <Alt><-> или  <Ctrl><Tab>  приведет  к
активизации процедуры.

ChangeHotKey
─────────────────────────────────────────────────────────────
Объявление

     function ChangeHotKey(OldHotKey, NewHotKey : Word) : Boolean;

Функция

     Меняет "горячие"  клавиши   "всплывающей"   процедуры   с
OldHotKey на NewHotKey.

Описание

     Данная процедура позволяет вам изменить "горячие" клавиши
определенной "всплывающей"  процедуры.  Существует только одно
ограничение, чтобы новый скан-код не конфликтовал с "горячими"
клавишами других "всплывающих" процедур. Например, конфликт не
возникнет, если   вы   попытаетесь   изменить  <Ctrl><Tab>  на
<Alt><Tab>,  но возникнет,  если вы попытаетесь изменить ее на
<Ctrl><Space>,  когда <Alt><Space> связаны с другой "всплываю-
щей" процедурой.

Пример

     const
        OldHotKey = $0829;   { <Alt><-> }
     var
        NewHotKey : Word;
     ...
        NewHotKey := $040F;  { <Ctrl><Tab> }
        if not ChangeHotKey(OldHotKey,NewHotKey) then
     { "горячие" клавиши не одинаковы, и новая уже используется }

     Изменяет "горячие" клавиши связанные с "всплывающей" про-
цедурой с <Alt><-> на <Ctrl><Tab>.

DefinePop
─────────────────────────────────────────────────────────────
Объявление

     function DefinePop(HotKey : Word; Routine : PopupProc;
                        StackPtr : Pointer;
                        DosWait : Boolean) : Boolean;

Функция

     Определяет "всплывающую" процедуру, связанную с указанны-
ми "горячими" клавишами.

Описание

     В отличие от DefinePopProc  данная  процедура  определяет
"всплывающую" процедуру,  которая  может быть активизирована с
помощью "горячих" клавиш. Routine - это имя "всплывающей" про-
цедуры.  SteckPtr указывает  на  конец  стека,  который  будет
использовать всплывающая процедура.  DosWait определяет, необ-
ходим ли данной процедуре доступ  к  функциям  DOS.  DefinePop
возвращает False, если нет свободных дескрипторов.

Пример

     const MyHotKey : Word = $0829;   { <Alt><-> }
     var MyStack : array[1..2048] of Bytr;
     ...
     if not DefinePop(MyHotKey, MyPopUp, @MyStack[2048], True)
        then { error }

     Правильно.

     if not DefinePop(MyHotKey, MyPopUp, @MyStack[1], True)
        then { error }

     Неверно. @MyStack[1]  адрес начала дополнительного стека,
а не конца.

Смотрите также

     DeletePop

DefinePopProc
─────────────────────────────────────────────────────────────
Объявление

     function DefinePopProc(var Handle : Byte;
                            Routine : PopupProc;
                            StackPtr : Pointer) : Boolean;

Функция

     Определяет "всплывающую" процедуру,  которая  может  быть
активизирована посредством SetPopTicker.

Описание

     В отличии от DefinePop, данная процедура используется для
определения "всплывающих" процедур, которые могут быть активи-
зированы посредством вызова SetPopTicker.  Routine -  это  имя
"всплывающей"  процедуры.  SteckPtr  указывает на конец стека,
который будет использовать  всплывающая  процедура.  Считается
что процедуре  необходим  доступ к функциям DOS.  Handle - это
номер,  который присваивается данной процедуре  DefinePopProc;
он необходим при вызове SetPopTicker.  DefinePopProc возвраща-
ет, False если нет свободных дескрипторов.

Пример

     var
        ClockHandle : Byte;
        ClockStack : array[1..1024] of Byte;
     ...
     if not DefinePopProc(ClockHandle, ClockPop, @ClockStack[2048])
        then { error }

     Правильно.

     if not DefinePopProc(ClockHandle, ClockPop, @ClockStack[2048])
        then { error }

     Неверно. @ClockStack[1] адрес начала дополнительного сте-
ка, а не конца.

Смотрите также

     SetPopTicker                      DeletePopProc

DeletePop
─────────────────────────────────────────────────────────────
Объявление

     procedure DeletePop(HotKey : Word);

Функция

     Удаляет "всплывающую" процедуру, связанную с данными "го-
рячими" клавишами.

Описание

     Эта процедура  позволяет  вам деактивизировать "всплываю-
щую" процедуру,  определенную с помощью DefinePop. Ее дескрип-
тор и "горячие" клавиши могут затем использоваться для  других
"всплывающих" процедур.

Пример

     const MyHotKey = $0439;
     ...
     DeletePop($0439);
     DeletePop(MyHotKey);

Смотрите также

     DefinePop

DeletePopProc
─────────────────────────────────────────────────────────────
Объявление

     procedure DeletePopProc(Handle : Byte);

Функция

     Удаляет "всплывающую"       процедуру,       определенную
DefinePopProc.

Описание

     Эта процедура позволяет вам  деактивизировать  "всплываю-
щую" процедуру,  определенную DefinePopProc. Ее дескриптор мо-
жет быть использован для другой процедуры.

Пример

     var   MyHandle : Byte;
           MyStack : array[1..2048] of Byte;
     ...
     if not DefginePopProc(MyHandle,MyPopup,@MySteck[2048])
         then { Error }
     ...
     DeletePopProc(MyHandle);

Смотрите также

     DefinePopProc

DisableTSR
─────────────────────────────────────────────────────────────
Объявление

     function DisableTSR : Boolean;

Функция

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

Описание

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

     Предупреждение: DisableTSR  не   должна   вызываться   из
"всплывающих" процедур,  которые  не  имеют доступа к функциям
DOS.

Пример

     if DisableTSR then Exit;

     Допустимо.

     if DisableTSR then Halt;

     Неверно! Никогда не вызывайте Halt, после того, как прог-
рамма стала резидентной.

Смотрите также

     RemovePops                   SafeToDisable

DosBusyFlag
─────────────────────────────────────────────────────────────
Объявление

     function DosBusyFlag : Byte;

Функция

     Возвращает текущее состояние флага занятости DOS.

Описание

     Эта процедура,  предназначенная для вызова из  "всплываю-
щих" процедур, показывает, была ли активна DOS в момент вызова
"всплывающей" процедуры.  Если значение равно 0, значит DOS не
была активна, во всех остальных случаях DOS была активна. Под-
разумевая,  что процедуре требуется  доступ  к  функциям  DOS,
возвращаемое значение будет не 0  только в случае, если вы ак-
тивизируете процедуру, находясь на подсказке DOS.

Пример

     if(DosBusyFlag <> 0) then begin
        StuffString(^['TURBO.EXE'^M);
        Exit;
     end;

     При запуске внутри "всплывающей" процедуры  она  выведет
<Esc>, символы 'TURBO.EXE' и символ "возврат каретки" в  буфер
клавиатуры и возвратит управление.

DosCriticalFlag
─────────────────────────────────────────────────────────────
Объявление

     function DosCriticalFlag : Byte;

Функция

     Возвращает текущее значение критического флага DOS.

Описание

     Эта процедура, предназначеная для вызова из "всплывающих"
процедур, показывает,  не  происходила  ли  в  момент   вызова
"всплывающей" процедуры обработка критической ошибки DOS. Если
значение равно 0, то нет, в противном случае происходит. Заме-
тим, что под некоторыми версиями DOS это значение равно значе-
нию, возвращаемому DosBusyFlag, и не имеет специального значе-
ния. Чтобы понять почему, проверьте InitTsrPtrs в OPTSR.ASM.

GetPSP
─────────────────────────────────────────────────────────────
Объявление

      function GetPSP : Word;

Функция

     Возвращает текущий PSP.

Описание

     Эта функция возвращает PSP  текущей  активной  программы.
Она предназначена  для использования только в процедурах внеш-
него интерфейса.  Детальное описание ее использования смотрите
в начале главы в разделе "Выгрузка резидентных программ".

Смотрите также

     SetPSP

InitPops
─────────────────────────────────────────────────────────────
Объявление

     procedure InitPops;

Функция

     Инициализирует структуры данных OPTSR.

Описание

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

Пример

     RemovePops;
     { ... что нибудь ... }
     InitPops;

Смотрите также

     RemovePops
     
InstallModule
─────────────────────────────────────────────────────────────
Объявление

     procedure InstallModule(var ModuleName : string;
                             CmdEntryRoutine : Pointer);

Функция

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

Описание

     ModuleName это строка, которая идентифицирует резидентный
модуль. CmdEntryRoutine,  если не  nil,  должна  указывать  на
программу внешнего интерфейса псевдопрограмме обработки преры-
вания,  которая  может  быть  запущена  посредством  процедуры
EmulateInt.  InstallModule добавляет модуль к связному  списку
резидентных модулей, которые используют стандартный интерфейс,
обеспечивая таким образом возможность нахождения ее другим мо-
дулем или второй копией ее самой.  Модуль может быть удален из
списка процедурой UninstallModule.

Пример

     const MyTsrName : string[5] = 'MYTSR';
     ...
     InstallModule(MyTsrName,nil);

Смотрите также

     EmulateInt              UninstallModule
     ModuleInstalled

MaxParagraphsToKeep
─────────────────────────────────────────────────────────────
Объявление

     function MaxParagraphsToKeep : Word;

Функция

     Максимальное число параграфов для захвата, при оставлении
программы резидентной.

Описание

     Эта функция  используется  для  определения максимального
допустимого значения   параметра   ParasToKeep,    посылаемого
StayRes. Если программа компилируется с установленным размером
"кучи" 640К (по умолчанию), то MaxParagraphsToKeep вернет объ-
ем памяти, который был доступен перед загрузкой программы. Та-
ким образом,  вычитая ParagraphsToKeep (или значение, посылае-
мое StayRes)  из  MaxParagraphsToKeep,  вы  можете  вычислить,
сколько  примерно  памяти останется свободной после того,  как
программа сделается резидентной.

Пример

     KbLeft :=   LongInt(MaxParagraphsToKeep-ParagraphsToKeep)
                 div 64;
     WriteLn(KbLeft,'K доступной памяти');
     StayRes(ParagraphsToKeep,0);

Смотрите также

     ParagraphsToKeep

ModuleInstalled
─────────────────────────────────────────────────────────────
Объявление

     function ModuleInstalled(var ModuleName:string):Boolean;

Функция

     Возвращает True, если ModuleName уже установлено.

Функция

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

Примеры

     const MyModuleName : string[5] = 'MYTSR'
           OtherModuleName : string[10] = 'SUPER MACS';
     ...
     if ModuleInstalled(MyModuleName) then begin
        Writeln('Уже загружен. Завершение...');
        Halt;
     end;
     if not ModuleInstalled(OtherModuleName) then begin
        Writeln('SMACS уже загружен. Завершение...');
        Halt;
     end;

Смотрите также

     ModulePtrByName

ModulePtrByName
─────────────────────────────────────────────────────────────
Объявление

     function ModulePtrByName(var ModuleName:string):IfcPtr;

Функция

     Возвращает указатель на структуру IfcRecord модуля с име-
нем ModuleName или nil;

Описание

     Хотя данная     процедура      практически      повторяет
ModuleInstalled, ее назначение состоит в том,  чтобы предоста-
вить вам доступ к внешнему интерфейсу другого модуля.

Пример

     const SmacsName : string[10] = 'SUPER MACS';
     var P : IfcPtr;
         EntryPointer : Pointer;
     begin
        P := ModulePtrByName(SmacsName);
        if P <> nil then EntryPointer := P^.CndEntryPtr
        else EntryPointer := nil;
     end;

Смотрите также

     ModuleInstalled

ParagraphsToKeep
─────────────────────────────────────────────────────────────
Объявление

     function ParagraphsToKeep : Word;

Функция

     Возвращает число параграфов,  используемых для кода, дан-
ных, стека и "кучи".

Описание

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

Примеры

     StayRes(ParagraphsToKeep,0);

     Становится резидентной без дополнительной памяти.

     StayRes(ParagraphsToKeep+1024,0);

     Становится резидентной  с дополнительными 16К (1024 * 16)
памяти.

Смотрите также

     MaxParagraphsToKeep          StayRes

PopupsOff
─────────────────────────────────────────────────────────────
Объявление

     procedure PopupsOff;

Функция

     Отключает "всплывающие" процедуры.

Описание

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

PopupsOn
─────────────────────────────────────────────────────────────
Объявление

     procedure PopupsOn;

Функция

     Включает "всплывающие" процедуры.

Описание

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

Пример

     PopupsOnж
     StayRes(ParagraphsToKeep,0);

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

     {$F+}
     procedure MyPopup(var Regs : Registers);
     begin
        PopupsOff;
        ...
        PopupsOn;
     end;

     Здесь другие "всплывающие" процедуры блокируются  до  за-
вершения MyPopup.

RemovePops
─────────────────────────────────────────────────────────────
Объявление

     procedure RemovePops;

Функция

     Удаляет все  "всплывающие"  процедуры и освобождает соот-
ветствующие вектора прерываний.

Описание

     Эта процедура  предназначена   только   для   внутреннего
использования. Она  вызывается  автоматически  при  завершении
программы. Заметим,  что эта функция выполняет не то же самое,
что DisableTSR, которая завершает выполнение резидентной прог-
раммы.

Смотрите также

     DisableTSR

SafeToDisable
─────────────────────────────────────────────────────────────
Объявление

     function SafeToDisable : Boolean;

Функция

     Возвращает True,  если удаление резидентной программы бе-
зопасно.

Описание

     Эта процедура позволяет вам определить, безопасно или нет
удалять  резидентную  программу  без  самого  удаления вызовом
DisableTSR. SafeToDisable вначале проверяет, что программа ре-
зидентна. Если нет, она возвращает False. Затем она проверяет,
имеются ли другие программы,  которые перехватили вектора пре-
рываний,  захваченные данной программой.  Если такие есть,  то
удалять программу опасно,  и она возвращает False. В противном
случае она возвращает True.

Смотрите также

     DisableTSR

SetPopTicker
─────────────────────────────────────────────────────────────
Объявление

     procedure SetPopTicker(Handle : Byte; TimeOut : Word);

Функция

     Устанавливает время вызова указанной  процедуры,  очищает
прерывания и возвращает управление.

Описание

     Сообщает диспетчеру, что необходимо искать удобный момент
для вызова процедуры,  связанной с дескриптором  Handle.  Если
число тиков таймера равное TimeOut прошло, а удобный случай не
представился, процедура не будет вызвана.  (Это примерно  18.2
тика в секунду). Очень важно: не производится проверка вхожде-
ния в диапазон 1..MaxPopups для Handle, поэтому это обязатель-
но должен быть верный дескриптор, значение которого возвратила
DefinePopProc.

Примеры

     SetPopTicker(MyPopupHandle,36);

     Правильно. Означает: попытайся в течении 2-х секунд акти-
визировать процедуру,  связанную  с дескриптором MyPopupHandle
(который вам вернула DefinePopProc).

     SetPopTicker(1,36);

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

Смотрите также

     DefinePopProc

SetPSP
─────────────────────────────────────────────────────────────
Объявление

     procedure SetPSP(PSP : Word);

Функция

     Устанавливает новый PSP.

Описание

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

Смотрите также

     GetPSP

StayRes
─────────────────────────────────────────────────────────────
Объявление

     procedure StayRes(ParasToKeep : Word; ExitCode : Byte);

Функция

     Завершает программу,  но оставляет ее резидентной,  осво-
бождая всю память, за исключением ParasToKeep.

Описание

     Эта процедура:  1)  освобождает  всю память,  захваченную
программой, за исключением ParasToKeep (обычно вычисляемой при
помощи ParagraphsToKeep);  2) подгоняет структуры данных прог-
раммы управления  памятью  таким  образом,  чтобы  резидентная
программа могла  динамически  выделять память;  3) Освобождает
вектора, захваченные  библиотекой  времени  выполнения   Turbo
Pascal. 4) Вызывает функцию DOS $31,  посылая ей ParasToKeep и
ExitCode (обычно 0).

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

Примеры

     StayRes(ParagraphsToKeep,0);

     Становится резидентной без дополнительной памяти.

     StayRes(ParagraphsToKeep+1024,0);

     Становится резидентной  с дополнительными 16К (1024 * 16)
памяти.

Смотрите также

     MaxParagraphsToKeep          PopupsOn
     ParagraphsToKeep

UninstallModule
─────────────────────────────────────────────────────────────
Объявление

     procedure UninstallModule;

Функция

     Удаляет модуль из связного списка модулей.

Описание

     Эта процедура удаляет текущий модуль из  связного  списка
модулей. Она  предназначена в основном для внутреннего исполь-
зования, (например DisableTSR).

Смотрите также

     DisableTSR                   InstallModule


         D.Обзор подкачиваемых резидентных программ.

     OPSWAP и OPSWAP1 реализуют систему,  с помощью которой вы
можете писать резидентные программы,  которые используют около
6К обычной  памяти DOS,  независимо от действительного размера
программы. OPSWAP1 очень похожа на OPTSR:  она содержит проце-
дуры, позволяющие определить,  что программа уже загружена для
определения "всплывающих" процедур для того,  чтобы  оставлять
программу в памяти резидентной.  OPSWAP - это ключ к подкачке,
она содержит ядро,  которое остается в памяти  резидентным  на
протяжении  всего  времени,  и отслеживает "горячие" клавиши и
другие события.  Когда OPSWAP определяет  наступление  момента
вызова  "всплывающей" процедуры,  она проверяет в начале безо-
пасность текущего состояния  системы  и  считывает  оставшуюся
часть  кода  и  данных резидентной программы из дополнительной
памяти (EMS) или с диска.  При этом она  естественно  затирает
программы,  которые были загружены после нее,  и поэтому перед
загрузкой своей оставшейся части она сохраняет содержимое  па-
мяти  в  дополнительной памяти или на диске.  После совершения
некоторых дополнительных  шагов,  OPSWAP  передает  управление
"всплывающей"  процедуре из вашей программы,  которая написана
идентично процедурам,  написанным под OPTSR.  После завершения
вашей  процедуры OPSWAP совершает описанные выше шаги в обрат-
ном порядке и возвращает управление прерванной программе.

     Правда просто? Это будет просто, если вы будете следовать
правилам, которые мы опишем.

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

     - Не   загружайте  резидентные  программы,  написанные  с
использованием OPSWAP,  перед  многозадачными  системами  типа
DeskView, OmnyView  и т.п.  Причина аналогична предыдущей,  но
краевые эффекты при этом не так опасны. Допустима загрузка ре-
зидентной программы внутри раздела,  если только раздел резер-
вирует память не только  для  ядра,  но  и  для  подкачиваемой
части.

     - Не  активизируйте  программу  во время работы программ,
использующих расширитель DOS для защищенного режима, типа Phar
Lap.  Расширители DOS перенастраивают систему под себя,  прог-
раммируют, например,  контроллер  прерываний  8259,  переводят
процессор  в защищенный режим и в случае процессора 386 меняют
отображение памяти.  Мы обнаружили, что некоторые расширители,
например, Rational Systems (используется в Lotus 1-2-3 v 3.0),
нормально сосуществуют с подкачиваемыми резидентными  програм-
мами, другие  же,  как  уже упоминавшийся Phar Lap,  полностью
несовместимы.

     Резидентные программы,  не активизируемые "горячими" кла-
вишами, обычно не используют OPSWAP. Причина не в возможности,
а в практике.  Давайте вернемся на время и  посмотрим,  почему
так происходит. При каждой активизации подкачиваемой резидент-
ной программы OPSWAP считывает код и данные  с  диска  или  из
EMS.  Число считываемых символов зависит от сложности програм-
мы, однако по сравнению с OPTSR имеется большая задержка между
получением сигнала и тем моментом,  когда ваша программа полу-
чит  управление.  Большие  резидентные   программы,   например
DESKPOP,  требуют  на загрузку несколько минут.  Для программ,
активизируемых "горячей" клавишей это обычно не является проб-
лемой, OPSWAP предоставляет вам в это время возможность выдать
пользователю сообщение.

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

     Имеется возможность обойти данное ограничение, сохраняя в
памяти не только 6К ядро, но и дополнительную порцию кода. Од-
нако этот код лучше писать на ассемблере,  а не на Pascal.  Мы
написали такую  процедуру для POPHELP,  резидентной справочной
системы по Object Professional.  POPHELP позволяет вам  встав-
лять части справочной информации в редактор. Для этого он под-
держивает резидентную невыгружаемую программу обработки клави-
атуры, которая  при  необходимости  моделирует нажатие клавиш.
Если вас интересует,  как это сделано,  обратитесь к исходному
тексту POPHELP. Однако мы не делаем данную технику общедоступ-
ной.

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

     Если вы перед изучением подкачиваемых  резидентных  прог-
рамм хотите испробовать такие программы на деле, мы предостав-
ляем вам несколько примеров таких программ: DESKPOP, описанная
в главе 1,  полная система управления рабочим столом для прог-
раммиста, которая бы без использования OPSWAP потребовала око-
ло 300К памяти. POPHELP, также описанная в главе 1, предостав-
ляет  вам  доступ  к   информации   о   подпрограммах   Object
Professional. Без использования OPSWAP она потребовала бы око-
ло 150К.  PSCREEN,  описанная в главе 4, захватывает текстовые
экраны и записывает их в файл на диске.  Она без использования
OPSWAP потребовала бы около 45К,  что в общем то допустимо, но
в разница с 6К все же ощутима. Мы уверены, что вы того же мне-
ния.

        Написание подкачиваемой резидентной программы.

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

     В подкачиваемых  резидентных  программам OPSWAP и OPSWAP1
занимают место OPINT и OPTSR (однако деление функций между ни-
ми не  аналогично).  Вы  не  должны в подкачиваемых программах
использовать последние две юниты,  поскольку в  лучшем  случае
это приведет к бесполезному коду,  а в худшем вызовет конфликт
и приведет к краху системы.

     OPSWAP и OPSWAP1 не зависят ни от одной  юниты  в  Object
Professional, за  исключением  OPINLINE.  Поэтому  возможности
подкачиваемых резидентных программ можно использовать с  Turbo
Professional 5.0 (без боязни того,  что объектно ориентирован-
ный интерфейс повлияет на код),  и с компилятором Turbo Pascal
5.0 и 5.5.

     Наибольшее отличие   в  использовании  OPSWAP  состоит  в
использовании его в операторе uses. Основное правило состоит в
том, что OPSWAP должен подключаться только к основной програм-
ме, и стоять в операторе uses последним.  Этим вы  заставляете
компилятор подключить  его  в самый младший сегмент памяти,  и
после него будет только основная программа.  Чем выше вы подк-
лючите OPSWAP,  тем большего размера будет ядро,  остающееся в
памяти. Если вы обнаружите,  что ядро занимает  слишком  много
места, то  первым делом проверьте порядок подключения OPSWAP в
различных юнитах.  Для этого,  укажите компилятору,  чтобы  он
выдавал файл    размещения    (ключ    /GS    для    TPC   или
Options/Linker/Map File/Segments для TURBO). Таблица сегментов
в начале  файла  размещения  покажет  вам  порядок подключения
юнит. Если при этом OPSWAP окажется  где  нибудь  ниже  второй
строки, то это означает ошибку в операторе uses.

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

     OPSWAP1 может использоваться в любых юнитах, кроме той, в
которой используется OPSWAP. OPSWAP1 содержит процедуры, кото-
рые соответствуют  процедурам  из  OPTSR,  поэтому  вы  должны
использовать ее   в   случае   нужды   в    процедурах    типа
InstallModule, DefinePop и StayResSwap.

     Применим эти правила к программе показанной ниже.  Основ-
ная программа получится такой:

     {$M 2048,0,655360}
     {$S-,R-}
     program BeepIt;
     uses
        BeepMain,           { сам код программы }
        OpSwap;             { резидентная часть OPSWAP }
     begin
        InitializeBeepIt;
     end.

     BEEPMAIN это юнита,  которая содержит код,  помещаемый  в
обычных случаях  в  основную  программу.  Помещая  этот  код в
BEEPMAIN, мы тем самым заставляем OPSWAP подкачивать его  в  и
из памяти. InitializeBeepIt это процедура взаимодействия между
BEEPMAIN и BEEPIT,  которая вызывает ее для выполнения необхо-
димых действий.  Остальные параметры этой программы, такие как
размер "кучи" и стека,  определяемые параметром $M  аналогичны
OPTSR.

     Теперь посмотрим на BEEPMAIN.PAS:

     {$S-,R-,V-,I-}
     unit BeepMain;
        { основная юнита примера подкачиваемой резидентной }
        { программы }
     Interface
     uses
        OpInline,           { встроенные макросы }
        OpString,           { строковые процедуры }
        OpCrt,              { функции видео и клавиатуры }
        OpSwap1;            { нерезидентная часть OPSWAP }
     procedure InitializeBeepIt;
     { инициализирует резидент, проверяет установку, иницииру-
       ет "всплывающие" процедуры, и становится резидентной }
     implementation

     const
        HotKey     = $0844;          {Alt-F10}
        OurName    = String[9] = 'BEEPIT1.0'  { имя модуля }
        SwapFile1  = String[15] = 'C:\BEEPSWP1.$$$';
        SwapFile2  = String[15] = 'C:\BEEPSWP2.$$$';

     {$F+}
     procedure MainPop;
        {Эта процедура вызывается при нажатии "горячих" клавиш}
     var
        I:Word;
     begin
        for I:=1 to 100 do begin
           Sound(I*10);
           Delay(1);
        end;
        NoSound;
     end;
     {$F-}

     procedure InstallCheck;
        { проверяет, не загружена ли уже данная программа }
     begin
        if ModuleInstalled(OurName) then begin
        WriteLn('BeepIt уже загружена');
        Halt;
        end;
     end;

     procedure InitOurPopups;
     { Определяет "всплывающие процедуры, разрешает их и уста-
        навливает этот модуль }
     begin
        if not DefinePop(HotKey,MainPop,Ptr(SSeg,Sptr)) then
        begin
           WriteLn("Ошибка при определении процедуры")ж
           Halt;
        end;
        PopupsOn;
        InstallModule(OurName,DisablePopup);
     end;

     procedure TryToUnload;
     { если  BeepIt  уже загружена,  попытаться ее выгрузить и
       сообщить результат }
     var
        Ifcp : IfcPtr;
     begin
        Ifcp := ModulePtrByName(OurName);
        if Ifcp = nil then begin
        WriteLn('BIIPIT не загружен и не может быть выгружен');
        Halt;
        end;

     { восстановить захваченные вектора }
     RestoreAllVectors;

     { вызвать CmdEntryPtr резидентной копии BeepIt }
     Ifcp^.CmdEntryPtr;
     if LongInt(Ifcp^.UserData) = 1 then
        WriteLn('BEEPIT выгружен');
     else
        WriteLn('не могу выгрузить BEEPIT');
     end;

     procedure CheckCommandLine;
        { Проверяет параметры командной строки }
     var
        S : String;
     begin
        if ParamCount > 0 then begin
           S := StUpcase(ParamStr(1));
           if S - '/U' then
              TryUnload;
           else
              WriteLn('Неизвестный параметр');
           Halt;
        end;
     end;

     procedure InitializeBeepIt;
        { Инициализирует резидентную программу }
     begin
        CheckCommandLine;
        InstallCheck;
        InitOurPopups;
        WriteLn('Alt-F10 для активизации');
        StayResSwap(ParagraphsToKeep,0,SwapFile1,SwapFile2,True);
        WriteLn('Ошибка при оставлении резидента');
     end;

     end.

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

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

     Интерфейсом к BEEPMAIN служит процедура InitializeBeepIt.
Для взаимодействия нет никакой другой возможности  и  основная
программа производит вызов данной процедуры.

     InitializeBeepIt проверяет  командную  строку и загружает
или выгружает "всплывающую" процедуру.  Для этого она вызывает
CheckCommandLine, которая если в командной строке присутствует
ключ '/U',  пытается выгрузить процедуру из памяти при  помощи
TryToUnload. Эта  процедура  проверяет был ли такой модуль уже
загружен. Если да, то как и в OPTSR ModulePtrByName возвращает
ненулевой указатель (на запись внешнего интерфейса).  В случае
BEEPIT поле CmdEntryPtr данной записи устанавливается для выг-
рузки программы из памяти.  Оператор 'P^.CmdEntryPtr' произво-
дит дальний вызов ранее загруженной копии BEEPIT,  сообщая ей,
что ее нужно выгрузить.  В отличие от OPTSR, внешний интерфейс
OPSWAP не получает  в  качестве  параметров  набор  регистров.
Вместо этого,  они могут взаимодействовать через поле UserData
записи модуля.  Мы вернемся  к  этому  позднее,  заметим,  что
UnloadRequested проверяет  была  ли успешной попытка выгрузки,
просматривая значение в поле UserData.

     Если выгрузка не требуется,  InitializeBeepIt  продолжает
проверять наличие уже загруженной копии резидентной программы.
Для этого вызывается InstallCheck,  которая использует функцию
OPSWAP1 ModuleInstalled,  которая  аналогична такой же функции
из OPTSR.

     InitOurPopups определяет  "всплывающую"  процедуру  -  ее
"горячие" клавиши,  процедуру,  которая получит управление при
их нажатии,  и стек, который она будет использовать. Это дела-
ется с помощью процедуры DefinePop, которая практически анало-
гична DefinePop из OPTSR. Отличие состоит в том, что DefinePop
из OPSWAP  не получает параметра DosWait.  Подкачиваемые рези-
дентные программы используют функции  DOS  и  поэтому  ожидают
окончания ее работы. Другое отличие состоит в том, что "всплы-
вающая" процедура в OPSWAP определяется не так, как в OPTSR. В
OPTSR "всплывающая" процедура объявляется следующим образом:

     {$F+}
     procedure OpTsrPopup(var Regs : Registers);

     Для OPSWAP "всплывающая" процедура не получает  параметра
Regs:

     {$F+}
     procedure OpSwapPopup;

     Редко, когда резидентной программе  требуется  содержимое
регистров прерванной программы. Если их содержимое требуется в
вашей прикладной программе, то OPSWAP не может предоставить их
вам. За  исключением  данных  отличий  "всплывающая" процедура
подкачиваемой резидентной программы может выполнять те же  ве-
щи, что и "всплывающая" процедура OPTSR.

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

     procedure OpTsrInterfaceProc(BP:Word); Interrupt;

     В OPSWAP это просто процедура типа FAR без параметров:

     {$F+}
     procedure OpSwapInterfaceProc;

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

     Наконец мы добрались  до  конца  BEEPMAIN.  Имеется  один
дополнительный шаг, оставление программы резидентной в памяти.
OPSWAP1 предоставляет вам специальную  версию  соответствующей
процедуры (StayRes) OPTSR. StayResSwap определяет число захва-
тываемых резидентной  частью  параграфов   памяти   аналогично
версии в OPTSR.  Заметим,  что это число параграфов "всплываю-
щей" процедуры а не резидентного ядра.  Вы не можете явным об-
разом указать  размер резидентного ядра,  за исключением того,
что мы рассказали раньше.  StayResSwap получает  три  дополни-
тельных параметра.  Два из них, определяют имена файлов, кото-
рые будет использовать OPSWAP при подкачки с диска и на  диск.
Последний параметр, типа Boolean, определяет необходимо ли во-
обще использовать  механизм  подкачки.  Если  он  будет  равен
False, то OPSWAP будет работать аналогично OPTSR: захватит всю
указанную память и не будет осуществлять подкачку.

     После того,  как BEEPIT будет загружен,  вы можете нажать
"горячие" клавиши  (<Alt><F10>),  и  она будет активизирована,
выдаст звуковой сигнал и вернет  управление.  Если  вы  хотите
выгрузить ее из памяти, наберите в командной строке BEEPIT /U.

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

                 Подкачка на диск или в EMS.

     Для сбрасываемой информации OPSWAP использует или  диско-
вую память или память EMS.  EMS используется если в ней доста-
точно места.  Естественно, что предпочтительнее EMS, поскольку
она быстрее и не зависит от того, что пользователь удалит файл
подкачки или вынет дискету с ним.

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

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

     - Каждый из этих файлов имеет примерно тот же объем,  что
и значение,  которое вы указали в первом параметре StayResSwap
(ParasToKeep), умноженное на 16.  На  устройстве  должно  быть
достаточно места  для  размещения  обоих  файлов одновременно.
(Файлы могут располагаться на разных устройствах, если это не-
обходимо.)

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

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

     - Электронный диск конечно предпочтительнее для  хранения
подкачиваемых файлов. Фактически, электронный диск размещенный
на дополнительной памяти единственная возможность  для  OPSWAP
для использования памяти такого рода.

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

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

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

                     Сообщения подкачки.

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

     По умолчанию выдача сообщений разрешена.  Они выводятся в
строке 25 в инверсном режиме (атрибут  $70).  При  активизации
резидентной программы OPSWAP сохраняет содержимое этой строки,
выводит сообщение 'Swapping in...' с позиции 1 и  очищает  все
остальные позиции строки с тем же атрибутом.  После завершения
подкачки OPSWAP восстанавливает предыдущее содержимое  строки.
При завершении  работы резидентной программы OPSWAP производит
те же действия,  и использует сообщение 'Swapping out...'. Для
вывода сообщений  OPSWAP использует функции BIOS ($10 прерыва-
ние).

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

     Вы можете изменить атрибут и строку выдачи сообщения под-
качки с  помощью процедур SetSwapMsgAttr и SetSwapMsgRow соот-
ветственно. Последняя процедура имеет  возможность  определить
последнюю строку  экрана даже при использования режимов 43 или
50 строк.  Если вы укажете строку 255 ($FF),  OPSWAP  запросит
BIOS определить  номер  последней  строки.  Данная возможность
поддерживается только для адаптеров MCGA,  EGA и VGA, правиль-
ное ее  использовании  остается на вашей совести.  В следующей
части программы  используется  переменная  CurrentDisplay   из
OPCRT для вывода сообщения в последней строке экрана:

     case CurrentDisplay of
        MCGA, EGA, VGA : SetSwapMsgRow(255);
     else
        SetSwapMsgRow(25);
     end;

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

     Пример программы PSCREEN демонстрирует различные комбина-
ции параметров сообщения подкачки. Смотрите PSMAIN.PAS.

                      Внешний интерфейс.

     Как и резидентные программы,  написанные с использованием
OPTSR, так и написанные с использованием OPSWAP, имеют возмож-
ность организовать внешний интерфейс.  Хотя возможности в слу-
чае OPSWAP   аналогичны,   однако  реализация  из  отличается,
поскольку основная часть резидентной  подкачиваемой  программы
не находится  в  памяти в момент вызова процедуры внешнего ин-
терфейса. В этом разделе мы покажем использование внешнего ин-
терфейси и  остановимся  на  отличиях в использовании внешнего
интерфейса OPSWAP.

     Интерфейс устанавливается аналогично OPTSR,  и также пре-
жоставляет необязательную процедуру, которая может быть вызва-
на из другой программы:

     const
        OurName   : String[9] = 'BEEPIT1.0';  { имя модуля }
     ...
     InstallModule(OurName,EntryRoutine);

     Строка (OurName)  позволяет  другим программам найти вашу
по имени. Вторым параметром InstallModule является сама проце-
дура внешнего интерфейса.

     Чаще всего внешний интерфейс используется для определения
того, что программа уже загружена  в  память,  для  исключения
повторной ее   загрузки.   OPSWAP1   предоставляет  вам  метод
ModuleInstalled, который предназначен именно для этого:

        if ModuleInstalled(OurName) then begin
        WriteLn('BeepIt уже загружена');
        Halt;
        end;

     Если вы  хотите лишь проверить наличие программы в памяти
то внешняя процедура вам вообще не нужна. OPSWAP предоставляет
вам идентификатор  NoExternalIfc  для передачи InstallModule в
качестве второго параметра в этом случае:

     InstallModule(OurName,NoExternalIfc);

     Если вы хотите иметь возможность выгружать  программу  из
памяти, то  необходимо  использовать процедуру внешнего интер-
фейса. OPSWAP1 предоставляет  вам  процедуру,  предназначенную
специально для этого случая DisablePopup:

     InstallModule(OurName,DisablePopup);

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

     procedure TryToUnload;
     { если  BeepIt  уже загружена,  попытаться ее выгрузить и
       сообщить результат }
     var
        Ifcp : IfcPtr;
     begin
        Ifcp := ModulePtrByName(OurName);
        if Ifcp = nil then begin
        WriteLn('BIIPIT не загружен и не может быть выгружен');
        Halt;
        end;

     if @Ifcp^CmdEntryPtr = nil then begin
        WriteLn(OurName,'не имеет процедуры внешнего интерфейса');
        Halt;
     end;

     { восстановить захваченные вектора }
     RestoreAllVectors;

     SaveMsgOn := Ifcp^.CSDataPtr^.SwapMsgOn;
     Ifcp^.CSDataPtr^.SwapMsgOn := False;
     { вызвать CmdEntryPtr резидентной копии BeepIt }
     Ifcp^.CmdEntryPtr;
     if LongInt(Ifcp^.UserData) = 1 then
        WriteLn('BEEPIT выгружен');
     else begin
        WriteLn('не могу выгрузить',OurName);
        Ifcp^.CSDataPtr^.SwapMsgOn := SaveMsgOn;
     end;
     end;

     ModulePtrByName возвращает  указатель  на запись внешнего
интерфейса резидентной программы, или nil если она не найдена.
Сравнение @P^.CmdEntryPtr  с nil необходимо,  чтобы убедиться,
что процедура      внешнего      интерфейса       установлена.
RestoreAllVectors освобождает вектора, захваченные OPSWAP, для
того, чтобы уже резидентная копия могла быть без проблем  выг-
ружена.

     Следующая последовательность   операторов   демонстрирует
различия между OPSWAP и OPTSR.  В случае OPSWAP, большая часть
данных, необходимая   для  программы  управления  резидентными
программами скрыто в кодовом сегменте OPSWAP,  и на них  можно
ссылаться через поле CSDataPtr из записи интерфейса. В отличие
от OPTSR,  OPSWAP  не  сохраняет  данные  в  сегменте  данных,
поскольку он перекрывается другой прикладной программой, когда
он не  нужен.  Большая  часть  данных,  на  которые  указывает
CSDataPtr предназначена для внутреннего использования програм-
мой управления.  Однако имеется несколько полей,  к которым вы
можете обращаться.  Одно из них,  SwapMsgOn показано ниже.  Мы
сохранили текущее значение,  временно отключили выдачу сообще-
ния подкачки,  выгрузили  резидентную программу и восстановили
исходное значение.  Эта последовательность отлавливает сообще-
ния, которые выдаются при выгрузке резидента. Она также делает
понятной тот факт,  что OPSWAP подкачивает весь  модуль  перед
тем, как передать управление процедуре внешнего интерфейса.

     Заметим, что  данный пример предназначен для того,  чтобы
вы могли выгрузить резидентную программу из второй копии,  ко-
торая запускается из командной строки DOS, или из другой прог-
раммы. Если  вы  хотите  выгрузить  резидентную  программу  из
"всплывающей" процедуры,  вы  може  те  просто воспользоваться
процедурой DosableTSR из OPSWAP1:

     {$F+}
     procedure MyPopup;
     begin
        ...
        if UnloadWasRequested then
           if DisableTSR then
              WriteLn('Резидентная программа выгружена');
           else
              WriteLn('Не могу выгрузить программу');
     end;
     {$F-}

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

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

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

     - память под резидентной программой,  типа неиспользуемые
       вектора прерываний  или  область связи прикладных прог-
       рамм BIOS (ICA).

     - сегмент префикса резидентной программы.

     - область,  зарезервированная в резидентном кодовом  сег-
       менте программы.

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

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

     P :+ Ptr($40,$F0);

     Для инициализации  P  на  префикс  сегмента  программы вы
должны написать:

     P := Ptr(PrefixSeg,$80);

     и для инициализации P для указания на резидентный кодовый
сегмент вы  должны написать:

     P := @MyDummyProcedure;

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

     CSSwapData^.ThisIfc.UserData := P;

     CSSwapData это   глобальная   переменная,  поддерживаемая
OPSWAP1.

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

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

     var
        IfcP : IfcPtr;
        P : OurDataPointer;
     begin
        IfcP := ModulePtrByName(OurName);
        if IfcP = nil then begin
           { программа не загружена }
           Halt;
        end;
        P := IfcP^.UserData;
        { ссылка по указателю }
        ...

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

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

     {$F+}
     procedure EntryRoutine;
     var
        ParamCode : Byte;
     begin
        { получить параметр из записи внешнего интерфейса }
        ParamCode := LongInt(CSSwapData^.ThisIfc.UserData);
        case ParamCode of
           0: { Выгрузить резидентную программу }
              ParamCode := Byte(DisableTSR);
           1: { Вывести сообщение }
              begin
                 WriteLn('Вызов процедуры с кодом 1');
                 ParamCode := 1;  { Код возврата }
              end;
        end;
        { Вернуть результат в записи внешнего интерфейса }
        LongInt(CSSwapData^.ThisIfc.UserData) := ParamCode;
     end;
     {$F-}

     Другая программа для вызова процедуры внешнего интерфейса
может использовать подпрограмму типа нижеприведенной:

     procedure CallExternalInterface(ParamCode : Byte);
     var
        IfcP : IfcPtr;
        P : OurDataPointer;
     begin
        IfcP := ModulePtrByName(OurName);
        if IfcP = nil then begin
           { программа не загружена }
           Halt;
        end;

     if @Ifcp^CmdEntryPtr = nil then begin
        { не имеет процедуры внешнего интерфейса }
        Halt;
     end;

     { восстановить захваченные вектора }
     RestoreAllVectors;

     { сохранить параметр }
     LongInt(IfcP^.UserData) :=  ParamCode;
     { сохранить состояние выдачи сообщений }
     SaveMsgOn := IfcP^.CSDataPtr^.SwapMsgOn;
     Ifcp^.CSDataPtr^.SwapMsgOn := False;
     { вызвать CmdEntryPtr }
     Ifcp^.CmdEntryPtr;
     { получить возвращаемое значение }
     ParamCode := LongInt(IfcP^.UserData);
     { восстановить состояние выдачи сообщений }
     IfcP^.CSDataPtr^.SwapMsgOn := SaveMsgOn;
     { проверить возвращаемое значение }
     ...
     end;

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

                   Отличие OPSWAP от OPTSR.

     Мы указали  многие вещи,  которые вы должны знать для на-
писания резидентных программ с использованием OPSWAP. В данном
разделе мы  суммируем  отличия и укажем несколько дополнитель-
ных.

     "Всплывающая" процедура для OPSWAP  объявляется  как  FAR
процедура без параметров:

     {$F+}
     procedure OpSwapPopup;

     Процедура внешнего интерфейса для OPSWAP  также  объявля-
ется как FAR процедура без параметров:

     {$F+}
     procedure OpSwapInterfaceProc;

     Резидентная программа, написанная с использованием OPSWAP
вызывает вместо StayRes StayResSwap.  StayResSwap получает три
дополнительных параметра.  В  дополнение   к   ParasToKeep   и
ExitCode она   требует  два  маршрута  и  один  параметр  типа
Boolean. Маршруты используются для файлов подкачки (они  игно-
рируются, если  OPSWAP  использует  EMS).  Если  параметр типа
Boolean, UseSwap равен False,  то прикладная программа  стано-
вится резидентной обычным путем (как StayRes из OPTSR).

     OPSWAP1 предоставляет новую процедуру WillSwapUseEms. Эта
процедура может быть вызвана перед тем,  как сделать программу
резидентной, чтобы  определить  возможность использования EMS.
Результат можно использовать для отключения или включения  вы-
дачи сообщений и для вывода сообщения для пользователя.

     OPSWAP1 не    предоставляет    процедуру,   эквивалентную
DefinePopProc OPTSR. Это происходит из-за того, что подразуме-
вается, что  все подкачиваемые процедуры активизируются "горя-
чими" клавишами, а не событиями типа тика таймера. OPSWAP пре-
доставляет процедуру SetPopTicker. Описание активизации подка-
чиваемых резидентных программ не посредсвом  "горячих"  клавиш
смотрите в следующем разделе.

                      Нарушение правил.

     Когда мы привели правила написания резидентных программ с
использованием OPSWAP,  мы сказали,  что  OPSWAP  должен  быть
последней юнитой,  используемой  в  списке  в операторе Uses в
основной программе.  Однако бывают ситуации,  когда необходимо
нарушить правила.  Любые юниты,  указанные после OPSWAP, оста-
нутся в памяти резидентно так же,  как и основная программа  и
ядро. Вы можете использовать этот факт, если: а) вы программи-
руете на ассемблере и б) вы очень осторожны.

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

     Достижение этой цели - довольно трудная задача, поскольку
постоянная  резидентная  часть  теряет несколько важных компо-
нент,  которые имеет обычная программа на Pascal.  Нет большей
части  кода программы (включая юниту SYSTEM),  глобальных дан-
ных,  стека,  "кучи". Остается только код основной программы и
часть OPSWAP.

     В этой  ситуации ваша программа может использовать только
процедуру SetPopTicker из OPSWAP.  Вызов SetPopTicker запраши-
вает подкачку  всей резидентной программы и вызов CmdEntryPtr.
В это время резидентная программа может работать  как  обычная
программа на Pascal.  Для процедуры типа будильника, вы должны
написать простую программу обработки прерывания таймера и про-
верять достижения определенного момента,  и когда он наступит,
вызывать SetPopTicker.

     Мы были бы рады остановиться на этом поподробнее,  однако
данное руководство  и так достаточно велико без руководства по
программированию на ассемблере. Если вы хотите знать как нару-
шать  правила,  то посмотрите исходные коды программ POPHELP и
ALARM, которые содержатся на дискете BONUS.



  E. OPSWAP1: Подкачиваемые резидентные программы, Часть 1.

     За некоторыми  исключениями  SetPopTicker,  OPSWAP1  пре-
доставляет все необходимые переменные и процедуры, которые мо-
гут понадобиться для подкачиваемых резидентных программ. Кроме
того, многие процедуры из OPSWAP1 объявлены и используются так
же,  как и такие же процедуры в OPTSR. Эти процедуры приведены
в конце следующих объявлений.  За  дополнительной  информацией
обратитесь к OPTSR.

                          Константы.

     EmsAllocated : Boolean = False;

     Получает значение True,  если для "всплывающей" процедуры
используется EMS.

     FileAllocated : Boolean = False;

     Получает значение True если для  "всплывающей"  процедуры
используются файлы на диске.

     MaxPopups = 8;

     Максимальное число "всплывающих" процедур,  которые могут
быть определены.

     MaxScanCode = $58;

     Максимальный скан-код, который может использовать OPSWAP.
Это значение изменять нельзя. Это скан-код клавиши <F12>.

     ReleaseEms : Boolean = False;

     Если равно True, то вызов DisableTSR вызовет освобождение
дескриптора EMS,  используемого системой управления  оверлеями
Turbo Pascal.  OPSWAP  не имеет возможности определить имеется
ли в наличии система управления оверлеями, и поэтому установка
данного флага в True лежит полностью на вас.

     SideKickLoaded : Boolean = False;

     Устанавливается инициализирующим   кодом,  если  загружен
SideKick.

     SwapFileMaxLen = 67;

     Максимальная длина имени файла подкачки.

     SwapUseEMS : Boolean = True;

     Если равно False,  то EMS не будет использована для  под-
качки даже в том случае, если это возможно.

                            Типы.

     CSSwapDataPtr = ^CSSwapDataType;
     CSSwapDataType =
        record
           ...
           SwapMsgOn : Boolean;
           SwapMsgAttr,SwapMsgRow : Byte;
           ThisIfc : IfcRecord;
           ModuleName : Str20;
           IfcInstalled : Boolean;
           ...
           SwapEnabled : Boolean;
           ...
        end;

     Запись определяет данные,  сохраняемые в кодовом сегменте
OPSWAP. Структура данной записи не должна  изменяться.  Данная
запись предназначена  для внутреннего использования,  однако с
ней необходимо взаимодействовать, и поэтому OPSWAP.PAS имеет к
ней доступ. Некоторые поля могут использоваться программистами
для прикладных задач.  Именно эти поля  показаны.  SwapEnabled
равно True, если используется подкачка. Смотрите раздел D.

     IfcPtr = ^IfcRecord;
     IfcRecord = record
        NamePtr : ^String;
        Version : Word;
        CmdEntryPtr : PopupProc;
        PrevIfc,NextIfc : IfcPtr;
        CSDataPtr : CSSwapDataPtr;
        UserData : Pointer;
     end;

     Запись, содержащая  информацию  об  определенном  модуле.
Этот тип данных  не  должен  изменяться.  Последние  два  поля
CSDataPtr и UserData уникальны для этой юниты. CSDataPtr - это
указатель  на  кодовый  сегмент  относительно  данных   внутри
OPSWAP. UserData - это указатель на данные, передаваемые в или
из процедуры внешнего интерфейса.

     IntRegisters =
        record
           case Byte of
              1:(BP,ES,DS,DI,SI,DX,CX,BX,AX,IP,CS,Flags:Word);
              2:(Dummy:Dummy5;DL,DH,CL,CH,BL,BH,AL,AH:Byte);
     end;

     Запись, сохраняющая содержимое стека в прерываемой проце-
дуре.

     PopupProc = procedure;

     Тип процедуры используемый для "всплывающих"  процедур  и
процедуры внешнего интерфейса.

                     Процедуры и функции.

     Следующие процедуры  и  функции идентичны подпрограммам с
теми же именами из OPINT и OPTSR:

     function AddHotKey(PrimaryHotKey,
                        SecondaryHotKey : Word) : Boolean;
        Добавляет дополнительные "горячие" клавиши процедуре,
        с которой связаны указанные.

     function AllocateStack(var P : Pointer;
                            SizeInBytes : Word) ; Boolean;
        Выделяет стек, размером SizeInBytes байт.

     function ChangeHotKey(OldHotKey,
                           NewHotKey : Word) : Boolean;
        Изменяет "горячие" клавиши "всплывающей" процедуры с
        OldHotKey на NewHotKey.

     procedure DeallocateStack(var P:Pointer);
        Освобождает стек.

     function DisableTSR : Boolean;
        Сбрасывает резидентную программу,  освобождая память и
        восстанавливая вектора прерываний. Это не останавлива-
        ет выполнение программы. Возвращает False, если выпол-
        нение процедуры небезопасно.

     function DosBusyFlag : Byte;
        Возвращает текущее состояние флага занятости DOS.

     procedure EmulateInt(var Regs : IntRegisters;
                          IntAddr : Pointer);
        Эмулирует прерывание,  заполняя регистры значениями из
        Regs, очищая прерывания,  помещает в стек регистр фла-
        гов и осуществляет дальний вызов IntAddr.

     procedure InterruptsOff;
        Выключает прерывания.

     procedure InterruptsOn;
        Включает прерывания.

     function ModuleInstalled(var ModuleName:string):Boolean;
        Возвращает True, если ModuleName уже установлено.

     function ModulePtrByName(var ModuleName:string):IfcPtr;
        Возвращает указатель на структуру IfcRecord  модуля  с
        именем ModuleName или nil.

     function ParagraphsToKeep : Word;
        Возвращает число параграфов,  используемых  для  кода,
        данных, стека и "кучи".

     procedure PopupsOff;
        Отключает "всплывающие" процедуры.

     procedure PopupsOn;
        Включает "всплывающие" процедуры.

     procedure RestoreAllVectores;
        Восстанавливает все захваченные вектора прерываний.

     function SafeToDisable : Boolean;
        Возвращает True,  если  удаление резидентной программы
        безопасно.

     procedure UninstallModule;
        Удаляет модуль из связного списка модулей.


DefinePop
─────────────────────────────────────────────────────────────
Объявление

     function DefinePop(HotKey : Word; Routine : PopupProc;
                        StackPtr : Pointer) : Boolean;

Функция

     Определяет "всплывающую" процедуру, связанную с указанны-
ми "горячими" клавишами.

Описание

     Эта процедура аналогична DefinePop из OPTSR.  Ее основное
отличие в том, что PopupProc в OPSWAP - это процедура типа FAR
без параметров.  Как и в OPTSR,  StackPtr должен указывать  на
вершину стека, который будет использовать "всплывающая" проце-
дура. Информацию по поводу стека вы найдете в разделе OPTSR.

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

Пример

     {$F+}
     procedure PopupEntryPoint;
     begin
        ...
     end;
     {$F-}
     ...
     if not DefinePop(OurHotKey,PopupEntryPoint,Ptr(SSeg,SPtr))
        then
           Abort('Не могу определить "всплывающую" процедуру');

Смотрите также

     DefinePop(OPTSR)

DisablePopup
─────────────────────────────────────────────────────────────
Объявление

     procedure DisablePopup;

Функция

     Процедура внешнего интерфейса (CmdEntryPtr),  которая пы-
тается отключить всплывающую процедуру и возвращает  результат
в поле UserData записи IfcRecord.

Описание

     Эта процедура является оболочкой вокруг DisableTSR, кото-
рая позволяет  использовать   ее   в   параметре   CmdEntryPtr
процедуры InstallModule. Она предоставляет простой способ уда-
ления резидентной программы  другой  программой  (или  той  же
программой, запущенной повторно).

     Когда DisablePopup   указывается   в  качестве  параметра
CmdEntryPtr процедуры InstallModule,  вызов CmdEntryPtr приво-
дит к  выгрузке резидентной программы.  Если резидентная прог-
рамма успешно  удалена  из  памяти,   поле   UserData   записи
IfcRecord получает  значение 1.  Поскольку поле UserData имеет
тип Pointer, то для сравнения его содержимого с 0 или 1 требу-
ется явное его приведение к LongInt.

Пример

     InstallModule(ModuleName,DisablePopup);
     ...
     if(ParamStr(1) = 'U' then begin
        { необходимо восстановить исходный код OPSWAP }
        RestoreAllVectors;
        { получить Ifc данного модуля }
        Ifc := ModulePtrByName(ModuleName);
        { он установлен? }
        if Ifc<>nill then begin
           { вызвать процедуру внешнего интерфейса }
           Ifc^.CmdEntryPtr;
           { проверить код завершения }
           if LongInt(Ifc^.UserData) = 1 then
              WriteLn(ModuleName,'выгружен');
           else
              WriteLn('не могу выгрузить',ModuleName);
        end else
        WriteLn(ModuleName,'не загружен и не может быть выгружен');
     end;

     Данный кусок  программы  позволяет  выгрузить резидентную
копию этой программы при запуске с ключем /U.

Смотрите также

     DisableTSR(OPTSR)            ModulePtrByName
     InstallModule

InstallModule
─────────────────────────────────────────────────────────────
Объявление

     procedure InstallModule(var ModuleName : string;
                             CmdEntryPtr : PopupProc);

Функция

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

Описание

     Эта процедура  аналогична   соответствующей   из   OPTSR.
Единственное отличие состоит в объявлении процедуры, передава-
емой в качестве параметра CmdEntryPtr: для подкачиваемых рези-
дентных программ  она  должна быть объявлена как FAR процедура
без параметров.

     OPSWAP1 предопределяет два идентификатора,  которые могут
быть переданы  процедуре  InstallModule  в  качестве параметра
CmdEntryPtr: NoExternalIfc и DisablePopup. NoExternalIfc пере-
дается когда нет CmdEntryPtr,  связанного с ним.  DisablePopup
используется для выгрузки подкачиваемых резидентных программ.

Пример

     const MyTsrName : string[5] = 'MYTSR';
     ...
     InstallModule(MyTsrName,DisablePopup);

Смотрите также

     DisablePopup                 ModuleInstalled
     InstallModule(OPTSR)

MaxParagraphsToKeep
─────────────────────────────────────────────────────────────
Объявление

     function MaxParagraphsToKeep : Word;

Функция

     Максимальное число  параграфов для захвата при оставлении
программы резидентной.

Описание

     Эта функция  используется  для  определения максимального
допустимого значения   параметра   ParasToKeep,    посылаемого
StayResSwap. Если программа компилируется с установленным раз-
мером "кучи" 640К (по умолчанию),  то MaxParagraphsToKeep вер-
нет объем памяти,  который был доступен перед загрузкой  прог-
раммы.  Таким образом, вычитая ParagraphsToKeep (или значение,
посылаемое StayResSwap) из MaxParagraphsToKeep,  вы можете вы-
числить сколько  примерно памяти останется свободной после то-
го, как программа сделается резидентной.

Пример

     StayResSwap(MaxParagraphsToKeep,0,SwapName1,
                 SwapName2,True);
     WriteLn('Ошибка при попытке остаться резидентной');

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

Смотрите также

     ParagraphsToKeep(OPTSR)      StayResSwap

SetSwapMsgAttr
─────────────────────────────────────────────────────────────
Объявление

     procedure SetSwapMsgAttr(Attr : Byte);

Функция

     Устанавливает атрибуты сообщения, выдаваемого при подкач-
ке.

Описание

     Attr это новый атрибут, который будет использован при вы-
даче сообщения  о подкачке (по умолчанию равен $70,  инверсный
режим).

Пример

     if OpCrt.UseColor then SetSwapMsgAttr($1F);

     Использует для цветного дисплея белый на голубом.

SetSwapMsgOn
─────────────────────────────────────────────────────────────
Объявление

     procedure SetSwapMsgOn(On : Boolean);

Функция

     Включает или выключает выдачу сообщений при подкачке.

Описание

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

Пример

     if WillSwapUseEMS(TotalParagraphsWeNeed) then begin
        WriteLn('Используется EMS');
        SetSwapMsgOn(False);
     end;

SetSwapMsgRow
─────────────────────────────────────────────────────────────
Объявление

     procedure SetSwapMsgRow(Row : Byte);

Функция

     Устанавливает, в какой строке будет выводиться сообщение.

Описание

     Row - это абсолютный номер строки на экране. По умолчанию
равен 25.  Для программ, работающих на системах, которые могут
отображать  больше 25 строк,  можно указать значение $FF.  Это
требование вывода строки в самой нижней  строке  экрана,  даже
если  пользователь  переключился  с  режима  25 строк на режим
43/50 строк или наоборот. Данная возможность работает только с
адаптерами,   совместимыми  с  MCGA,  EGA  и  VGA.  Переменная
CurrentDisplay из OPCRT содержит текущий тип дисплея.

Пример

     case CurrentDisplay of
        MCGA, EGA, VGA : SetSwapMsgRow($FF);
     end;

     Если плата поддерживает режимы с числом строк больше  25,
то используется нижняя строка.

SetUserExitProc
─────────────────────────────────────────────────────────────
Объявление

     procedure SetUserExitProc(UserExitProc : PopupProc);

Функция

     Устанавливает указатель на дальнюю процедуру, которая вы-
зывается перед выгрузкой резидентной программы.

Описание

     В некоторых случаях подкачиваемые  резидентные  программы
перед  выгрузкой должны проделать некоторую работу.  Однако вы
не можете вызвать процедуру Turbo Pascal  ExitProc,  поскольку
резидентная программа никогда в действительности на останавли-
вается. Для выполнения таких действий DisableTSR, также неявно
вызываемая DisablePopup,  вызывает пользовательскую процедуру.
Вы указываете данную процедуру с помощью SetUserExitProc. Ука-
занная процедура должна быть откомпилирована в модели FAR, она
должна быть глобальной и не должна иметь параметров. Она вызы-
вается непосредственно перед выгрузкой резидентной программы.

Пример

     {$F+}
     procedure ClearHouse;
     begin
        CloseAllOpenFiles; { закрывает все файлы }
     end;
     {$F-}

     { установить модуль }
     InstallModule(ModuleName,DisablePopup);
     { установить процедуру выхода }
     SetUserExitProc(ClearHouse);

Смотрите также

     DisablePopup                 DisableTSR

StayResSwap
─────────────────────────────────────────────────────────────
Объявление

     procedure StayResSwap(ParasToKeep : Word; ExitCode : Byte;
                           SwapFileName1,SwapFileName2:String;
                           UseSwapping:Boolean);

Функция

     Завершает программу, но оставляет ее резидентной и необя-
зательно подкачиваемой.

Описание

     ParasToKeep и ExitCode интерпретируются точно также,  как
и параметры с теми же именами в StayRes в OPTSR.

     SwapFileName1 и SwapFileName2 определяют файлы, использу-
емые для  подкачки.  Они должны определять полный маршрут DOS:
устройство, директорию и имя файла.  Они не должны  определять
один и тот же файл.

     Если UseSwapping равно False, то подкачка не используется
и программа занимает ParasToKeep параграфов памяти.  Если этот
параметр равен  True,  то в памяти остается только система уп-
равления подкачкой,  а сама прикладная программа  сбрасывается
на диск.

     При успешном  завершении StayResSwap основная программа в
данной точке завершается. StayResSwap завершается и возвращает
управление только в том случае,  когда не может оставить прог-
рамму в памяти резидентно.

Пример

     { остаться резидентной }
     StayResSwap(TotalParasWeNeed,0,SwapName1,SwapName2,True);
     { если достигли данной точки, значит ошибка }
     WriteLn('Ошибка');

Смотрите также

     StayRes(OPTSR)

WillSwapUseEms
─────────────────────────────────────────────────────────────
Объявление

     function WillSwapUseEms(ParasToKeep : Word):Boolean;

Функция

     Возвращает True,  если OPSWAP будет использовать для под-
качки память EMS.

Описание

     Для правильного  результата  ParasToKeep   должен   соот-
ветствовать значению  параметра  с тем же именем,  посылаемого
StayResSwap.

Пример

     { вычислить необходимый объем "кучи плюс 8K }
     TotalParasWeNeed := ParagraphsToKeep + (8192 div 16);
     { теперь определим, можно ли использовать EMS, и если да, }
     { сообщим пользователю }
     if WillSwapUseEms(TotalParasWeNeed) then begin
        WriteLn('Использую для подкачки EMS');
        SetSwapMsgOn(False);
     end;
     { остаться резидентно в памяти }
     StayResSwap(TotalParasWeNeed,0,SwapName1,SwapName2,True);
     { если достигли данной точки, значит ошибка }
     WriteLn('Ошибка');


   F. OPSWAP: Подкачиваемые резидентные программы, Часть 2.

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

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

SetPopTicker
─────────────────────────────────────────────────────────────
Объявление

     procedure SetPopTicker(TimeOut : Word);

Функция

     Устанавливает время вызова процедуры CmdEntryPtr.

Описание

     Большинству подкачиваемых  резидентных программ нет необ-
ходимости вызывать данную процедуру.  Она может использоваться
в основном в ассемблерных процедурах обработки прерываний. Ни-
же приведен ассемблерный код из программы ALARM.

Пример

     CODE    SEGMENT BYTE PUBLIC

             ASSUME CS:CODE,DS:NOTHING
             EXTERN SetPopTicker:Far
             ...
             MOV    AX,182        ; Задержка = 10 секундам
             PUSH   AX            ; Передать параметр SetPopTicker
             CALL   SetPopTicker  ; Вызвать SetPopTicker из OPSWAP
             ...

Смотрите также

     InstallModule                SetPopTicker(OPTSR)


                  G. OP8087: Поддержка 8087.

     Как мы  говорили  раньше,  OP8087 предназначена для рези-
дентных программ,  которые  используют  арифметический  сопро-
цессор для вычислений над числами с плавающей точкой.  Она со-
держит функцию (Install8087),  которая позволяет проверить на-
личие сопроцессора,  однако  необходимость  в ее использовании
возникает редко, поскольку Turbo Pascal предоставляет средства
эмуляции вычислений с плавающей точкой.  Save8087, Restore8087
и Reinit 8087 очень важны для резидентных программ, использую-
щих  сопроцессор.  Хотя  диспетчеры  "всплывающих" процедур из
OPSWAP и OPTSR сохраняют состояние регистров центрального про-
цессора, они не делают этого для регистров сопроцессора. Таким
образом,  если "всплывающая" процедура использует вычисления с
плавающей точкой на сопроцессоре 8087 и была откомпилирована с
директивой {$N+,E-} (без эмуляции),  основная процедура должна
выглядеть следующим образом:

     {$F+}
     procedure PopupEntryPoint;
     var Save8087Regs : SaveBuffer8087;
     begin
        Save8087(Save8087Regs);
        Reinit8087;
        ...
        Restore8087(Save8087Regs);
     end;

     Если ваша  резидентная  программа или программа обработки
прерывания использует эмуляцию вычислений с плавающей  точкой,
то вы    должны    использовать    две    другие    процедуры:
ReinitEmulator, которая реинициализирует эмулятор вычислений с
плавающей точкой Turbo Pascal 5,  и UseEmulator, которая опре-
деляет, используется или нет эмулятор.  Главное, что вы должны
помнить, что вызов ReinitEmulator вы можете осуществлять толь-
ко после того, как вы переключились на дополнительный стек. (В
"всплывающих" процедурах переключение осуществляется автомати-
чески,  в программах обработки прерываний вы должны  использо-
вать  для  этого  SwapStackAndCall  или SwapStackAndCallNear.)
ReinitEmulator производит повторную инициализацию 230 байтовой
области  данных,  начинающейся  с адреса SSeg:0,  используемой
эмулятором,  и производит некоторые действия,  которые  должны
быть выполнены до использования эмулятора. Конечно, ваш допол-
нительный стек должен быть на 230 байт больше,  чем это обычно
необходимо,  для того, чтобы было достаточно места для области
данных эмулятора.

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

     {$N+,E+}
     program UseEmulator
     uses Op8087,OpInt;
     const D : Double = 2.0;
     var
        I : Word;
        UseEmu : Boolean;
        AltStack : array[1..2048] of Byte;

        procedure Dummy(var Regs:IntRegisters);
        var
           SB : SaveBuffer8087;
           D : Double;
        begin
           if UseEmu then
              { повторная инициализация 8087 }
              ReinitEmulator;
           else begin
              { сохранить регистры 8087 }
              Save8087(SB);
              { инициализация 8087 }
              Reinit8087;
           end;

           { выполнить вычисления }
           D := 1.0;
           D := D* 2.0;

           if not UseEmu then
              { восстановить регистры 8087 }
              Restore8087(SB);
        end;

        procedure Int1C(BP : Word); interrupt;
        const
           Active : Boolean = False;
        var
           Regs : IntRegisters absolute BP;
        begin
           if not Active then begin
              Active := True;
              SwapStackAndCallNear(Ofs(Dummy),@AltStack(2048),Regs);
              Active := False;
           end;
        end;
     begin
        UseEmu := UseEmulator;
        if UseEmu then
           WriteLn('Использую эмулятор...');
        else
           WriteLn('Использую 8087...');
        { установить программу обработки $1C }
        if not InitVector($1C,20,@Int1C) then
           Halt;
        WriteLn('Умножение...');
        for I := 1 to 2000 do
           if D*D <> 4.0 then begin
              WriteLn('Error at',I);
              Halt(1);
           end;
        WriteLn('Done...');
     end.

     Аналогичная стратегия применяется в резидентных  програм-
мах. Перед тем, как оставить программу в памяти резидентно, вы
должны вызвать UseEmulator и сохранить значение  в  глобальной
переменной.  Внутри  "всплывающей"  процедуры (проццедурах) вы
должны проверять эту переменную.  Если используется  эмулятор,
то вы должны использовать ReinitEmulator при входе в "всплыва-
ющую"  процедуру,  если  не  используется,   то   Save8087   и
Reinit8087, а при выходе использовать Restore8087.

     По умолчанию  Turbo  Pascal предоставляет программу обра-
ботки ошибок, если используются математические вычисления. Де-
ление на 0,  переполнение, квадратный корень от отрицательного
числа и другие ошибки останавливают выполнение вашей  програм-
мы. Не может помочь даже юнита OPERRHAN, поскольку ее програм-
ма обработки ошибок при получении управления непоправимо  раз-
рушает внутренний стек сопроцессора.

     OP8087 предоставляет  вам  решение  данной  проблемы  при
использовании двух простых процедур.  Процедура  Exception8087
получает один  параметр типа Boolean,  который сообщает сопро-
цессору генерировать исключительную ситуацию (которая приведет
к  ошибке времени выполнения),  или вернуть ошибку через слово
состояния сопроцессора и числовой результат.  Вторая процедура
Error8087  позволяет вашей программе проверять слово состояния
сопроцессора для определения наличия ошибки в предыдущем пото-
ке  операций.  Процедура  Error8087 возвращает результат как и
стандартная функция IoResult: любое ненулевое значение говорит
об ошибке (которая может быть определена по значению),  и про-
цесс чтения слова состояния процедурой 8087 приводит к  сбросу
слова состояния для продолжения вычислений.

     Важно заметить, что поведение OP8087 несколько отличается
в зависимости от того, как она была откомпилирована. Если была
указана директива  $N-,  то  все  процедуры используют команды
сопроцессора 8087.  При компилировании с директивой  $N+,  все
подпрограммы эмулируют команды сопроцессора 8087, которые юни-
та SYSTEM или передает сопроцессору 8087,  или передает эмуля-
тору.  В оставшейся части главы все ссылки на сопроцессор 8087
так же подразумевают эмулятор вычислений  с  плавающей  точкой
Turbo Pascal.

                            Типы.

     SaveBuffer8087 =
        record
           Control   : Word;      { управляющее слово }
           Status    : Word;      { слово состояния }
           Tag       : Word;      { таг для стека }
           InstPtr   : LongInt;   { счетчик команд }
           OpPtr     : LongInt;   { указатель на данные }
           Regs8087  : array[0..7] of Extended;  { стек }
        end;

     Используется для  сохранения  и  восстановления состояния
сопроцессора процедурами Save8087 и Restore8087.

Error8087
─────────────────────────────────────────────────────────────
Объявление

     function Error8087 : Word;

Функция

     Возвращает (и очищает) слово состояния ошибки 8087.

Описание

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

     Если результат функции равен 0,  ошибок нет.  В противном
случае вы  можете  определить,  какая конкретно исключительная
ситуация возникла, проверив отдельные биты результата:

Бит      Значение        Описание
──────────────────────────────────────────────────────────────
 5         32            Потеря точности (неточный результат -
                         был округлен).
 4         16            Переполнение вниз.
 3          8            Переполнение.
 2          4            Деление на 0.
 1          2            Ненормализованный операнд (результат
                         настолько мал,  что имеются начальные
                         нули).
 0          1            Неверная операция (т.е., SQRT или LN от
                         отрицательного числа).
──────────────────────────────────────────────────────────────

     Замечание: Биты 5 и 1 часто  устанавливаются  в  случаях,
которые вы не считаете ошибкой. Если нужно, вы можете замаски-
ровать данные биты,  производя операцию  AND  над  результатом
функции и значением $0D.

Пример

     Exception8087(False);
     ...
     Extended3 := Extended1 * Extended2;
     ErrorCheck := Error8087 (and $0D);

     Вызов Error8087 помещает в  ErrorCheck  содержимое  слова
состояния сопроцессора и очищает его.

Exceptions8087
─────────────────────────────────────────────────────────────
Объявление

     procedure Exceptions8087(On : Boolean);

Функция

     Включает или  выключает  прерывания исключительных ситуа-
ций.

Описание

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

     Замечание: Команды FINIT и FNINIT,  используемые процеду-
рами типа Installed8087 (компилируется с $N-) для проверки на-
личия сопроцессора, перепрограммируют сопроцессор на генерацию
прерываний.  Если вы запустите другую программу после отключе-
ния  прерываний,  то  после  завершения  вызова  Exec вызовите
Exceptions8087, чтобы быть уверенным, что они выключены.

Пример

     Exceptions8087(False);
     ...
     Code := ExecDos('',True,NoExecDosProc);
     Exceptions8087(False);

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

Installed8087
─────────────────────────────────────────────────────────────
Объявление

     function Installed8087 : Boolean;

Функция

     Возвращает True при наличии сопроцессора 8087.

Описание

     Эта функция позволяет вам определить,  имеется ли на дан-
ной машине сопроцессор или нет.  Поскольку бибилиотека времени
выполнения Turbo Pascal имеет свой собственный тест и не  заг-
ружает  программы,  которые его используют при его отсутствии,
то необходимость в Installed8087 возникает редко. Фактически в
OP8087, откомпилированной   с  директивой  $N+,  Installed8087
просто использует переменную Test8087 из SYSTEM для  определе-
ния наличия сопроцессора.

     Installed8087 удобно  использовать  для  конфигурирования
программ,  которым требуется сопроцессор.  Для использования в
таком роде OP8087 должна быть откомпилирована с ключем {$N-}.

Пример

     if not Installed8087 then begin
        WriteLn('Загруженная программа требует сопроцессор');
        Halt;
     end;

Reinit8087
─────────────────────────────────────────────────────────────
Объявление

     procedure Reinit8087;

Функция

     Заново инициализирует сопроцессор.

Описание

     Эта процедура заново инициализирует сопроцессор 8087. Она
должна вызываться сразу же за Save8087.

Пример

     Смотрите пример программы в введении к данному разделу.

Смотрите также

     Save8087

ReinitEmulator
─────────────────────────────────────────────────────────────
Объявление

     procedure ReinitEmulator;

Функция

     Заново инициализирует эмулятор.

Описание

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

Пример

     Смотрите пример программы в введении к данному разделу.

Смотрите также

     UseEmulator

Restore8087
─────────────────────────────────────────────────────────────
Объявление

     procedure Restore8087(var SaveBuf : SaveBuffer8087);

Функция

     Восстанавливает состояние  регистров  сопроцессора  8087,
сохраненных в буфере.

Описание

     Эта процедура используется во  "всплывающих"  процедурах,
написанных с OPTSR или OPSWAP.  Она позволяет вам восстановить
состояние сопроцессора (т.е.  его регистры и стек) таким обра-
зом, что  операции,  выполняемые "всплывающей" процедурой,  не
повлияют на прерванную программу.  Любому  вызову  Restore8087
должен предшествовать вызов Save8087, который сохраняет состо-
яние сопроцессора.

Пример

     Смотрите пример программы в введении к данному разделу.

Save8087
─────────────────────────────────────────────────────────────
Объявление

     procedure Save8087(var SaveBuf : SaveBuffer8087);

Функция

     Сохраняет состояние регистров сопроцессора  8087.

Описание

     Эта процедура используется во  "всплывающих"  процедурах,
написанных с  OPTSR  или  OPSWAP.  Она позволяет вам сохранить
состояние сопроцессора (т.е.  его регистры и стек) таким обра-
зом, что  операции,  выполняемые "всплывающей" процедурой,  не
повлияют на прерванную программу.  Любой вызов Save8087 должен
предшествовать  вызову  Restore8087,  который  восстанавливает
состояние сопроцессора.

Пример

     Смотрите пример программы в введении к данному разделу.

UseEmulator
─────────────────────────────────────────────────────────────
Объявление

     function UseEmulator : Boolean;

Функция

     Возвращает True,  если  ReinitEmulator  должна  использо-
ваться вместо Reinit8087.

Описание

     Эта процедура возвращает True, если имеется эмулятор пла-
вающей точки,  и если  он  дослупен.  Чаще  всего  UseEmulator
используется для  определения  того,  что  нужно  использовать
ReinitEmulator или Reinit8087.
Смотрите также

Пример

     Смотрите пример программы в введении к данному разделу.

Смотрите также

     ReinitEmulator


                     H. Примеры программ.

     Имеется пять   примеров  программ,  включенных  в  Object
Professional: SMACS  и  PTIME  (используют  OPTSR),   PSCREEN,
POPHELP и DESKPOP (используют OPSWAP). Здесь описывается толь-
ко PTIME,  однако SMACS описывается в главе  13  (макрокоманды
клавиатуры), поскольку   она  использует  OPMACRO  и  OPMACED.
PSCREEN описана в конце 4 главы (Оконные  объекты),  т.к.  это
удобная   служебная   программа   для   работы   с   объектами
PackedWindow.  POPHELP описана в главе 1 (Введение), поскольку
она  интересна  любому,  кто  использует  Object Professional.
DESKPOP также описана в главе  1,  т.к.  демонстрирует  многие
возможности Object Professional.

   PTIME: Служебная   программа  управления  временем  для
                        программистов.

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

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

     После того, как PTIME будет загружена в память, вы можете
в любой момент активизировать ее, нажав <Ctrl><RigthShift><T>.
Когда вы сделаете это, вы увидите на экране следующее:

┌──────────  PTIME: Programmer's Time Management Utility 5.00 ────────┐
│ Alarm: 12:00 Macro: 00:00 Start 09:07 Elapsed: 01:42 Time 11:03:50  │
│ File: C:\TURBO\CURPROJ.DAT      Timer: ON  Pause: ON  Clock: OFF    │
│                                                                     │
│ [Esc] exit Alarm Clock Disable Load Macro Pause Timer Update        │
└─────────────────────────────────────────────────────────────────────┘

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

                            Alarm.

     Когда вы нажимаете <A>, появляется вопрос:

     Enter the time in "HH:MM" fornat (HH = 0-23, MM = 0-59):
     { введите время в формате ... }

     Если вы  хотите  установить  будильник на час дня,  то вы
должны ввести:

     13:00

     Для отмены установки будильника до  того,  как  он  будет
установлен,  нажмите <Esc> при запросе времени. Если будильник
уже установлен,  то для его отмены нажмите <A> и <Y> для подт-
верждения того, что вы хотите его сбросить.

                            Clock.

     Нажав <C>,  вы  активизируете экранные часы.  После того,
как вы выйдете из PTIME,  время будет отображаться на  экране,
модифицируясь  3  раза  в секунду.  Для выключения отображения
часов активизируйте PTIME и нажмите <C> еще раз.  Заметим, что
экранные часы отключаются в момент активизации PTIME.

                           Disable.

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

                            Load.

     Когда вы нажимаете <L>,  PTIME запрашивает у вас имя фай-
ла, в котором хранятся данные.  Если он не существует,  то  он
создается. Иначе, PTIME просто запоминает имя файла для после-
дующих ссылок. Эта команда не работает, пока включен таймер.

                            Macro.

     Эта команда позволяет вам  записать  макрокоманду  SMACS.
Если SMACS не загружен,  то при нажатии <M> появится сообщение
об ошибке.  Иначе,  PTIME попросит вас ввести клавишу, которой
будет соответствовать макрокоманда,  затем ввести время, когда
она должна быть выполнена.  Например,  если вы хотите, чтобы в
5.30 была выполнена команда,  связанная с клавишами <AltP>, вы
нажмете <AltP> и введете

     17:30

     Как и в команде Alarm,  вы можете отменить установку мак-
рокоманды перед тем,  как она была установлена клавишей <Esc>,
а позднее нажав <M> второй раз.

                            Pause.

     Pause позволяет вам на некоторое время  прервать  работу.
Если включен таймер,  а вам нужно прерваться, вы можете нажать
<P> и он будет временно выключен.  Хотя таймер остается  вклю-
ченным, время прошедшее в него не засчитывается, пока включена
пауза.

                            Timer.

     Это самая основная команда PTIME. Когда вы нажимаете <T>,
программа включает таймер и начинает отсчитывать время,  и до-
бавляет новую запись в текущий файл данных.  Если файл не  был
выбран командой Load,  то она запросит его у вас. Для выключе-
ния таймера нажмите <T> еще раз. При этом будет модифицирована
последняя запись в файле данных.

                           Update.

     Команда Update  предназначена  для  того,  чтобы вы могли
восстановить систему при сбое,  когда был включен таймер. Если
это произошло,  вы просто перезагружаете PTIME и активизируете
ее, нажимаете <U> для Update и вводите имя файла данных. После
этого PTIME модифицирует последнюю запись в файле данных, и вы
можете после этого включить таймер. При крахе системы теряются
только данные о паузах,  которые происходили при вашей работе.
Для этого случая вы можете использовать команду  Update  после
каждой паузы. Заметим, что вы можете отредактировать последную
запись в файле данных вручную (смотрите ниже).

                  Формат файла данных PTIME.

     PTIME сохраняет записи в файле в виде ASCII строк  фикси-
рованной длины. Каждая запись содержит две строки: одна с дан-
ными таймера,  а вторая с комментариями к данным.  Строка ком-
ментариев  может  быть  пустой,  однако она всегда заполняется
пробелами до макимальной  длины.  Каждая  строка  оканчивается
символами "возврат каретки"/"перевод строки".  Первая запись в
файле является заголовком файла данных.

     Остальные записи имеют следующий формат:

     mm/dd/yy hh:mm - mm/dd/yy hh:mm elapsed hh:mm update allowed: x
     comment line

     Первая пара  -  дата/время  -  определяет время включения
таймера, вторая пара время выключения.  Elapsed - это интервал
времени между этими событиями.  Поле "update allowed" содержит
или "Y",  или "N".  "Y" определяет, что модификация допустима,
поскольку  таймер  не был остановлен.  "N" говорит о том,  что
данная запись полная и не должна изменяться. Флаг используется
PTIME  для определения необходимости изменения записи.  Вторая
строка содержит комментарии,  которые вводит пользователь  при
остановке таймера.  Комментарий может иметь длину до 76 симво-
лов. Обе строки заполняются пробелами до 76 символов и в конце
к ним  присоединяются символы "перевод строки"/"возврат карет-
ки".

     Те из  вас,  кто  знаком  с  версией   PTIME   из   Turbo
Professional, могут заметить,  что данные имеют другой формат.
Целью этих изменений было исключить необходимость в  программе
PTRPT. Поскольку данные теперь хранятся в текстовом виде, а не
в двоичном,  то для их просмотра больше не требуется отдельная
программа.

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