Сохранение регистров
При взаимодействии Турбо Ассемблера и Borland C++ вызываемые из программы на языке С++ функции Ассемблера могут делать все что угодно, но при этом они должны сохранять регистры BP, SP, CS, DS и SS. Хотя при выполнении функции Ассемблера эти регистры можно изменять, при возврате из вызываемой подпрограммы они должны иметь в точности такие значения, какие они имели при ее вызове.
Регистры AX, BX, CX, DX и ES, а также флаги могут произвольно изменяться.
Регистры DI и SI представляют собой особый случай, так как в Borland C++ они используются для регистровых переменных. Если в модуле С++, из которого вызывается ваша функция на Ассемблере, использование регистровых переменных разрешено, то вы должны сохранить регистры SI и DI, если же нет, то сохранять их не нужно.
Однако неплохо всегда сохранять эти регистры, независимо от того, разрешено или запрещено использование регистровых переменных. Трудно заранее гарантировать, что вам не придется компоновать данный модуль Ассемблера с другим модулем на языке С++, или перекомпилировать модуль С++ с разрешением использования регистровых переменных. При этом вы можете забыть, что изменения нужно также внести и в код Ассемблера.
Возврат значений
Вызываемые из программы на языке С++ функции на Ассемблере, так же как и функции С++, могут возвращать значения. Значения функций возвращаются следующим образом:
Тип возвращаемого значения | Где находится возвращаемое значение |
---|
unsigned char | AX | char | AX | enum | AX | unsigned short | AX | short | AX | unsigned int | AX | int | AX | unsigned long | DX:AX | long | DX:AX | float | регистр вершины стека сопроцессора 8087 (ST(0)) | double | регистр вершины стека сопроцессора 8087 (ST(0)) | long double | регистр вершины стека сопроцессора 8087 (ST(0)) | near* | AX | far* | DX:AX |
В общем случае 8- и 16-битовые значения возвращаются в регистре AX, а 32-битовые значения - в AX:DX (при этом старшие 16 бит значения находятся в регистре DX). Значения с плавающей точкой возвращаются в регистре ST(0), который представляет собой регистр вершины стека сопроцессора 8087 или эмулятора сопроцессора 8087, если используется эмулятор операций с плавающей точкой.
Со структурами дело обстоит несколько сложнее. Структуры, имеющие длину 1 или 2 байта, возвращаются в регистре AX, а структуры длиной 4 байта - в регистрах AX:DX. Трехбайтовые структуры и структуры, превышающие 4 байта должны храниться в области статических данных, при этом должен возвращаться указатель на эти статические данные. Как и все указатели, указатели на структуры, которые имеют ближний тип (NEAR), возвращаются в регистре AX, а указатели дальнего типа - в паре регистров AX:DX.
Давайте рассмотрим вызываемую из программы на языке С++ функцию на Ассемблере с малой моделью памяти FindLastChar, которая возвращает указатель на последний символ передаваемой строки. На языке С++ прототип этой функции выглядел бы следующим образом:
extern char * FindLastChar(char * StringToScan);
где StringToScan - это непустая строка, для которой должен возвращаться указатель на последний символ.
Функция FindLastChar имеет следующий вид:
.MODEL SMALL .CODE PUBLIC _FindLastChar _FindLastChar PROC push bp mov bp,sp cld ; в строковой инструкции нужно ; выполнять отсчет в прямом направлении mov ax,ds mov es,ax ; теперь ES указывает на ближний сегмент данных mov di, ; теперь ES:DI указывает на начало передаваемой строки mov al,0 ; найти нулевой символ, завершающий строку mov cx,0ffffh ; работать в пределах 64К-1 байт repne scasb ; найти нулевой символ dec di ; установить указатель обратно на 0 dec di ; ссылка обратно на последний символ mov ax,dx ; возвратить в AX указатель ближнего типа pop bp ret _FindLastChar ENDP END
Конечный результат, указатель на передаваемую строку, возвращается в регистре AX.
Вызов функции Турбо Ассемблера из Borland C++
Теперь мы рассмотрим пример программы на Borland C++, вызывающей функцию Турбо Ассемблера. Модуль Турбо Ассемблера COUNT.ASM содержит функцию LineCount, которая возвращает значение счетчика числа строк и символов в передаваемой строке:
; Вызываемая из С++ функция на Ассемблере с малой моделью ; памяти для подсчета числа строк и символов в завершающейся нулем ; "строке". ; ; Прототип функции: ; extern unsigned int LineCount(char * near StringToCount, ; unsigned int near * CharacterCountPtr); ; ; Ввод: ; char near * StringToCount: указатель на "строку", в ; которой нужно выполнить подсчет строк. ; ; unsigned int near * CharacterCountPtr: указатель на ; целую переменную, в которую нужно записать значение ; счетчика NEWLINE EQU 0ah ; символ перевода строки в Си .MODEL SMALL .CODE PUBLIC _LineCount __LineCount PROC push bp mov bp,sp push si ; сохранить регистровую ; переменную вызывающей программы mov si,[bp+4] ; SI указывает на строку sub cx,cx ; установить значение счетчика символов в 0 mov dx,cx ; установить в 0 счетчик строк LineCountLoop: lodsb ; получить следующий символ and al,al ; это 0? конец строки? jz EndLineCount ; да, выполнено inc cx ; нет, подсчитать следующий символ cmp al,NEWLINE ; это новая строка? jnz LineCountLoop ; нет, проверить следующий символ inc dx ; да, подсчитать еще одну строку jmp LineCountLoop EndLineCount: inc dx ; подсчитать строку, которая завершается нулевым символом mov [bx],cx ; задать значение переменной-счетчика mov ax,dx ; возвратить счетчик строк в качестве значения счетчика pop si ; восстановить регистровую переменную вызывающей программы pop bp ret _LineCount ENDP END
Следующий модуль на языке С++ с именем CALLCT.CPP представляет собой пример вызова функции LineCount:
char * TestString="Line 1\nline 2\nline 3"; extern "C" { unsigned int LineCount(char * StringToCount, unsigned int near * CharacterCountPtr); } main() { unsigned int LCount; unsigned int CCount;
Lcount = LineCount(TestString, &CCount); printf("Lines: %d\nCharacters: %d\n", LCount, CCount); }
Два модуля компилируются и компонуются вместе с помощью командной строки:
bcc -ms callct.cpp count.asm
Как здесь показано, функция LineCount будет работать только при компоновке с программами на языке С++, в которых используется малая модель памяти, так как в других моделях размеры указателей и адресов в стеке изменятся. Приведем пример версии функции LineCount (COUNTLG.ASM), которая будет работать с программами на С++, использующим большую модель памяти (но не малую модель: поскольку передаются дальние указатель, функция LineCount также описана, как функция дальнего типа):
; Вызываемая из С++ функция на Ассемблере для подсчета числа ; строк и символов в завершающейся нулем "строке". ; ; Прототип функции: ; extern unsigned int LineCount(char * far StringToCount, ; unsigned int far * CharacterCountPtr); ; ; Ввод: ; char far * StringToCount: указатель на "строку", в ; которой нужно выполнить подсчет строк. ; ; unsigned int far * CharacterCountPtr: указатель на ; целочисленную переменную, в которую нужно записать ; значение счетчика NEWLINE EQU 0ah ; символ перевода строки в Си .MODEL LARGE .CODE PUBLIC _LinaCount _LineCount PROC push bp mov bp,sp push si ; сохранить регистровую переменную вызывающей программы push ds ; сохранить стандартный сегмент данных lds si,[bp+6] ; DS:SI указывает на строку sub cx,cx ; установить значение счетчика символов в 0 mov dx,cx ; установить в 0 счетчик строк LineCountLoop: lodsb ; получить следующий символ and al,al ; это 0? конец строки? jz EndLineCount ; да, выполнено inc cx ; нет, подсчитать следующий символ cmp al,NEWLINE ; это новая строка? jnz LineCountLoop ; нет, проверить следующий символ inc dx ; да, подсчитать еще одну строку jmp LineCountLoop EndLineCount: inc dx ; подсчитать строку, которая завершается нулевым символом les bx,[bp+10] ; ES:BX указывает на ячейку, в которой возвращается значение счетчика mov es:[bx],cx ; задать значение переменной-счетчика mov ax,dx ; возвратить счетчик строк в качестве значения счетчика pop ds ; восстановить стандартный сегмент данных Си pop si ; восстановить регистровую переменную вызывающей программы pop bp ret _LineCount ENDP END
Программу COUNTLG.ASM можно скомпоновать с CALLCT.CPP с помощью следующей командной строки:
bcc -ml callct.cpp countlg.asm
1 2 3 4 5 6 7 8 9
8 8 8
| |