- •Связь Ассемблера и Си
- •Void main()
- •Ограничения на встроенное ассемблирование.
- •Вызов функций, написанных на языке Ассемблера из Си-программ
- •Передача параметров в функциях Си
- •Вызов функций Си из программ на языке Ассемблера
- •Include macro.Inc
- •If type number eq 0
- •If number le 0fffFh
- •X dw 0fcd5h
- •Void main()
Void main()
{
char Str1[MAX_SIZE], Str2[MAX_SIZE];
char FinStr[ 2 * MAX_SIZE];
unsigned int lenstr;
puts("Введите первую строку: ");
gets( Str1);
puts("Введите вторую строку: ");
gets( Str2);
lenstr = ConStr( Str1, Str2, FinStr);
puts("Объединённая строка: ");
puts( FinStr);
printf( "Длина строки: %u\n", lenstr);
}
А теперь программу на языке Ассемблера. Сначала нарисуем стековый кадр.
|
bp |
|
старое BP |
|
|
|
адрес возврата |
|
bp + 4 |
|
Str1 |
|
bp + 6 |
|
Str2 |
|
bp + 8 |
|
FinStr |
Файл constr.asm
.MODEL small
.CODE
GLOBAL _ConStr:PROC
_ConStr PROC
push bp
mov bp, sp
push si di
cld
mov di,@data
mov es,di
mov si, [bp+4] ; Адрес Str1 - в SI
mov di, [bp+8] ; Адрес FinStr - в DI
Str1Loop:
lodsb ; В AL- элемент строки
and al,al ; Конец строки?
jz DoStr2 ; Да - будем присоединять Str2
stosb ; Нет - перепишем в FinStr
jmp Str1Loop
DoStr2:
mov si,[bp+6] ; Адрес Str2 - в SI
Str2Loop:
lodsb
stosb
and al,al ; Конец строки?
jnz Str2Loop ; Нет - повторять копирование
mov ax,di ; Адрес терминатора строки - в AX
dec ax ; Терминатор не включать
sub ax, [bp+8] ; Определить длину строки
pop di si bp
ret
_ConStr ENDP
END
В программе на языке Ассемблера для нас были неприятные моменты:
приходилось следить за подчёркиванием в начале глобальных имён, хотя в исходном модуле на Си этого не было,
приходилось тщательно выписывать пролог и эпилог программы, хотя они, очевидно стандартны,
приходилось рисовать стековый кадр и тщательно отслеживать смещения относительно bp, что чревато ошибками; хотелось бы использовать символические имена.
В TASMимеются средства для исправления положения.
Директива .MODEL.
Если в начале файла размещать директиву
.MODELsmall,C
то можно не указывать символ подчёркивания. Пролог и эпилог в подпрограммах генерируется автоматически.
Директива PROC
Знакомая нам директива расширяется так:
имя PROC USES список_сохранямого_в_стеке
как правило, в стеке сохраняются регистры, их разделяют пробелами.
(Но если не указать .MODEL small,Cто предупреждение
USES has no effect without language)
Директива ARG
В директиве ARGперечисляем параметры и их типы в порядке их расположения в списке формальных параметров, например
ARG Str1:word, Str2:word
Тогда вместо команды mov si, Str1 или mov si, OFFSET Str1 Ассемблер сгенерирует mov si, [bp+4]. Разумеется, при этом обязательно использование директивы.MODEL small,C.
Пример. Перепишем заново программуConStr
.MODEL small,C
.CODE
GLOBAL ConStr:PROC
ConStr PROC USES si di
ARG Str1:word, Str2:word, FinStr:word
cld
mov di,@data
mov es,di
mov si,Str1
mov di,FinStr
Str1Loop:
lodsb
and al,al
jz DoStr2
stosb
jmp Str1Loop
DoStr2:
mov si,Str2
Str2Loop:
lodsb
stosb
and al,al
jnz Str2Loop
mov ax,di
dec ax
sub ax, OFFSET FinStr
ret
ConStr ENDP
END
Она стала намного проще для восприятия.
Перепишем программу для использования большой модели памяти. Теперь стековый кадр имеет вид (адрес возврата и параметры — двойные слова — сегмент:смещение)
|
bp |
|
старое BP |
|
|
|
адрес возврата |
|
|
|
|
|
bp + 6 |
|
Str1 |
|
|
|
|
|
bp + A |
|
Str2 |
|
|
|
|
|
bp + E |
|
FinStr |
|
|
|
|
Но благодаря использованию символических имен, можно не рассчитывать смещения. Зато загрузку указателей теперь придётся осуществлять с помощью команд ldsиles.
файл constrl.asm
.MODEL large,C
.CODE
GLOBAL ConStr:PROC
ConStr PROC USES si di ds
ARG Str1:dword, Str2:dword, FinStr:dword
cld
lds si,Str1
les di,FinStr
Str1Loop:
lodsb
and al,al
jz DoStr2
stosb
jmp Str1Loop
DoStr2:
lds si,Str2
Str2Loop:
lodsb
stosb
and al,al
jnz Str2Loop
mov ax,di
dec ax
sub ax, OFFSET FinStr
ret
ConStr ENDP
END
Команда для создания exe-файла
tcc -ml test.c constrl.asm
Ключ -mlуказывает на использование большой модели памяти.
Особенности интерфейса при использовании C++.
Первый шаг в переходе от CкC++ — изменить расширение файла с .cна .cpp. В результате файл будет обрабатываться компиляторомC++, что повлечёт например более строгую проверку типов и т.д.
Проделаем это. Переименуем test.cвtest.cpp. При компоновке нас ожидает неудача:
tcc test.cpp constr.asm
…
Turbo Link Version 3.0 …
Error: Undefined symbol ConStr(char near*,char near*,char near*) in module test.cpp
Для разгадки столь неожиданного сообщения сгенерируем ассемблерный файл и посмотрим его:
tcc -S test.cpp
В файле test.asmмы найдём строку
call near ptr @ConStr$qpzct1t1
Оказывается, компилятор C++ изменяет имена функций, добавляя в них закодированную информацию о типах параметров и возвращаемого значения. Поэтому компоновщик не нашёл этого глобального имени в модулеconstr.obj.
Исправление несложно. В файле test.cppизменим описание прототипа:
extern "C" unsigned int ConStr( char*, char*, char*);
Теперь компилятор будет генерировать внешнее имя по правилам языка Си, а не C++.
Интерфейс Turbo Pascal и Assembler.
Кратко, не приводя примеров, рассмотрим особенности интерфейса меду модулями, написанными на Паскале и Ассемблере.
1) Передача параметров происходит не как в Си — справа налево, а наоборот — слева направо.
2) Уничтожение стекового кадра возложено на подпрограмму и выполняется командой ret n, гдеn— число байтов в стековом кадре (разумеется,n— чётное число).
Эти правила носят название — паскалевское соглашение о связях. В языке Си имеются модификаторы, которые используются при описании прототипов функций:
int pascal f( p1, p2, …)— передача параметров по правилам языка Паскаль,
int cdecl f( p1, p2, …)— передача параметров по правилам языка Си.
Итак, если прототип функции Constrимеет вид:
unsigned int pascal ConStr( char*, char*, char*);
то в программе надо сделать следующие изменения:
во втором варианте изменить одну директиву
.MODELsmall,pascal
а в первом варианте
убрать символ подчёркивания у глобальных имён и записать имена большими буквами,
заменить ret наret 6,
изменить ссылки на элементы стекового кадра: [bp+4]заменить на[bp+8]и наоборот.
При программировании для Win16 использовалось паскалевское соглашение о связях. Но вWin32 ввели новое соглашение о связях: модификатор__stdcall. Параметры передаются справа налево — как в Си, а уничтожение стекового кадра производится командойret n. Пример мы увидим ниже.
