 |
В этом случае будут не только выполняться far-вызовы подп-
рограмм, но и передавемые указатели так же будут far. Модифици-
руйте вашу программу таким образом, чтобы она включала в себя но-
вый файл заголовка:
#include
main()
{
char near *mystr;
mystr = "Htllo, world\n";
printf(mystr);
}
Скомпилируйте вашу программу в TCC, затем скомпонуйте в
TLINK, специфицируя библиотеку большой модели, например, CL.LIB.
Смешение моделей - очень скрупулезное дело, но оно может быть вы-
полнено. Однако при наличии неточностей вас ожидают большие труд-
ности.
- 369,370 -
Программирование с совмещением языков
-----------------------------------------------------------------
Турбо Си позволяет вашим программам в Си вызывать подпрог-
раммы, написанные на других языках и, наоборот, программам, напи-
санным на других языках, вызывать ваши подпрограммы в Си. В этой
главе разъясняется, насколько легко совмещается Турбо Си с други-
ми языками и как обеспечиваются эти интерфейсы.
Вначале остановимся на двух главных типах последовательнос-
тей передачи параметров, а затем продемонстрируем способ написа-
ния модуля на ассемблере.
Последовательности передачи параметров типа Си и Паскаль
-----------------------------------------------------------------
В Турбо Си предусмотрены 2 метода передачи параметров функ-
циям. Один из них - стандартный метод Си, который будет рассмот-
рен первым, второй - типа Паскаль.
Последовательность передачи параметров типа Си
-----------------------------------------------------------------
Предположим, что вами объявлен следующий прототип функции:
void funca(int p1, int p2, int p3);
По умолчанию, Турбо Си использует последовательность переда-
чи параметров типа Си, называемую также Си-соглашением по вызову.
При вызове указанной функции (funca) параметры заносятся в стек
справа налево (p3, p2, p1); следом за ними заносится адрес возв-
рата. Так, если вы сделали вызов
main()
{
int i,j;
long k;
...
i = 5; j = 7; k = 0x1407AA;
funca(i,j,k);
...
}
- 371,372 -
Стек будет выглядеть следующим образом (непосредственно пе-
ред занесением адреса возврата):
SP + 06: 0014
SP + 04: 07AA k = p3
SP + 02: 0007 j = p2
SP: 0005 i = p1
(Памятуя о том, что стек в 8086 возрастает от старших адре-
сов памяти к младшим, можно утверждать, что i в данный момент на-
ходится на вершине стека). Вызванной подпрограмме нет необходи-
мости (да это и невозможно в данном случае) точно знать, сколько
параметров уже помещено в стеке. Предполагается, что все необхо-
димые параметры там уже помещены.
И еще очень важно то, что вызываемая подпрограмма не удаляет
из стека параметры, так как это сделает вызывающая программа.
Например, программа на ассемблере, созданная транслятором из ис-
ходной программы на Си, для главной функции выглядит так:
mov word ptr [bp-8],5 ; i = 5
mov word ptr [bp-6],7 ; j = 7
mov word ptr [bp-2],0014h ; k = 0x1407AA
mov word ptr [bp-4],07AAh
push word ptr [bp-2] ; Push старший полубайт k
push word ptr [bp-4] ; Push младший полубайт k
push word ptr [bp-6] ; Push j
push word ptr [bp-8] ; Push i
call near ptr funca ; вызов funca (push addr)
add sp,8 ; восстановить стек
Обратим внимание на последнюю команду: add sp,8. Транслятор
знает, сколько параметров помещено в стеке в данный момент; кроме
того он знает, что адрес возврата, помещенный в стек во время вы-
зова funca, уже вынут из него командой ret в конце funca.
Последовательность передачи параметров типа Паскаль
-----------------------------------------------------------------
Другой метод передачи параметров - стандартный метод типа
Паскаль (известный также как Паскаль-соглашение по вызову). Но
- 373,374 -
нельзя сказать, что вы можете вызывать функции Турбо Паскаля из
Турбо Си: это неверно. Рассматриваемая последовательность заносит
параметры слева направо; таким образом, если funca объявлена как
void pascal funca (int p1, int p2, int p3 );
то при вызове этой функции параметры в стек заносятся слева нап-
раво (p1, p2, p3), а следом за ними в стек помещается адрес возв-
рата. Таким образом, если вы даете вызов
main ()
{
int i,j;
long k;
...
i = 5; j = 7; k = 0x1407AA;
funca(i,j,k);
...
}
то стек будет выглядеть так (в момент перед занесением в стек ад-
реса возврата):
SP+06: 0005 i = p1
SP+04: 0007 j = p2
SP+02: 0014
SP: 07AA k = p3
В чем же проявляется отличие такого типа последовательности
передачи параметров от рассмотренного ранее? Кроме изменения по-
рядка занесения параметров, последовательность передачи парамет-
ров типа Паскаль предполагает, что вызванная подпрограмма (funca)
знает, сколько параметров передано и, соответственно, помещено в
стек. Другими словами, подпрограмма вызова funca на ассемблере
теперь выглядит так:
push word ptr[bp-8] ; Push i
push word ptr [bp-6] ; Push j
push word ptr [bp-2] ; Push старший байт k
push word ptr [bp-4] ; Push младший байт k
call near ptr funca ; вызов funca (Push addr)
Заметим, что здесь нет команды add sp,8 после вызова. Вместо
этого funca использует команду ret 8 при завершении для того,
- 375,376 -
чтобы очистить стек перед возвратом к main. По умолчанию все
функции, написанные на Турбо Си, используют метод передачи пара-
метров типа Си. Исключение составляет случай, когда вы используе-
те -р опцию транслятора (соглашение по вызову...Паскаль), когда
все функции используют метод типа Паскаль. В этой ситуации вы мо-
жете заставить нужную функцию применить метод передачи параметров
типа Си с помощью модификатора cdecl:
void cdecl funca (int p1, int p2, int p3);
Этот оператор игнорирует -p опцию.
Зачем вообще нужно Паскаль-соглашение по вызову? По трем ос-
новным соображениям:
- Вы можете быть вызваны подпрограммой, написаной на ассемб-
лере, которая использует данное соглашение по вызову.
- Вы можете быть вызваны подпрограммами, написаными на дру-
гих языках.
- Вызов создаваемой программы будет менее громоздким, пос-
кольку теперь не нужно очищать стек после ее окончания.
 |
|