Глава11. ДРУГИЕ ВОЗМОЖНОСТИ ТУРБО ПАСКАЛЯ

  11.2 Использование встроенных машинных кодов 
  11.3 Обращение к функциям операционной системы 
  11.4 Поддержка процедур обработки прерываний
  11.5 Запуск внешних программ
  11.6 Оверлей 
  11.7 Прямое обращение к памяти и портам ввода вывода
  11.8 Встроенный Ассемблер

   оглавление


11.1. ВНЕШНИЕ ПРОЦЕДУРЫ (Функции)


     С помощью внешних процедур (функций) можно осуществить вызов из программы процедур или функций, написанных на языке ассемблера. Ассемблером обычно называют компилятор, обеспечивающий компиляцию программ, написанных на машинно-ориентированном языке программирования низкого уровня (на языке ассемблера). В Турбо Паскале версии 6.0 есть собственный встроенный ассемблер (см. п.11.8). В JTOM разделе речь идет о программах, написанных и откомпилированных: помощью внешнего ассемблера, такого, как, например, ассемблер фирмы Microsoft или Турбо-Ассемблер фирмы Borland.
   Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие возможности использования вcex особенностей архитектуры ПК. Ассемблерные программы выполняется значительно быстрее и занимают меньший объем памяти, чем программы, написанные на Турбо Паскале, однако низкий уровень языка Ассемблера существенно снижает производительность труда программиста и резко усложняет отладку программ. Как правило, на языке ассемблера пишутся сравнительно небольшие фрагменты программ, в которых «пользуются недоступные из Турбо Паскаля особенности архитектуры ПК.
   Внешняя процедура (функция) в программе, написанной на Турбо Паскале, объявляется своим заголовком, за которым следует стандартная директива ZXTERNAL, например:
                     Function LoCase (ch : char) : char;
                     external; 
                            Procedure Swapping (var a,b; N : word);
                        externa I;
   Как видно из этих примеров, тело внешней процедуры (функции) отсутствует - его заменяет директива EXTERNAL. Для подключения ассемблерной программы необходимо предварительно ее откомпилировать
получить ОВJ-файл с перемещаемым кодом программы (в файлах с расширением .OBJ обычно сохраняются результаты компиляции ассемблерных программ). Непосредственно перед описанием внешней процедуры (функции) в тело основной программы вставляется директива компилятора {$L <имя файла>}, где <имя файла> - имя OBJ-файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указываются опцией OPTIONS / DIRECTORIES / OBJECT DIRECTORIES (см. прил.1).
Перед передачей управления внешней процедуре (функции) программа помещает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры ВР, SP, SS и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления в Турбо Паскалевую программу. Остальные регистры можно не сохранять и соответственно не восстанавливать.
Параметры могут передаваться по ссылке или по значению. Если параметр передается по ссылке, в стек помещается указатель, содержащий абсолютный адрес параметра, если по значению - в стек помещается сам параметр, точнее - его значение. Все параметры-переменные, т.е. параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Параметры-значения могут передаваться по ссылке или по значению в зависимости от длины внутреннего представления соответствующего параметра. В общем случае используется следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, он передается своим значением, т.е. его значение помещается в стек. Точно так же через стек передаются и все вещественные данные длиной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, начиная с версии 5.0 -через стек центрального процессора 8086/80286). Во всех остальных случаях, если длина внутреннего представления больше 4 байт, соответствующий параметр передается по ссылке.
Ассемблерные функции в зависимости от длины внутреннего представления результата должны возвращать его через регистры центрального процессора или сопроцессора по следующим правилам:
• äëèíîé â 1 áàéò - â ðåãèñòðå AL; . äëèíîé â 2 áàéòà - â ðåãèñòðå ÀÕ;
. длиной в 4 байта - в регистрах DX-.AX (старшее слово в DX); . тип REAL (6 байт) - в регистрах DX:BX:AX; . типы SINGLE, DOUBLE, EXTENDED и СОМР - через стек сопроцессора 8087/80287;
. указатели - в регистрах DX:AX (сегмент в DX);
• ñòðîêè âîçâðàùàþòñÿ ïî ññûëêå: àäðåñ íà÷àëà ñòðîêè ïîìåùàåòñÿ â DX-.AX (ñåãìåíò â DX).
   Все ассемблерные процедуры должны размещаться в сегменте с именем CODE или CSEG, или с именем, оканчивающимся на ТЕХТ; инициализированные локальные переменные помещаются в сегмент с именем CONST или с именем, оканчивающимся на _DATA. Все другие локальные переменные необходимо размещать в сегменте с именем DATA или DSEG, или с именем, оканчивающимся на _BSS. Любые другие объявления сегментов игнорируются. Все имена, объявленные в интерфейсной части модулей программы, написанной на Турбо Паскале, становятся доступны ассемблерной процедуре (функции) после их объявления директивой EXTRN. Точно так же все имена ассемблерных процедур и функций, которые должны быть доступны программе на Турбо Паскале, следует объявлять директивой PUBLIC.
 
                                 Вверх

11.2. ИСПОЛЬЗОВАНИЕ ВСТРОЕННЫХ МАШИННЫХ КОДОВ


  
В Турбо Паскале имеется возможность включения в программу не-хшьших фрагментов, написанных непосредственно в машинных кодах. Для этого используется стандартная директива INLINE, за которой в круглых скобках следует один или несколько элементов машинного кода, разделяемых косыми чертами. Элемент кода, в свою очередь, строится из одного или более элементов данных, разделенных знаками «+» или «-».
В качестве элемента данных может использоваться целая константа, идентификатор (переменной, константы или функции) или ссылка на счетчик адреса («*»). Каждый элемент данных вызывает генерацию 1 или 2 байт кода программы. Значение этого кода получается сложением или считанием элементов данных в соответствии с разделяющим их знаком. Значением идентификатора переменной, константы, функции служит адрес соответствующего объекта, значением ссылки на счетчик адреса является тот адрес, по которому будет размещаться следующий байт кода.
Элемент кода будет генерировать 1 байт кода, если этот элемент состоит только из целых констант и значение результата не превышает мощности одного байта, т.е. находится в диапазоне от 0 до 255. Если значение превышает 255 или элемент кода содержит ссылку на счетчик адреса, генерируется 2 байта. Знаки «>» и «<» могут использоваться для отмены автоматического выбора размера генерируемого кода. Если элемент кода начинается со знака «<», в код заносится только 1 байт (с младшей значимостью), даже если само значение занимает 2 байта. Наоборот, если элемент начинается со знака >», в код заносится 2 байта (старший байт может оказаться нулевым).
Значением идентификатора является смещение соответствующего объекта. Если переменная - глобальная, смещение задается относительно сегмента данных, хранящегося в регистре DS, если это локальная переменная, - относительно сегмента стека (регистр SP). Базовым сегментом типизированной константы является сегмент кода (регистр CS).
В следующем примере приводятся две короткие процедуры, с помощью которых можно ввести или вывести данные через любой порт ПК
                              FUNCTION InPortfPort: Word): Word;
                                       var
                              pp: Word;
                                      cc:Char;
                                   BEGIN
                                           pp:=port; 
                                            inline
                             $8b/$96/pp/ { mov DX,pp[bp] }
                                        SEC/ IN AX.DX }
                                         $88/$86/cc); { mov cc[bp],AX }
                            InPort r=ord(cc);
                               END; 
                          PROCEDURE OgtPort(Port,Bt: Word): 
                       VAR 
                             pp: Word: 
                              cc:Char; 
                                  BEGIN 
                                  pp:=port;

                                  cc:=chr(Bt); 
                                       inline(
                          $8a/$86/cc/ {mov AX.ccIbp] }
                                      { S8b/$96/pp/ {mov DX.pp bp] }
                                           {$EE) {OUT DX.AX }
                                   END;
      Операторы INLINE могут произвольным образом смешиваться с другими операторами Турбо Паскаля, однако при выходе из процедуры (функции) содержимое регистров BP, SP, DS и SS должно быть таким же, как и при входе в нее.
С помощью директивы INLINE можно также задавать последовательность машинных кодов, которую необходимо несколько раз вставить а программу. Для этого используется описание INLINE-npoцедypa, например:
                       PROCEDURE Disablelnterrupts:
                          inline(SFA); {CLI}
         INLINE -процедура имеет обычный для Турбо Паскаля заголовок, в то время как тело процедуры пишется целиком с помощью оператора INLINE. Всякий раз, когда в программе будет встречаться оператор вызова INLINE-процедуры, компилятор Турбо Паскаля будет вставлять на это место не код вызова процедуры, а нужные машинные коды. Например, вместо вызова процедуры в операторе
Disable!nterrupt:
компилятор вставит команду запрета прерываний CLI. Таким образом, INLINE процедуры служат своеобразным средством расширения возможностей стандартного компилятора Турбо Паскаля и подобны макросам ассемблера. Использование INLINE-процедур увеличивает скорость исполнения программы, так как не осуществляется генерация (и исполнение) команд передачи управления в процедуру. По этой причине в INLINE -процедуpax не следует использовать команды выхода из подпрограммы..INLINE-процедура может иметь параметры, однако на них нельзя ссылаться в INLINE- директивах (на другие символы Турбо Паскаля ссылаться можно). В следующем примере перемножаются два числа типа INTEGER, результат имеет тип LONGINT:


                                      FUNCTION LongMul(X,Y : Integer) : Longlnt; 
                            inIine(
                          $5A/ { POP AX; получить в АХ число X }
                             $58/ { POP DX; получить в DX число Y }
                              SF7/SEA): I IMUL DX: DX:AX :- X " Y }

      Отметим, что в силу упоминавшегося сходства с макросами ассемблера, имена .INLINE-подпрограмм не могут использоваться в качестве аргументов в операторах @ или служить параметрами функций ADDR,
OFS и SEG.
 
                            Вверх

11.3. ОБРАЩЕНИЕ К ФУНКЦИЯМ ОПЕРАЦИОННОЙ СИСТЕМЫ


     Турбо Паскаль предоставляет программисту практически неограниченные возможности использования любых функций стандартной операционной системы MS DOS. При внимательном анализе материала этой книги Вы, очевидно, заметите, что значительную его часть составляет описание многочисленных библиотечных процедур и функций. Собственно язык Паскаль весьма прост и лаконичен, что, по мнению многих специалистов, и послужило одной из причин его широкого распространения. Библиотечные же процедуры и функции, в своей значительной части, являются, по существу, своеобразным интерфейсом между языковыми средствами Турбо Паскаля и функциями операционной системы. Разумеется, можно только приветствовать усилия разработчиков Турбо Паскаля по созданию мощных библиотек TURBO.TPL и GRAPH.TPU, однако ясно, что таким способом невозможно запрограммировать все допустимые обращения к средствам ДОС. Вот почему в Турбо Паскаль включены две процедуры, с помощью которых программист может сам сформировать вызов той или иной функции дисковой операционной системы (ДОС).
Следует учесть, что единственным механизмом обращения к функциям ДОС является инициация программного прерывания. Прерывание это особое состояние вычислительного процесса. В момент прерывания нарушается нормальный порядок выполнения команд программы и управление передается специальной процедуре, которая входит в состав ДОС и называется процедурой обработки прерывания. Каждое прерывание характеризуется в рамках ДОС порядковым номером и связано со своей процедурой обработки. В архитектуре центрального процессора ПК предусмотрены прерывания двух типов - аппаратные и программные. Аппаратные прерывания создаются схемами контроля и управления ПК и сигнализируют операционной системе о переходе какого-либо устройства в новое состояние или о возникновении неисправности. Программные прерывания инициируются при выполнении одной из двух специальных команд микропроцессора (INT или INTO) и служат для обращения к средствам ДОС.
Описываемые ниже процедуры входят в состав библиотечного модуля DOS. TPU становятся доступными после объявления USES DOS. При возникновении программного прерывания в большинстве случаев необходимо передать процедуре обработки прерывания некоторые параметры, в которых конкретизируется запрос нужной функции. Эти параметры, а также выходная информация (результат обработки прерывания) передаются от программы к процедуре и обратно через регистры центрального процессора. В составе модуля DOS.TPU для этих целей определен специальный тип:
                              type
                              Registers = record 
                               case Integer of
                        0: (AX, BX, CX. BP, SI, 01, DS, ES, Flags : word); 
                               1: (AL, AH, BL, BH, CL, CH, DL. OH : byte) end;
       Этот тип имитирует регистры центрального процессора и дает возможность обращаться к ним как к 16-битным или 8-битным регистрам.
Процедура INTR. С помощью этой процедуры инициируется программное прерывание с требуемым номером. Обращение: 

I                        NTR (<N>,<регистры>)

Здесь <N> - выражение типа Byte; номер прерывании, <регистры> - переменная типа REGISTERS; в этой переменной процедуре обработки прерывания передается содержимое регистров и в ней же возвращается выходная информация. Например, прерывание с номером 18 ($12) возвращает в регистре АХ объем оперативной памяти ПК. Короткая программа, представленная в примере 20, выведет на экран сообщение об этом объеме.
                             П р и м е р 20.
                                      Uses DOS; var
                                        r : registers; 
                                       BEGIN
                                     tntr ($1Z, r):
                                  wrlteln ('Объем памяти = ', r.АХ, ' Кбайт') 
                                       END.
  Процедура MSDOS. Инициирует прерывание с номером 33 ($21).
Формат обращения:
                                MSDOS (<регистры>)
Программное прерывание с номером 33 ($21) стоит особняком, так как оно дает доступ к большому количеству функций ДОС (этим прерыванием вызывается 85 функций). Рассматриваемая процедура полностью эквивалентна вызову процедуры INTR с номером прерывания 33. Например, следующая программа (пример 21) выведет на экран версию операционной системы:
                    Пример 21.
                Uses DOS;
                      var
                        r: registers; 
                           BEGIN
                                               r.АН:= $30;
                                      MsDos(r):
                                 wrIteln('Версия операционной системы: ',r.AL, '.' r.АН)
                            END.
 
                                       Вверх

11.4. ПОДДЕРЖКА ПРОЦЕДУР ОБРАБОТКИ ПРЕРЫВАНИЙ

     При написании процедур обработки прерываний существенными являются два обстоятельства. Во-первых, процедура обработки прерывания не должна искажать работу прерванной программы. Для этого необходимо сначала сохранить регистры центрального процессора, а перед выходом из процедуры - восстановить их. Во-вторых, процедура должна строиться по принципу реентерабельности (повторной входимости): ее работа может быть прервана в любой момент другими прерываниями и ДОС может обратиться к соответствующей функции до завершения обработки предыдущего прерывания.
Турбо Паскаль предоставляет программисту возможность написания процедур обработки прерывания на языке высокого уровня, хотя обычно такие процедуры пишутся на языке ассемблера.
Процедура обработки прерывания, написанная на Турбо Паскале, должна начинаться стандартной директивой INTERRUPT (прерывание):
например:
         Procedure IntProc (Flags, CS, IP, AX, BX, CX, OX,
                 SI, OF. OS, ES, BP : word);
                      Inerrupt;
                begin
              end;
       Формальные параметры в заголовке процедуры должны перечисляться в указанном порядке - через эти параметры все регистры прерванной программы становятся доступны процедуре обработки прерывания. Количество перечисляемых в заголовке процедуры параметров-регистров может быть любым, но не больше 12. Если в списке опущен какой-либо параметр, должны быть опущены также и все предшествующие ему параметры. Например, описание
                 Procedure intProc(S), DP, ES: word); Interrupt: 

будет неверным (опущены параметры DS и ВР); правильное описание:
                 Procedure lntProc(Sl, OP, DS, ES, BP: word); Interrupt; 

Заметим, что компилятор не контролирует порядок перечисления параметров в заголовке процедуры обработки прерывания.
Директива INTERRUT вызывает генерацию специальных машинных кодов, обеспечивающих сталкивание регистров в стек при входе в процедуру и извлечение их из стека перед выходом из нее.
При входе в процедуру:

                          push ax
                          push bх
                          push ex
                          push dx
                          push si
                          push di
                          push ds
                          push es
                          push bp
                          mov bp, si
                      sub sp, LocalSIze
                    mov ax, SEG DATA
                         mov ds, ax
      При выходе из процедуры:
                          mov sp, bp
                           pop bp
                            pop es
                            pop ds
                            pop di
                            pop si 
                            pop dx
                            pop ex
                            pop bx
                            pop ax
                              Irep

   В самой процедуре обработки прерывания не рекомендуется обращаться к другим функциям ДОС, так как некоторые из них, в том числе все функции ввода-вывода, нереентерабельны.
Для связи с любыми процедурами прерываний, а следовательно, и с процедурами, написанными программистом, используются векторы прерываний - четырехбайтные абсолютные адреса точек входа в эти процедуры. Векторы прерываний располагаются в младших адресах оперативной памяти, начиная с нулевого адреса: прерывание номер 0 - по адресу О, номер 1 - по адресу 1*4-4, номер N - по адресу N * 4. С помощью следующих двух процедур программист может прочитать содержимое любого вектора или установить его новое значение.
   Процедура GETINTVEC.
Возвращает вектор прерывания с указанным номером. 
Обращение:
                   GETINTVEC (<N>,<векmop>)

Здесь <N> - выражение типа BYTE; номер прерывания; <вектор> - переменная типа POINTER; адрес точки входа в процедуру обработки прерывания. Представленная в примере 22 программа выводит на экран содержимое всех ненулевых векторов прерываний.
Пример 22.
                      Uses DOS;
                            var
                          i : byte;
                           p : pointer; 
                                    BEGIN
                                          for i := 0 to 255 do
                                        begin
                                            GetlntVec (i, p);
                                  if (Seg (р^)<> 0) or (Ofs (р^) <> 0) then
                                 writeln (' N =', 1:3. ' Seg =', Seg (р^):5,
                                        ‘Ofs =', Ofs (ð^):5) 
                               end 
                             END.

Процедура SETINTVEC. Устанавливает новое значение вектора прерывания. Формат обращения:
SETINTVEC (<N>,<adpec>)
Здесь <N> - выражение типа BYTE; номер прерывания;
<адрес> - выражение типа POINTER; адрес точки входа в процедуру обработки прерывания.
При нормальном завершении программы она выгружается из памяти, что делает невозможным разработку резидентных в памяти процедур обработки прерываний. Вы можете прекратить работу программы и оставить ее резидентной в памяти, если воспользуетесь процедурой KEEP.
Процедура KEEP. Завершает работу программы и оставляет ее резидентной в памяти. Обращение:
KEEP (<код>)
Здесь <код> - выражение типа WORD - код завершения программы. Код завершения представляет собой фактически единственный механизм передачи сообщений от запущенной программы к программе, которая ее запустила. Он может быть проанализирован в вызывающей программе с помощью функции DOSEXITCODE.
Функция DOSEXITCODE. Возвращает значение типа WORD – êîä çàâåðøåíèÿ ïîä÷èíåííîé программы. 
Обращение: 
DOSEX1TCODE

Вверх


11.5. ЗАПУСК ВНЕШНИХ ПРОГРАММ


 
Из программы, написанной на Турбо Паскале, можно запустить любую другую готовую к работе программу. Для этого используется процедура ЕХЕС из библиотечного модуля DOS. Формат обращения к процедуре:
                     ЕХЕС (<имя>,<параметры>)
Здесь <имя> - выражение типа STRING; имя файла с вызываемой программой;
<параметры> - выражение типа STRING; параметры вызова.
Имени запускаемой программы может предшествовать путь к файлу. Параметры передаются запускаемой программе в виде текстовой строки и могут быть проанализированы ею с помощью двух следующих функций.
Функция PARAMCOUNT. Возвращает общее количество параметров вызова программы (значение типа WORD). Обращение: PARAMCOUNT
Параметры вызова обычно следуют в командной строке ДОС сразу за именем вызываемой программы и отделяются от этого имени и друг от друга пробелами, например:
                          C:\>TURBO MYPROG.PAS 
                           C:\>S1AM A:\SYSTEM1.SIA
Здесь MYPROG.PAS и A:\SYSTEMJ.SJA - параметры, передаваемые программам TURBO и SIAM.
При вызове программы непосредственно из среды Турбо Паскаля ей можно передать параметры с помощью опции OPTIONS/PARAMETERS (см. прил.1).
Функция PARAMSTR. Возвращает значение типа STRING, соответствующее нужному параметру вызова. Формат обращения: PARAMSTR (<N>)
Здесь <N> - выражение типа WORD; порядковый номер параметра.
Заметим, что программе всегда передается параметр, соответствующий N = 0. В этом параметре ДОС сообщает полное имя запущенной программы с указанием диска и каталога, откуда она была загружена.
Использование процедуры ЕХЕС имеет ряд особенностей. Прежде всего необходимо отметить, что сама вызывающая программа остается резидентной в памяти, поэтому она не должна занимать всю оперативную память. Объем выделяемой программе памяти регулируется опцией OPTIONS/MEMORY SIZE (см. прил.1). По умолчанию параметры LOW HEAP LIMIT и HIGH HE АР LIMIT этой опции таковы (соответственно 0 и 655360 байт), что вызывающая программа, написанная на Турбо Паскале, занимает весь доступный объем памяти, и вызываемая программа не будет загружена. Полезно включить в текст вызывающей программы директиву компилятора, в которой изменяются принятые по умолчанию размеры памяти. Например, так:
{$M 2048, 0, 0}
Такая директива ограничивает используемую программой область стека величиной 2 Кбайта и исключает возможность использования в ней динамической памяти. Разумеется, Вы можете установить и другие знания параметров в этой директиве.
Специфические особенности исполнения Турбо Паскалевых программ требуют изменения стандартных значений некоторых векторов прерываний. К ним относятся векторы со следующими шестнадцатиричными номерами:

                           $00, $02, $18, $23, $24, $34, $35, $36, $37, 
                           $38, $39, $ЗА, $ЗВ, $ЗС, $3D, $3E, $3F, $75.

     Начальные значения этих векторов сохраняются в восемнадцати (переменных с именами SAVEINTXX из библиотечного модуля SYSTEM, где XX - шестнадцатиричный номер прерывания. Поэтому непосредстнно перед запуском внешней программы и сразу после возврата из нее рекомендуется вызывать библиотечную процедуру без параметров WAPVECTORS, которая обменивает содержимое векторов прерывания перечисленных переменных.
Программа примера 23 читает с клавиатуры любую команду ДОС, затем вызывает командный процессор COMMAND.COM операционной системы и передает ему эту команду.
Обратите внимание: для указания файла COMMAND.COM и пути к нему используется обращение к библиотечной функции GETENV, с помощью которой можно получить параметры настройки операционной системы. В частности, параметр COMSPEC определяет спецификацию файла, содержащего командный процессор.
П р и м е р 23.
                         {$М 1024, 0, 0} 
                          Uses DOS: 
                                   var
                                            st : string[79];
                                BEGIN
                            write ('Введите команду ДОС: '); readln (st);
                         if st <> " then
                                       begi n
                                            st := '/C '+st:
                                                SwapVectors;
                                        Exec (GetEnv (‘COMSPEC’), st); 
                                     SwapVectors
                                  End
                          END.
Функция ENVCOUNT. Возвращает значение типа INTEGER, в котором содержится общее количество установленных в ДОС параметров.
Обращение:
ENVCOUNT
Функция ENVSTR. Возвращает значение типа STRING, содержащее имя и значение нужного параметра настройки операционной системы. Формат обращения:
ENVSTR (<N>)
Здесь <N> - выражение типа INTEGER; номер параметра.

Эта функция возвращает строку типа NAME=VALUE, где NAME -имя, a VALVE - значение соответствующего параметра настройки.
Функция GETENV. Возвращает значение типа STRING, в котором содержится параметр настройки ДОС. Формат обращения: GETENV (<имя>)
Здесь <имя> - выражение типа STRING', имя параметра.
Эта функция имеет параметр обращения NAME, а возвращает значение VALVE (см. функцию ENVSTR).

 

                         Вверх


11.6. ОВЕРЛЕЙ


   Как отмечалось в гл.9, максимальный размер модуля не может превышать 64 Кбайта, однако количество модулей не ограничено, что дает возможность разрабатывать весьма крупные программы, занимающие, например, всю доступную оперативную память ПК (приблизительно 580 Кбайт). Тем не менее, в некоторых случаях и этот объем может оказаться недостаточным. Турбо Паскаль предоставляет в распоряжение программиста простой и достаточно эффективный механизм оверлея, с помощью которого можно создавать программы практически неограниченной длины (следует оговориться, что речь идет только о длине кода программы; два важных размера - длина сегмента данных и программного стека - в Турбо Паскале не могут превышать 64 Кбайта независимо от структуры программы).
   Оверлей - это такой способ использования оперативной памяти, при котором в один и тот же участок памяти, называемый оверлейным буфером, попеременно по мере надобности загружаются различные оверлейные (перекрывающиеся) модули. При этом все оверлейные модули в готовом к работе виде хранятся на диске, а в оперативной памяти в каждый момент находится лишь один активный модуль и, возможно, небольшое число неактивных.
   Пусть, например, программа (рис. 12) состоит из главной части MAIN и двух модулей А и В, a LM, LA и LB - соответственно длина главной части и обоих модулей, причем LA > LB. Тогда неоверлейная программа займет в памяти LM+LA+LB байт, в то время как оверлейная программа - лишь LM+LA байт.
Рис. 12. Пример структуры программ: а) неоверлейная; Ь) оверлейная

  При исполнении оверлейной программы в память первоначально загружается главная часть и один из модулей, например, модуль А. Если в процессе исполнения программы встретится обращение к модулю В, программа приостановит свою работу, с диска в оверлейный буфер будет загружен модуль В (модуль А при этом частично уничтожается), после чего программа продолжит свою работу. Если в дальнейшем встретится обращение к А, точно таким же образом будет загружен модуль А, причем загрузка нужных модулей в оверлейный буфер осуществляется автоматически и программисту не нужно об этом заботиться.
     Описанный механизм выявляет главное преимущество оверлейной структуры: объем оперативной памяти, занимаемой оверлейной программой, определяется длиной ее главной части и наибольшего из перекрывающихся модулей, в то время как при неоверлейной структуре в этот объем входит суммарная длина всех модулей. Чем больше в программе оверлейных модулей и чем меньше длина наибольшего из них, тем больший выигрыш в памяти дает оверлейная структура. Однако совершенно очевиден и главный недостаток таких структур: на каждую загрузку оверлей одного модуля с диска в оверлейный буфер требуется дополнительное время, поэтому оверлейная программа будет исполняться с меньшей скоростью;
Работа оверлейных программ обеспечивается с помощью процедур и функций библиотечного модуля OVERLAY, входящего в библиотечный файл TURBO.TPL.
При создании оверлейных программ нужно руководствоваться следующей последовательностью действий.
    Выделить главную часть программы и разбить оставшуюся часть на несколько модулей. Отметим, что никаких дополнительных ограничений на модули, по сравнению с описанными в гл.9, не накладывается за одним исключением: в оверлейных модулях нельзя использовать процедуры обработки прерываний. Желательно продумать состав модулей таким образом, чтобы по возможности минимизировать количество их перезагрузок в оверлейный буфер в процессе исполнения программы.
   В главной части программы указать с помощью директив компилятора вида {$О <имя>} те модули, которые будут оверлейными, например:
                Program Mai n:
              Uses CRT, DOS, Graph, Overlay, UnitA, UnltB;
                 {$0 DOS}
               {$0 UnltAV}
                  {$0 UnitB}

         Следует подчеркнуть, что из всех стандартных библиотечных модулей, описанных в гл.9, только один модуль DOS может быть оверлейным, остальные модули (CRT, Graph, Printer и т.д.) не могут объявляться оверлейными.
• Ïðåäóñìîòðåòü ïåðåä ïåðâûì ïî ëîãèêå работы программы обращением к оверлейному модулю вызов процедуры инициализации оверлея OVRINIT. Здесь же, если это необходимо, следует установить размер оверлейного буфера и указать возможность использования расширенной памяти (см. ниже).
•  íà÷àëå ãëàâíîé ïðîãðàììû è êàæäîãî оверлейного модуля необходимо поместить директивы компилятора {$О+} и {$F+} или установить опции OPTIONS /COMPILE/FORCE FAR CALLS и OPTIONS/ COMPILE/ OVERLAYS ALLOWED (см. прил.1) в активное состояние, после чего откомпилировать программу на диск, программа готова к работе.
    Таким образом, все процедуры и функции в оверлейной программе должны использовать дальнюю модель вызова - это обязательное условие. Отметим, что попытка компиляции оверлейного модуля, в начале которого отсутствует директива {$О+} (предполагается, что опция среды OPTIONS/COMPILE/OVERLAY ALLOWED неактивна), будет обнаружена компилятором, в то время как неправильная (ближняя) модель вызова оверлейных подпрограмм компилятором не контролируется и может привести к непредсказуемым результатам при исполнении программы.
Далее, инициация оверлея (осуществляется вызовом процедуры OVRINIT, см. ниже) должна происходить до вызова любого из оверлейных модулей. Это требование кажется тривиальным, однако множество проблем в оверлейных программах обычно связано именно с ним. Дело в том, что обращение к оверлейному модулю может происходить еще да начала работы основной программы: напомню, что любой модуль (в том числе и оверлейный) может иметь инициирующую часть, которая исполняется перед началом работы основной программы. В связи с этим рекомендую придерживаться следующего простого правила: никогда не используйте оператор BEGIN в конце модуля, если Вам нет нужды в инициирующих действиях; пустая инициирующая часть содержит пустой оператор, которому будет передано управление на этапе инициации. Таким образом, пустая инициирующая часть оверлейного модуля очень часто может вызывать сообщение об ошибке периода исполнения с кодом 208 (не установлена система управления оверлеем). Как же быть, если в оверлейном модуле все-таки нужна инициирующая часть? В этом случае можно рекомендовать следующий прием. Создайте лишний модуль, в котором будут пустыми все части, кроме инициирующей. В этой части разместите команды инициации оверлея. Новый модуль не должен быть оверлейным и его имя должно стоять в предложении USES основной программы перед именем любого оверлейного модуля.
Процедура OVRINIT. Инициализирует оверлейный файл. Обращение:
OVRINIT(<имя>)
   Здесь <имя> - выражение типа STRING', имя файла с оверлейной частью программы. При компиляции оверлейной программы создается специальный файл с именем, совпадающим с именем главной программы, и расширением .OVR. В этот файл компилятор помещает все оверлейные модули, из него же эти модули загружаются в оверлейный буфер в процессе исполнения программы. Файл с оверлейной частью программы должен размещаться в том же каталоге, что и файле главной частью (с расширением .ЕХЕ). Отметим, что имя оверлейного файла необходимо дополнять расширением (даже если это стандартное расширение .OVR\).
Обычно размер оверлейного буфера определяется автоматически таким образом, чтобы в нем мог разместиться самый крупный из всех оверлейных модулей. Программист может увеличить размер буфера. Тогда при загрузке в буфер очередного модуля программа проверит, достаточно ли в буфере свободного места и, если достаточно, загрузит новый модуль сразу за старым, который, таким образом, не будет уничтожен. Такой механизм способствует минимизации потерь времени на перезагрузку модулей. Если установлен очень большой размер буфера, то в нем, возможно, смогут разместиться все оверлейные модули и потери времени будут сведены к нулю, однако в этом случае оверлейная структура становится просто ненужной.
Процедура OVSSETBUF. Устанавливает больший размер оверлейного буфера. Формат обращения:
              OVRSETBUF (<длина>)
  Здесь <длина> - выражение типа LONGINT; новая длина буфера. Новая длина задается в байтах и не может быть меньше той, которую устанавливает сама система автоматически. Расширение буфера идет за счет соответствующего уменьшения доступной динамической памяти, поэтому к моменту вызова этой процедуры куча должна быть пустой.
Функция OVRGETBUF. Возвращает значение типа LONGINT, содержащее текущий размер оверлейного буфера. Обращение:
              OVRGETBUF
   Процедура OVRINITEMS. Обеспечивает использование дополнительной памяти. Если Ваш ПК относится к классу компьютеров типа IBM PC/AT и в нем имеется дополнительная память (общий объем памяти -свыше 1024 Кбайт), Вы можете использовать эту память для размещения в ней оверлейного файла. О VR. Поскольку время доступа к дополнительной памяти значительно меньше времени чтения с диска, такое размещение увеличивает скорость исполнения оверлейной программы. Обращение:
              OVRINITEMS
    При обращении к этой процедуре программа прежде всего проверит, подключена ли к Вашему ПК так называемая EМS-память (Expanded Memory Specification - расширенная память, удовлетворяющая стандарту фирмы Loius/Intel/Microsoft) нужного для размещения оверлейной части объема. Если это так, то оверлейный файл считывается в расширенную память, сам файл закрывается, и программа будет считывать оверлейные модули из этой памяти. Если же расширенная память отсутствует или ее объем недостаточен для размещения оверлейного файла, обращение к процедуре игнорируется, и программа будет считывать оверлейные модули с диска.
    Все управление оверлеем осуществляется стандартной программой, которая называется администратором оверлея. В версии 6.0 внесен ряд изменений в логику работы администратора оверлея, делающих эту программу более гибкой. Прежде всего, в оверлейном буфере выделяется так называемая контрольная зона, в которую перемещается оверлейный модуль из оверлейного буфера в том случае, когда пространство этого буфера понадобилось для размещения очередного оверлея. Если в момент, когда оверлей находится в контрольной зоне, программа вновь обратится к нему, он вернется на свое старое место и таким образом затраты времени на обмен данными с диском будут уменьшены. Программист может задать размер контрольной зоны с помощью обращения к процедуре OVRSETRETRY и получить этот размер с помощью функции OVRGETRETRY. Обычно размер контрольной зоны составляет от одной трети до половины размера оверлейного буфера. Вы можете подобрать этот размер экспериментально в ходе пробного прогона программы. Для этого используются две переменные модуля OVERLAY: var
OvrTrapCount: Word; {Счетчик обращений к администратору} OvrLoadCount: Word; {Счетчик загрузок в оверлейный буфер} Всякое обращение программы к оверлейному модулю, которого нет в оверлейном буфере (в том числе и к модулю, находящемуся в контрольной зоне), приводит к наращиванию содержимого переменной OVRTRAPCOUNT на единицу. Всякая загрузка оверлейного модуля из файла в буфер увеличивает на единицу счетчик OVRLOADCOUNT.
     В интерфейсной части модуля OVERLAY объявлены еще три переменные, которые могут оказаться полезными для некоторых применений. 
                    type
              OvrReadFunc = Function (OvrSeg: Word): Integer; 
                    var
               OvrReadBuf: OvrReadFunc; {Функция чтения из оверлейного файла}
                OvrResult: Integer: {Признак ошибки оверлея}
               OvrFileMode: Byte; {Способ доступа к оверлейному файлу}

  Переменная OVRRESULT содержит код, указывающий на успех или неуспех каждой очередной операции администратора оверлея. Значения этой переменной могут быть такими: О - операция прошла успешно;
-1 - общая ошибка;
-2 - не найден оверлейный файл;
-3 - не хватает памяти для оверлейного буфера;
-4 - ошибка чтения-записи оверлейного файла;
-5 - не работает драйвер EMS-памяти;
-6 - не хватает JEAfS-памяти.
Переменная OVRFILEMODE обычно содержит 0, что трактуется как возможность доступа к оверлейному файлу только для чтения информации. Перед вызовом процедуры О VRJN1T программа может установить
другое значение этой переменной и таким образом изменить доступ к
файлу, что бывает необходимым, если ПК подключен к сети ЭВМ.
В переменной OVRREADBUF содержится имя функции, к которой обращается администратор оверлея при каждом чтении из оверлейного файла. Программа может перехватить обращение к этой функции и проанализировать результат операции. Для этого необходимо в основной программе сохранить имя стандартной функции чтения в глобальной переменной типа OVRREADFUNC и поместить в переменную OVRREADBUF для новой функции. В эту новую функцию администратор будет передавать управление всякий раз, когда появится необходимость чтения из оверлейного файла. Программа может проверить состояние дисков перед исполнением операции (например, наличие нужного сменного диска), выполнить саму операцию (путем вызова функции, сохраненной в глобальной переменной), проверить результат обращения и предпринять необходимые действия. Отметим, что нормальное завершение операции чтения указывается нулевым значением функции чтения, ненулевое значение означает ту или иную ошибку; код ошибки стандартен для ДОС (см. прил. 3) и/или для драйвера EMS-памяти.
При желании Вы можете пристыковать оверлейный файл в конец ЕХЕ-
файла основной программы. При этом следует учесть, что интегрированная
среда пристыковывает в конец EХЕ-файла отладочные таблицы, поэтому про
грамму и все ее модули следует компилировать в режиме отключенных опций
OPTIONS/COMPILER/DEBUGINFORMATION и OPTIONS/COMPILER/LOCAL SYMBOLS (см. прил. 1.2.8). Для объединения ЕХЕ-файла с оверлейным файлом необходимо дать такую команду
ДОС:
COPY IB NAME.EXE+NAME.OVR
ÇäåñüNAME.EXE – èìÿ EÕE-ôàéëà, NAME.OVR - èìÿ оверлейного файла. Чтобы оверлеи читались из EХE-файла, нужно просто у казать имя этого файла при обращении к OVRINIT:
OvrInit(ParamStr(0));
(в программу всегда передается параметр ParamStr(O), в котором ДОС сообщает полное имя запущенной программы - с указанием диска и каталога, откуда была загружена программа).
 
                   Вверх

11.7. ПРЯМОЕ ОБРАЩЕНИЕ К ПАМЯТИ И ПОРТАМ ВВОДА-ВЫВОДА


   В Турбо Паскале имеется пять предварительно объявленных массивов: MEM, MEMW, MEML, PORT и PORTW. Первые три обеспечивают доступ к любому участку оперативной памяти по абсолютному адресу, два других -доступ к портам ввода-вывода.
Компонентами массива MEM являются данные типа BYTE, массива MEM W - типа WORD, массива MEML - типа LONCINT. Обращение к элементам этих массивов, т.е. их индексация, имеет специальный вид: каждый индекс представляет собой абсолютный адрес и состоит из двух выражений типа WORD; первое дает сегментную часть адреса, второе смещение; выражения разделяются двоеточием. Например:
                    Меm[$0000:$1000] := 0;
                         DataMem := MemW [Seg(p):0fs(p)];
               MemLong := MemL [64:i*SizeOf(real)];

  Как следует из технического описания операционной системы MS DOS, в памяти по адресу $FOOO:$FFFE располагается байт-указатель типа компьютера. Следующая программа (пример 24) прочтет этот байт и выведет на экран тип Вашего ПК. Пример 24.
       BEGIN
      Write (' Тип компьютера: '); 
        case Mem [$FOOO:SFFFE] of 
          $FF: writeln (‘PC’); 
           $FE: writeln ('XT'); 
                 $FD writeln ('PCjr'); 
                         $FC writeln I 'AT'); 
                    $F9 writeln ('совместимый с PC') 
                     end 
             End.

    Компонентами массива PORT являются байты (тип BYTE), а массива PORTW - слова (тип WORD). Индексами этих массивов должно быть выражение типа BYTE, указывающее номер нужного порта. Присвоение значения элементу массива PORT или РОRTW приведет к записи в порт, упоминание элемента в выражении - к чтению из порта. Компоненты массивов PORT и PORTW нельзя передавать в качестве параметров процедурам или функциям. Эти идентификаторы не употребляются без индексных выражений.
 
                              Вверх

11.8. ВСТРОЕННЫЙ АССЕМБЛЕР


    В версии 6.0 системы Турбо Паскаль появилась возможность писать программы или их части на языке встроенного ассемблера. В этом разделе приводятся минимальные сведения об этом средстве Турбо Паскаля. Более полное руководство с примерами см. в кн. З «Программирование на Турбо Паскале».
Фрагменты, написанные на языке ассемблера, могут произвольным образом чередоваться с частями, написанными на Турбо Паскале, однако программист должен строго соблюдать следующее условие: к моменту завершения ассемблерной части содержимое регистров ВР, SP, SS и DS центрального процессора должно быть точно таким, каким оно было при входе в ассемблерную часть программы.
  Ассемблерная часть начинается зарезервированным словом ASM и заканчивается словом END; между этими словами могут размещаться одно или несколько предложений языка ассемблера:
             asm
                <Предложения языка ассемблера> 
          end;
   Каждое предложение размещается на отдельной строке. Допускается также размещать несколько предложений в одной строке, разделяя их символами «;» или комментариями, принятыми в Турбо Паскале.
  В общем случае каждое предложение имеет следующую структуру: <метка> <префиксы> <код инструкций <операнды>
Любая из этих частей может быть опущена.
Часть <метка> именует команду программы и определяет локальную метку. Метки используются в качестве операндов в командах передачи управления. Помимо локальных меток в операндах таких команд могут также использоваться глобальные метки. Глобальные метки - это обычные метки Турбо Паскаля (описываются в разделе описаний меток после слова LABEL). Глобальные метки доступны любым ассемблерным фрагментам внутри блока, в котором они описаны. В отличие от них локальные метки известны только внутри данного ассемблерного фрагмента. Локальные метки начинаются символом @ (поскольку этот символ не может использоваться в метках Турбо Паскаля, локальные метки легко распознаются ассемблером и не нуждаются в предварительном описании) и составляются по правилам определения идентификаторов Турбо Паскаля. В конце локальной метки ставится символ «:».
   Часть <префиксы> может содержать не более трех следующих префиксных команд языка ассемблера:
LOCK - выработать признак захвата шины;
REP - повторить строковую команду заданное число раз (количество повторений определяется содержимым регистра СХ);
REPE - повторять строковую команду заданное число раз или до тех пор, пока флаг ZF-1 (пока установлен признак равенства);
REPZ - синоним REPE;
REPNE - повторять строковую команду заданное число раз или до тех пор, пока флаг ZF-0 (пока сброшен признак равенства);
REPNZ - синоним REPNE;
SEGCS - использовать сегментную часть регистра CS;
SEGDS - использовать сегментную часть регистра DS;
SEGES - использовать сегментную часть регистра ES;
SEGSC - использовать сегментную часть регистра SC.
Если в предложении опущена часть <код инструкции>, префиксы действуют на следующее ассемблерное предложение. Если в предложении указано больше трех префиксов, «лишние» префиксы игнорируются.
Часть <код инструкций> определяет код команды центрального процессора ПК и/или арифметического сопроцессора (Intel 8086, 8088, 80286, 8087, 80287 или их аналоги).
Часть <операнды> содержит необходимые операнды данной команды процессора (сопроцессора). Операндами считаются 32-разрядные результаты вычисления операндных выражений. Последние составляются по правилам формирования выражений в Турбо Паскале со следующими отличиями:
• ассемблер имеет свое множество зарезервированных слов, не совпадающее с множеством зарезервированных слов Турбо Паскаля:
АН CL FAR SEG СН
AL CS HIGH SHL ES
AND СХ LOW SHR QWORD
AX DH MOD SI WORD
BH DI NEAR SP XOR
BL DL NOT SS
BP DS OFFSET ST
BX DWORD OR TBYTE
BYTE DX PTR TYPE
Зарезервированные слова всегда имеют приоритет над идентификаторами пользователя;
• идентификаторы переменных определяют 32-разрядный указатель, содержащий адрес переменной;
• результат вычисления есть целое 32-разрядное число.
Любую подпрограмму Вы можете объявить как ассемблерную, т.е. реализуемую целиком на языке встроенного ассемблера. Для этого сразу за заголовком подпрограммы ставится стандартная директива ASSEMBLER. Такая директива отменяет стандартные действия компилятора Турбо Паскаля, с помощью которых осуществляется связь с подпрограммой, в том числе:
• параметры-значения строкового типа и длиной в 1, 2 или 4 байта не будут копироваться во временную память; это означает, что такие параметры внутри ассемблерной части программы должны рассматриваться как параметры-переменные;
• не создпется локальный стек, т.е. все изменения локальных переменных становятся доступны вызывающей программе;
• для ассемблерных функций не создается локальная переменная RESULT, поэтому ссылка на @RESULT является ошибкой; исключение составляет функция, возвращающая результат строкового типа;
• автоматический генерируется следующий код для связи с внешними подпрограммами:
Push BP
mov BP.SP
sub SP, Locals
Mov SP.BP
Pop BP
Ret Params
где LOCALS - размер локальных переменных, PARAMS - размер параметров. Если LOCALS и PARAMS равны нулю (процедура не использует локальные переменные и не имеет параметров обращения), входной код отсутствует, а выходной состоит из инструкции RET.
Функции, объявленные с директивой ASSEMBLER, возвращают результат следующим образом:
• функция порядкового типа - в регистре AL (8-битное значение), в регистре АХ (16-битное значение) или в ВХ:АХ (32-битное значение);
• функция типа REAL - в регистрах DX:BX:AX;
• функция вещественного типа(кроме REAL) - в регистре ST(0) сопроцессора;
• результат типа указатель - в DX:BX;
• результат строкового типа - через временную память, на которую указывает ©RESULT.
 
                          Вверх    оглавление
Хостинг от uCoz