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



 

Часть 15

                             ГЛАВА 13.

                             ОВЕРЛЕИ.

     Оверлеи - это части программы, которые разделяют общую область
памяти. Только те части программы, которые требуются для выполнения
данной функции,  размещаются в памяти в это время;  затем они могут
быть перекрыты другими программами.
     Оверлеи могут   значительно   сократить   количество   памяти,
требуемое при выполнении программы.  В действительности,  Вы можете
выполнять программы,  которые  намного  больше,  чем  доступная   в
системе  память,  поскольку  в  каждый  момент в памяти размещается
только часть программы.
     Turbo Pascal   управляет  оверлеями  на  уровне  модулей;  это
наименьшая часть программы, которую можно сделать оверлейной. Когда
оверлейная   программа   компилируется,   Turbo  Pascal  генерирует
оверлейный файл (с расширением .OVR)  в  дополнение  к  выполнимому
файлу (с   расширением   .EXE).   .EXE  файл  содержит  статическую
(неперекрываемую) часть программы и .OVR файл содержит  все модули,
которые будут   перекачиваться  в/из  памяти  во  время  выполнения
программы.
     За исключением  нескольких правил программирования, оверлейные
модули идентичны с неоверлейными модулями. В действительности, если
Вы соблюдаете эти правила,  Вам даже не нужно перекомпилировать эти
модули,  чтобы  сделать  их  оверлейными.  Решение,  будет   модуль
оверлейным или нет, принимает главная программа.
     Когда оверлей загружается в память, он помещается в оверлейный
буфер,  который  размещается  в  памяти  между  стеком и кучей.  По
умолчанию,  размер оверлейного  буфера  устанавливается  как  можно
меньше,  но  он  может  быть  легко  увеличен  во время выполнения,
выделением дополнительной памяти из  кучи.  Как  сегмент  данных  и
минимальный  размер  кучи,  размер  буфера  оверлеев (по умолчанию)
распределяется при загрузке .EXE файла.  Если памяти  недостаточно,
DOS  выдает  сообщение  об  ошибке  (программа  слишком  велика для
загрузки  в  память)  или  интегрированной  средой   Turbo   Pascal
(недостаточно памяти для выполнения программы).
     Есть очень важная возможность монитора оверлеев  при  загрузке
оверлейного  файла  в  случае,  когда  в  системе достаточный объем
расширенной  памяти.  Для  этих  целей  Turbo  Pascal  поддерживает
Спецификацию Расширенной    Памяти    фирм    Lotus/Intel/Microsoft
(Expanded Memory Specification - EMS)  версии  3.2  и  выше.  После
загрузки  в  EMS,  оверлейный  файл закрывается и загрузка оверлеев
выполняется быстрым копированием в памяти.


                         Монитор оверлеев.

     Монитор оверлеев  Turbo  Pascal реализован стандартным модулем
Overlay.  Техника управления буфером, используемая модулем Overlay,
всегда  гарантирует  оптимальную  производительность  в  выделенной
памяти.  Например,  монитор  оверлеев  всегда  сохраняет  в  буфере
оверлеев как можно больше оверлеев, для того чтобы сократить чтение
оверлеев с диска.  После того как оверлей загружен, вызов любой его
программы   производится   также  быстро,  как  вызов  неоверлейных
программ.  Более того,  когда монитору оверлеев  требуется  удалить
оверлей,  чтобы освободить память для другого оверлея,  он пытается
удалить те оверлеи,  которые неактивны (которые не  имеют  активных
программ в данное время).
     Для реализации  своей  техники  управления  оверлеями,   Turbo
Pascal требует, чтобы Вы соблюдали два важных правила при написании
своих программ:
     - Все оверлейные модули должны иметь директиву {$O+},  которая
заставляет компилятор   быть уверенным,  что генерируемый код может
быть оверлейным.
     - Для  вызова любой оверлейной процедуры или функции Вы должны
гарантировать, что  все активные  процедуры  и  функции  используют
дальнюю модель вызова (far call).

     Оба правила будут объяснены в разделе  "Разработка  оверлейных
программ". Сейчас заметим только, что Вы можете легко выполнить эти
требования,  вставляя  директиву  компилятора  {$O+,F+}  в   начало
каждого  оверлейного  модуля и директиву {$F+} в начало всех других
модулей и главной программы.

     Примечание: Если  директива {$F+} будет пропущена в оверлейной
программе, то при выполнении программы возникнут непредсказуемые и,
возможно, катастрофические результаты.

     Директива компилятора {$O имя модуля} используется в программе
для  указания,  какой модуль должен быть оверлейным.  Эта директива
должна быть  размещена  после  оператора  uses  в  программе,  и  в
операторе  uses  стандартный модуль Оverlay должен стоять до любого
оверлейного модуля. Пример:

   programm Editor;
   {$F+}       {Задать дальний вызов для всех процедур и функций}

   uses
      Overlay, Crt, EdInOut, EdFormat, EdPrint, EdFind, EdMain;

  {$O EdInOut}
  {$O EdFormat}
  {$O EdPrint}
  {$O EdFind}
  {$O EdMain}

     Примечание: Компилятор выдает сообщение  об  ошибке,  если  Вы
пытаетесь сделать оверлейным модуль,  который не был откомпилирован
в состоянии {$O+}.  Из стандартных модулей  может  быть  оверлейным
только   модуль  Dos;  все  остальные  стандартные  модули  System,
Overlay, Graph,  Crt,  Turbo3,  Graph3 не могут  быть  оверлейными.
Кроме того,  программы,  содержащие оверлейные модули,  должны быть
откомпилированы на  диск;  компилятор  выдает   ошибку,   если   Вы
пытаетесь откомпилировать такие программы в памяти.


                     Монитор буфера оверлеев.

     Оверлейный буфер  Turbo  Pascal  лучше   всего   описать   как
кольцевой буфер,  который имеет указатель на начало и хвост буфера.
Оверлеи всегда загружаются с начала буфера, выталкивая более старые
к   хвосту   буфера.   Когда  буфер  заполняется  (т.е.  когда  нет
достаточного свободного  пространства  между  началом  и  хвостом),
оверлеи  выталкиваются  с  конца буфера,  для того чтобы освободить
пространство для нового оверлея.
     Поскольку обычная  память  не  является  циклической  по своей
природе,  действительная  реализация   буфера   оверлеев   включает
несколько  больше  шагов  для  того,  чтобы представить буфер ввиде
кольца. Рис.  13.1 иллюстрирует этот  процесс.  Рисунок  показывает
динамику подгрузки оверлеев в предварительно пустой буфер оверлеев.
Оверлей А загружается первым,  за ним загружается В,  за ним - С  и
наконец  -  D.  Темная  область  показывает  свободное пространство
буфера.

                             Рис. 13.1
                 Загрузка и выталкивание оверлеев.

                       Шаг 1                         Шаг 2
                   ЪДДДДДДДДДДДДї               ЪДДДДДДДДДДДДї
                   і °°°°°°°°°° і               і °°°°°°°°°° і
                   і °°°°°°°°°° і               і °°°°°°°°°° і
                   і °°°°°°°°°° і               і °°°°°°°°°° і
                   і °°°°°°°°°° і   Голова ДДДц ГДДДДДДДДДДДДґ
                   і °°°°°°°°°° і               і  Оверлей B і
      Голова ДДДДц ГДДДДДДДДДДДДґ               ГДДДДДДДДДДДДґ
                   і  Оверлей А і               і  Оверлей А і
      Хвост  ДДДДц АДДДДДДДДДДДДЩ   Хвост  ДДДц АДДДДДДДДДДДДЩ


                       Шаг 3                         Шаг 4
                   ЪДДДДДДДДДДДДї               ЪДДДДДДДДДДДДї
                   і °°°°°°°°°° і               і  Оверлей С і
                   і °°°°°°°°°° і               ГДДДДДДДДДДДДґ
      Голова ДДДДц ГДДДДДДДДДДДДґ               і  Оверлей В і
                   і  Оверлей С і   Хвост  ДДДц ГДДДДДДДДДДДДґ
                   ГДДДДДДДДДДДДґ               і °°°°°°°°°° і
                   і  Оверлей В і               і °°°°°°°°°° і
                   ГДДДДДДДДДДДДґ   Голова ДДДц ГДДДДДДДДДДДДґ
                   і  Оверлей А і               і  Оверлей D і
      Хвост  ДДДДц АДДДДДДДДДДДДЩ               АДДДДДДДДДДДДЩ


     Как Вы можете видеть,  интересующие нас случаи получаются  при
переходе  от  шага  3  к  шагу  4.  Во-первых:  указатель на начало
перескакивает  через  нижнюю  границу  буфера  оверлеев,  заставляя
монитор  оверлеев  сдвигать все загруженные оверлеи (и указатель на
хвост) вверх.  Этот  сдвиг  необходим  для  того,  чтобы  сохранить
свободную  область,  расположенную между указателями на начало и на
хвост.  Во-вторых:  для того,  чтобы загрузить оверлей  D,  монитор
оверлеев выталкивает оверлей А из хвоста буфера.
     Оверлей А в этом случае является самым ранним  из  загруженных
оверлеев, и,  следовательно, лучшим выбором для выталкивания, когда
это необходимо.  Монитор оверлеев продолжает выталкивать оверлеи из
хвоста  пока не освободит место для нового оверлея в голове буфера,
и каждый раз,  когда указатель на начало достигает границы  буфера,
операция сдвига повторяется.
     Так работает по умолчанию монитор оверлеев Turbo  Pascal  6.0.
Однако,  монитор  оверлеев  Turbo  Pascal  может  работать в режиме
оптимизации.
     Предположим, что  оверлей  А  содержит  ряд часто используемых
программ.  Даже, если эти программы используются все время, А будет
всегда выталкиваться из оверлейного буфера,  вновь загружаясь через
короткое время.  Проблема здесь  заключается  в  том,  что  монитор
оверлеев  ничего не знает о частоте вызовов программ из А - а знает
только,  что когда идет вызов программы из А,  и А нет в памяти, он
должен загрузить А.  Одно из решений этой проблемы может заключатся
в прерывании каждого вызова программы из А,  и тогда  каждый  вызов
передвигает А в голову оверлейного буфера, чтобы отразить его новый
статус,  как  самого  последнего  использованного  оверлея.   Такое
прерывание  вызовов,  к  сожалению,  очень дорого в смысле скорости
выполнения,  и в некоторых случаях может замедлить  программу  даже
больше, чем операция загрузки оверлеев.
     Turbo Pascal  обеспечивает  компромиссное   решение,   которое
практически  не  дает потери производительности и достигает высокой
степени успеха при определении часто используемых оверлеев (которые
не должны выталкиваться): когда оверлей близок к хвосту оверлейного
буфера,  он попадает на "испытание".  Если,  во время этого периода
испытания   происходит   вызов   программы  из  оверлея,  испытание
"отменяется",  оверлей не будет вытолкнут,  когда он достиг  хвоста
оверлейного  буфера.  Вместо этого он просто передвигается в голову
буфера, т.е. пересекает границу кольца оверлейного буфера. С другой
стороны,  если не было вызовов к этому оверлею во время его периода
испытаний,  что говорит о  низкой  частоте  использования,  оверлей
выталкивается, когда достигает хвоста оверлейного буфера.
     Затраты на   схему   испытаний/отмены,   при   которой   часто
используемые  оверлеи сохраняются в буфере оверлеев,  заключаются в
перехватывании только  одного  вызова  каждый  раз,  когда  оверлей
приближается к хвосту оверлейного буфера.
     Две новые   программы   монитора   оверлеев   OvrSetRetry    и
OvrGetRetry управляют   механизмом   испытаний/отмены.  OvrSetRetry
устанавливает  размер  области  в  буфере   оверлеев,   в   которой
происходят  испытания,  а OvrGetRetry возвращает текущую установку.
Если оверлей  попадает  внутрь  последних  OvrGetRetry  байт  перед
хвостом буфера  оверлеев,  он  автоматически попадает на испытания.
Любое свободное пространство в буфере оверлеев  рассматривается как
часть области испытаний.


                      Константы и переменные.

     Этот раздел   кратко   описывает   константы   и   переменные,
определенные в модуле Overlay.


                            OvrResult.

     Каждая процедура модуля  Overlay  возвращает  код  возврата  в
переменную OvrResult.

     var OvrResult : Integer;

     Возможные коды  возврата  определены  константами,  описанными
ниже. Код ноль означает успешный возврат.

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


                           OvrTrapCount.

     var OvrTrapCount : Word;

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


                           OvrLoadCount.

     var OvrLoadCount : Word;

     Каждый раз, когда оверлей загружается, переменная OvrLoadCount
увеличивается на единицу. Начальное значение OvrLoadCount ноль.
     Проверкой OvrTrapCount  и  OvrLoadCount  (например,   в   окне
Watch отладчика)  при  идентичном  выполнении  программ,  Вы можете
управлять   действием   области   испытаний   различных    размеров
(устанавливается  OvrSetRetry)  для нахождения оптимального размера
Вашей конкретной программы.


                           OvrFileMode.

     var OvrFileMode : Вyte;

     Переменная OvrFileMode   используется   для  определения  кода
доступа,  передаваемого в DOS,  когда открывается файл оверлеев. По
умолчанию  OvrFileMode  ноль,  что соответствует доступу только для
чтения. Присваиванием нового значения OvrFileMode до вызова OvrInit
Вы можете изменить код доступа,  например для того, чтобы разрешить
распределенный доступ в сети.  Более детально со  значениями  кодов
доступа   можно   ознакомиться   в   Вашем  Справочном  руководстве
программиста по DOS.


                            OvrReadBuf.

     Type OvrReadFunc = Function(OvrSeg : Word) : Integer;
     var OvrReadBuf : OvrReadFunc;

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

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

     Для того,  чтобы установить Вашу  собственную  функцию  чтения
оверлея, Вы должны вначале сохранить предыдущее значение OvrReadBuf
в переменной типа OvrReadFunc и затем присвоить Вашу функцию чтения
оверлея  OvrReadBuf.  Внутри Вашей функции чтения Вы должны вызвать
сохраненную  функцию  чтения,   чтобы   произвести   действительную
операцию  загрузки.  Любые проверки,  которые Вы хотите произвести,
такие  как  проверка  на  наличие  гибкого   диска,   должны   быть
произведены  до  вызова сохраненной функции чтения и проверка любых
ошибок должна производиться после этого вызова.
     Код для  установки  функции  оверлеев  должен находиться сразу
после вызова OvrInit и в это время OvrReadBuf будет содержать адрес
стандартной функции чтения диска.
     Если Вы так  же  вызываете  OvrInitEMS,  она  использует  Вашу
функцию чтения оверлеев с диска в EMS-память,  если не было ошибок,
она запоминает адрес стандартной функции чтения EMS  в  OvrReadBuf.
Если  Вы  так  же  желаете  перекрыть  функцию  чтения EMS,  просто
повторите процесс установки после вызова OvrInitEMS.
     Стандартная функция  чтения  с  диска возвращает ноль в случае
успеха и код ошибки DOS в противном случае. Аналогично, стандартная
функция чтения EMS возвращает ноль в случае успеха и код ошибки EMS
в противном случае (значение в диапазоне $80 -  $FF).  Коды  ошибок
DOS см.  в разделе "Ошибки времени выполнения" в приложении А этого
руководства по Turbo Pascal.  Коды ошибок EMS смотри в спецификации
расширенной памяти LOTUS/INTEL/MICROSOFT.
     Следующий фрагмент   кода   демонстрирует   как    писать    и
устанавливать функцию чтения оверлея.  Новая функция чтения оверлея
постоянно вызывает сохраненную функцию чтения оверлеев до  тех пор,
пока  не  возникнет  ошибка.  Любая  ошибка  передается в процедуры
DOSError или EMSError (не показанные  здесь)  так,  что  они  могут
выдать  ошибку  пользователю.  Заметим,  что параметр OvrSeg только
передается в сохраненную функцию чтения оверлеев и никогда прямо не
управляется новой функцией чтения оверлеев.

     uses Overlay;
     var
        SaveOvrRead: OvrReadFunc;
        UsingEMS: Boolean;

     function MyOvrRead(OvrSeg: Word): Integer;
     var
        E: Integer;
     begin
        repeat
           E := SaveOvrRead(OvrSeg);
           if E <> 0 then
              if UsingEms then
                 EMSError(E)
              else
                 DOSError(E);
         until E = 0;
         MyOvrRead := 0;
      end;

      begin
         OvrInit('MYPROG.OVR);
         SaveOvrRead := OvrReadBuf;   {стандартная функция
                                       сохранения диска}
         OvrReadBuf := MyOvrRead;     {установка своей функции}
         UsingEMS := False;
         OvrInitEMS;
         if (OvrResult = OvrOk) then
         begin
            SaveOvrRead := OvrReadBuf;  {стандартная функция
                                         сохранения EMS}
            OvrReadBuf := MyOvrRead;    {установка своей функции}
            UsingEMS := True;
         end;
         ...
      end.


                          Коды возврата.

     Ошибки модуля Overlay  выдаются  через  переменную  OvrResult.
     Определены следующие коды:

                 Таблица 13.1. Значения OvrResult.
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
    Константа       Значение               Описание
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
    ovrOK             0          успешно
    ovrError         -1          ошибка монитора оверлеев
    ovrNotFound      -2          файл оверлеев не найден
    ovrNoMemory      -3          нет памяти для буфера оверлеев
    ovrIOError       -4          ошибка в/в оверлейного файла
    ovrNoEMSDriver   -5          драйвер EMS не установлен
    ovrNoEMSMemory   -6          недостаточно EMS памяти
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД


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

     В модуле  Overlay  определены  процедуры OvrInit,  OvrInitEMS,
OvrSetBuf, OvrClearBuf,   OvrSetRetry    и    функции    OvrGetBuf,
OvrGetRetry. Здесь  они  кратко описаны.


                             OvrInit.

     procedure OvrInit(FileName : String);

     Инициализирует монитор оверлеев и открывает  оверлейный  файл.
Если параметр  FileName  не  задает  устройство или справочник,  то
монитор оверлеев ищет  файл  в  текущем  справочнике,  справочнике,
содержащем файл  .EXE  (под  DOS  3.x)  и в справочниках,  заданных
переменной  среды  DOS  PATH.  Возможные  коды  ошибок  OvrError  и
OvrNotFound.  В случае ошибки монитор оверлеев не устанавливается и
попытка вызвать оверлейную программу будет генерировать  код ошибки
времени выполнения 208.

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


                            OvrInitEMS.

     procedure OvrInitEMS;

     Если возможно,  загружают оверлейный файл в EMS. Если успешно,
оверлейный файл закрывается и  все  последующие  загрузки  оверлеев
ускоряются  из-за быстрой передачи в памяти.  Возможные коды ошибок
OvrError,  OvrIOError,  OvrNoEmsDriver  и  OvrNoEmsMemory.  Монитор
оверлеев  будет  продолжать  работать,  если  OvrInitEMS возвращает
ошибку, но оверлеи будут читаться с диска.
     Примечание: Использование   OvrInitEMS  для  размещения  файла
оверлеев в EMS не отменяет необходимости в буфере оверлеев. Оверлеи
будут  копироваться из EMS в "нормальную" память оверлейного буфера
до того,  как они могут быть  выполнены.  Однако,  поскольку  такая
передача  в  памяти  значительно  быс  трее,  чем  чтение  с диска,
требование  к  увеличению  размера   буфера   оверлеев   становятся
значительно меньше.


                            OvrSetBuf.

     procedure OvrSetBuf(Size : LongInt);

     Устанавливает размер  оверлейного  буфера.   Заданный   размер
должен быть больше или равен начальному значению буфера оверлеев, и
меньше или равен значению MemAvail плюс текущий  размер оверлейного
буфера.  Если заданный размер больше чем текущий, то из начала кучи
будет выделено  дополнительное  пространство  (что  уменьшит размер
кучи).  Если же заданный размер меньше текущего,  то излишек памяти
будет возвращен в кучу.  OvrSetBuf требует, чтобы куча была пустая,
если же динамические переменные были уже распределены через New или
GetMem,  будет возвращена ошибка. Возможные коды ошибок оvrNoMemory
и  ovrError.  Монитор  оверлеев  будет  продолжать  работать   если
OvrSetBuf   возвращает   ошибку,   а   размер  буфера  оверлеев  не
изменяется.


                            OvrGetBuf.

     function OvrGetBuf : LongInt;

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

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


                           OvrClearBuf.

      procedure OvrClearBuf;

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


                           OvrSetRetry.

     procedure OvrSetRetry(Size : LongInt);

     Процедура OvrSetRetry устанавливает размер области испытаний в
буфере оверлеев.  Если оверлей  попадает  внутрь  Size  байт  перед
хвостом  буфера  оверлеев,  он автоматически начинает испытываться.
Любое свободное пространство в буфере оверлеев  рассматривается как
часть  области  испытаний.  Для  совместимости  с  ранними версиями
монитора оверлеев,  размер области испытаний по умолчанию - 0,  при
этом механизм испытаний/отмены отключается.
     Пример использования OvrSetRetry:

     OvrInit('MYPROG.OVR');
     OvrSetBuf(BuferSize);
     OvrSetRetry(BuferSize div 3);

     Не существует    эмпирической    формулы    для    определения
оптимального   размера  области  испытаний  -  однако  эксперименты
показывают,  что лучшие результаты получаются в диапазоне от 1/3 до
1/2 размера буфера оверлеев.


                           OvrGetRetry.

     function OvrGetRetry : Longint;

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


                   Создание оверлейных программ.

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


                    Генерация оверлейного кода.

     Turbo Pascal позволяет модулю быть  оверлейным,  если  он  был
откомпилирован  с  {$O+}.  В  этом  случае  генератор  кода  делает
специальные предосторожности при передаче  строковых  констант  или
 констант множеств  из  одной  оверлейной  процедуры  или функций в
другую.  Например,  если  UnitA  содержит  процедуру  со  следующим
описанием:

     procedure WriteStr(S: String);

и UnitB содержит оператор

     WriteStr('Hello world...');

     то Turbo  Pascal помещает строковую константу 'Hello world...'
в кодовый сегмент UnitB и передает указатель в  процедуру WriteStr.
Однако,  если  оба  модуля  оверлейные,  то  это не будет работать,
поскольку при вызове WriteStr,  модуль UnitB  может  быть  перекрыт
модулем  UnitA,  что  сделает  указатель неверным.  Директива {$O+}
используется  для  избежания  таких  проблем;  когда  Turbo  Pascal
находит вызов одного модуля, откомпилированного с {$O+}, компилятор
делает копию всех констант из кодового сегмента во  временный  стек
до передачи указателя на них.

     Использование {$O+} в модуле  не  заставляет  вас  делать  его
оверлейным. Это только заставляет Turbo  Pascal  обеспечить,  чтобы
при желании,  модуль мог быть оверлейным.  Если Вы создаете модуль,
который хотите использовать как оверлейный  и  как  не  оверлейный,
компилируйте его с {$O+}, чтобы иметь одну версию одного модуля.


                      Дальняя модель вызова.

     Как сказано ранее,  для вызова любой процедуры или функции  из
другого  модуля,  Вы  должны  гарантировать,  что  все  процедуры и
функции, активные в данный момент, используют дальнюю модель вызова
(FAR call).
     Например: Пусть OvrA - процедура в оверлейном модуле и MainB и
MainC  -  процедуры  в  главной  программе.  Если главная программа
вызывает MainC,  которая вызывает MainB,  которая  в  свою  очередь
вызывает OvrA,  то при вызове OvrA,  MainB и MainC активны (они еще
не  вернули  управление  в  главную   программу),   т.е   требуется
использовать дальнюю модель вызова.
     Объявленные в главной программе MainB  и  MainC  по  умолчанию
используют ближнюю модель (NEAR call),  в данном случае должна быть
использована директива {$F+} для управления дальней  модели вызова.
     Простейший способ  удовлетворения  требования  дальней  модели
вызова,  это поместить директиву {$F+} в начало главной программы и
каждого модуля.  Альтернативно, Вы можете установить директиву $F в
{$F+}, используя в командной строке директиву /$F (для ТРС.EXE) или
Оptions/Compiler в окне Force Far Calls.
     По сравнению со смешанными вызовами ближней и  дальней модели,
Вы будете  дополнительно  затрачивать:  одно дополнительное слово в
памяти стека на каждую активную  процедуру  и  один  дополнительный
байт на каждый вызов.


                 Инициализация монитора оверлеев.

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

     begin
        OvrInit('EDITOR.OVR');
     end;

     Здесь нет  обработки  ошибок,  и  если недостаточно памяти для
буфера оверлеев или оверлейный  файл  не  найден,  то  при  попытке
вызвать оверлейную программу возникнет ошибка 208 (монитор оверлеев
не установлен).
     Расширим предыдущий пример:

     begin
        OvrInit('EDITOR.OVR');
        OvrInitEMS;
     end;

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

     const
        OvrMaxSize = 80000;
     begin
        OvrInit('EDITOR.OVR');
        OvrInitEMS;
        OvrSetBuf(OvrMaxSize);
     end;

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

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

     Помните, что OvrSetBuf увеличивает  буфер  оверлеев,  сокращая
кучу.  Следовательно  куча  должна быть пустой,  иначе OvrSetBuf не
будет работать.  Если Вы используете модуль Graph,  Вы должны  быть
уверены  в  том,  что  Вы  вызвали  OvrSetBuf  до вызова InitGraph,
который распределяет память в куче.
     Приведем детальный  пример  инициализации  монитора оверлеев с
полной проверкой ошибок:

     соnst
        OvrMaxSize = 80000;
     var
        OvrName: String[79];
        Size: Longint;
     begin
        OvrName := 'EDITOR.OVR';
        repeat
           OvrInit(OvrName);
           if OvrResult = OvrNotFound then
           begin
              WriteLn('Oверлейный файл не найден:', OvrName, '.');
              Write('Bведите правильное имя оверлейного файла: ');
              ReadLn(OvrName);
           end;
        until OvrResult <> OvrNotFound;
        if OvrResult <> OvrOk then
        begin
           WriteLn('Oшибка монитороа оверлеев.');
           Halt(1);
        end;
        OvrInitEMS;
        if OvrResult <> OvrOk then
        begin
           case OvrResult of
              ovrIOError:  Write('Oшибка в/в оверлейного файла');
              ovrNoEMSDriver: Write('EMS драйвер не установлен');
              ovrNoEMSMemory: Write('Hедостаточно EMS памяти');
           end;
           Write('Hажмите ввод...');
           ReadLn;
        end;
        OvrSetBuf(OvrMaxSize);
     end;

     Если имя оверлейного файла не верно,  то пользователю выдается
подсказка для ввода корректного имени.
     Затем проверяется  нет  ли  других ошибок инициализации.  Если
обнаружена  ошибка,  программа  завершается,  поскольку  ошибки   в
OvrInit  являются  фатальными  (если их проигнорировать,  возникнет
ошибка при вызове первой оверлейной программы).
     После успешной   инициализации,  вызовом  OvrInitEMS  делается
попытка загрузить файл оверлеев в EMS если это возможно.  В  случае
ошибки   выдается   диагностическое   сообщение,  но  программа  не
завершается, вместо этого программа будет продолжать читать оверлеи
с диска.
     Наконец OvrSetBuf  устанавливает  приемлемый   размер   буфера
оверлеев.    Этот    размер    определяется    путем    анализа   и
экспериментирования  с  конкретной  программой.  Ошибки   OvrSetBuf
игнорируются, хотя  OvrResult может содержать код -3 (OvrNoMemory).
Если  памяти  не  хватило,  монитор   оверлеев   будет   продолжать
использовать   минимальный   буфер,   распределенный   при   старте
программы.


           Инициализационная часть в оверлейных модулях.

     Как и   статические  модули,  оверлейные  модули  могут  иметь
инициализационную часть.  Хотя  инициализационный  код  оверлейного
модуля  не  отличается  от  нормального  оверлейного кода,  монитор
оверлеев должен быть  инициализирован  первым,  так  чтобы  он  мог
загрузить и выполнить оверлейный модуль.
     Возвращаясь к  программе  Editor,  предположим,   что   модули
EdInOut  и EdMain имеют инициализационный код.  Это требует,  чтобы
OvrInit был вызван до инициализационного кода  EdInOut.  Для  этого
есть только один путь - создать дополнительный неоверлейный модуль,
который   стоит   до   EdInOut   и   вызывает   OvrInit   в   своей
инициализационной части:

     unit EdInit;
     interface
     implementation
     uses Overlay;
     const
        OvrMaxSize = 80000;
     begin
        OvrInit('EDITOR.OVR');
        OvrInitEMS;
        OvrSetBuf(OvrMaxSize);
    end.

     Модуль EdInit  должен  стоять  в  операторе  uses программы до
любого оверлейного модуля:

     program Editor;
     {$F+}
     uses Overlay, Crt, Dos, EdInit, EdInOut, EdFormat, EdPrint,
          EdFind, EdMain;
     {$O EdInOut}
     {$O EdFormat}
     {$O EdPrint}
     {$O EdFind}
     {$O EdMain}

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


                       Что нельзя в оверлее.

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

     - Модули, компилированные в состоянии {$O-}. Компилятор выдаст
ошибку,  если Вы попытаетесь сделать оверлейным модуль,  который не
был откомпилирован в состоянии {$O+}. Такими неоверлейными модулями
являются System,  Overlay,  Crt, Graph, Turbo3, Graph3. (Примечание
переводчика: Модуль Dos тоже не может быть оверлейным).

     - Модули,  которые  содержат  обработчики  прерываний.   Из-за
нереентерабельной структуры DOS модуль, который реализует interrupt
процедуры, не  должен  быть  оверлейным.  Например,  таким  модулем
является Crt, который реализует обработчик прерывания Ctrl-Break.

     - Зарегистрированные    с    помощью   RegisterBGIdriver   или
RegisterBGIfont  BGI   драйверы   или   шрифты.

     Монитор оверлеев  в  Turbo Pascal полностью поддерживает вызов
оверлейных программ через указатели на процедуры.  Примерами  таких
указателей процедур являются: процедуры выхода и драйверы устройств
текстовых файлов.
     Кроме того,  передача  оверлейных  процедур  и   функций   как
параметров  процедурного  типа и присваивание оверлейных процедур и
функций   переменным   процедурного   типа   так    же    полностью
поддерживается.


                         Отладка оверлеев.

     Большинство отладчиков   поддерживают    очень    ограниченные
способности отладки  оверлеев.  В Turbo Pascal и Turbo Debugger это
не так.  Интегрированный отладчик полностью поддерживает  пошаговое
выполнение  и  точки прерывания в оверлеях.  Используя оверлеи,  вы
можете легко  разрабатывать  и  отлаживать  огромные  программы  из
интегрированной среды Turbo Pascal или Turbo Debugger.


                   Внешние программы в оверлеях.

     Так же как и обычные  процедуры  и  функции  Паскаля,  внешние
программы,   написанные   на   Ассемблере,  должны  соответствовать
определенным правилам программирования,  чтобы работать корректно с
монитором оверлеев.
     Если программа на Ассемблере  вызывает  л ю б у ю   оверлейную
процедуру  или функцию,  Ассемблерная программа должна использовать
дальнюю  модель  вызова  и  должна  устанавливать  стек,  используя
регистр ВР.   Например,   предположим,   что  оверлейная  процедура
OtherProc находится в другом модуле и  что  Ассемблерная  программа
ExternProc вызывает ее. Тогда ExternProc должна быть дальней модели
(far) и должна устанавливать стек:

     ExternProc      PROC    FAR

          push       bp             ; сохранить BP
          mov        bp,sp          ; установить стек
          sub        sp,LocalSize   ; распределить локальные
                                    ; переменные
          ...
          call       OtherProc      ; вызвать другой оверлейный
                                    ; модуль
          ...
          mov        sp,bp          ; удалить локальные
                                    ; переменные
          pop        bp             ; восстановить BP
          ret        ParamSize      ; возврат

     ExternProc      ENDP

     где  LocalSize  -  размер  локальных  переменных и ParamSize -
размер параметров. Если LocalSize = 0, то строки с распределением и
удалением локальных переменных можно опустить.
     Эти требования  останутся  такими  же,  если ExternProc делает
непрямой вызов  оверлейных  процедур  или функций.  Например,  если
OtherProc вызывает оверлейные процедуры или  функции,  но  сама  не
оверлейная,  то ExternProc также должна использовать дальнюю модель
вызова и устанавливать стек.
     В случае,  когда  Ассемблерная программа не делает прямого или
непрямого вызова  оверлейной  процедуры  или  функции,  специальных
требований   к   ней   не   предъявляется:  Ассемблерная  программа
может использовать ближнюю модель вызова (near) и  не устанавливает
стек.
     Оверлейная Ассемблерная программа не должна  создавать перемен
ные в  кодовом сегменте,  поскольку любые модификации,  сделанные в
оверлейном кодовом сегменте,  будут потеряны,  когда оверлей  будет
удален. Кроме  того,  значения указателей на объекты, находящиеся в
оверлейном кодовом сегменте,  могут изменяться после вызова  других
оверлеев, поскольку  монитор оверлеев свободно перемещает и удаляет
оверлейные кодовые сегменты.


                      Оверлеи в .EXE файлах.

     Turbo Pascal  предоставляет возможность сохранить Ваши оверлеи
в конце .EXE файла Вашей прикладной программы,  а  не  в  отдельном
.OVR  файле.  Для  присоединения  .OVR  файла  к  концу  .EXE файла
используйте команду Copy DOS с опцией /b. Например

     Copy/b MYPROG.EXE + MYPROG.OVR

     Вы должны быть уверены,  что .EXE файл был откомпилирован  без
отладочной информации  Turbo Debugger.  Для этого в IDE необходимо,
чтобы Debug/StandAlone  Debugging  было  установлено   в   Off;   с
командной версией компилятора не должна быть указана опция /V.
     Для чтения оверлеев с конца .EXE файла а не из отдельного .OVR
файла,  просто  укажите  имя  .EXE файла в вызове OvrInit.  Если Вы
работаете под DOS 3.X,  Вы можете использовать стандартную  функцию
ParamStr для получения имени .EXE файла. Например:

     OvrInit(ParamStr(0));


Яндекс цитирования