Мы с вами уже говорили о том, что в различных языках программирования используются различные правила вызова подпрограмм, и что для совместимости с ними в языке Delphi существуют директивы register, stdcall, pascal и cdecl. Применение этих директив становится особенно актуальным при разработке динамически загружаемых библиотек, которые используются в программах, написанных на других языках программирования.
Чтобы разобраться с применением директив, обратимся к механизму вызова подпрограмм. Он основан на использовании стека.
Стек — это область памяти, в которую данные помещаются в прямом порядке, а и извлекаются в обратном, по аналогии с наполнением и опустошением магазина патронов у стрелкового оружия. Очередность работы с элементами в стеке обозначается термином LIFO (от англ. Last In, First Out — последним вошел, первым вышел). (Существует еще обычная очередность работы с элементами, обозначаемая термином FIFO (от англ. First In, First Out — первым вошел, первым вышел)).
Для каждой программы на время работы создается свой стек. Через него передаются параметры подпрограмм и в нем же сохраняются адреса возврата из этих подпрограмм. Именно благодаря стеку подпрограммы могут вызывать друг друга, или даже рекурсивно сами себя.
Вызов подпрограммы состоит из «заталкивания» в стек всех аргументов и адреса следующей команды (для возврата к ней), а затем передачи управления на начало подпрограммы. По окончании работы подпрограммы из стека извлекается адрес возврата с передачей управления на этот адрес; одновременно с этим из стека выталкиваются аргументы. Происходит так называемая очистка стека. Это общая схема работы и у нее бывают разные реализации. В частности, аргументы могут помещаться в стек либо в прямом порядке (слева направо, как они перечислены в описании подпрограммы), либо в обратном порядке (справа налево), либо вообще, не через стек, а через свободные регистры процессора для повышения скорости работы. Кроме того, очистку стека может выполнять либо вызываемая подпрограмма, либо вызывающая программа. Выбор конкретного соглашения о вызове обеспечивают директивы register, pascal, cdecl и stdcall.
Их смысл поясняет следующая таблица соглашений о вызове подпрограмм.
Директива
| Порядок занесения аргументов в стек
| Кто отвечает за очистку стека
| Передача аргументов через регистры
|
register
| Слева направо
| Подпрограмма
| Да
|
pascal
| Слева направо
| Подпрограмма
| Нет
|
cdecl
| Справа налево
| Вызывающая программа
| Нет
|
stdcall
| Справа налево
| Подпрограмма
| Нет
|
Примечание: директива register не означает, что все аргументы обязательно передаются через регистры процессора. Если число аргументов больше числа свободных регистров, то часть аргументов передается через стек.
Возникает резонный вопрос: какое соглашение о вызове следует выбирать для процедур и функций динамически загружаемых библиотек. Ответ — соглашение stdcall:
procedure BubleSort(var Arr: array of Integer); stdcall;procedure QuickSort(var Arr: array of Integer); stdcall; |
Именно соглашение stdcall, изначально предназначенное для вызова подпрограмм операционной системы, лучше всего подходит для взаимодействия программ и библиотек, написанных на разных языках программирования. Все программы так или иначе используют функции операционной системы, следовательно они обязательно поддерживают соглашение stdcall.