Глава 22. Вопросы управления
В данной главе подробно описываются различные способы реали-
зации в 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.
Назад | Содержание | Вперед