Взаимосвязь языков C и ассемблера (ASM_R4)

Посмотреть архив целиком

Раздел 4: Вызов функций С из языка ассемблера


4.1 Вызов С и С++ из ассемблера


До сих пор рассматривалось, как разделить переменные между С или C++ и ассемблером, а также как вызывать внешние ассемблерные функции из программ, написанных на С или C++. Теперь подойдем к этому с другой стороны, т.е. к вызову функций С или C++ из ассемблерного модуля - это также возможно, но требует большего внимания.

Если функция не обладает параметрами, процесс достаточно прост. Надо объявить функцию С или C++ в директиве EXTRN и воспользоваться инструкцией call:

CODESEG

EXTRN _cfuntion:proc

......

call _cfunction

Здесь предполагается, что функция, названная _cfunction, существует в программе, подлежащей компоновке вместе с ассемблерным модулем.

Если функции требуются параметры, процесс усложняется. Простые параметры, такие как символы и целые числа часто передаются непосредственно в стек. Сложные переменные, такие как строки, структуры и множества, передаются посредством ссылок, т.е. по адресу. Кроме того, многие функции возвращают результат в специфичные регистры. При вызове функций С или C++ из языка ассемблера надо самим позаботиться о подобных нюансах.

Сперва рассмотрим простейший случай вызова функции с одним целочисленным параметром:

void showscore( int thescore)

{

printf(“\nThe score is: %d\n, thescore);

}

Чтобы вызвать функцию showscore из ассемблерного модуля, передавая значение переменной типа слова в качестве thescore, можно написать:

CODESEG

EXTRN showscore: proc

mov ах, 76 ; Присвоение score регистру

push ах ; Передача параметра в стек

call _showscore ; Вызов функции С

pop ах ; Фиксация стека


Прежде всего, значение score присваивается ах (любой другой регистр точно так же подойдет для этого), а затем выталкивается в стек перед вызовом showscore. После возврата из функции слово выталкивается из стека. Это необходимо потому, что в С и C++ вызывающая программа удаляет параметры из стека. Если имеется несколько параметров, может быть, будет лучше просто прибавить их общее число байтов к sp. Например, чтобы вызвать функцию, которая оперирует четырьмя 16-битовыми параметрами, можно воспользоваться следующими командами:

push [vl] ; Выталкивание четырех переменных

push [v2] ; (не показанных) в стек

push [v3]

push [v4]

call _aCfunction ; Вызов функции С

add sp, 8 ; Удаление параметров

Выталкивание нескольких параметров осуществляется в порядке, обратном тому, в каком они объявляются функции С или C++. Исходя из предположения, что функция fillstring определена как

void fillstring( unsigned char far *thestring, int stringLength, char fillchar );

для вызова этой функции из языка ассемблера и заполнения строковой переменной пробелами требуются несколько шагов. Сперва ассемблерный модуль объявляется строковой переменной:

DATASEG

PUBLIC _astring

_astring db 80 dup (0)

Затем этот же модуль объявляет fillstring в директиве EXTRN и вызывает функцию для заполнения строчной переменной пробелами:

CODESEG

EXTRN _fillstring: ргос

xor ah, ah ; Обнуление ст. половины ах

mov al,’ ‘ ; Присвоение пробела а1

push ах ; Проталкивание пар-ра fillchar

mov ах, 79 ; Присвоение длины строки ах

push ах ;Проталкивание пар-ра дл. строки

push ds ;Проталкивание сег-та адреса строки

mov ах, offset _astring ;Присвоение смещения адреса ах

push ах ;Проталкивание смещ. адреса строки

call _fillstring ; Вызов функции

add sp, 8 ; Удаление параметров из стека

Каждый из параметров - заполняющий символ, длина строки и 32-битовый указатель строковой переменной- проталкивается в стек в порядке, обратном перечисленному в определении функции. Применительно к указателю - сегмент адреса проталкивается перед смещением. После обращения к _fillstring к указателю стека sp добавляются 8 байт, удаляя параметры из стека.

Несмотря на то что в этом примере функция _fillstring в действительности написана на языке ассемблера, вызовы функций С и C++ ничем не отличаются.


4.2 Локальные переменные


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

void countup()

{

int i;

for (i = 0; i < 10; i++)

printer("%d", i);

}

Целая переменная i помещается в памяти в стек при запуске функции countup и существует только до тех пор пока выполняется эта функция. В ассемблерном модуле можно проделать тоже самое с помощью директивы LOCAL. Вот пример законченной функции:

PROС _cfunction NEAR

LOCAL i:Word=stacksize

push bp

mov bp, sp

sub sp, stacksize

mov [i], 0

@@10:

inc [ i ]

;

;--Код, использующий локальную переменную [i]

;

cmp [i], 10

jne @@10

mov sp, bp

pop bp

ret ; Возврат в точку вызова

ENDP _cfunction

Директива LOCAL в этом примере подготавливает переменную i типа Word (слово). Указание = stacksize назначает общее число байтов, занимаемое всеми локальными переменными - в данном случае 2 байта. Это значение вычитается из sp после подготовки адресации переменных в стек. Затем, для ссылки на i, используются такие инструкции, как mov, inc и crop. Благодаря директиве LOCAL ссылки типа [i] переводятся следующим образом:

mov [bp-2], 0

inc [bp-2]

и т.д. При использовании LOCAL нет необходимости вычислять отрицательные смещения относительно bp, чтобы определить местоположение переменных в стеке, -достаточно воспользоваться именами переменных.

Поскольку bp не изменяется во время выполнения этой функции, можно восстановить sp по средством bp, удаляя область локальной переменной из стека, или прибавить stacksize к sp с помощью команды

add sp, stacksize

Подходят оба метода, но восстановление sp посредством bp - быстрее. Можно также объявить несколько локальных переменных операторами, подобными следующему:

LOCAL i:Word; j:Word; c:Byte=stacksize

Теперь, после вычитания stacksize из указателя стека для резервирования области в стеке, можно использовать три локальные переменные - i, j и с. (Необходимо всегда делать LOCAL, что упрощает адресацию локальных переменных, это не создает область для переменных в памяти.)


4.3 Передача аргументов


Совместное использование C++ и ассемблера становится более сложным, когда к функциям добавляются аргументы. Приходится очень внимательно программировать, обращаясь к функциям из различных модулей и выбирая аргументы из стека. Но следующая директива берет на себя задачу сама произвести ломку имен и занести их в стек:

ARG c_offset:byte, k_pffset:word

Аргументы, перечисленные таким образом, не являются объектами данных; они смещаются в стеке относительно регистра bр. Использование ARG подобным образом позволяет ассемблеру вычислить смещения вместо нас - но мы должны специфицировать правильные типы данных. Символьная переменная в C++ является байтом в ассемблере, целая в C++ - эквивалентом ассемблерного слова и т.д.

Обратный процесс - передача аргументов из языка ассемблера в C++ - требует иной тактики:

proc _asmfunction C c_arg:byte, k_arg:word

“C” после имени функции указывает, что аргументы приводятся для языка С (т.е. они выталкиваются в стек в порядке справа налево). Остальное также, как и для директивы ARG.

В результате Turbo Assembler автоматически пишет инструкции для сохранения, инициализации и восстановления bp. При использовании этой альтернативной методики не приходится проводить точные операции по выталкиванию bp. За исключением этого отличия, в остальном процесс программирования остается тем же самым.


19




Случайные файлы

Файл
158823.rtf
8184.rtf
136613.rtf
116682.rtf
25160.rtf




Чтобы не видеть здесь видео-рекламу достаточно стать зарегистрированным пользователем.
Чтобы не видеть никакую рекламу на сайте, нужно стать VIP-пользователем.
Это можно сделать совершенно бесплатно. Читайте подробности тут.