Выполнение вызова
Все, что требуется от вас для передачи параметров в функцию C++, это занесение в стек самого правого параметра первым, затем следующего по порядку параметра и так далее, пока в стеке не окажется самый левый параметр. После этого нужно просто вызвать функцию. Например, при программировании на Borland C++ для вызова библиотечной функции Borland C++ strcpy для копирования строки SourceString в строку DestString можно ввести:
strcpy(DestString, SourceString);
Для выполнения того же вызова на Ассемблере нужно использовать инструкции:
lea ax,SourceString ; правый параметр push ax lea ax,DestString ; левый параметр push ax call _strcpy ; скопировать строку add sp,4 ; отбросить параметры
При настройке SP после вызова не забывайте очищать стек от параметров.
Можно упростить ваш код и сделать его независимым от языка, воспользовавшись расширением команды Турбо Ассемблера CALL:
call назначение [язык [,аргумент_1] .]
где "язык" - это C, PASCAL, BASIC, FORTRAN, PROLOG или NOLANGUAGE, а "аргумент_n" это любой допустимый аргумент программы, который может быть прямо помещен в стек процессора.
Используя данное средство, можно записать:
lea ax,SourceString lea bx,DestString call strcpy c,bx,ax
Турбо Ассемблер автоматически вставит команды помещения аргументов в стек в последовательности, принятой в С++ (сначала AX, затем BX), выполнит вызов _strcopy (перед именами С++ Турбо Ассемблер автоматически вставляет символ подчеркивания), и очищает стек после вызова.
Если вы вызываете функцию С++, которая использует соглашения Паскаля, заносите в стек параметры слева направо. После вызова настраивать указатель стека SP не требуется.
lea ax,DestString ; левый параметр push ax lea ax,SourceString ; правый параметр push ax call CTRCPY ; скопировать строку
Можно опять упростить ваш код, воспользовавшись расширением команды Турбо Ассемблера CALL:
lea bx,DestString ; самый левый параметр lea ax,SourceString ; самый правый параметр call strcpy pascal,bx,ax
Турбо Ассемблер автоматически вставит команды помещения аргументов в стек в последовательности, принятой в Паскале (сначала BX, затем AX), и выполнит вызов STRCPY (преобразуя имя к верхнему регистру, как принято в соглашениях Паскаля).
В последнем случае конечно подразумевается, что вы перекомпилировали функцию strcpy с параметром -p, так как в стандартной библиотечной версии данной функции используются соглашения по вызову, принятые в С++, а не в Паскале.
Функции С++ сохраняют следующие регистры (и только их): SI, DI, BP, DS, SS, SP и CS. Регистры AX, BX, CX, DX, ES и флаги могут произвольно изменяться.
Вызов из Турбо Ассемблера функции Borland C++
Одним из случаев, когда вам может потребоваться вызвать из Турбо Ассемблера функцию Borland C++, является необходимость выполнения сложных вычислений, поскольку вычисления гораздо проще выполнять на С++, чем на Ассемблера. Особенно это относится к случаю смешанных вычислений, где используются и значения с плавающей точкой и целые числа. Лучше возложить функции по выполнению преобразования типов и реализации арифметики с плавающей точкой на С++.
Давайте рассмотрим пример программы на Ассемблере, которая вызывает функцию Borland C++, чтобы выполнить вычисления с плавающей точкой. Фактически в данном примере функция Borland C++ передает последовательность целых чисел другой функции Турбо Ассемблера, которая суммирует числа и в свою очередь вызывает другую функцию Borland C++ для выполнения вычислений с плавающей точкой (вычисление среднего значения).
Часть программы CALCAVG.CPP, реализованная на С++ (CALCAVG.CPP), выглядит следующим образом:
#include <stdio.h> extern float Average(int far * ValuePtr, int NumberOfValues); #define NUMBER_OF_TEST_VALUES 10 int TestValues(NUMBER_OF_TEST_VALUES) = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
main() { printf("Среднее арифметическое равно: %f\n", Average(TestValues, NUMBER_OF_TEST_VALUES)); } float IntDivide(int Divedent, int Divisor) } return( (float) Divident / (float) Divisor );
}
а часть программы на Ассемблере (AVERAGE.ASM) имеет вид:
; Вызываемая из С++ функция с малой моделью памяти, ; которая возвращает среднее арифметическое последова- ; тельности целых чисел. Для выполнения завершающего ; деления вызывает функцию С++ IntDivide(). ; ; Прототип функции: ; extern float Average(int far * ValuePtr, ; int NumberOfValues); ; ; Ввод: ; int far * ValuePtr: ; массив значений для вычисления среднего ; int NumberOfValues: ; число значений для вычисления среднего .MODEL SMALL EXTRN _IntDivide:PROC .CODE PUBLIC _Average _Average PROC push bp mov bp,sp les bx,[bp+4] ; ES:BX указывает на массив значений mov cx,[bp+8] ; число значений, для которых нужно вычислить среднее mov ax,0 AverageLoop: add ax,es:[bx] ; прибавить текущее значение add ax,2 ; ссылка на следующее значение loop AverageLoop push WORD PTR [bp+8] ; получить снова число значений, переданных ; в функцию IntDivide в правом параметре push ax ; передать сумму в левом параметре call _IntDivide ; вычислить среднее значение с плавающей точкой add sp,4 ; отбросить параметры pop bp ret ; среднее значение в регистре вершины стека сопроцессора 8087
_Average ENDP END
Основная функция (main) на языке С++ передает указатель на массив целых чисел TestValues и длину массива в функцию на Ассемблере Average. Эта функция вычисляет сумму целых чисел, а затем передает эту сумму и число значений в функцию С++ IntDivide. Функция IntDivide приводит сумму и число значений к типу с плавающей точкой и вычисляет среднее значение (делая это с помощью одной строки на С++, в то время как на Ассемблере для этого потребовалось бы несколько строк). Функция IntDivide возвращает среднее значение (Average) в регистре вершины стека сопроцессора 8087 и передает управление обратно основной функции.
Программы CALCAVG.CPP и AVERAGE.ASM можно скомпилировать и скомпоновать в выполняемую программу CALCAVG.EXE с помощью команды:
bcc calcavg.cpp average.asm
Отметим, что функция Average будет работать как с малой, так и с большой моделью данных без необходимости изменения ее исходного кода, так как во всех моделях передается указатель дальнего типа. Для поддержки больших моделей кода (сверхбольшой, большой и средней) пришлось бы только изменить соответствующую директиву .MODEL.
Пользуясь преимуществами расширений, обеспечивающих независимость Турбо Ассемблера от языка, ассемблерный код из предыдущего примера можно записать более сжато (CONSISE.ASM):
.MODEL small,C EXTRN C IntDivide:PROC .CODE PUBLIC C Average Average PROC C ValuePtr:DWORD, NumberOfValues:WORD les bx,ValuePtr mov cx,NumberOfValues mov ax,0 AverageLoop: add ax,es:[bx] add bx,2 ;установить указатель на следующее значение loop AverageLoop call _IntDivide C,ax,NumberOfValues ret Average ENDP END
1 2 3 4 5 6 7 8 9
8 8 8
|