С помощью директивы компилятора $L можно выполнить компонов-
ку программ или модулей на языке Паскаль и процедур и функций на
языке ассемблера. Из исходного файла на языке ассемблера можно с
помощью ассемблера получить объектный файл (с расширением .OBJ).
Используя компоновщик, несколько объектных файлов можно скомпоно-
вать с программой или модулем. При этом используется директива
компилятора $L.
В программе или модуле на языке Паскаль процедуры или функ-
ции, написанные на языке ассемблера, должны быть описаны как
внешние. Например:
function LoCase(Ch : Char): Char; external;
В соответствующем файле
на языке ассемблера все
процедуры
или функции должны находиться в
сегменте с именем CОDЕ или CSEG,
или
в сегменте, имя
которого заканчивается на _TEXT,
а имена
внешних процедур и функций
должны быть указаны
в директивах
PUВLIC.
Вы должны обеспечить
соответствие процедуры или функции ее
определению в Паскале. Это относится в типу ее вызова
(ближний
или дальний), числу и типу параметров
и типу результата.
В исходном файле на языке ассемблера могут описываться
ини-
циализированные переменные, содержащиеся в сегменте с
именем
CONST или в сегменте, оканчивающемся на _DAТA, и неинициализиро-
ванные переменные в сегменте с именем
DATA или DSEG, или в сег-
менте, имя которого оканчивается на _BSS. В исходном файле на
языке ассемблера эти переменные
являются частными, и на них нель-
зя ссылаться из модуля или программы
на Паскале. Они, однако, на-
ходятся в том же сегменте, что и
глобальные переменные Паскаля, и
доступны через регистр сегмента DS.
На все процедуры, функции и переменные, описанные в модуле
или программе на Паскале и на те из
них, которые описаны в интер-
фейсной секции используемых модулей,
можно ссылаться из исходного
файла на языке ассемблера с
помощью директивы EXTRN.
При этом
обязанность обеспечить корректный тип
в определении EXTRN также
возлагается на вас.
Когда объектный файл
указывается в директиве $L, Borland
Pascal преобразует файл из формата
перемещаемых объектных модулей
(.OBJ) фирмы Intel в свой собственный
внутренний формат перемеща-
емых модулей. Это преобразование
возможно лишь при соблюдении не-
которых правил:
1. Все процедуры и функции должны быть помещены в сегмент с
именем CODЕ или CSEG, или в сегмент, имя которого окан-
чивается на _TEXT. Все инициализированные частные пере-
менные должны помещаться в сегмент с именем Const
или в
сегмент, имя которого оканчивается на _DATA. Все
неини-
циализированные
частные переменные должны быть помещены
в сегмент, имя которого
оканчивается на _DAТA. Неинициа-
лизированные локальные
переменные должны помещаться
в
сегмент с именем DATA или
DSEG, или в сегмент, имя кото-
рого оканчивается на _BSS.
Все другие сегменты игнориру-
ются, поэтому
имеется директива GRОUР. В
определениях
сегмента может задаваться
выравнивание на границу слова
или байта (WORD или ВYTE). При компоновке они всегда
вы-
равниваются на границу
слова. В определениях сегментов
могут указываться директивы
PUВLIС и имя класса (они иг-
норируются).
2. Borland Pascal
игнорирует все данные для сегментов,
от-
личных от
сегмента кода (CODE, CSEG или xxxx_TEXT) и
инициализированного сегмента
данных (CONST или
xxxx_DATA). Поэтому
при описании переменных в сегменте
неинициализированных данных
(DAТA, DSEG или xxxx_BSS)
для определения значения
всегда используйте вопроситель-
ный знак (?). Например:
Buffer DB 128 DUP(?)
3.
Байтовые ссылки на
идентификаторы типа EXTRN недопусти-
мы. Это означает,
например, что операторы НIGНТ и
LОW
нельзя использовать с
идентификаторами типа EXTRN.
Турбо Ассемблер (TASM)
значительно облегчает разработку
программ на языке ассемблера
и организации в них интерфейса с
программами ческое использование сегментов, схему
памяти и языковую поддержку
для программистов, работающих на
Borland Pascal.
Используя ключевое слово
PASCAL и директиву .MODEL, можно
обеспечить соблюдение соглашений о
вызовах с Borland Pascal, оп-
ределить имена сегментов, выполнить инструкции PUSH
BP и MOV
PB,SP, а также обеспечить возврат
управления с помощью операторов
POP BP и RET N (где N - это число байт
параметра). Директива
.MODEL имеет следующий синтаксис:
.MODEL xxxx, PASCAL
где xxxx - это модель памяти (обычно
LARGE).
Задание в директиве
.MODEL языка PASCAL
сообщает Турбо
Ассемблеру, что параметры были
занесены в стек слева-направо - в
том порядке, в котором они обнаружены
в исходном операторе, вызы-
вающем процедуру.
Директива PROC позволяет вам
задать параметры в том же по-
рядке, как они определены в программе
Borland Pascal. Если вы оп-
ределяете функцию, которая возвращает строку, обратите внимание
на
то, что директива PROC имеет
опцию RETURNS, позволяющую вам
получить доступ к временному указателю
строки в стеке и не оказы-
вающую влияния на число байт параметра,
добавляемых в операторе
RET.
Приведем примеры кода,
в которых используются директивы
.MODEL
LARGE, PASCAL
.CODE
MyProc
PROC FAR 1:BYTE, j : BYTE RETURNS
result : DWORD
PUBLIC MyProc
les di,result ; получить адрес временной строки
mov al,i ; получить первый параметр i
mov bl,j ; получить второй параметр j
.
.
.
ret
Определение функции в Borland
Pascal будет выглядеть следую-
щим
function MyProc(i,j : char) : string; external;
Следующая программа является примером модуля и представляет
собой две программы на
ассемблере, предназначенные для
обработки
строк. Функция UppеrCаsе преобразует
символы строки в прописные
буквы, а функция StringOf возвращает
строку символов заданной
длины
unit Strings;
interface
function UpperCase(S: string): string;
function
StringOf(Ch: char; Count: byte): string;
inplementation
{$L STRS}
function
UpperCase; external;
function
StringOf; external;
end.
Далее приведен файл на языке
ассемблера, в котором реализо-
ваны программы StringOf и UppеrCаsе.
Перед компиляцией модуля
Strings этот файл должен быть
ассемблирован в файл
с именем
STRS.OBJ. Обратите внимание на
то, что в программах используется
дальний тип вызова, так как они описаны в
интерфейсной секции
блока
CODE SEGMENT BYTE PUBLIC
ASSUME CS:CODE
PUBLIC UpperCase, StringOf ; объявить имена
function
Uppercase(S: String): String
UpperRes EQU
DWORD PTR [BP+10]
UpperStr EQU
DWORD PTR [BP+6]
Uppercase PROC FAR
PUSH BP ; сохранить регистр BP
MOV BP,SP
; установить стек
PUSH DS ; сохранить регистр DS
LDS SI,UpperStr ; загрузить адрес строки
LES DI,UpperRes ; загрузить адрес результата
CLD ; переместить строку
LODSB ; загрузить длину строки
STOSB ; скопировать результат
MOV CL,AL ; поместить длину строки в СХ
XOR CH,CH
JCXZ U3
; пропустить в случае пустой
;
строки
U1: LODSB ;
пропустить, если символ отличен
; от
'а'...'z'
CPM AL,'a'
CPM AL,'z'
JA U2 ; переместить строку
SUB AL,'a'-'A' ; преобразовать в прописные буквы
U2: STOBS ;
сохранить результат
LOOP U1 ; цикл по всем символам
U3: POP DS ; восстановить регистр DS
POP BP ; восстановить регистр ВР
RET 4 ; удалить параметры и возвратить
UpperCase ENDP
;
function StringOf(Ch: Char; Count: Byte): String
StrOfRes EQU
DWORD PTR [BP + 10]
StrOfChar EQU
BYTE PTR [BP + 8]
StrOfCOunt EQU
BYTE PTR [BP + 6]
StringOf PROC FAR
PUSH BP ; сохранить регистр ВР
MOV BP,SP ;
установить границы стека
LES DI,StrOfRes ;
загрузить адрес результата
MOV AL,StrOfCount ;
загрузить счетчик
CLD ; продвинуться на строку
STOSB ; сохранить длину
MOV CL,AL ;
поместить значение счетчика в CX
MOV AL,StrOfChar ; загрузить символ
REP STOSB ; сохранить строку символов
POP
; восстановить ВР
RET ; извлечь параметры и выйти
CODE ENDS
END
Чтобы ассемблировать этот
пример и скомпилировать модуль,
можно использовать следующие команды:
BPC stringer
Методы, реализованные на языке ассемблера, можно
скомпоно-
вать с программами Borland Pascal с
помощью директивы компилятора
$L и зарезервированного ключевого
слова external. Описание внеш-
него метода в объектном типе не отличается от обычного метода;
однако в реализации метода
перечисляется только заголовок метода,
за которым следует зарезервированной слово external. В исходном
тексте на ассемблере вместо точки (.)
для записи уточненных иден-
тификаторов следует
использовать операцию @ (точка в
ассемблере
уже имеет другой смысл и не может быть
частью идентификатора).
Например, идентификатор Паскаля
Rect.Init записывается на ассемб-
лере как Rest@Init. Синтаксис @ можно использовать как в иденти-
фикаторах PUBLIC, так и EXTRN.
Для небольших подпрограмм на
языке ассемблера очень удобно
использовать внутренние директивы
и операторы Borland
Pascal
(операторы inline). Они позволяют вставлять инструкции машинного
кода непосредственно в программу или
текст блока, вместо того,
чтобы использовать объектный файл.
Оператор inline состоит из зарезервированного
слова Inline,
за которым следует одна или более
встроенных записей (записей ма-
шинного кода), разделенных косой чертой и заключенных
в круглые
скобки:
inline(10/$2345/Count+1/Data-Offset);
Оператор inline имеет следующий синтаксис:
ЪДДДДДДДДї ЪДДДї
ЪДДДДДДДДДДї ЪДДДї
подставляемый ДД>і inline ГД>і
( ГДДДД>і запись в ГДВД>і ) ГД>
оператор АДДДДДДДДЩ АДДДЩ
^ і машинном і і АДДДЩ
і і коде і і
і АДДДДДДДДДДЩ і
і ЪДДДї і
АДДДДДДґ / і<ДДДДЩ
АДДДЩ
Каждый оператор inline состоит из
необязательного специфика-
тора размера, < или >, и константы или
идентификатора переменой,
за
которой следуют ноль или более спецификаторов смещения (см.
описанный далее синтаксис). Спецификатор смещения состоит из +
или -, за которым следует константа.
ЪДДДДДДДДДДДї
запись во
ДДВДДДДДДДДДДДДДДДДДДДД>і константа ГДДДДДДДДДДДДДДД>
встроенном і ЪДДДї ^
АДДДДДДДДДДДЩ ^
машинном ГДД>і < ГДДДДДДґ і
коде і АДДДЩ і і
і ЪДДДї
і і
ГДД>і >
ГДДДДДДЩ і
і АДДДЩ і
і ЪДДДДДДДДДДДДДДДї і
АД>і идентификатор ГДВДДДДДДДДДДДДДДДДДДДДЩ
і переменной
і і ^
АДДДДДДДДДДДДДДДЩ
і і
ЪДДДДЩ
АДДДДДДДДДї
і
ЪДДДДї ЪДДДДДДДДДї і
АДДДДД>ізнакГДД>іконстантаіДДВДДДДЩ
^ АДДДДЩ
АДДДДДДДДДЩ і
АДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Каждая запись inline
порождает 1 байт или одно слово кода.
Значения вычисляется, исходя из значения первой константы или
смещения идентификатора переменной, к
которому добавляется или из
которого вычитается значение каждой из
последующих констант.
Если запись в
машинном коде состоит только из
констант и,
если ее значение лежит в 8-битовом
диапазоне (0..255), то она по-
рождает один байт кода. Если значение выходит за границу 8-бито-
вого диапазона или если запись inline
ссылается на переменную, то
генерируется одно слово кода (младший
байт следует первым).
Операции < и > могут
использоваться для отмены автоматичес-
кого
выбора размера, который
был описан ранее. Если оператор
inline начинается с операции
<, то в код включается только млад-
ший
значащий байт значения, даже если это 16-битовое значение.
Если оператор inline начинается с
операции >, то в код включается
всегда слово, даже если старший значащий байт равен 0.
Например,
оператор:
inline(<$1234/>$44);
гененирует код длиной три байта:
$34,$44,$00.
Значение идентификатора
переменной в записи inline представ-
ляет собой адрес смещения переменной
внутри ее базового сегмента.
Базовый сегмент глобальных переменных
(переменных, описанных на
самом внешнем уровне в модуле или
программе) и типизованные конс-
танты, доступ к которым организован
через регистр DS, представля-
ют
собой сегмент данных.
Базовый сегмент локальных переменных
(переменных, описанных внутри
подпрограммы) является сегментом
стека. В этом случае смещение переменной относится к регистру ВР,
что автоматически влечет за собой
выбор сегмента стека.
Примечание: Регистры BP,
SP, SS и DS должны сохранять-
ся с помощью операторов
inline. Значение всех других
ре-
гистров можно изменять.
В следующем примере оператора
inline генерируется машинный
код
для записи заданного числа слов или данных в указанную пере-
менную. При вызове процедуры FillWord Count слов со значением
Data записывается в памяти, начиная с
первого байта, обозначенно-
го как Dest.
procedure FillWord(var Dest,
Count, Data: word);
begin
inline(
$C4/$BE/Dest/ { LES DI,Dest[BP] }
$8B/$8e/Count/ { MOV CX,Xount[BP] }
$8B/$86/Data/ { MOV AX,Data[BP] }
$FC/ { CLD }
$F3/$AB); { REP STOSW }
В операторной части
блока операторы inline могут
свободно
чередоваться с другими операторами.
Директивы inline позволяют писать
процедуры и функции, кото-
рые преобразуются при каждом вызове в
заданную последовательность
инструкций, представляющих собой
машинный код. Синтаксис у дирек-
тивы inline такой же, как у оператора
inline:
ЪДДДДДДДДДДДДї
директива
ДДДДДДДДДДДДДДДДДДДДДД>і
оператор ГДДДДДДДДДДДД>
inline і
inline і
АДДДДДДДДДДДДЩ
При вызове обычной процедуры или
функции (включая те, кото-
рые содержат в себе операторы
inline) компилятором генерируется
такой
код, в котором параметры (если они имеются) помещаются в
стек, а затем уже для обращения к
процедуре или функции генериру-
ется
инструкция CALL. Однако, когда вы обращаетесь к процедуре
или функции типа inline, компилятор вместо инструкции CALL гене-
рирует код из директивы inline. Вот
короткий пример двух директив
inline:
procedure DisableInterrupts;
inline($FA); { CLI }
procedure EnableInterrupts;
inline($FB); { STI }
Когда вызывается процедура DisableInterrupt то генерируется
один байт кода - инструкция CLI.
Процедуры или функции, описанные с помощью директив inline,
могут иметь параметры, однако на параметры нельзя ссылаться сим-
волически (хотя для других переменных
это допускается). К
тому
же,
поскольку такие процедуры
или функции фактически являются
макрокомандами, у них отсутствуют
автоматический код с инструкци-
ями
входа или выхода и никаких инструкций возврата управления не
требуется.
Следующая функция выполняет
умножение двух целых значений, в
результате чего получается число
длинного целого типа:
function LongMul(X,Y : Integer):
Longint;
inline(
$58/ { POP DS ; извлечь из
стека Y }
$5A/ { POP AX ; извлечь из
стека X }
$F7/$EA); { IMUL DX ; DX:AX = X*Y }
Обратите внимание на отсутствие инструкций входа и выхода и
инструкции возврата управления. Их
присутствия не требуется, пос-
кольку при вызове этой функции содержащиеся в ней четыре байта
просто включаются в текст программы.
Директивы inline
предназначены только для
очень коротких
(менее 10 байт) процедур и функций.
Из-за того, что процедуры и
функции типа inline имеют харак-
тер макроопределений, они не могут использоваться в качестве ар-
гумента операции @ или в функциях
Addr, Offs и Seg.