Глава 19. Интерфейс Турбо Ассемблера с Турбо Паскалем
В Турбо Ассемблере предусмотрены расширенные и мощные
средства, позволяющие вам добавлять код Ассемблера к программам
Турбо Паскаля. В данной главе мы подробно расскажем вам о том,
что нужно знать, чтобы полностью использовать данные средства,
приведем множество примеров и дадим некоторую более глубокую ин-
формацию.
Большинство программ, которые вы захотите написать, можно
реализовать целиком на Турбо Паскале. В отличие от большинства
других компиляторов Паскаля, Турбо Паскаль позволяет вам с по-
мощью массивов Port[], Mem[], MemW[] и MemL[] непосредственно об-
ращаться ко всем ресурсам компьютера, а с помощью процедур
Intr() и MsDos() вы можете обращаться к базовой системе ввода-вы-
вода (BIOS) и операционной системе.
Для чего же тогда может потребоваться использовать совместно
с Турбо Паскалем Ассемблер? Для этого существуют две вероятные
причины: выполнение некоторого небольшого числа операций, которые
непосредственно в Турбо Паскале недоступны, и использование преи-
муществ высокой скорости работы, которые дает Ассемблер. (Сам
Турбо Паскаль работает достаточно быстро, потому что он написан
на языке Ассемблера.) В данной главе мы покажем вам, как можно
использовать в Турбо Паскале преимущества Ассемблера.
Совместное с Турбо Паскалем использование данных
Далее следует содержимое главы.
Директива компилятора $L и внешние подпрограммы
Два ключевых момента при использовании Турбо Ассемблера с
Турбо Паскалем - это директива компилятора (Турбо Паскаля) {$L} и
описание внешней (external) подпрограммы. Директива {$L
MYFILE.OBJ} приводит к тому, что Турбо Паскаль будет искать файл
объектный MYFILE.OBJ (файл в стандартном пригодном для компоновки
формате MS-DOS) и компоновать его с вашей программой Турбо Паска-
ля. Если у файла в директиве {$L} расширение не указывается, то
подразумевается расширение .OBJ.
Каждая процедура или функция Турбо Ассемблера, которую вы
хотите сделать доступной в программе Турбо Паскаля, должна объяв-
ляться, как идентификатор PUBLIC, и ей должно соответствовать в
программе описание external (внешняя). Синтаксис описания внешней
процедуры или функции в Турбо Паскале аналогичен опережающему
(forward) описанию:
procedure AsmProc(a : integer; b : real); external;
function AsmFunc(c : word; d : byte); external;
Эти описания должны соответствовать следующим описаниям в
программе Турбо Ассемблера:
CODE SEGMENT BYTE PUBLIC
AsmProc PROC NEAR
PUBLIC AsmProc
.
.
.
AsmProc ENDP
AsmFunc PROC FAR
PUBLIC Bar
.
.
.
AsmFunc ENDP
CODE ENDS
Описание внешней (external) процедуры Турбо Паскаля должно
находиться на самом внешнем уровне программы или модуля, то есть
оно не должно быть вложенным по отношению к другому описанию про-
цедуры или функции. Попытка описать процедуру или функцию на лю-
бом другом уровне приведет к ошибке этапа компиляции.
Турбо Паскаль не делает проверку, чтобы убедиться, что все
процедуры, описанные с атрибутами NEAR или FAR, соответствуют
ближним или дальним подпрограммам в программе Турбо Паскаля. Фак-
тически, он даже не проверяет, являются ли метки AsmProc и
AsmFunc именами процедур. Поэтому вы должны обеспечить, чтобы
описания в Ассемблере и Паскале были правильными.
Директива PUBLIC
В Турбо Паскале доступны только те метки Ассемблера, которые
объявлены в модуле на языке Ассемблера, как общедоступные
(PUBLIC). Метки представляют собой единственные объекты, которые
могут передаваться из языка Ассемблера в Турбо Паскаль. Более
того, каждой общедоступной метке должно соответствовать описание
процедуры или функции в программе Турбо Паскаля, иначе компилятор
выдаст сообщение об ошибке. Причем не требуется, чтобы общедос-
тупная метка была частью описания PROC. Что касается Турбо Паска-
ля, то для него описания:
AsmLabel PROC FAR
PUBLIC Bar
и
AsmLabel:
PUBLIC Bar
эквивалентны.
Вы можете определять такие идентификаторы как PUBLIC только
в сегменте CODE. Турбо Паскаль не разрешает определения идентифи-
каторов PUBLIC в сегменте данных, поэтому создаваемые вами ас-
семблерные модули для компоновки с программой на Турбо Паскале
также не должны иметь в сегменте данных.
Директива EXTRN
Модуль Турбо Ассемблера может обращаться к любой процедуре,
функции, переменной или типизованной константе Турбо Паскаля, ко-
торая описывается на самом внешнем уровне программы или модуля, с
которым она компонуется. (Заметим, что это включает в себя пере-
менные, описанные после директивы компилятора {$L} и внешние опи-
сания, связанные с данным модулем.) Метки и обычные константы
Турбо Паскаля языку Ассемблера недоступны.
Примечание: Эти включает в себя переменные, указанные
после директивы компилятора $L и описаниях external, свя-
занных с данным модулем.
Предположим, в вашем программе Турбо Паскаля описываются
следующие глобальные переменные:
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 ; 8-байтовое значение с плавающей
; точкой (двойной точности) в
; формате IEEE
EXTRN H : TBYTE ; 10-байтовое значение с плавающей
; точкой во временном формате
EXTRN I : QWORD ; 8-байтовое целое со знаком в
; формате IEEE (сопроцессор 8087)
EXTRN J : DWORD ; указатель Турбо Паскаля
Аналогичным образом можно получить доступ к процедурам и
функциям Турбо Паскаля, включая библиотечные. Предположим, у вас
имеется модуль Турбо Паскаля, который выглядит следующим образом:
unit Sample;
{ Пример модуля, в котором определяется нескольку процедур
Паскаля, вызываемых из процедуры на языке Ассемблера }
interface
procedure TestSample;
procedure PublicProc; { для обращения извне должна
быть дальнего типа }
inplementation
var
A : word;
procedure AsmProc; external;
{$L ASMPROC.OBJ}
procedure PublicProc;
begin { PublicProc }
Writeln('В PublicProc');
end { PublicProc }
procedure NearProc; { должна быть ближнего типа }
begin { NearProc }
Writeln('B NearProc');
end; { NearProc }
{$F+}
procedure FarProc { должна иметь дальний тип согласно
директиве компилятора }
begin { FarProc }
Writeln('B FarProc');
end { FarProc }
{$F-}
procedure TestSample;
begin { TestSample }
Writeln('B 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 ; взять переменную из
; модуля
sub cx,2 ; изменить ее
mov ds:A,cx ; записать ее обратно
RET
AsmProc ENDP
CODE ENDS
END
Основная программа, которая проверяет эту программу на Ас-
семблере и модуль Паскаля, выглядит следующим образом:
program TSample;
uses Sample;
begin
TestSample;
end.
Чтобы сформировать пример программы с помощью компилятора,
работающего в режиме командной строки, и Ассемблера, используйте
следующие команды (или командный файл):
TASM ASMPROC
TPC /B SAMPLE
TSAMPLE
Так как внешняя подпрограмма должна объявляться в программе
Турбо Паскаля на самом внешнем уровне процедур, вы не можете для
доступа к объектам, являющимся локальными по отношению к процеду-
рам или функциям использовать описания EXTRN. Однако, ваша прог-
рамма на Турбо Ассемблере при вызове из программы Турбо Паскаля
может получить эти объекты, как значения параметров-переменных.
Ограничения при использовании объектов типа EXTRN
Синтаксис уточненного идентификатора Турбо Паскаля, при ко-
тором для доступа к объекту в заданном модуле используется имя
модуля и точка, несовместим с синтаксическими правилами Турбо Ас-
семблера и будет, таким образом, отвергнут. Описание:
EXTRN SYSTEM.Assing : FAR
приведет к тому, что Турбо Ассемблер выдаст сообщение об ошибке.
Имеется также два других ограничения на использование в Тур-
бо Паскале объектов EXTRN. Первое из них состоит в том, что в
ссылках на процедуру или функцию не могут выполняться арифмети-
ческие операции с адресами. Таким образом, если вы объявите:
EXTRN PublicProc : FAR
то не сможете записать оператор вида:
call PublicProc + 42
Второе ограничение относится к тому, что компоновщик Турбо
Паскаля не будет распознавать операции, которые разделяют слова
на байты, поэтому вы не можете применять такие операции к объек-
там EXTRN. Например, если вы объявите:
EXTRN i : WORD
то не сможете использовать в модуле Турбо Ассемблера выражения
LOW i или HIGH i.
Использование корректировок сегментов
Турбо Паскаль генерирует файлы .EXE, которые могут загру-
жаться в память компьютера РС по любому доступному адресу. Пос-
кольку в программе заранее неизвестно, куда будет загружен данный
сегмент программы, компоновщик указывает загрузчику DOS.EXE, что
нужно при загрузке скорректировать в программе все ссылки на сег-
менты. После выполнения этих корректировок все ссылки на сегменты
(такие, как CODE или DATA) будут содержать корректные значения.
Ваша программа на Турбо Ассемблере может использовать это
средство для получения адресов объектов во время выполнения.
Предположим, например, что в вашей программе требуется изменить
значение регистра DS, но вы не хотите сохранять в цикле исходное
содержимое стека или перемещать эти значения во временную об-
ласть. Вместо этого вы можете использовать операцию Турбо Ассем-
блера SEG:
.
.
.
mov ax,SEG DATA ; получить фактический
; адрес глобального значения
; DS Турбо Паскаля
mov ds,ax ; поместить его в DS для
; использования Турбо
; Паскалем
.
.
.
Когда ваша программа будет загружаться, DOS поместит коррек-
тное значение SEG DATA прямо в поле промежуточного операнда инст-
рукции MOV. Это наиболее быстрый путь перезагрузки сегментного
регистра.
Данный метод нужно также использовать, чтобы программы обс-
луживания прерываний сохраняли информацию в глобальном сегменте
данных Турбо Паскаля. Регистр DS не обязательно во время прерыва-
ния содержит значение DS Турбо Паскаля, но для получения доступа
к переменным и типизованным константам Турбо Паскаля можно ис-
пользовать указанную выше последовательность.
Устранение неиспользуемого кода
В Турбо Паскале имеются средства, обеспечивающие устранение
неиспользуемого кода. Это означает, что в полученный в результате
файл .EXE не будет включаться код процедур и функций, который ни-
когда не выполняется. Но поскольку нет полной информации о содер-
жимом модулей Турбо Ассемблера, Турбо Паскаль может выполнять для
них только ограниченную оптимизацию.
Турбо Паскаль будет устранять код модуля .OBJ в том и только
в том случае, если к любой доступной процедуре или функции этого
модуля нет обращения. Если же на какую либо процедуру или функцию
имеется ссылка, то весь этот модуль используется.
Чтобы добиться большей эффективности использования средства
Турбо Паскаля по устранению неиспользуемого кода, неплохо было бы
разбить программу на Ассемблере на небольшие модули, которые со-
держали бы только несколько процедур или функций. Это позволило
бы Турбо Паскалю, если он может это сделать, уменьшить объем ва-
шей конечной программы.
Соглашения Турбо Паскаля по передаче параметров
Турбо Паскаль использует для передачи параметров стек цен-
трального процессора (или, в случае передачи значений параметров
с одинарной, двойной, расширенной точностью или сложного типа,
стек арифметического сопроцессора). Параметры всегда вычисляются
и заносятся в стек в том порядке, в котором они указываются в
описании подпрограммы, слева направо. В данном разделе мы пояс-
ним, как эти параметры представляются.
Параметры-значения
Параметр-значение - это параметр, значение которого не может
изменяться подпрограммой, в которую он передается. В отличие от
многих компиляторов, Турбо Паскаль не выполняет слепого копирова-
ния в стек каждого параметра-значения: как мы далее поясним, ис-
пользуемый метод зависит от типа.
Скалярные типы
Параметры-значения всех скалярных типов (boolean, char,
shortint, byte, integer, word, longint, отрезки типов и перечис-
лимые типы) передаются как значения через стек процессора. Если
размер объекта составляет 1 байт, он заносится в стек, как полное
16-битовое слово, однако более значащий (старший) байт слова не
содержит полезной информации. Если размер объекта равен двум бай-
там, то он просто заносится в стек "как есть". Если объект имеет
размер 4 байта (длинное целое), он заносится в стек, как два
16-битовых слова. В соответствии со стандартом процессоров серии
8088 наиболее значащее (старшее) слово заносится в стек первым и
занимает в стеке старшие адреса.
Вещественные значения
Параметры-значения вещественного типа (real) передаются, как
6 байт в стеке (в Турбо Паскале это тип представляет собой 6-бай-
товый программно-эмулируемый тип с плавающей точкой). Это единс-
твенный тип, превышающий 4 байта, который может передаваться че-
рез стек.
Типы Single, Double, Extended и Comp и типы сопроцессора 8087
Турбо Паскаль использует те же соглашения о передаче пера-
метров для 8087, что и семейство компиляторов Borland C++. Пара-
метры передаются через главный стек центрального процессора,
вместе с остальными параметрами.
Указатели
Значения параметров для всех типов указателей заносятся не-
посредственно в стек, как указатели дальнего типа: сначала слово,
содержащее сегмент, затем другое слово, содержащее смещение. Сег-
мент занимает старший адрес, в соответствии с соглашениями фирмы
Intel. Для извлечения параметра-указателя в программе Турбо Ас-
семблера можно использовать инструкции LDS или LES.
Строки
Строковые параметры, независимо от размера, обычно никогда
не заносятся в стек. Вместо этого Турбо Паскаль заносит в стек
указатель (дальнего типа) на строку. Вызываемая подпрограмма не
должна изменять строку, на которую ссылается указатель. Если это
необходимо, подпрограмма может создать и работать с копией стро-
ки.
Единственное исключение из этого правила - это случай, когда
подпрограмма в перекрываемом (оверлейном) модуле A передает как
параметр-значение строковую константу подпрограмме в перекрывае-
мом модуле B. В этом контексте перекрываемый модуль означает лю-
бой модуль, скомпилированный с директивой {$O+} (допускаются
оверлеи). В этом случае перед тем, как будет сделан вызов и адрес
стека будет передан программе в модуле B, в стеке для строковой
константы резервируется временная память.
Записи и массивы
Записи и массивы, занимающие ровно 1, 2 или 4 байта, дубли-
руются непосредственно в стек и передаются, как параметры-значе-
ния. Если массив или запись имеет какой-либо другой размер (вклю-
чая 3 байта), то в стек заносится указатель на этот массив или
запись. В этом случае, если подпрограмма модифицирует такую
структуру, то она должна создать ее локальную копию.
Множества
Множества, как и строки, обычно никогда не заносятся непос-
редственно в стек. Вместо этого в стек заносится указатель на
множество. Первый бит младшего байта множества всегда соответс-
твует элементу базового типа (или порождающего типа) с порядковым
значением 0.
Единственное исключение из этого правила - это случай, когда
подпрограмма в перекрываемом (оверлейном) модуле A передает как
параметр-значение константу-множество подпрограмме в оверлейном
модуле B. В этом контексте перекрываемый модуль означает любой
модуль, компилированный с директивой {$O+} (допускаются оверлеи).
В этом случае перед тем, как будет сделан вызов и адрес стека бу-
дет передан программе в модуле B, в стеке для множества-константы
резервируется временная память.
Параметры-переменные
Все параметры-переменные (var) передаются точно также: как
указатель дальнего типа на их действительные адреса в памяти.
Обеспечение стека
Турбо Паскаль ожидает, что перед возвратом управления из
подпрограммы все параметры в стеке центрального процессора будут
удалены.
Есть два способа настройки стека. Вы можете использовать ин-
струкцию RET N (где N - это число байт передаваемых, то есть за-
несенных в стек, параметров), либо сохранить адрес возврата в ре-
гистрах (или в памяти) и извлечь параметры из стека поочередно.
Такую технику извлечения полезно использовать для оптимизации по
скорости при работе с процессором 8086 или 8088 (самые "медлен-
ные" процессоры серии), когда на адресацию типа "база плюс смеще-
ние" затрачивается минимум 8 циклов за обращение. Это позволяет
также сэкономить место, так как инструкция POP занимает только
один байт.
Примечание: Если вы используете директивы .MODEL, PROC
и ARG, то Ассемблер автоматически добавляет во все инструк-
ции RET число байт извлекаемых параметров.
Доступ к параметрам
Когда получает управление ваша подпрограмма на Турбо Ассемб-
лере, вершина стека будет содержать адрес возврата (два или четы-
ре слова, в зависимости от того, является ли подпрограмма ближней
или дальней), а далее будут находится передаваемые параметры.
Примечание: При вычислении адресов параметров нужно
принимать во внимание регистры, такие как BP, содержимое
которых также может быть занесено в стек.)
Существует три основных метода доступа к параметрам, переда-
ваемых Турбо Паскалем вашей подпрограмме на Турбо Ассемблере. Вы
можете:
- использовать для адресации к стеку регистр BP;
- для получения параметров использовать другой базовый или
индексный регистр;
- извлечь из стека адрес возврата, а затем параметры.
Первый и второй методы более сложны, и мы расскажем о них в
следующих двух разделах. Третий метод предусматривает извлечение
из стека и сохранение адреса возврата, а затем извлечения пара-
метров и записи их в регистры. Лучше всего этот метод работает,
когда ваша подпрограмма не требует пространства для локальных пе-
ременных.
Использование для адресации к стеку регистра BP
Первый и наиболее часто используемый метод доступа к пара-
метрам, передаваемым из Турбо Паскаля в Турбо Ассемблер, заключа-
ется в том, чтобы использовать для адресации к стеку регистр 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. Один из способов обойти это заключает-
ся в том, чтобы использовать более описательные имена параметров,
чтобы они не повторялись, либо можно ассемблировать каждую подп-
рограмму Ассемблера отдельно.
Директива ARG
Хотя можно обращаться к параметрам через регистр BP, Турбо
Ассемблер предусматривает альтернативу вычислению смещений в сте-
ке и выполнению текстовых присваиваний. Это директива ARG. При
использовании ее в процедуре директива ARG автоматически опреде-
ляет смещения параметров относительно регистра BP. Она вычисляет
также размер блока параметров и использует его в инструкции RET.
Поскольку идентификаторы, создаваемые по директиве ARG, определе-
ны только в соответствующей процедуре, в каждой процедуре или
функции вам не требуется использовать уникальные имена парамет-
ров.
Покажем, как будет выглядеть пример предыдущего раздела,
если переписать его, используя директиву 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
.
.
.
Директива ARG Турбо Ассемблера создает локальные идентифика-
торы для параметров i и j. На время выполнения процедуры строка:
ARG j : WORD, i : WORD = RetBytes
автоматически приравнивает идентификатор i к [WORD PTR BP+6],
идентификатор j к [WORD PTR BP+8], а идентификатор RetBytes - к
числу 4 (размеру в байтах блока параметров). В значениях учитыва-
ется и занесенное в стек значение BP, и размер адреса возврата:
если бы процедура MyProc имела ближний тип, то i было бы прирав-
нено к значению [BP+4], j - к [BP+6], а RetBytes также было бы
равно 4 (в любом случае процедура MyProc может завершить выполне-
ние с помощью инструкции RET RetBytes).
При использовании директивы ARG нужно помнить, что параметры
должны перечисляться в обратном порядке. Последний параметр про-
цедуры или функции Турбо Паскаля нужно размещать в директиве ARG
первым и наоборот.
Относительно использования директивы ARG с Турбо Паскалем
можно сделать еще одно замечание. В отличие от других языков,
Турбо Паскаль всегда заносит в стек параметр-значение размером в
байт, как 16-битовое слово. При этом сообщить Турбо Ассемблеру о
дополнительном байте должны вы. Предположим, например, что вы на-
писали функцию, описание которой в Паскале выглядит следующим об-
разом:
function MyProc(i, j : char) : string; external;
Директива ARG для этой функции должна была бы выглядеть так:
ARG j: BYTE: 2, i:BYTE: 2 = RetBytes RETURN result: DWORD
Здесь 2 после каждого аргумента необходимо указывать для то-
го, чтобы сообщить Ассемблеру, что каждый идентификатор заносится
в стек, как массив из 2 байт (где, в данном случае, младший байт
каждой пары содержит полезную информацию).
В функции, возвращающей строковое значение (как данная функ-
ция), параметр RETURNS в директиве ARG позволяет вам определить
переменную, приравненную к тому месту в стеке, которое указывает
на временный результат функции. Переменная в RETURNS на размер (в
байтах) блока параметров.
Турбо Паскаль и директива .MODEL
Директива .MODEL с параметром TPASCAL задает упрощенную сег-
ментацию, модель памяти и языковую поддержку. Обычно используется
большая модель памяти (large) Ранее мы уже видели, что нужно сде-
лать в программах Ассемблера, чтобы можно было использовать про-
цедуры и функции Паскаля. Преобразуем пример, используя в нем ди-
рективы .MODEL и PROC:
.MODEL large, PASCAL
.CODE
MyProc PROC FAR i:BYTE,j:BYTE result:DWORD
PUBLIC MyProc
mov ax,i
.
.
.
ret
Заметим, что теперь не нужно задавать параметры в обратном
порядке. Не требуется также масса других операторов. Использова-
ние в директиве .MODEL ключевого слова PASCAL задает использова-
ние соглашений Паскаля, определяет имена сегментов, выполняет
инструкции PUSH BP и MOV BP,SP и задает также возврат с помощью
инструкций POP BP и RETn (где n - число байт параметров).
Использование другого базового или индексного регистра
Второй способ доступа к параметрам состоит в использовании
для получения этих параметров другого базового или индексного ре-
гистра (BX, SI или DI). Нужно однако помнить, что по умолчанию
сегментным регистром для них является регистр DS, а не SS. Поэ-
тому для их использования вам придется применять префикс переоп-
ределения сегмента.
Приведем пример использования для получения параметров ре-
гистра BX:
CODE SEGMENT
ASSUME CS:CODE
MyProc PROC FAR ; procedure MyProc(i,j : integer);
PUBLIC MyProc
j EQU WORD PTR SS:[BX+4] ; j находится над сохраненным
; BP и адресом возврата
i EQU WORD PTR SS:[bp+8] ; i располагается над j
mov bx,sp ; BX теперь указывает на вершину
; стека
mov ax,i ; адресуемся к i через BX
.
.
.
В тех программах, где нет большого числа ссылок на парамет-
ры, такой метод позволяет сэкономить время и место. Почему? Пото-
му, что в отличие от BP, регистр BX не требуется восстанавливать
в конце программы.
Результаты функции в Турбо Паскале
В зависимости от типа результата функции Турбо Паскаля возв-
ращают свои результаты различными способами.
Результаты функции скалярного типа
Результаты функции скалярных типов возвращаются в регистрах
центрального процессора (ЦП). Байтовые значения возвращаются в
регистре AL, значения размером в 2 байта - в регистре AX,
4-байтовые значения - в паре регистров DX:AX (старшее слово нахо-
дится в регистре DX).
Результаты функции вещественного типа
Результаты используемого в Турбо Паскале 6-байтового прог-
раммно эмулируемого вещественного типа возвращаются в трех ре-
гистрах ЦП. Наиболее значащее (старшее) слово возвращается в DX,
среднее - в BX, а наименее значащее - в AX.
Результаты функции типов сопроцессора 8087
Результаты типов, использующихся сопроцессором 8087, возвра-
щаются в регистре вершины стека ST(0) (или просто ST).
Результаты функции строкового типа
Результаты строкового типа возвращаются во временной рабочей
области, выделяемой Турбо Паскалем перед вызовом. Указатель даль-
него типа на эту область заносится в стек перед занесением перво-
го параметра. Заметим, что этот указатель не является частью
списка параметров.
Примечание: Не удаляйте из стека полученный в резуль-
тате указатель, так как Турбо Паскаль ожидает, что после
вызова он будет доступен.
Результаты функции типа указатель
Результаты указатель возвращаются в паре регистров DX:AX
(сегмент:смещение).
Выделение пространства для локальных данных
Ваши программы, написанные на Турбо Ассемблере, могут выде-
лять пространство для своих собственных переменных, как постоян-
ных (статических), то есть сохраняющихся в промежутке между вызо-
вами, так и для временных (которые после вызова будут потеряны).
Оба этих случая обсуждаются в следующих разделах.
Выделение статической памяти
Турбо Паскаль позволяет в программах Турбо Ассемблера резер-
вировать пространство для статических переменных в сегментах гло-
бальных данных (DATA или DSEG). Чтобы выделить это пространство,
можно просто использовать такие директивы, как DB, DW и т.д. Нап-
ример:
DATA SEGMENT PUBLIC
MyInt DW ? ; зарезервировать слово
MyByte DB ? ; зарезервировать байт
.
.
.
DATA ENDS
Переменных, выделяемых Турбо Ассемблером в сегменте глобаль-
ных данных, касаются два важных ограничения. Во-первых, эти пере-
менными являются "частными", они недоступны программе Турбо Пас-
каля (хотя вы можете передавать указатели на них). Во-вторых, они
не могут быть предварительно инициализированы, как типизованные
константы. Оператор:
MyInt DW 42 ; это не инициализирует
; MyInt значением 42
не вызовет ошибки при компоновке модуля с программой Турбо Паска-
ля, однако MyInt при выполнении программы не будет иметь значение
42.
Эти ограничения можно обойти, описав переменные или типизо-
ванные константы Турбо Паскаля с помощью директивы EXTRN, что
сделает их доступными Турбо Ассемблеру.
Выделение временной памяти
В ваших программах на Турбо Паскале можно выделять также
временную память (локальные переменные) в стеке на время выполне-
ния каждого вызова. Перед возвратом управления эта память должна
быть освобождена, а значение регистра 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 ; загрузить в AX начальное
; значение A
mov a,ax ; и в A
xor ax,ax ; очистить регистр AX
mov b,ax ; инициализировать B нулем
mov b,ax ; выполнить нужные действия
.
.
.
mov sp,bp ; восстановить исходное
; значение SP
mov bp ; восстановить исходное
; значение регистра BP
ret 2
MyProc ENDP
CODE ENDS
END
Примечание: Директива Турбо Ассемблера 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 ; и занести 0 в b
Если вы используете этот способ, нужно внимательно отслежи-
вать стек! Не следует ссылаться на идентификаторы a и b перед
тем, как они занесены в стек.
Другой вид оптимизации предусматривает использование инст-
рукции PUSH CONST для инициализации локальных переменных (ее мож-
но использовать при наличии процессором 80186, 80286 и 80386),
или сохранение BP в регистре вместо занесения его в стек (если
есть неиспользованные регистры).
Примеры подпрограмм на Ассемблере для Турбо Паскаля
В данном разделе вы дадим некоторые примеры подпрограмм на
языке Ассемблера, которые вы можете вызывать из программ Турбо
Паскаля.
Подпрограмма шестнадцатиричного преобразования общего назначения
Содержащиеся в параметре num байты преобразуются в строку
шестнадцатиричных цифр длины (byteCount * 2). Поскольку каждый
байт порождает два символа, максимальное значение byteCount равно
127 (не проверяется). Для преобразования каждой группы (по 4 би-
та) в шестнадцатиричную цифру мы для скорости используем последо-
вательность add-daa-adc-daa.
Процедура HexStr (ее можно найти в файле HEX.ASM) написана
так, что вызываться она должна с помощью вызова дальнего типа.
Это означает, что ее следует описывать в интерфейсной части моду-
ля Турбо Паскаля или с помощью директивы компилятора {$F+}.
CODE SEGMENT
ASSUME cs:CODE,ds: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 Турбо
; Паскаля в DX
lds si,sum ; получить адрес числа
mov al,byteCount ; сколько байт?
xor ah,ah ; слово
mov cx,ax ; отслеживать число
; байт в CX
add si,ax ; начать со старшего
; байта числа
dec si
shl ax,1 ; сколько цифр?
; (2/байт)
cld ; сохранить число цифр
; (работать в прямом
; направлении)
stosb ; в приемнике - байт
; длины строки
NextLoop:
std ; сканировать число от
; старшего байта к
; младшему
lodsb ; получить следующий
; байт
mov ah,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
pop bp
ret 6 ; параметры занимают
; 6 байт
HexStr ENDP
CODE ENDS
END
Пример программы на Паскале, где используется функция
HexStr, имеет следующий вид:
Program HexTest;
var
num : word;
{$F+}
function HexStr(var num; byteCount : byte) : string;
external;
{$L HEXSTR.OBJ}
{$F-}
begin
num := word;
Writeln('Преобразованная строка имеет шестнадцатиричное
представление: ', HexStr(num,Sizeof(num)),'*');
end.
Для построения и запуска примеров программы на Паскале и
программы Ассемблера используйте следующие команды командного
файла:
TASM HEXSTR
TPC HEXTEST
HEXTEST
Если вы используете директиву .MODEL, то программу HexStr
можно записать следующим образом (файл HEXMOD.ASM):
.MODEL large, PASCAL
.CODE
HexStr PROC FAR num:DWORD,byteCount:BYTE RETURNS resultPtr:DWORD
PUBLIC HexStr
les di,resultPtr ; получить адрес
; результата функции
mov dx,ds ; сохранить DS Турбо
; Паскаля в DX
lds si,sum ; получить адрес числа
mov al,byteCount ; сколько байт?
xor ah,ah ; слово
mov cx,ax ; отслеживать число
; байт в CX
add si,ax ; начать со старшего
; байта числа
dec si
shl ax,1 ; сколько цифр?
; (2/байт)
cld ; сохранить число цифр
; (работать в прямом
; направлении)
stosb ; в приемнике - байт
; длины строки
NextLoop:
std ; сканировать число от
; старшего байта к
; младшему
lodsb ; получить следующий
; байт
mov ah,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
; Турбо Паскаля
ret
HexStr ENDP
CODE ENDS
END
При этом вы можете использовать ту же программу на Паскале и
просто ассемблировать альтернативный вариант HexStr и перекомпи-
лировать программу с помощью того же командного файла.
Пример обмена содержимого двух переменных
С помощью данной процедуры (VAREXCH.ASM) вы можете выполнить
обмен содержимого двух переменных размера count. Если count имеет
значение 0, то то процессор попытается перекопировать 64К.
CODE SEGMENT
ASSUME cs:CODE,ds:NOTHING
; Параметры (заметим, что из-за push bp смещение
; увеличивается на 2)
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 ExchangeWord ; если не нечетный байт,
; войти в цикл
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/2 раз
Finis:
mov ds,dx ; получить обратно DS
; Турбо Паскаля
pop bp
ret 10
Exchange ENDP
CODE ENDS
END
Программа Турбо Паскаля, которая использует функцию Exchange
(файл varexch.pas), имеет вид:
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';
State := 'NY';
Zip := ' 10000-1111';
end;
Writeln('Before: ',OldEmployee.Name,' ',NewEmployee.Name);
Exchange(OldEmployee,NewEmployee,sizeof(OldEmployee));
Writeln('After: ',OldEmployeeName,' ',NewEmployee.Name);
Exchange(OldEmployee,NewEmployee,sizeof(OldEmployee));
Writeln('After: ',OldEmployeeName,' ',NewEmployee.Name);
end.
Чтобы сформировать и запустить данные программы на Паскале и
Ассемблере, используйте следующие команды командного файла:
TASM XCHANGE
TPC XCHANGE
XCHANGE
Если использовать директиву .MODEL, то программа Exchange на
Ассемблере будет выглядеть следующим образом:
.MODEL large, PASCAL
.CODE
Exchange PROC FAR var1:DWORD,var2:DWORD,count:WORD
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 ExchangeWord ; если не нечетный байт,
; войти в цикл
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/2 раз
Finis:
mov ds,dx ; получить обратно DS
; Турбо Паскаля
ret
Exchage ENDP
CODE ENDS
END
Вы можете использовать ту же программу на Паскале и просто
ассемблировать альтернативный вариант процедуры Exchаnge и пере-
компилировать программу с помощью того же командного файла.
Пример анализа операционной среды DOS
С помощью функции EnvString вы сможете просмотреть операци-
онную среду DOS и найти строку вида "s=НЕЧТО" и возвратить НЕЧТО,
если это найдено.
DATA SEGMENT PUBLIC
EXTRN prefixSeg : Word ; дает адрес PSP
DATA ENDS
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 ; она равна 0?
jz RetNul ; да, возврат
mov ah,al ; в противном случае
; сохранить ее в AH
mov dx,si ; DS:SI содержат указатель
; на первый параметр
; char
xor al,al ; сделать его равным 0
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 ; DI: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 ; проверить на 0
jz Done ; если 0, выполнено
stosb ; занести его в результат
loop CopyLoop ; переместить до 255
; байт
Done: not cl ; при сохранении мы
; уменьшали от CL до 255
mov es:[bx],cl ; сохранить длину
mov ax,SEG DATE
mov ds,ax ; восстановить DS
ASSUME ds:DATA
pop bp
ret 4
ASSUME ds:NOTHING
Skip:
dec di ; проверить на 0
NoEqual:
mov cx,7FFFh ; длинный поиск, если
; нужно
sub cx,di ; операционная среда
; никогда не превышает
; 32К
jbe RetNul ; если конец, выйти
repne scasb ; посмотреть следующий
; 0
jcxz RetNul ; выйти, если не найден
cmp byte ptr es:[di],al ; второй 0 в строке?
jne Compare ; если нет, попытаться
; снова
RetNul:
les di,ss:[bp+10] ; получить адрес
; результата
stosb ; сохранить там 0
mov ax,SEG DATA
mov ds,ax ; восстановить DS
ASSUME ds:DATA
pop bp
ret 4
EnvString ENDP
CODE ENDS
END
Программа на Паскале, которая использует функцию EnvString,
выглядит следующим образом:
program EnvTest;
{ программа ищет строки операционной среды }
var
EnvVariable : string;
EnvValue : string;
{$F+}
function EnvString(s:string) : string; external;
{$L ENVSTRING.OBJ}
{$F-}
begin
EnvVariable := 'PROMPT';
EnvValue := EnvString(EnvVariable);
if EnvValue = '' then EnvValue := '*** не найдена ***';
Writeln('Переменная операционной среды: ',
EnvVariable,' Значение: ',EnvValue);
end.
Чтобы сформировать и запустить данные программы на Паскале и
Ассемблере, используйте следующие команды командного файла:
TASM ENVSTR
TPC ENVTEST
ENVTEST
Если использовать директиву .MODEL, то функция EnvString на
Ассемблере будет выглядеть следующим образом (ENVMOD.ASM):
.MODEL large, PASCAL
.DATA
EXTRN prefixSeg : Word ; дает адрес PSP
.CODE
EnvString PROC FAR EnvVar:DWORD RETURNS EnvVal:DWORD
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 ; она равна 0?
jz RetNul ; да, возврат
mov ah,al ; в противном случае
; сохранить ее в AH
mov dx,si ; DS:SI содержат указатель
; на первый параметр
; char
xor al,al ; сделать его равным 0
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 ; DI: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 ; проверить на 0
jz Done ; если 0, выполнено
stosb ; занести его в результат
loop CopyLoop ; переместить до 255
; байт
Done: not cl ; при сохранении мы
; уменьшали от CL до 255
mov es:[bx],cl ; сохранить длину
mov ax,SEG DATE
mov ds,ax ; восстановить DS
ASSUME ds:DATA
pop bp
ret 4
ASSUME ds:NOTHING
Skip:
dec di ; проверять на 0
NoEqual:
mov cx,7FFFh ; длинный поиск, если
; нужно
sub cx,di ; операционная среда
; никогда не превышает
; 32К
jbe RetNul ; если конец, выйти
repne scasb ; посмотреть следующий
; 0
jcxz RetNul ; выйти, если не найден
cmp byte ptr es:[di],al ; второй 0 в строке?
jne Compare ; если нет, попытаться
; снова
RetNul:
les di,ss:[bp+10] ; получить адрес
; результата
stosb ; сохранить там 0
mov ax,SEG DATA
mov ds,ax ; восстановить DS
ASSUME ds:DATA
ret 4
EnvString ENDP
CODE ENDS
END
Вы можете использовать ту же программу на Паскале и просто
ассемблировать альтернативный вариант функции EnvString и пере-
компилировать программу с помощью того же командного файла.
Далее следует содержимое приложений.
Назад | Содержание | Вперед