Глава 8. Связь Turbo Assembler с Turbo Pascal.


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

Зачем использовать Turbo Assembler из Turbo Pascal?
Большинство программ, которые вам могут понадобиться, можно
написать просто на Паскале. В отличие от большинства версий
Паскаля Turbo Pascal позволяет произвольный доступ ко всем
машинным ресурсам через массивы Port[], Mem[], MemW[] и MemL[], а
также разрешает обращения к BIOS и операционной системе при помощи
процедур Intr() и MsDos().

Зачем же тогда обращаться из Turbo Pascal к Turbo Assem-
bler? Для этого имеется две наиболее вероятных причины: выполнение
некоторых немногочисленных операций, недоступных непосредственно
из Turbo Pascal, а также необходимость в максимально высоком
быстродействии программы, достижимая только в языке ассемблера.
(Сам Turbo Pascal работает так быстро только благодаря тому, что
сам написан на ассемблере). В этой главе показано, где и как можно
извлечь выгоду из использования языка ассемблера в Turbo Pascal.

Примечание: если номер версии Turbo pascal не указан явно, то
предполагается версия 4.0 и старше.

Карта памяти Turbo Pascal
——————————————————————

Прежде чем вы сможете начать писать ассемблерные коды для
работы в программах на Turbo Pascal, важно понять, как компилятор
размещает информацию в памяти. Модель памяти в Turbo Pascal
включает в себя аспекты моделей памяти medium и large, описанных в
главе 5. Имеется один глобальный сегмент данных, позволяющий
выполнять быстрый доступ к глобальным переменным и типизованным
константам через DS. Однако, каждый модуль имеет свой собственный
кодовый сегмент, а размер «кучи» может увеличиваться, чтобы
использовалась вся доступная память. Адреса в Turbo Pascal всегда
передаются в виде дальних (32- битовых) указателей, что позволяет
с их помощью обращаться к любым объектам в памяти.

Карта памяти Turbo Pascal выглядит следующим образом:

Младшие адреса памяти
————————————
| Префикс сегмента программы |
| (256 байт) |
————————————
| Кодовый сегмент |Максимальный размер
| главной программы |кодового сегмента:64К
————————————
|Кодовый сегмент последнего модуля|
————————————
.
.
.
————————————
| Кодовый сегмент первого модуля |
————————————
| Кодовый сегмент библиотеки |
| исполняющей системы |
DS ->————————————
| Типизованные константы |
|- — — — — — — — — — — — — — — — -|<- Конец файла .EXE | Глобальные переменные | SS ->——————————————————
| ° | Размер стека
| Стек (растет в направлении | Минимальный : 1К
| уменьшения адресов памяти) | По умолчанию: 16К
| | Максимальный: 64К
——————————————————
| Куча (растет в направлении | Предельного
| увеличения адресов памяти) | размера
Указа-| ? | не имеет
тель->————————————
кучи | ° |
| Список свободных областей кучи | Максимальный размер
| (растет в направлении уменьшения| списка свободных
| адресов памяти) | областей кучи: 64К
————————————
Старшие адреса памяти

Рис.8.1 Карта памяти программы в Turbo Pascal 5.0

Префикс сегмента программы ————————————

Префикс сегмента программы (PSP) это 256-байтовая область,
создаваемая MS-DOS при загрузке программы. Помимо прочего, она
содержит информацию о параметрах командной строки, заданных во
время запуска программы, количестве доступной памяти и контексте
DOS (списке используемых DOS строковых переменных).

В Turbo Pascal 3.0 адрес сегмента psp был тем же самым, что и
для остальной части программы. В последующих версиях это не так. В
Turbo Pascal версий 4.0 и старше главная программа, используемые
ей модули и библиотека исполняющей системы занимают разные
сегменты. Следовательно, Turbo Pascal хранит адрес сегмента с PSP
во встроенной глобальной переменной PrefixSeg, через которую вы
можете получить информацию о PSP.

Кодовые сегменты ————————————————

Каждая программа на Turbo Pascal имеет как минимум два
кодовых сегмента: один из них для кодов главной программы, а
второй — для библиотеки исполняющей системы. Кроме того, каждый
дополнительный модуль программы занимает собственный кодовый
сегмент. Поскольку каждый кодовый сегмент может иметь размер до
64К, то ваша программа может занимать столько памяти, сколько
потребуется (в зависимости, разумеется, от общего размера
оперативной памяти машины). Программисты, ранее для получения
программ, превышающих 64К, использовавшие структуры с
перекрытиями, могут теперь держать в оперативной памяти всю
программу сразу, что увеличивает скорость ее выполнения. Кодовый
сегмент, в который компонуется модуль на языке ассемблера, в Turbo
Assembler называется CODE или CSEG.

Сегмент глобальных данных ————————————-

Сегмент глобальных данных в Turbo Pascal расположен после
кодового сегмента библиотеки исполняющей системы. Он содержит до
64Кб инициализированных и неинициализированных данных:
типизованные константы и глобальные переменные. Как и в Turbo
Pascal 3.0, типизованные константы фактически являются вовсе не
константами, а переменными, которые при загрузке программы
инициализируются некоторыми заранее определенными значениями.
Однако в отличие от Turbo Pascal 3.0, Turbo Pascal 4.0 не помещает
типизованные константы в кодовый сегмент. Вместо этого Turbo
Pascal 4.0 помещает типизованные константы в сегмент глобальных
данных, где доступ к ним выполняется даже быстрее, чем в Turbo
Pascal 3.0. Сегмент глобальных данных в модуле на Turbo Assembler
называется DATA или DSEG.

Стек ———————————————————-

В turbo Pascal 4.0 и старше сегмент глобальных данных
находится над стеком. Отметим, что такая организация размещения в
памяти отличается от версии Turbo Pascal 3.0. Стек и куча растут
не в направлении друг друга. Вместо этого стеку распределяется
некоторая фиксированная область. Для подавляющего большинства
программ размер стека 16К оказывается более чем достаточным;
однако вы имеете возможность задать меньший размер стека, до 1К
(для коротких программ), либо больший, до 64К (для программ с
большим количеством рекурсий. Размеры стека и кучи выбираются при
помощи директивы компилятора $m.

В большинстве программ для 80×86 указатель стека начинается с
вершины стекового сегмента и растет в направлении уменьшения
адресов. При вызове процедуры или функции Turbo Pascal обычно
проверяет переполнение стека. Такую проверку можно отключить при
помощи директивы компилятора {$s-}.

Куча ———————————————————-

В верхней части карты памяти Turbo Pascal находится куча. По
умолчанию куча занимает всю память, не распределенную кодовым
сегментам, сегментам данных и стека; однако при помощи директивы
компилятора $M можно ограничить максимальный размер кучи. (С ее
помощью можно также запретить выполнение программы, если не
имеется минимально необходимого ей размера кучи.)

Память в куче распределяется динамически, начиная с дна кучи,
всякий раз при выполнении процедур New() или GetMem().
Освобождение памяти в куче происходит при выполнении Dispose,
Release или FreeMem. При использовании Dispose и FreeMem Turbo
Pascal 4.0 следит за наличием свободных областей памяти в середине
кучи при помощи структуры данных, называемой списком свободных
областей (free list). Список свободных областей кучи может иметь
размер до 64К и растет в сторону уменьшения адресов памяти,
начиная с вершины области кучи.

Использование в Turbo Pascal регистров
——————————————————————

Подобно Turbo Pascal 3.0, Turbo Pascal 4.0 накладывает на
использование регистров минимум органичений. Когда выполняется
вызов функции или процедуры, требуется сохранить значения только
трех регистров: стекового сегмента (SS), сегмента данных (DS) и
указателя базы (BP). DS указывает на сегмент глобальных данных
(называемый DATA), а SS указывает на стековый сегмент. BP
используется каждой вызываемой процедурой или функцией для
обращения к ее записи активации — области стека, которую она
использует для хранения параметров, локальных переменных и
временной памяти. Все подпрограммы перед выходом из них должны
также соответствующим образом изменить указатель стека (SP), таким
образом, чтобы в стеке не осталось не нужных более параметров.

Ближние или дальние?
——————————————————————

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

Каждая подпрограмма в вашей программе на Turbo Pascal должна
быть создана (либо компилятором, либо вами) таким образом, чтобы
вызов ее выполнялся одним из этих двух способов. Который из них
выбрать? Подпрограммы, объявленные в разделе модуля interface,
всегда должны быть дальними, поскольку они могут быть вызваны из
других модулей. Однако подпрограммы, объявленные в главной
программе, либо объявленные только в разделе implementation
модуля, обычно являются ближними.

(Любую подпрограмму можно принудительно сделать дальней при
помощи директивы компилятора {$f+}.)

При создании подпрограмм на языке ассемблера для дальнейшего
их вызова из Turbo Pascal вы должны гарантировать, чтобы эти
подпрограммы имели правильное «расстояние». Turbo Pascal не
сообщит об ошибке, если вы объявили в ассемблере PROC ближней, а
при этом соответствующее объявление процедуры external расположено
таким образом, что требуются дальние ссылки.

Разделение информации с Turbo Pascal
——————————————————————

Директива компилятора $L и внешние подпрограммы —————-

Два ключевых способа использования Turbo Assembler в Turbo
Pascal — это директива компилятора {$l} и объявление подпрограммы
как external. Директива ($l MYFILE.OBJ} заставит turbo Pascal
найти файл MYFILE.OBJ, имеющий стандартный объектный формат для
компоновки, и скомпоновать его с вашей программой на Turbo Pascal.
Если заданное в директиве {$l} имя файла не имеет расширения, то
предполагается расширение .OBJ.

Каждая процедура или функция на Turbo Assembler, которую вы
хотите сделать доступной из программы на Turbo Pascal, должна быть
объявлена как символическое имя общего доступа PUBLIC и должна в
вызывающей программе быть объявлена как внешняя. Синтаксис
объявления в Turbo Pascal внешней процедуры или функции (external)
аналогичен объявлению forward:

procedure AsmProc(a : integer; b : real); external;
function AsmFunc(c : word; d : byte); external;

Эти объявления могут соответствовать следующим объявлениям в
программе на Turbo assembler:

CODE SEGMENT BYTE PUBLIC
AsmProc PROC NEAR
PUBLIC AsmProc
.
.
.
AsmProc ENDP

AsmFunc PROC FAR
PUBLIC Bar
.
.
.
AsmFunc ENDP
CODE ENDS

Объявление в Turbo Pascal процедуры как external должно
находиться на верхнем уровне программы или модуля; то есть, оно не
должно быть вложенным в объявление другой процедуры. Попытка
объявить процедуру external на любом другом уровне вызовет ошибку
компиляции.

Turbo Pascal не проверяет, соответствуют ли процедуры,
объявленные как PROC с ближними или дальними аттрибутами, ближним
или дальним подпрограммам в вашей программе на Turbo Pascal.
Фактически он даже не проверяет, являются ли именами процедур
(PROC) общие (public) метки AsmProc и AsmFunc. За
непротиворечивость объявлений в модулях на языке ассемблера и на
Паскале отвечаете вы сами.

Директива PUBLIC
—————-

В Turbo Pascal доступны лишь те метки модуля на языке
ассемблера, которые объявлены как PUBLIC. Метки представляют собой
единственные объекты, которые могут быть экспортированы из языка
ассемблера в Turbo Pascal. Кроме того, каждая метка, объявленная
как PUBLIC, должна иметь соответствующее объявление в процедуре
или функции в программе на Turbo Pascal, иначе компилятор сообщит
об ошибке. Общая метка не должна быть частью объявления PROC. С
точки зрения Turbo Pascal

AsmLabel PROC FAR
PUBLIC Bar

и

AsmLabel:
PUBLIC Bar

эквивалентны.

Директива EXTRN
—————

Модуль на Turbo Assembler может обращаться к любой процедуре,
функции, переменной или типизованной константе Turbo Pascal,
которая объявлена на старшем уровне компонуемой программы или
модуля.

(Отметим, что сюда входят переменные, объявляемые после
директивы компилятора {$l} и объявление (объявления) external,
связанные с данным модулем). Метки Turbo Pascal и обычные
константы в языке ассемблера недоступны.

Предположим, что в вашей программе на Turbo Pascal объявлены
следующие глобальные переменные:

var
a : byte;
b : word;
c : shortInt;
d : integer;
e : real;
f : single;
g : double;
h : extended;
i : comp;
j : pointer;

Вы можете обращаться к любой из этих переменных из программы
на языке ассемблера, объявив их как EXTRN:

EXTRN A : BYTE ;1 байт
EXTRN B : WORD ;2 байта
EXTRN C : BYTE ;В языке ассемблера байт со знаком и
;без знака обрабатывается одинаково
EXTRN D : WORD ;То же самое
EXTRN E : FWORD ;Программно реализуемое действительное
;6 байт
EXTRN F : DWORD ;4-байтовое значение с плавающей точкой
;в формате IEEE
EXTRN G : QWORD ;4-байтовое значение с плавающей точкой
;двойной точности в формате IEEE
EXTRN H : TBYTE ;10-байтовое временное значение с
;плавающей точкой в формате IEEE
EXTRN I : QWORD ;8-байтовое целое со знаком для 8087
EXTRN J : DWORD ;указатель Turbo Pascal

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

unit Sample;
{ Пример модуля, в котором определяется несколько процедур
на Паскале, вызываемых из процедуры на языке ассемблера. }
interface
procedure TestSample;
procedure PublicProc; {Должна быть дальней, т.к. доступна извне}
implementation
var
A : word;
procedure AsmProc; external;
{$L ASMPROC.OBJ}
procedure PublicProc;
begin { PublicProc }
Writeln(‘В PublicProc’);
end; { PublicProc }
procedure NearProc; {Должна быть ближней}
begin { NearProc }
Writeln(‘В NearProc’);
end; { NearProc }
{$F+}
procedure FarProc; {Должна быть дальней, в соответствии с
директивой компилятора}
begin { FarProc }
Writeln(‘В FarProc’);
end; {FarProc }
{$F-}
procedure TestSample;
begin { TestSample }
Writeln(‘В TestSample’);
A := 10;
Writeln(‘Значение A до выполнения ASMPROC = ‘,A);
AsmProc;
Writeln(‘Значение A после выполнения ASMPROC = ‘,A);
end; { TestSample };
end;

Процедура AsmProc может вызывать процедуры PublicProc,
NearProc или FarProc, используя для этого директивы EXTRN
следующим образом:

DATA SEGMENT WORD PUBLIC
ASSUME DS:DATA
EXTRN A:WORD ;переменная из модуля
DATA ENDS
CODE SEGMENT BYTE PUBLIC
ASSUME CS:CODE
EXTRN PublicProc : FAR ;дальняя процедура
;(экспортируется модулем)
EXTRN NearProc : NEAR ;ближняя процедура (лока-
;льная относительно модуля)
EXTRN FarProc : FAR ;дальняя процедура (лока-
;льная, но принудительно
;устанавливается дальней
AsmProc PROC NEAR
PUBLIC AsmProc
CALL FAR PTR PublicProc
CALL NearProc
CALL FAR PTR FarProc
mov cx,ds:A ;извлекает переменную A
;из модуля
sub cx,2 ;любое изменяющее ее
;действие
mov ds:A,cx ;записывает ее назад
RET
AsmProc ENDP
CODE ENDS
END

Ниже показана главная программа, проверяющая работу
приведенных примеров модуля на Паскале и ассемблерных кодов:

program TSample;
uses Sample;
begin;
TestSample;
end.

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

TASM ASMPROC
TPC /B TSAMPLE
TSAMPLE

Поскольку внешняя подпрограмма должна быть объявлена на
старшем уровне процедур программы на Turbo Pascal, вы не можете
для доступа к объектам, являющимся внутренними относительно
процедуры или функции, воспользоваться объявлениями EXTRN. Однако,
ваша подпрограмма на Turbo Assembler может принимать при вызове из
Turbo Pascal эти объекты либо по значению, либо как переменные.

Ограничения на использование объектов, объявленных как EXTRN
————————————————————

Существующий в Turbo Pascal синтаксис уточненных
идентификаторов, в котором для доступа к объекту в конкретном
модуле берется имя этого модуля плюс точка, не совместим с
правилами синтаксиса Turbo Assembler и потому в данном случае
недействителен. Объявление

EXTRN SYSTEM.Assign : FAR

вызовет в Turbo Assembler сообщение об ошибке.

Имеются еще два, менее важные ограничения использования
объектов EXTRN в Turbo Pascal. Первое из них состоит в том, что
ссылки на процедуры и функции не могут использовать в записи
адреса арифметические действия. Таким образом, если вы объявили

EXTRN PublicProc : FAR

то нельзя записать оператор типа

call PublicProc + 42

Второе ограничение состоит в том, что компоновщик Turbo
Pascal не распознает операции, разделяющие слова на байты, что
делает невозможным применение таких операций к объектам EXTRN.
Например, если вы объявили

EXTRN i : WORD

то нельзя использовать в модуле на Turbo Assembler выражения
LOW i или HIGH i.

Использование фиксации сегментов ———————————

Turbo Pascal генерирует .EXE файлы, которые могут быть
загружены в компьютер, начиная с любого доступного адреса памяти.
Поскольку программа не может знать заранее, куда именно будет
загружен тот или иной сегмент программы, компоновщик сообщает
загрузчику DOS.EXE о необходимости зафиксировать при загрузке
сегмента все ссылки на него. После выполнения фиксации все ссылки
на сегменты (например, CODE и DATA) содержат правильные значения.

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

.
.
.
mov ax,SEG DATA ;прием фактического адреса глобального DS
;Turbo Pascal
mov ds,ax ;помещение его в DS для использования
;Turbo Pascal
.
.
.

Когда ваша программа загружается, DOS вставит правильное
значение SEG DATA прямо в непосредственное поле операнда команды
MOV. Это быстрейший способ перезагрузки сегментного регистра.

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

Устранение недействующих кодов ———————————

Turbo Pascal имеет средство устранения недействующих кодов;
это означает, что при записи итогового .EXE файла он не включает в
него коды никогда не выполняемых подпрограмм. В связи же с тем,
что Turbo Pascal не имеет полной информации о содержимом ваших
модулей на Turbo Assembler, то оптимизация их ограничена.

Turbo Pascal устраняет коды .OBJ модуля в том и только том
случае, если ни к одной доступной функции или процедуре этого
модуле не делается никаких обращений. И наоборот, если выполняется
обращение хотя бы к одной подпрограмме модуля, то остается весь
модуль.

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

Соглашения Паскаля о передаче параметров
——————————————————————

Turbo Pascal передает параметры через стек центрального
процессора (или, в случае числовых параметров типа single, double,
extended или comp, через стек математического сопроцессора).
Параметры всегда вычисляются и помещаются в стек в той
последовательности, в которой они появляются в объявлении
подпрограммы, слева-направо. В данном разделе мы объясним, в каком
виде параметры представлены.

Параметры-константа ———————————————

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

Скалярные типы
—————

Параметры-константы всех скалярных типов (boolean, char,
shortint, byte, integer, word, longint, поддиапазоны и
перечисляемые типы) передаются по своим значениям через стек
центрального процессора. Если объект имеет размер в 1 байт, он
помещается в стек в виде полного 16-битового слова; однако самый
значащий байт слова не содержит полезной информации. (При этом
нельзя считать его равным 0, как это было в версии 3.0 и раньше).
Если объект имеет размер 2 байта, он просто помещается в стек, как
он есть. Если его длина составляет 4 байта (longint), то он
занимает в стеке 2 16-битовых слова. Стандартом для процессоров
семейства 8086 является то, что наиболее значащее слово помещается
в стек первым и занимает в стеке более старший адрес.

Отметим, что тип comp, будучи целочисленным, не входит в ряд
скалярных типов при передаче параметров. Таким образом, в Turbo
Pascal 4.0 параметры-константы такого типа передаются в стек 8087,
а не стек центрального процессора. В Turbo Pascal 5.0 значения
типа comp передаются через главный стек центрального процессора.

Действительные числа
———————

Параметры-константы типа real (тип программно-реализуемых в
Turbo Pascal чисел с плавающей точкой) передаются в стек в виде
6-байтовых значений. Это единственный тип, превышающий 4 байта и
подаваемый в стек.

Single, Double, Extended и Comp : типы для 8087
————————————————

В Turbo Pascal 4.0 параметры-константы типов для 8087
передаются в стек сопроцессора, а не в стек центрального
процессора. Поскольку стек 8087 может иметь глубину вложенности до
восьми уровней, подпрограмма на Turbo Pascal 4.0 может иметь не
более восьми параметров типов для 8087. Все такие параметры перед
выходом из подпрограммы должны быть извлечены из стека.

Turbo Pascal 5.0 использует те же соглашения о передаче
параметров для 8087, что и Turbo C: они передаются в главный стек
центрального процессора вместе с остальными параметрами.

Указатели
———

Параметры-константы всех типов указателей помещаются в стек
непосредственно в виде дальних указателей — сначала слово,
содержащее сегмент, а затем другое, содержащее смещение. Сегмент
занимает старший адрес, в соответствии с соглашениями Intel. Ваша
программа на Turbo Assembler для обращению к параметру-указателю
может использовать команды LDS или LES.

Строки символов
—————

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

Единственное исключение из этого правила делается в том
случае, когда подпрограмма в оверлейном модуле A передает
строковую константу как параметр-константу в подпрограмму
оверлейного модуля B. В этом контексте оверлейный модуль означает
модуль, компилированный с директивой компилятора {$O+} (оверлейные
структуры разрешены). В таком случае перед выполнением вызова в
стеке для строковой константы резервируется временная область
хранения, и этот адрес в стеке передается подпрограмме в модуле B.

Дополнительную информацию см. в главе 13, «Оверлейные
структуры» в Руководстве пользователя Turbo Pascal (5.0).

Записи и массивы
—————-

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

Множества
———

Множества, как и строки символов, никогда как правило не
помещаются прямо в стек. Вместо этого в стек помещается указатель
на это множество. Указатель, принятый подпрограммой, указывает на
«нормализованное» 32-байтовое представление множества. Первый бит
младшего байта этого множества всегда соответствует элементы
базового типа (или породившего его типа) со значением
перечисляемого типа 0.

Единственное исключение из этого правила делается в том
случае, когда подпрограмма в оверлейном модуле A передает
константу-множество как параметр-константу в подпрограмму
оверлейного модуля B. В этом контексте оверлейный модуль означает
модуль, компилированный с директивой компилятора {$O+} (оверлейные
структуры разрешены). В таком случае перед выполнением вызова в
стеке для константы-множества резервируется временная область
хранения, и этот адрес в стеке передается подпрограмме в модуле B.

Дополнительную информацию см. в главе 13, «Оверлейные
структуры» в Руководстве пользователя Turbo Pascal (5.0).

Параметры-переменные ——————————————

Все параметры типа var передаются одинаковым образом: в виде
дальних указателей на их фактическое местоположение в памяти.

Работа со стеком ———————————————-

Turbo Pascal предполагает, что все параметры перед возвратом
из подпрограммы будут удалены.

Существует два способа установки состояния стека. Вы можете
либо использовать команду RET N (где N это число байтов
параметров, помещенных в стек), либо вы можете записать адрес
возврата в регистры (или в память) и извлекать параметры из стека
по одному. Последний метод полезен для оптимизации по скорости
выполнения программы на 8086 и 8088 (самых медленно работающих
процессорах семейства), где адресация вида «базаплюс-смещение»
занимает восемь машинных циклов (минимум) за каждое обращение.
Этот метод помогает также экономить память, так как команда POP
занимает всего один байт.

Примечание: Если вы используете директивы .MODEL, PROC и ARG,
то ассемблер автоматически добавит число извлекаемых из стека
байтов параметров ко всем командам RET.

Доступ к параметрам ——————————————-

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

Примечание: при вычислении адресов параметров не забудьте
принять во внимание все регистры, такие как BP, содержимое которых
вы могли поместить на стек.

Существует три основных метода доступа к параметрам,
переданным в вашу подпрограмму на Turbo Assembler из Turbo Pascal.
Вы можете:

— Использовать для адресации стека регистр BP.

— Использовать для приема параметров другой базовый или индексный
регистр.

— Извлечь из стека адрес возврата, а затем извлечь параметры.

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

Использование регистра BP для адресации стека
———————————————

Первый (и наиболее часто используемый) метод доступа к
переданным из Turbo Pascal в Turbo assembler параметрам
заключается в использовании для адресации стека регистра BP,
например:

CODE SEGMENT
ASSUME cs:CODE
MyProc PROC FAR ;procedure MyProc(i,j : integer);
PUBLIC MyProc
j EQU WORD PTR [bp+6] ;j находится выше BP и
;адреса возврата
i EQU WORD PTR [bp+8] ;i находится над j
push bp ;нужно сохранить значение
;BP вызывающей программы
mov bp,sp ;теперь BP указывает на
;вершину стека
mov ax,i ;адресация i через BP
.
.
.

При вычислении смещений параметров в стеке для доступа к ним
этим методом не забудьте, что 2 байта идет на сохранение регистра
BP.

Отметим, что в данном примере для параметров используются
текстовые директивы эквивалентности. Они позволяют сделать ко ды
более мнемоничными. Однако они имеют следующий недостаток:
поскольку для установления такого рода эквивалентности применима
только директива EQU (а не директива =), вы не сможете
переопределить символические имена i и j в том же самом исходном
файле Turbo Assembler. Один из способов обойти это неудобство
состоит в том, чтобы использовать более описательные имена
параметров, чтобы они не повторялись; другой способ заключается в
раздельной компиляции каждой подпрограммы.

Директива ARG
————-

Однако, при доступе к параметрам через регистр BP Turbo
Assembler обеспечивает альтернативный способ вычисления смещений в
стеке и выполнения текстовых эквивалентностей — директиву ARG. При
использовании внутри PROC директива ARG автоатически определяет
смещения параметров относительно BP. Она вычисляет также размер
блока параметров для использования в команде RET. Поскольку
символические имена, созданные в директиве ARG, определены только
в окружающей PROC, уникальные имена параметров для каждой
процедуры или функции не требуются.

Вот как выглядит предыдущий пример, если переписать его с
использованием директивы ARG:

CODE SEGMENT
ASSUME cs:CODE
MyProc PROC FAR ;procedure MyProc(i,j : integer);
external;
PUBLIC MyProc
ARG j : WORD, i : WORD = RetBytes
push bp ;нужно сохранить значение
;BP вызывающей программы
mov bp,sp ;теперь BP указывает на
;вершину стека
mov ax,i ;адресация i через BP
.
.
.

Директива Turbo Assembler ARG создает для параметров i и j
локальные символические имена. Строка

ARG j : WORD, i : WORD = RetBytes

автоматически устанавливает эквивалентность символического
имени i и [WORD PTR BP+6], символического имени j и [WORD PTR
BP+8], а также символического имени RetBytes и числа 4 (размер в
байтах блока параметров) на все время выполнения процедуры. Эти
значения учитывают как помещенный в стек BP, так и размер адреса
возврата; если бы MyProc была объявлена как NEAR PROC, то i была
бы эквивалентна [BP+4], j — [BP+6], а RetBytes так же имела
значение 4 (т.е. в любом случае MyProc может заканчиваться
командой RET RetBytes).

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

Имеется еще одна предосторожность, связанная с директивой ARG
при интерфейсе с Turbo Pascal. В отличие от некоторых других
языков Turbo Pascal помещает в стек байтовые параметры в виде
полного 16-битового слова — и вы сами отвечаете за то, чтобы Turbo
Assembler учитывал этот дополнительный байт. Например,
предположим, что вы написали функцию, объявление которой на
Паскале имеет следующий вид:

function MyProc(i,j : char) : string; external;

Директива ARG для данной процедуры будет выглядеть так:

ARG j : BYTE :2, i : BYTE : 2 = RetBytes RETURNS result : DWORD

Символы «: 2» после каждого аргумента необходимы для того,
чтобы сообщить Turbo Assembler, что каждый символ помещается в
стек в виде массива из 2 байтов (где в данном случае старший байт
в каждой такой паре не содержит полезной информации).

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

Более полную информацию о директиве ARG см. в главе 3
Справочного руководства.

.MODEL и Turbo Pascal
———————

Директива .MODEL с параметром TPASCAL устанавливает
упрощенную сегментацию, модель памяти и поддержку языка. Ранее вы
узнали, как выполнять установки в ассемблерной программе для
процедур и функций Pascal. Ниже приводится тот же пример,
записанный с использованием директив .MODEL и PROC:

.MODEL TPASCAL
.CODE
MyProc PROC FAR i:BYTE,j:BYTE RETURNS result:DWORD
PUBLIC MyProc
mov ax,i
.
.
.
ret

Обратите внимание, что теперь параметры задаются не в
обратном порядке и что многие операторы стали не нужны.
Использование TPASCAL с директивой .MODEL устанавливает соглашения
о связах Паскаля, определяет имена сегментов, выполняет команды
PUSH BP и MOV BP,SP, а также устанавливает возврат при помощи
команд POP BP и RET N (где N это длина параметров в байтах).

Использование другого базового или индексного регистра
——————————————————

Второй способ доступа к параметрам заключается в
использовании для приема их из стека другого базового или
индексного регистра -BX, SI или DI. Однако, следует помнить, что
по умолчанию сегментом для этих регистров является не SS, а DS;
для того, чтобы работать с этими регистрами, вы должны либо
использовать переопределение сегментов, либо изменить сегментный
регистр.

Ниже показано, как использовать BX для приема параметров:

CODE SEGMENT
ASSUME cs:CODE
MyProc PROC FAR ;procedure MyProc(i,j : integer);
PUBLIC MyProc
j EQU WORD PTR [bp+6] ;j находится выше
;адреса возврата
i EQU WORD PTR [bp+8] ;i находится над j
mov bx,sp ;теперь BP указывает на
;вершину стека
mov ax,i ;адресация i через BP
.
.
.

В подпрограммах, где к параметрам выполняется небольшое число
обращений, этот метод позволяет экономить время выполнения и
память. Почему? Дело в том, что BX, в отличие от BP, не требуется
в конце подпрограммы восстанавливать.

Результаты функций в Turbo Pascal
——————————————————————

Функции Turbo Pascal возвращают результат различными
способами, в зависимости от типа результата.

Результаты скалярных функций
—————————-

Результаты функций скалярных типов возвращаются через
регистры центрального процессора. Значения длиной в 1 байт
возвращаются в регистре AL, 2 байта в AX, а 4 байта — в DX:AX (при
этом наиболее значащее слово помещается в DX).

Результаты действительных функций
———————————

Результаты функций, являющиеся программно реализуемыми в
Turbo Pascal действительными числами, возвращаются через регистры
центрального процессора. Самое значащее слово при этом помещается
в DX, среднее слово в BX, а младшее — в AX.

Результаты функций 8087
————————

Результаты функций типа 8087 возвращаются через регистр 8087
«вершина стека» («top-of-stack») — ST(0) (или просто ST).

Результаты строковых функций
—————————-

Результаты функций строкового типа возвращаются через
временную область, распределяемую Turbo Pascal перед вызовом.
Перед помещением в стек первого параметра туда помещается дальний
указатель на эту область. Отметим, что данный указатель не
является частью списка параметров.

Примечание: не удаляйте из стека указатель результата
функции, поскольку Turbo Pascal ожидает, что после вызова он будет
доступен.

Результаты функции типа указатель
———————————

Результаты функции типа указатель возвращаются через DX:AX
(сегмент:смещение).

Распределение области для локальных данных
——————————————————————

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

Распределение частной (private) статической области ————

Turbo Pascal позволяет вашей программе на Turbo Assembler
резервировать область статических переменных в глобальном сегменте
данных (DATA, или DSEG). Для распределения этой области нужно
просто использовать директивы DB, DW и т.д., как показано в
следующем примере:

DATA SEGMENT PUBLIC
MyINT DW ? ;резервирование слова
MyByte DB ? ;резервирование байта
.
.
.
DATA ENDS

Распределение переменных Turbo Assembler в глобальном
сегменте данных имеет два существенных ограничения. Во-первых, эти
переменные являются «частными» — они недоступны из программы на
Turbo Pascal (хотя вы и можете передать туда указатели на них).
Во-вторых, в отличие от типизованных констант, они не могут быть
предварительно инициализированы. Оператор

MyInt DW 42 ;этот оператор не инициализирует MyInt в
;значение 42

не вызовет ошибку при компоновке модуля с программой на Turbo
Pascal, однако при запуске программы MyInt фактически не будет
инициализировано в значение 42.

Эти ограничения можно обойти, объявив переменные в Turbo
Pascal или типизованные константы и используя директиву EXTRN для
того, чтобы они стали доступны Turbo Assembler.

Распределение уничтожаемой (volatile) области памяти ———-

Ваши подпрограммы на Turbo Pascal могут также распределять в
стеке область уничтожаемой памяти (локальных переменных), которая
будет сохраняться только на время выполнения вызванной
подпрограммы. Перед выходом из подпрограммы эта память должна
быть освобождена, и регистр BP восстановлен. В следующем примере
процедура MyProc резервирует область памяти для двух целочисленных
переменных a и b:

CODE SEGMENT
ASSUME CS:CODE
MyProc PROC FAR ;procedure MyProc(i : Integer);
PUBLIC MyProc
LOCAL a : WORD, b : WORD = LocalSpace
;a находится в [bp-2],b в [bp-4]
i equ word ptr [bp+6] ;параметр i находится выше со-
;храненного значения BP и адреса
;возврата
push bp ;сохранить BP вызывающей
;программы
mov bp,sp ;устанавливает BP как указатель
;на вершину стека
sub sp,LocalSpace ;создать область для двух слов
mov ax,42 ;загрузить исходное значение A
;в AX
mov a,ax ;и оттуда в A
xor ax,ax ;очистить AX
mov b,ax ;и инициализировать B в 0
. ;выполнить все требуемые действия
.
.
mov sp,bp ;восстанавливает исходное
;значение SP
pop bp ;восстанавливает исходное
;значение BP
ret 2 ;извлечение из стека параметра
;размером в слово
MyProc ENDP
CODE ENDS
END

Отметим, что директива Turbo Assembler LOCAL используется для
создания символических имен и распределения области для локальных
переменных. Оперетор

LOCAL a : WORD, b : WORD = LocalSpace

устанавливает эквивалентность символического имени a и
[BP-2], b и [BP-4], а также символического имени LocalSpace и
числа 4 (размер области локальных переменных) на время выполнения
процедуры. Соответствующего оператора для создания символических
имен, ссылающихся на параметры, соответствующего оператора не
существует, поэтому следует просто приравнять i к [BP+6].

Более умный способ инициализации локальных переменных состоит
в помещении их в стек вместо декрементирования SP. Таким образом,
вы должны заменить SUB SP,LocalSpace на

mov ax,42 ;прием исходного значения для A
push ax ;помещение его в A
xor ax,ax ;обнуление AX
push ax ;и запись нуля в B

Если вы пользуетесь этим методом, тщательно следите за
стеком! Ссылки на символические имена a и b до помещения в стек
выполняться не должны.

Другой способ оптимизации включает в себя использование для
инициализации локальных переменных команд PUSH CONST (они доступны
на процессорах 80186, 80286 и 80386), либо запись BP в регистр,
вместо помещения его в стек (если имеется свободный регистр.)

Примеры подпрограмм на языке ассемблера для Turbo Pascal
——————————————————————

В данном разделе будет приведено несколько примеров
подпрограмм на языке ассемблера, которые могут быть вызваны из
программы на Turbo Pascal.

Универсальная подпрограмма шестнадцатиричных преобразований —-

Байты из num преобразовываются в строку шестнадцатиричных
цифр длины (byteCount * 2). Поскольку каждый байт дает в итоге два
символа, максмальное значение byteCount равно 127 (без провеки).
Для большей скорости выполнения для преобразования каждго
полубайта в шестнадцатиричную цифру (1 полубайт равен четырем
битам) используется последовательность add-daa- adc-daa.

Обращение к HexStr выполняется дальним вызовом. Это означает,
что объявление ее должно находиться либо в разделе interface
модуля Turbo Pascal, либо при активной директиве компилятора $f+.

CODE SEGMENT
ASSUME cs:CODE,Dds:NOTHING
; Параметры (+2 вследствие команды push bp)
byteCount equ byte ptr ss:[bp+6]
num equ dword ptr ss:[bp+8]
; Адрес результата функции (+2 вследствие команды push bp)
resultPtr equ dword ptr ss:[bp+12]

HexStr PROC FAR
PUBLIC HexStr

push bp
mov bp,sp ;прием указателя на стек
les di,resultPtr ;прием адреса результата функции
mov dx,ds ;записать DS Turbo в DX
lds si,num ;прием адреса числа
mov al,byteCount ;сколько байтов?
xor ah,ah ;сделать слово
mov cx,ax ;отслеживание байтов в CX
add si,ax ;начать с байта MS числа
dec si
shl ax,1 ;сколько цифр? (2/байт)
cld ;записать число знаков (идя
;в прямом направлении)
stosb ;байт длины строки назначения
HexLoop:
std ;сканирование числа от MSB к LSB
lodsb ;прием следующего байта
mov ax,al ;записать его
shr al,1 ;извлечение старшего полубайта
shr al,1
shr al,1
shr al,1
add al,90h ;специальная последовательность
;шестнадцатиричных преобразований
daa ;использование команд ADD и DAA
adc al,40h
daa ;теперь полубайт преобразован
;в ASCII
cld ;запись ASCII в возрасающем
;порядке
stosb
mov al,ah ;повторение преобразования для
;младшего полубайта
and al,0Fh
add al,90h
daa
adc al,40h
daa
stosb
loop HexLoop ;продолжить выполнение, пока все
;не будет выполнено
mov ds,dx ;восстановить DS Turbo
pop bp
ret 6 ;параметры занимают 6 байт
HexStr ENDP
CODE ENDS
END

Пример программы на Паскале, которая использует HexStr:

program HexTest;
var
num : word;
{$F+}
function HexStr (var num; byteCount : byte) : string; exterbnal;
{$L HEXSTR.OBJ}
{$F-}
begin;
num := $face;
Writeln(‘Преобразованная шестнадцатиричная строка «‘,
HexStr(num,sizeof(num)),'»‘);
end.

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

TASM HEXSTR
TPC HEXTEST
HEXTEST

При использовании директивы .MODEL программу HexStr можно
переписать следующим образом:

.MODEL TPASCAL
.CODE
HexStr PROC FAR num:DWORD,byteCount:BYTE RETURNS resultPtr:DWORD
PUBLIC HexStr
les di,resultPtr ;прием адреса результата функции
mov dx,ds ;записать DS Turbo в DX
lds si,num ;прием адреса числа
mov al,byteCount ;сколько байтов?
xor ah,ah ;сделать слово
mov cx,ax ;отслеживание байтов в CX
add si,ax ;начать с байта MS числа
dec si
shl ax,1 ;сколько цифр? (2/байт)
cld ;записать число знаков (идя
;в прямом направлении)
stosb ;байт длины строки назначения
HexLoop:
std ;сканирование числа от MSB к LSB
lodsb ;прием следующего байта
mov ax,al ;записать его
shr al,1 ;извлечение старшего полубайта
shr al,1
shr al,1
shr al,1
add al,90h ;специальная последовательность
;шестнадцатиричных преобразований
daa ;использование команд ADD и DAA
adc al,40h
daa ;теперь полубайт преобразован
;в ASCII
cld ;запись ASCII в возрасающем
;порядке
stosb
mov al,ah ;повторение преобразования для
;младшего полубайта
and al,0Fh
add al,90h
daa
adc al,40h
daa
stosb
loop HexLoop ;продолжить выполнение, пока все
;не будет выполнено
mov ds,dx ;восстановить DS Turbo
ret
HexStr ENDP
CODE ENDS
END

Вы можете использовать тот же пример программы на Паскале,
ассемблируя лишь изменившуюся подпрограмму HexStr, рекомпилируя
затем программу тем же пакетным файлом.

Обмен двумя переменными —————————————

В данной процедуре обмениваются две переменные размера count.
Если count равна 0, то процессор попытается сделать обмен для 64К.

CODE SEGMENT
ASSUME cs:CODE,ds:NOTHING
; Параметры (отметим, что смещения равны +2 вследствие
; выполнения команды push bp)

var1 equ DWORD PTR ss:[bp+12]
var2 equ DWORD PTR ss:[bp+8]
count equ WORD PTR ss:[bp+6]

Exchange PROC FAR
PUBLIC Exchange
cld ;обмен выполняется в прямом
;направлении
mov dx,ds ;сохранить DS
push bp
mov bp,sp ;прием базы стека
lds si,var1 ;прием первого адреса
les di,var2 ;прием второго адреса
mov cx,count ;прием количества пересылаемых
;байтов
shr cx,1 ;прием счетчика слов
;(младший бит -> перенос)
jnc ExchangeWords ;если не нечетный байт, вход
;в цикл
mov al,es:[di] ;чтение нечетного байта из var2
movsb ;пересылка байта из var1 в var2
mov [si-1],al ;запись байта var2 в var1
jz Finis ;если осталось выполнить обмен
;одного байта, то конец работы
ExchangeWords:
mov bx,-2 ;в BX удобно хранить число -2
ExchangeLoop:
mov ax,es:[di] ;чтение слова из var2
movsw ;выполнить пересылку из var1
;в var2
mov [bx][si],ax ;запись слова var2 в var1
loop ExchangeLoop ;повторение «count div 2» раз
Finis:
mov ds,dx ;восстановление DS Turbo
pop bp
ret 10
Exchange ENDP
CODE ENDS
END

Ниже приводится пример программы на Паскале, использующей
подпрограмму Exchange:

program TextExchange;
type
EmployeeRecord = record
Name : string[30];
Address : string[30];
City : string[15];
State : string[2];
Zip : string[10];
end;
var
OldEmployee, NewEmployee : EmployeeRecord;
{$F+}
procedure Exchange(var var1,var2; count : word); external;
{$L XCHANGE.OBJ}
{$F-}
begin
with OldEmployee do
begin
Name := ‘John Smith’;
Address := ‘123 F Street’;
City := ‘Scotts Valley’;
State := ‘CA’;
Zip := ‘90000-0000’;
end;
with NewEmployee do
begin
Name := ‘Mary Jones’;
Address := ‘9471 41st Avenue’;
City := ‘New York’;
Zip := ‘10000-1111’;
end;
Writeln(‘До: ‘,OldEmployee.Name,’ ‘,NewEmployee.Name);
Exchange(OldEmployee,NewEmployee,sizeof(OldEmployee));
Writeln(‘После: ‘,OldEmployee.Name,’ ‘,NewEmployee.Name);
Exchange(OldEmployee,NewEmployee,sizeof(OldEmployee));
Writeln(‘После: ‘,OldEmployee.Name,’ ‘,NewEmployee.Name);
end.

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

TASM XCHANGE
TPC XCHANGE
XCHANGE

При использовании директивы .MODEL программа на языке
ассемблера будет иметь следующий вид:

.model TPASCAL
.CODE
Exchange PROC FAR var1:DWORD,var2:DWORD,count:WORD
PUBLIC Exchange
cld ;обмен выполняется в прямом
;направлении
mov dx,ds ;сохранить DS
lds si,var1 ;прием первого адреса
les di,var2 ;прием второго адреса
mov cx,count ;прием количества пересылаемых
;байтов
shr cx,1 ;прием счетчика слов
;(младший бит -> перенос)
jnc ExchangeWords ;если не нечетный байт, вход
;в цикл
mov al,es:[di] ;чтение нечетного байта из var2
movsb ;пересылка байта из var1 в var2
mov [si-1],al ;запись байта var2 в var1
jz Finis ;если осталось выполнить обмен
;одного байта, то конец работы
ExchangeWords:
mov bx,-2 ;в BX удобно хранить число -2
ExchangeLoop:
mov ax,es:[di] ;чтение слова из var2
movsw ;выполнить пересылку из var1
;в var2
mov [bx][si],ax ;запись слова var2 в var1
loop ExchangeLoop ;повторение «count div 2» раз
Finis:
mov ds,dx ;восстановление DS Turbo
ret
Exchange ENDP
CODE ENDS
END

Программу на Паскале можно использовать ту же; следует просто
ассемблировать данную версию Exchange и перекомпилировать
программу тем же пакетным файлом.

Сканирование контекста DOS ————————————

Функция EnvString позволяет сканировать контекст DOS в
поисках строки вида «s=ЧТО-ТО» и возвращает это ЧТО-ТО, если такая
строка найдена.

DATA SEGMENT PUBLIC
EXTRN prefixseg : Word ;дает местоположение PSP
DATA ENDS
CODE SEGMENT PUBLIC
ASSUME cs:CODE,ds:DATA
EnvString PROC FAR
PUBLIC EnvString
push bp
cld ;прямое направление работы
;строковых команд
mov es,[prefixSeg] ;поиск PSP
mov es,es[2Ch] ;ES:DI указывает на контекст
xor di,di ;выравненный по границе
;параметра
mov bp,sp ;поиск адреса параметра
lds si,ss:[bp+6] ;находящегося непосредст-
;венно над адресом возврата
ASSUME ds:NOTHING
lodsb ;поиск длины
or al,al ;это ноль?
jz RetNul ;в этом случае возврат
mov ah,al ;иначе запись в AH
mov dx,si ;DS:DX содержит указатель
;на первый символ параметра
xor al,al ;обнуление
Compare:
mov ch,al ;для следующего цикла, если
;он будет, мы желаем сделать
;ch=0
mov si,dx ;прием указателя на строку
mov cl,ah ;прием длины
mov si,dx ;прием указателя на строку
repe cmpsb ;сравнение байтов
jne Skip ;если сравнение закончилось
;неудачно, то переход к сле-
;дующей строке символов
cmp byte ptr es:[di],’=’
;сравнение закончилось уда-
;чно. Является ли следующий
;символ знаком «=»?
jne NoEqual ;если нет, то совпадения
;не произошло
Found:
mov ax,es ;установка DS:SI на данную
;строку
mov ds,ax
mov si,di
inc si ;переход через знак равен-
;ства (=)
les bx,ss:[bp+10] ;прием адреса результата
;функции
mov di,bx ;помещение его в ES:DI
inc di ;переход через байт длины
mov cl,255 ;установка максимальной
;длины
CopyLoop:
lodsb ;прием байта
or al,al ;проверка нуля
jz Done ;если ноль, то конец работы
stosb ;помещение его в результат
loop CopyLoop ;цикла до 255 байт
Done: not cl ;при записи мы уменьшили
;cl от значения 255
mov es:[bx],cl ;запись длины
mov ax,SEG DATA
pop bp
ret 4
ASSUME ds:NOTHING
Skip:
dec di ;проверка нуля, начиная с
;данного символа
NoEqual:
mov cx,7FFFh ;при необходимости поиск
;выполняется с длинной
;стороны
sub cx,di ;контекст не бывает > 32К
jbe RetNul ;мы прошли весь контекст,
;выход
repne scasb ;поиск следующего нуля
jcxz RetNul ;если он не найден, то выход
cmp byte ptr es:[di],al
;второй ноль в строке?
jne Compare ;если нет, то повторить
RetNul:
les di,ss:[bp+10] ;прием адреса результата
stosb ;запись туда нуля
mov ax,SEG DATA
mov ds,ax ;восстановление DS
ASSUME ds:DATA
pop bp
ret
EnvString ENDP
CODE ENDS
END

Ниже приводится пример программы на Паскале, использующей
подпрограмму EnvString:

program EnvString;
{программа ищет в контексте строки символов}
var
EnvVariable : string;
EnvValue : string;
{$F+}
function EnvString(s:string) : string; external;
{$L ENVSTR.OBJ}
{$F-}
begin
EnvVariable := ‘PROMPT’;
EnvValue := EnvString(EnvVariable);
if EnvValue=» then EnvValue := ‘*** не найдена ***’;
Writeln(‘Переменная контекста: ‘,EnvVariable,
‘ Значение: ‘,EnvValue);
end.

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

TASM ENVSTR
TPC ENVTEST
ENVTEST

При использовании директивы .MODEL программа на языке
ассемблера EnvString будет иметь следующий вид:

.model TPASCAL
.DATA
EXTRN prefixSeg : Word ;задает местоположение PSP
.CODE
EnvString PROC FAR EnvVar:DWORD RETURNS EnvVal:DWORD
PUBLIC EnvString
cld ;прямое направление работы
;строковых команд
mov es,[prefixSeg] ;поиск PSP
mov es,es[2Ch] ;ES:DI указывает на контекст
xor di,di ;выравненный по границе
;параметра
mov bp,sp ;поиск адреса параметра
lds si,EnvVar ;находящегося непосредст-
;венно над адресом возврата
ASSUME ds:NOTHING
lodsb ;поиск длины
or al,al ;это ноль?
jz RetNul ;в этом случае возврат
mov ah,al ;иначе запись в AH
mov dx,si ;DS:DX содержит указатель
;на первый символ параметра
xor al,al ;обнуление
Compare:
mov ch,al ;для следующего цикла, если
;он будет, мы желаем сделать
;ch=0
mov si,dx ;прием указателя на строку
mov cl,ah ;прием длины
mov si,dx ;прием указателя на строку
repe cmpsb ;сравнение байтов
jne Skip ;если сравнение закончилось
;неудачно, то переход к сле-
;дующей строке символов
cmp byte ptr es:[di],’=’
;сравнение закончилось уда-
;чно. Является ли следующий
;символ знаком «=»?
jne NoEqual ;если нет, то совпадения
;не произошло
Found:
mov ax,es ;установка DS:SI на данную
;строку
mov ds,ax
mov si,di
inc si ;переход через знак равен-
;ства (=)
les bx,EnvVal ;прием адреса результата
;функции
mov di,bx ;помещение его в ES:DI
inc di ;переход через байт длины
mov cl,255 ;установка максимальной
;длины
CopyLoop:
lodsb ;прием байта
or al,al ;проверка нуля
jz Done ;если ноль, то конец работы
stosb ;помещение его в результат
loop CopyLoop ;цикла до 255 байт
Done: not cl ;при записи мы уменьшили
;cl от значения 255
mov es:[bx],cl ;запись длины
mov ax,SEG DATA
mov ds,ax ;восстановление DS
ASSUME ds:NOTHING
ret
ASSUME ds:NOTHING
Skip:
dec di ;проверка нуля, начиная с
;данного символа
NoEqual:
mov cx,7FFFh ;при необходимости поиск
;выполняется с длинной
;стороны
sub cx,di ;контекст не бывает > 32К
jbe RetNul ;мы прошли весь контекст,
;выход
repne scasb ;поиск следующего нуля
jcxz RetNul ;если он не найден, то выход
cmp byte ptr es:[di],al
;второй ноль в строке?
jne Compare ;если нет, то повторить
RetNul:
les di,EnvVal ;прием адреса результата
stosb ;запись туда нуля
mov ax,SEG DATA
mov ds,ax ;восстановление DS
ASSUME ds:DATA
ret
EnvString ENDP
CODE ENDS
END

Программу на Паскале можно использовать ту же; следует просто
ассемблировать данную версию EnvString и перекомпилировать
программу тем же пакетным файлом.