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

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

Примерный вызов процедуры или функции можно представить следующим образом:

PUSH Param1

PUSH Param2

  .

  .

  .

PUSH ParamX

Call ProcOrFunc

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

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

Параметры-значения передаются по значению или по  ссылке,  в  зависимости  от  их  типа и размера.  В общем случае,  если параметр-значение занимает 1,  2 или 4 байта,  то значение помещается  непосредственно в стек. В противном случае в стек помещается указатель на значение, а процедура или функция копирует затем значение в локальную ячейку памяти.

В процессоре 8086 не поддерживаются байтовые инструкции РUSН  и РОР,  поэтому байтовые параметры всегда передаются в стеке, как  слова. Младший байт слова содержит значение, а старший байт слова  свободен (и неопределен).

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

Параметр символьного типа (Char) передается,  как  байт без знака.

Параметр булевского  типа (Boolean) передается,  как байт со  значением 0 или 1.

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

Параметр вещественного типа  (Real, значения  с  одинарной,  двойной  или  повышенной  точностью или  сложного типа - Single,  Double, Extended, Comp), передаются через стек как 4, 6, 8 или 10  байт. Это  является  исключением из того правила,  что 1-, 2- и  4-байтовые значение передаются непосредственно в стеке.

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

Параметр строкового типа передается, как указатель на значение.

Параметр множественного типа передается в виде  байта  (если  границы элемента  установлены в  диапазоне  от 0 до 7) или слова  (если границы элемента установлены в диапазоне от  0  до 15).  В  противном  случае оно передается в виде указателя на "неупакованное" множество длиной 32 байта.

Массив или запись из 1,  2 или 4 байт помещается  непосредственно в стек.  Другие массивы и записи передаются, как указатели  на значения.

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

Открытые параметры-массивы передаются занесением в стек сначала указателя на массив, а затем слова, содержащего атрибут размера (число элементов массива минус 1).

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

procedure FillString(var Str: OpenString; Chr: Char);

assebmler;

asm

   LES   DI,Str   { ES:DI = @Str }

   MOV   CX,Str,Str.Word[-2]  { Cx = igh(Str) }

   MOV   AL,CL

   CLD

   STOSB { установить Str[0] }

   MOV   AL,Chr

   REP   STOSB { установить Str[1..High] }

end;

Результаты функций порядкового типа возвращаются в регистрах  центрального процессора:  байты возвращаются в регистре AL, слова  - в регистре AХ,  двойные слова - в DX:AX (старшее слово - в DХ,  младшее - в AХ).

Результаты функций вещественного типа (значения вещественного типа  Real) возвращаются в регистрах DХ:ВХ:AX (старшее слово -  в регистре DХ,  среднее слово - в регистре ВХ,  младшее слово - в  AX).

Результаты функции, имеющие один из типов, использующихся в  процессоре 8087,  (значения с одинарной,  двойной или повышенной  точностью или сложного типа - Single,  Double,  Extended и Comp),  возвращаются в регистре вершины стека сопроцессора 8087 (SТ(0)).

Результаты функции типа указатель возвращаются в регистре DХ:

  AX (адрес сегмента - в DХ, а смещение - в AX).

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

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

Инструкция ближнего  обращения CALL помещает в стек 16-битовый адрес возврата (только смещение), а инструкция дальнего вызова помещает  в  стек 32-битовый адрес возврата (адрес сегмента и  смещение). Соответствующая  инструкция RET  извлекает  из стека  только смещение или адрес сегмента и смещение.

На основе описания процедуры в Borland Pascal будет  автоматически выбираться правильный тип обращения. Процедуры, описанные  в интерфейсной секции модуля соответствуют дальнему  обращению  и  могут вызываться из других блоков.  Процедуры,  описанные в программе в секции реализации модуля (implementation), являются ближними и могут вызываться только из этой программы или данного модуля.

Для некоторых конкретных целей можно потребовать, чтобы процедура  имела  дальний  тип вызова. Например,  процедура выхода,  драйверы устройств для текстовых файлов и  другие  средства, использующие  указатели  на процедуры.  Директива компилятора {$F+}  указывает на необходимость использования дальнего  типа  вызовов.  Процедуры или  функции, скомпилированные  с  данной директивой,  всегда будут  иметь  дальний  тип вызова.  При  использовании  в  Borland  Pascal директивы {$F-} правильная схема вызова будет выбираться автоматически. По умолчанию назначается режим {$F-}.

Процедура или функция считается вложенной,  когда она описывается внутри другой процедуры или функции.  По умолчанию вложенные процедуры  и  функции всегда  используют ближний тип вызова  (NEAR),  поскольку они доступны только внутри определенной процедуры или функции в том же сегменте кода.  Однако в оверлейных задачах обычно для того, чтобы обеспечить для всех процедур и функций дальний тип вызова (FAR), используется директива {$F+}.

При вызове вложенной процедуры или функции компилятор непосредственно перед инструкцией CALL генерирует инструкцию  PUSH BP,  фактически  передавая  регистр BP вызывающей программы в качестве  дополнительного параметра.  После того, как вызываемая процедура  установит свой собственный регистр BP, регистр ВР вызывающей процедуры доступен,  как слово, сохраненное в [BP+4] или  в  [BP+6]  (если процедура имеет дальний тип вызова).  Используя связь через  [BP+4] и [BP+6], вызываемая процедура может получить доступ к локальным переменным в границах стека вызывающей процедуры. Следующий пример показывает,  как можно получить доступ к локальным переменным из оператора inline во вложенной процедуре:

procedure A; near;

   var IntA: integer;

 

procedure B; far;

   var IntB: integer;

 

procedure C; near;

   var IntC: integer;

begin

inline(

   $8B/$46/<IntC>/   { MOV AX,[BP+IntC]  ;AX = IntC   }

   $8B/$5E/$04/   { MOV BX,[BP+4]  ;BX = стек В }

   $36/$8b/$47/<IntB>/  { 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/<IntA>); { MOV AX,SS:[BX+IntA]  ;AX =IntA }

end;

 

begin C end;

 

begin B end;

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

Примечание: Блок inline в приведенном примере можно записать  также в виде:

begin

   asm

   MOV AX,[BP+IntC]  { AX = IntC   }

   MOV BX,[BP+4]  { BX = стек В }

   MOV AX,SS:[BX+IntB]  { AX = IntB   }

   MOV BX,[BP+4]  { BX = кадр стек B }

   MOV BX,SS:[BX+6]  { BX = кадр стек A }

   MOV AX,SS:[BX+IntA]  { AX =IntA }

end;

end;

Методы используют те же соглашения о вызовах,  что и обычные  процедуры и функции,  за тем исключением,  что каждый метод имеет  неявный дополнительный параметр Self, который соответствует параметру-переменной того же типа,  что и объектный тип данного метода. Параметр Self всегда передается последним и всегда имеет форму 32-разрядного  указателя на экземпляр,  из которого вызывается  метод. Например, если переменная PP имеет тип PPoint, как определено выше,  то вызов PP^.MoveTo (10, 20) кодируется следующим образом:

mov   ax, 10 ; загрузить 10 в  AX

push  ax  ; передать PX как параметр

mov   ax, 20 ; загрузить 20 в AX

push  ax  ; передать PY как параметр

les   di, PP ; загрузить PP в ES:DI

push  es  ; передать, как параметр Self

push  di

mov   di, es:[di + 6] ; извлечь смещение ТВМ из поля ТВМ

call  DWORD PTR [di + 16] ; вызвать запись ТВМ для MoveTo

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

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

Для вызова  виртуального  метода  компилятор генерирует код,  который выбирает адрес таблицы виртуальных методов из поля таблицы виртуальных методов объекта, и затем вызывает метод, используя  связанную с ним точку входа.  Например, если дана переменная  PP  типа Point, то вызов PP^.Show будет генерировать следующий код:

les di, PP ; загрузить PP в ES:DI

push   es  ; передать, как параметр Self

push   di

mov di, es:[di + 6] ; извлечь смещение ТВМ из поля ТВМ

call   DWORD PTR [di + 12] ; вызвать запись ТВМ для  Show

Правила совместимости типов для объектных типов позволяют PP  указывать на Point и на TCircle  или на  любых  других потомков  TPoint.  И если вы просмотрите показанные здесь таблицы виртуальных методов,  то вы увидите,  что для типа TPoint точка входа  со  смещением   12   в   таблицы виртуальных  методов  указывает на  TPoint.Show. Таким образом, в зависимости от фактического во время выполнения типа PP, инструкция CALL вызывает либо TPoint.Show,  либо TCircle.Show, либо метод любого другого потомка TPoint.

Если Show  является статическим  методом,  то для   вызова  PP.Show будет генерироваться следующий код:

les di, PP  ; загрузить PP в ES:DI

push   es   ; передать, как параметр Self

push   di

call   TPoint.Show   ; непосредственно вызвать TPonit.Show

В данном случае не имеет значения,  на что указывает  PP, и  код всегда будет вызывать метод TPoint.Show.

Диспетчеризация вызова динамического метода несколько  более  сложна и требует больше времени, чем диспетчеризация виртуального  метода.  Вместо использования инструкции CALL  для  вызова  через  указатель  метода  по статическому смещению в таблице виртуальных  методов, таблица динамических методов объектного типа  и  таблица  динамических  методов  его предка должны просматриваться в поиске  "самого верхнего" вхождения индекса конкретного динамического метода, а вызов затем должен выполняться через соответствующий указатель метода.  Этот процесс  требует использования  существенно  большего числа инструкций,  которые можно записать, как "встроенные" (inline),  поэтому Турбо Паскаль  обеспечивает  подпрограмму  диспетчеризации, используемую при вызове динамического метода.

Если бы метод Show показанного выше типа TPoint  описывался как динамический метод (с индексом динамического  метода 200), то вызов PP^.Show, где PP имеет тип TPointPtr,  привел бы к генерации следующего кода:

les   di,PP   ; загрузка PP в ED:DI

push  es   ; передача, как параметра

   ; Self

push  di

mow   di,es:[di+6]  ; извлечение смещения

   ; таблицы виртуальных методов

   ; из поля таблицы

   ; виртуальных методов

mov   ax,200  ; загрузка в AX индекса

   ; динамического метода

call  Dispatch   ; вызов подпрограммы

   ; диспетчеризации

Диспетчер выбирает сначала смещение таблицы динамических методов от  таблицы виртуальных методов,  на которое указывает регистр DI. Затем используется "индекс в кеше" - поле таблицы динамических методов. Диспетчер проверяет, является ли индекс вызванного динамического метода индексом того динамического метода, который вызывался последним.  Если это так,  он немедленно передает  этому методу управление (путем перехода с помощью указателя метода, записанного по смещению, заданному полем "смещение записи").

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

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

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

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

Более того, если конструктор вызывается для размещения динамического  объекта  с помощью расширенного синтаксиса стандартной  процедуры New,  через параметр Self передается указатель nil. Это  заставляет конструктор размещать новый динамический объект, адрес которого передается вызывающей программе через DX:AX при возврате  из конструктора.  Если конструктор не может разместить объект, то  в DX:AX возвращается пустой указатель nil.  (См. далее "Обнаружение ошибок конструктора").

Наконец, если  конструктор вызывается с использованием уточненного идентификатора метода (т.е.  идентификатора типа объекта,  за которым следуют точка и идентификатор метода),  то в параметре  таблицы виртуальных методов передается нулевое значение.  Это является указанием конструктору на то, что ему не следует инициализировать поле Self таблицы виртуальных методов.  Для деструкторов  нулевое значение параметра таблицы виртуальных  методов  означает  обычный вызов, а ненулевое указывает, что деструктор был вызван с  использованием  расширенного  синтаксиса стандартной   процедуры  Dispose.  Это  заставляет деструктор удалить Self непосредственно  перед возвратом (размер Self определяется из первого слова Self в  ТВМ).

Каждая процедура и функция Borland Pascal начинается  и  заканчивается стандартным набором операторов, которые позволяют активизировать и деактивизировать процедуру или функцию.

Стандартным входом служит следующая группа операторов:

PUSH BP  ; сохранить регистр ВР

MOV BP,SP   ; установить границы стека

SUB SP,LocalSize  ; выделить память для локальных  переменных

В этом примере LocalSize - это размер  локальных переменных.  Инструкция SUВ присутствует только в том случае,  когда LocalSize  не равно нулю.  Если тип обращения к процедуре является  ближним,  то параметры начинаются с BP+4, если для вызова процедуры используется дальний тип обращения, то они начинаются с BP+6.

Для программ  DOS код входа и выхода для подпрограммы, использующей дальнюю модель вызова,  тот же, что и для подпрограммы  с ближним типом вызов,  но для возврата из подпрограммы используется инструкция  RETF.  Это справедливо  также  для  программы  Windows, cкомпилированной в состоянии {$W-}.

Примечание: Об использовании процедур входа и выхода в  DLL рассказывается в Главе 11 "Динамически компонуемые библиотеки".

Стандартной группой операторов выхода является:

MOV SP,BP  ; освободить память, выделенную для; локальных переменных

POP BP  ; восстановить регистр ВР

RET ParamSize ; удалить параметры и выполнить возврат; управления

Здесь РаrамSizе - это размер параметров.  Инструкция RET является  инструкцией ближнего или дальнего типа,  в зависимости от  типа обращения к процедуре.

В состоянии {$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 и DEC BP (сгенерированных в состоянии {$W+}) на входе  и выходе для подпрограмм far и export.

Заметим, что  использование  {$W+}  требуют  только реальный  режим Windows.  Если вы не поддерживаете реальный режим,  укажите  {$W-}. Вы получите программу меньшего размера и некоторый выигрыш  в скорости.

При разработке программы защищенного  режима  Windows может  оказаться полезным   использование   состояния  {$W+}.  Некоторые средства отладки,  отличные от средств Borland, требуют этого для  корректной работы.

По умолчанию  Borland Pascal автоматически генерирует эффективные системные вызовы для процедур  и  функций,  экспортируемых  прикладной программой. При компоновке прикладной программы в состоянии {$K+} (по умолчанию) отладчик ищет в каждой экспортируемой  точке входа последовательность инструкций MOV AX,DS с последующей  инструкцией NOP,  заменяя их на MOV AX.DS на MOV AX,SS. Это изменение ослабляет  требование  использования при создании программ  системного  вызова  подпрограмм  API Windows  MakeProcInstanc  и  FreeProcInstance (хотя это не возбраняется). Можно также вызывать  экспортируемые точки входа из самой прикладной программы.

В состоянии {$K-} при создании динамически компонуемой  библиотеки компоновщик  Borland Pascal  не модифицирует код входа и  выхода экспортированной точки входа. Если подпрограмма системного  вызова в  приложении должна вызываться из другой прикладной программы, выбирать состояние {$K-} не следует.

При загрузке прикладной программы или динамически  компонуемой библиотеки  Windows ищет в каждой экспортируемой точке входа  последовательность инструкций MOV AX,DS с последующей инструкцией  NOP. Для  прикладной  программы Windows изменяет первые три байта  на три инструкции NOP, чтобы подготовить подпрограмму для использования  ее  функцией  Windows MakeProcInstance.  Для  библиотек  Windows изменяет первые три байта в инструкции MOV  AX,xxxx,  где  xxxx - селектор (адрес сегмента) сегмента динамических локальных  данных библиотеки.

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

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

Реализовать процедуру выхода вам позволяет переменная-указатель EхitProc.  Процедура выхода всегда получает вызов при завершении работы программы,  независимо от того,  является ли это заершение  нормальным окончанием  работы  программы, завершением  после обращения к функции Наlt, или работа программы прекратилась  из-за ошибки во время выполнения.

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

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

Чтобы сохранить  цепочку выхода  в  неприкосновенности,  вы  должны перед изменением указателя EхitPrос на адрес  вашей  собственной  процедуры  сохранить текущее содержимое этого указателя.  Далее,  непосредственно перед возвратом управления ваша процедура  выхода должна восстановить сохраненное значение EхitProc.  В следующей программе показаны основы метода реализации такой процедуры выхода.

program Testexit;

var

   ExitSave: Pointer;

 

procedure MyExit; far

begin

   ExitProc := ExitSave; { всегда восстанавливает сначала  старый вектор }

   .

   .

   .

end;

 

begin

   ExitProc := ExitSave;

   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 содержит адрес ошибочного оператора.

Примечание: О процедурах выхода для DLL рассказывается  в Главе 11.

Последняя процедура выхода (которая содержится  в библиотеке  исполняющей  системы) закрывает файлы Input и Output и восстанавливает векторы прерываний,  которые были перехвачены Турбо Паскалем. При этом, если указатель ErrorAddr имеет значение, отличное  от nil,  то процедура выхода выводит сообщение об ошибке во время  выполнения  программы.  Если  вы хотите выводить свои собственные  сообщения об ошибках во время выполнения,  используйте  процедуру выхода, которая проверяет ErrorAddr и выводит сообщение об  ошибке, если  его  значение отлично от nil.  В добавок к этому перед  возвратом  управления необходимо  обеспечить,  чтобы  указатель  ErrorAddr был установлен в значение nil, чтобы сообщение об ошибке не выдавалось снова другой процедурой выхода.

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

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

Для Windows подпрограммы обработки прерываний писать не следует. Если вы это сделаете, последствием может быть сбой системы.

Процедуры обработки прерываний описываются с помощью  директивы 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

Обратите внимание на отсутствие процедуры СLI,  чтобы разрешить дальнейшие прерывания. С помощью оператора 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.