ГЛАВА 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));