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



 

Часть 20

Глава 18. Вопросы управления
В данной главе подробно описываются различные способы реализации в Турбо Паскале управления программой. Сюда включены соглашения по вызовам, процедуры выхода, обработка прерываний и обработка ошибок. 
                      Соглашения по вызовам

     Параметры процедурам и функциям передаются через стек. Перед вызовом процедуры или функции параметры помещаются в стек в порядке их описания. Перед выходом из процедуры или функции все параметры извлекаются из стека. 
     Примерный вызов процедуры или функции можно представить следующим образом: 
     PUSH Param1
     PUSH Param2
      .
      .
      .
     PUSH ParamX
     Call ProcOrFunc

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

     Параметры-переменные (параметры var) всегда передаются по ссылке, то есть указатель ссылается на ячейку памяти с фактическим значением. 
                       Параметры-значения

     Параметры-значения передаются по значению или по ссылке, в зависимости от их типа и размера. В общем случае, если параметр-значение занимает 1, 2 или 4 байта, то значение помещается непосредственно в стек. В противном случае в стек помещается указат
ель на значение, а процедура или функция копирует затем значение в локальную ячейку памяти. 
     В процессоре 8086 не поддерживаются байтовые инструкции РUSН и РОР, поэтому байтовые параметры всегда передаются в стеке, как слова. Младший байт слова содержит значение, а старший байт слова свободен (и неопределен). 
     Значение или параметр целого типа передается как байт, слово или двойное слово. При этом используется такой же формат, как для представления переменной целого типа. (Для двойных слов старшее слово помещается в стек перед младшим словом, так что млад
шее слово размещается в более младших адресах.) 
     Параметр символьного типа (Char) передается, как байт без знака. 
     Параметр булевского типа передается, как байт со значением 0 или 1. 
     Параметр перечислимого типа передается, как байт без знака, если нумерация не превышает 256. В противном случае он передается, как слово без знака. 
     Параметр вещественного типа (вещественное значение Real) передается, как 6 байт в стеке, представляя собой, таким образом, исключение из того правила, что в непосредственно в стеке передаются только 1, 2 или 4 байта. 
     В версии 4.0 Турбо Паскаля параметры тех типов, которые используются в процессоре 8087 (значения с одинарной, двойной или повышенной точностью или сложного типа Single, Double, Extended, Comp), передаются через внутренний стек математического сопроц
ессора 80х87. В целях совместимости в данной версии используется стек процессора 8086. 
     Параметр типа указатель передается в виде двойного слова (адрес сегмента помещается в стек перед смещением, так что часть, представляющая собой смещение, заканчивается в самом младшем адресе). 
     Параметр строкового типа передается, как указатель на значение. 
     Параметр множественного типа передается в виде указателя на "неупакованное" множество длиной 32 байта. 
     Массив или запись из 1, 2 или 4 байт помещается непосредственно в стек. Другие массивы и записи передаются, как указатели на значения. 
                       Результаты функций

     Результаты функций перечислимого типа (целые, символьные, булевские, перечислимых типов) возвращаются в регистрах центрального процессора: байты возвращаются в регистре AL, слова - в регистре AХ, двойные слова - в DX:AX (старшее слово - в DХ, младше
е - в AХ). 
     Результаты функций вещественного типа (значения вещественного типа Real) возвращаются в регистрах DХ:ВХ:AX (старшее слово - в регистре DХ, среднее слово - в регистре ВХ, младшее слово - в AX). 
     Результаты функции, имеющие один из типов, использующихся в процессоре 8087, (значения с одинарной, двойной или повышенной точностью или сложного типа - Single, Double, Extended, Comp), возвращаются в регистре вершины стека сопроцессора 80х87 (SТ(0)
). 
     Резльтаты функции типа указатель возвращаются в регистрах DХ:AX (адрес сегмента - в DХ, а смещение - в AX). 
     Что касается результата функции строкового типа, то вызывающая программа помещает в стек перед передачей каких-либо параметров временную ячейку памяти, а функция возвращает строковое значение в этой временной ячейке. Функция не должна удалять указат
ель. 
                 Ближние и дальние типы вызовов

     В центральном процессоре 8086 поддерживается два типа вызовов и инструкций возврата управления - ближние и дальние. Ближние вызовы передают управление другой ячейке в пределах того же программного сегмента, а дальние вызовы позволяют перейти в друго
й программный сегмент. 
     Инструкция ближнего обращения СALL помещает в стек 16-ьитовый адрес возврата (только смещение), а инструкция дальнего вызова помещает в стек 32-битовый адрес возврата (адрес сегмента и смещение). Соответствующая инструкция RET извлекает из стека тол
ько смещение или адрес сегмента и смещение. 
     На основе описания процедуры в Турбо Паскале будет автоматически выбираться правильный тип обращения. Процедуры, описанные в интерфейсной секции модуля соответствуют дальнему обращению и могут вызываться из других блоков. Процедуры, описанные в прог
рамме в секции реализации модуля, являются ближними и могут вызываться только из этой программы или данного модуля. 
     Для некоторых конкретных целей можно потребовать, чтобы процедура имела дальний тип вызова. Например, процедура выхода, драйверы устройств для текстовых файлов и другие средства, использующие указатели на процедуры. Директива компилятора {$F+} указы
вает на необходимость использования дальнего типа вызовов. Процедуры или функции, скомпилированные с данной директивой, всегда будут иметь дальний тип вызова. При использовании в Турбо Паскале директивы {$F-} правильная схема вызова будет выбираться авто
матически. По умолчанию назначается режим {$F-}. 
                  Вложенные процедуры и функции

     Процедура или функция считается вложенной, когда она описывается внутри другой процедуры или функции. По умолчанию вложенные процедуры и функции всегда используют ближний тип вызова (NEAR), поскольку они доступны только внутри определенной процедуры
 или функции в том же сегменте кода. Однако в оверлейных задачах обычно для того, чтобы обеспечить для всех процедур и функций дальний тип вызова (FAR), используется директива {$F+}. 
     При вызове вложенной процедуры или функции компилятор непосредственно перед инструкцией CALL генерирует инструкцию PUSH BP, фактически передавая регистр BP вызывающей программы в качестве дополнительного параметра. После того, как вызываемая процеду
ра установит свой собственный регистр BP, регистр ВР вызывающей процедуры доступен, как слово, сохраненное в [BP+4] или в [BP+6] (если процедура имеет дальний тип вызова). Используя связь через [BP+4] и [BP+6], вызываемая процедура может получить доступ 
к локальным переменным в границах стека вызывающей процедуры. Следующий пример показывает, как можно получить доступ к локальным переменным из оператора inline во вложенной процедуре: 
     {$F+}
     procedure PA;
     var
       IntA: integer;
     {$F+}
     procedure B;
     var
       IntB: integer;
     {$F-}
     procedure C;
     var
       IntC: integer;
     begin
     inline(
       $8B/$46//      { MOV AX,[BP+IntC]     ;AX = IntC   }
       $8B/$5E/$04/         { MOV BX,[BP+4]        ;BX = стек В }
       $36/$8b/$47//  { MOV AX,SS:[BX+IntB]  ;AX = IntB   }
       $8B/$5E/$04/         { MOV BX,[BP+4]        ;BX = стек B }
       $36/8B/$5F/$06/      { MOV BX,SS:[BX+6]     ;BX = стек A }
       $36/$8B/$47/); { MOV AX,SS:[BX+IntA]  ;AX =IntA    }
     end;
     begin end;
     begin end;

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

     Стандартным кодом входа и выхода для процедуры или функции, использующей ближнюю модель вызова является следующий код: 
     PUSH BP              ; сохранить регистр ВР
     MOV BP,SP            ; установить кадр стека
     SUB SP,LocalSize     ; выделить память для локальных переменных
     .
     .
     .
     MOV SP,BP            ; освободить память, выделенную для
                          ; локальных переменных
     POP BP               ; восстановить регистр ВР
     RET ParamSize        ; удалить параметры и выполнить возврат
                          ; управления


     Примечание: Информацию по использованию кода входа и выхода в DLL можно найти в Главе 10 "Динамически компонуемые библиотеки" 
     Код входа и выхода для подпрограммы с дальним типом вызова в состоянии {$W-} совпадает с этим кодом для подпрограммы, использующей ближнюю модель вызова, но для возврата из подпрограммы используется инструкция дальнего возврата RETF. 
     В состоянии {$W+} (по умолчанию) в подпрограмме, испльзующей дальнюю модель вызова, код выхода и выхода выглядит следующим образом: 
     INC BP               ; указывает на кадр стека FAR
     PUSH BP              ; сохранить регистр ВР
     MOV BP,SP            ; установить кадр стека
     PUSH DS              ; сохранить DS
     SUB SP,LocalSize     ; выделить память для локальных переменных
     .
     .
     .
     MOV SP,BP            ; освободить память, выделенную для
                          ; локальных переменных
     POP BP               ; восстановить регистр ВР
     DEC PB               ; настроить BP
     RETF ParamSize       ; удалить параметры и выполнить возврат
                          ; управления

     Код входа и выхода для экспортируемой подпрограммы (процедуры или функции, скомпилированной с директивой компилятора export) выглядит следующим образом: 
     mov     AXC,DS      ; загрузить селектор DS в AX
     nop                 ; дополнительное пространство для
                         ; корректировок
     inc     BP          ; указывает на дальний кадр стека
     push    BP          ; сохранить BP
     mov     BP,SP       ; установить кадр стека
     push    DS          ; сохранить DS
     mov     DS,AX       ; инициализация регистра DS
     sub     SP,LocalSize ; распределени локальных переменных
     .                   ; (если они имеются)
     .
     .
     pop     DI          ; восстановить DI
     pop     SI          ; восстановить SI
     lea     SP,[BP-2]   ; освободить память, выделенную для
                         ; локальных переменных
     pop     DS          ; восстановить DS
     pop     BP          ; восстановить BP
     dec     BP          ; настроить регистр BP
     retf    ParamSize   ; удаление параметров и возврат
                         ; управления

     Для всех моделей вызова, если подпрограмма не содержит локальных переменных, инструкции выделения и освобождения памяти для локальных переменных  можно опустить. 
     Чтобы различать ближний и дальний кадр стека, Windows требует, чтобы все кадры стека (включая кадры стека экспортируемых подпрограмм) сохраняли в слове по адресу [BP+0] нечетное значение BP. Кроме того, Windows требует, чтобы слово по адресу [BP-2] 
содержало селектор сегмента данных вызывающей программы. Это объявняет использование инструкций INC BP, PUSH DS b DEC BP на входе и выходе для подпрограмм far и export. 
     Экспортируемая подпрограмма должна сохранять регистры SI и DI, поэтому Турбо Паскаль включает в код входа и выхода инструкции, которые заносят в стек и извлекают эти регистры из стека. Для экспортируемых подпрограмм Windows требует, чтобы первые три
 байта подпрограммы содержали последовательность инструкций MOV AX,DS с последующей инструкцией NOP. Если экспортируемая подпрограмма в прикладной программе существует (подпрограмма вызова), Windows изменяет первые три байта на три инструкции NOP, чтобы 
подготовить подпрограмму для использования ее функцией Windows MakeProcInstance. Если экспортируемая подпрограмма имеется в библиотеке, Windows изменяет первые три байта в инструкции MOV AX,xxxx, где xxxx - селектор (адрес сегмента) сегмента динамических
 локальных данных библиотеки. 
            Соглашения по сохранению регистров

     В процедурах и функциях следует сохранять регистры ВР, SР, SS и DS. Значения всех других регистров можно изменять. Кроме того, экспортируемые подпрограммы должны сохранять регистры SI и DI. 
                        Процедуры выхода

     В помощью процедур выхода (или процедур завершения) вы можете управлять процессом завершения работы программы. Это полезно в том случае, когда вы хотите перед прекращением работы программы обеспечить выполнение определенных действий (типичным пример
ом является обновление и закрытие файлов). 
     Реализовать процедуру выхода вам позволяет переменная- указатель EхitProc. Процедура выхода всегда получает вызов при завершении работы программы, независимо от того, является ли это завершение нормальным окончанием работы программы, завершением пос
ле обращения к функции Наlt, или работа программы прекратилась из-за ошибки во время выполнения. 
     Параметры для процедуры выхода не требуются, и для того, чтобы использовался дальний тип вызова, она должна компилироваться с указанием директивы компилятора {$F+}. 
     Когда процедура выхода должным образом реализована, она в действительности становится частью цепочки процедур выхода. Эта цепочка позволяет реализовать процедуры выхода как для модулей, так и для программ. В некоторых модулях процедура выхода реализ
уется, как часть самого модуля, а выполнение некоторых завершающих действий после выхода из модуля, например, закрытие файлов или восстановление векторов прерываний, возлагается на конкретную процедуру. Процедуры в цепочке выхода выполняются в последоват
ельности, обратной порядку их реализации. Этим обеспечивается, что операторы выхода одного блока не выполняются, пока не будут выполнены операторы выхода какого-либо зависящего от него модуля. 
     Чтобы сохранить цепочку выхода в неприкосновенности, вы должны перед изменением указателя EхitPrос на адрес вашей собственной процедуры сохранить текущее содержимое этого указателя. Далее, непосредственно перед возвратом управления ваша процедура вы
хода должна должна восстановить сохраненное значение EхitProc. В следующей программе показаны основы метода реализации такой процедуры выхода. 
     program Testexit;
     var
       ExitSave: pointer;

     {$F+} procedure MyExit ; {$F-}
     begin
       ExitProc := ExitSave;       { старый вектор всегда
       ExitProc := MyExit;           восстанавливается первым }
       .
       .
       .
     end;

     begin
        ExitSave := ExitProc;
        ExitProc := @MyExit;
        .
        .
        .
     end.

     При входе в программу содержимое EхitProc сохраняется с EхitSave, а затем следует процедура выхода МуEхit. После того, как она будет вызвана в качестве элемента процесса завершения работы программы, процедура МуEхit восстановит предыдущую процедуру 
выхода. 
     Программа завершения в библиотеке исполняющей системы будет вызывать процедуры выхода, пока указатель EхitPrос не примет значение nil. Во избежании зацикливания EхitPrос устанавливается в nil перед каждым обращением, так что следующая процедура выхо
да вызывается только в том случае, если текущая процедура выхода устанавливает для EхitPrос ее адрес. Если при выполнении процедуры выхода возникает ошибка, то в ней не успеет еще выполниться присваивание нового адреса указателю EхitPrос, так как это дел
ается непосредственно перед тем, как процедура выхода выполнит возврат управления. 
     Процедура выхода может распознавать причину завершения работы программы путем проверки целочисленной переменной EхitCode и переменной-указателя ErrorAddr. 
     В случае нормального завершения в EхitCode содержится нулевое значение и ErrorAddr имеет значение nil. В случае завершения через обращение к процедуре Наlt EхitCode содержит значение, переданное функции Наlt, а ErrorAddr имеет значение nil. Наконец,
 в случае прекращения работы программы из-за ощибки во время ее выполнения EхitCode содержит код ошибки, а ErrorAddr содержит адрес ошибочного оператора. 
     Последняя процедура выхода (которая содержится в библиотеке исполняющей системы) закрывает файлы Input и Output и восстанавливает векторы прерываний, которые были перехвачены Турбо Паскалем. При этом, если указатель ErrorAddr имеет значение, отлично
е от nil, то процедура выхода выводит сообщение об ошибке во время выполнения программы. Если вы хотите выводить свои собственные сообщения об ошибках во время выполнения, используйте процедуру выхода, которая проверяет ErrorAddr и выводит сообщение об о
шибке, если это значение отлично от nil. В добавок в этому перед возвратом управления необходимо обеспечить, чтобы указатель ErrorAddr был установлен в значение nil, чтобы сообщение об ошибке не выдавалось снова другой процедурой выхода. 
     Если вы хотите сами выводить сообщения об ошибках этапа выполнения, установите процедуру выхода, которая проверяет ErrorAddr и выводит сообщение, если это значение отлично от нуля. Кроме того, перед возвратом управления убедитесь, что переменная Err
orAddr установлена в значение nil, после чего об ошибке не будут сообщать другие процедуры выхода. 
     После того, как библиотека исполняющей системы обращается в процедурам выхода, она возвращает управление DOS и передает в качестве кода возврата значение, содержащееся в ЕхitCode. 
                      Обработка прерываний

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

     Процедуры обработки прерываний описываются с помощью директивы Interrupt. В каждой процедуре обработки прерываний должен определяться следующий заголовок процедуры (или, как будет поясняться далее, его подмножество): 
     procedure IntHandler(Flags,CS,IPAX,BX,CX,DX,SI,DI,DS,ES,BP:
                       word);
     interrupt;
      begin
        .
        .
        .
      end;

     Как можно видеть, все регистры передаются в качестве псевдопараметров, так что вы можете их использовать и изменять в своей программе. Вы можете опустить некоторые из параметров или все параметры, начиная с параметра Flag и кончая ВР. Попытка описат
ь большее количество параметров или попытка опустить отдельный параметр без пропуска также того параметра, за которым он следует, является ошибкой, хотя сообщения о ней не выдается. Например: 
     procedure IntHandler(DI,ES,BP : word);
     procedure IntHandler(SI,DI,DS,ES,BP : word);

     При входе в нее процедура обработки прерываний автоматически сохраняет все регистры (независимо от заголовка процедуры) и инициализирует регистр DS: 
     PUSH AX
     PUSH BX
     PUSH DX
     PUSH SI
     PUSH DI
     PUSH DS
     PUSH ES
     PUSH BP
     MOV  BP,SP
     SUB  SP,LocalSize
     MOV  AX,SEG DATA
     MOV  DS,AX

     Обратите внимание на отсутствие инструкции STI, чтобы разрешить дальнейшие прерывания. С помощью оператора inline вы можете написать ее сами (если это необходимо). Набор операторов выхода восстанавливает регистры и выполняет функцию возврата прерыва
ния: 
     MOV SP,BP
     POP BP
     POP ES
     POP DS
     POP DI
     POP SI
     POP DX
     POP CX
     POP BX
     POP AX
     IRET

     Процедура обработки прерываний может модифицировать свои параметры. Когда обработчик прерываний возвратит управление, изменение описанных параметров приведет к изменению содержимого соответствующих регистров. Это может оказаться полезным, когда вы и
спользуете обработчик прерываний в качестве пользовательского сервисного средства, аналогичного вызову функции DOS по инструкции INТ 21Н. 
     В процедурах обработки прерываний, обслуживающих прерывания, получаемые от аппаратных схем, следует воздерживаться от использования каких-либо программ ввода-вывода Турбо Паскаля или программ распределения памяти, поскольку они не являются реентераб
ельными. Из-за их нереентерабельности нельзя также использовать никакие функции DOS. 


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