Подтвердить что ты не робот

В чем смысл и использование __stdcall?

В наши дни я много сталкивался с __stdcall.

MSDN не очень четко объясняет, что это на самом деле означает, когда и почему его следует использовать, если оно вообще есть.

Буду признателен, если кто-нибудь даст объяснение, желательно с примером или двумя.

4b9b3361

Ответ 1

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

Наиболее популярными соглашениями о вызовах в Windows являются

  • __stdcall, помещает параметры в стек в обратном порядке (справа налево)
  • __cdecl, помещает параметры в стек в обратном порядке (справа налево)
  • __clrcall, Загрузка параметров в стек выражений CLR в порядке (слева направо).
  • __fastcall, хранится в регистрах, затем помещается в стек
  • __thiscall, помещено в стек; этот указатель хранится в ECX

Добавление этого спецификатора к объявлению функции по существу говорит компилятору, что вы хотите, чтобы эта конкретная функция имела это конкретное соглашение о вызовах.

Соглашения о вызовах описаны здесь

Раймонд Чен также сделал длинную серию по истории различных соглашений о вызовах (5 частей), начиная здесь.

Ответ 2

Традиционно вызовы C-функций выполняются с вызовом, вызывающим некоторые параметры в стек, вызовом функции, а затем сбрасыванием стека для очистки этих нажатых аргументов.

/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"

Примечание. Соглашение по умолчанию, показанное выше, называется __cdecl.

Другим самым популярным соглашением является __stdcall. В нем параметры снова нажимаются вызывающим, но стек очищается вызываемым пользователем. Это стандартное соглашение для функций Win32 API (как определено макросом WINAPI), а также иногда называется конвенцией о вызове "Pascal".

/* example of __stdcall */
push arg1 
push arg2 
push arg3 
call function // no stack cleanup - callee does this

Это выглядит как незначительная техническая деталь, но если есть разногласия относительно того, как стек управляется между вызывающим и вызываемым, стек будет уничтожен таким образом, который вряд ли будет восстановлен. Поскольку __stdcall выполняет очистку стека, (очень маленький) код для выполнения этой задачи находится только в одном месте, а не дублируется в каждом вызывающем устройстве, как в __cdecl. Это делает код очень немного меньшим, хотя влияние размера видно только в больших программах.

Функции Variadic, такие как printf(), почти невозможно получить с помощью __stdcall, потому что только вызывающий абонент действительно знает, сколько аргументов было передано для их очистки. Вызов может сделать некоторые хорошие догадки (скажем, глядя на строку формата), но очистка стека должна определяться фактической логикой функции, а не самим механизмом вызова-конвенции. Следовательно, только __cdecl поддерживает вариационные функции, чтобы вызывающий мог выполнить очистку.

Условные обозначения имен компоновщика: Как упоминалось в вышеприведенном пункте, вызов функции с "неправильным" соглашением может быть катастрофическим, поэтому у Microsoft есть механизм, позволяющий избежать этого. Он работает хорошо, хотя может быть безумным, если вы не знаете, в чем причина. Они решили разрешить это путем кодирования вызывающего соглашения в имена функций низкого уровня с дополнительными символами (которые часто называются "украшениями" ), и они рассматриваются как несвязанные имена компоновщиком. Стандартным вызовом является __cdecl, но каждый может быть запрошен явно с помощью /G? параметр компилятору.

__ cdecl (cl/Gd...)

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

__ stdcall (cl/Gz...)

Эти имена функций имеют префикс с подчеркиванием и добавляются вместе с @плюс количество байтов переданных параметров. По этому механизму невозможно вызвать функцию с "неправильным" типом или даже с неправильным количеством параметров.

__ fastcall (cl/Gr...)

Эти имена функций начинаются с знака @и суффиксы с параметром @parameter, как __stdcall.

Примеры:

Declaration                        ----------------------->    decorated name


void __cdecl foo(void);            ----------------------->    _foo

void __cdecl foo(int a);           ----------------------->    _foo

void __cdecl foo(int a, int b);    ----------------------->    _foo

void __stdcall foo(void);          ----------------------->    [email protected]

void __stdcall foo(int a);         ----------------------->    [email protected]

void __stdcall foo(int a, int b);  ----------------------->    [email protected]

void __fastcall foo(void);         ----------------------->    @[email protected]

void __fastcall foo(int a);        ----------------------->    @[email protected]

void __fastcall foo(int a, int b); ----------------------->    @[email protected]

Ответ 3

__ stdcall - это соглашение о вызове: способ определения того, как параметры передаются функции (в стеке или в регистрах), и кто отвечает за очистку после возвращения функции (вызывающий или вызываемый).

Раймонд Чен написал блог о основных соглашениях о вызовах x86, и там есть хороший CodeProject.

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

Ответ 4

К сожалению, нет простого ответа на вопрос, когда его использовать, а когда нет.

__ stdcall означает, что аргументы функции вставляются в стек от первого до последнего. Это в отличие от __cdecl, а это означает, что аргументы переносятся от последнего к первому и __fastcall, который помещает первые четыре (я думаю) аргументы в регистры, а остальные - в стек.

Вам просто нужно знать, что ожидает собеседник, или если вы пишете библиотеку, что ожидаете ваши звонящие, и убедитесь, что вы документируете выбранное вами соглашение.

Ответ 5

Он определяет соглашение о вызове для функции. Вызывающее соглашение представляет собой набор правил, как параметры передаются функции: в каком порядке, на адрес или на копию, кто должен очищать параметры (вызывающий или вызываемый) и т.д.

Ответ 6

__ stdcall обозначает соглашение о вызове (см. этот PDF для некоторых деталей). Это означает, что он указывает, как аргументы функции выталкиваются и выгружаются из стека, и кто несет ответственность.

__ stdcall - это всего лишь одна из нескольких конвенций вызова и используется во всем WINAPI. Вы должны использовать его, если вы предоставляете указатели на функции как обратные вызовы для некоторых из этих функций. В общем, вам не нужно указывать какое-либо конкретное соглашение о вызове в вашем коде, но просто используйте параметр компилятора по умолчанию, за исключением случая, указанного выше (предоставление обратных вызовов для стороннего кода).

Ответ 7

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

Если вызывающий и вызываемый коды используют разные соглашения, вы запускаете поведение undefined (например такое странное появление сбоя).

Компиляторы С++ не используют __stdcall по умолчанию - они используют другие соглашения. Поэтому, чтобы вызывать функции WinAPI из С++, вам нужно указать, что они используют __stdcall - это обычно делается в заголовочных файлах Windoes SDK, и вы также выполняете это при объявлении указателей функций.

Ответ 8

просто добавляется при вызове функции, он загружается в стек/регистр. __stdcall - это одно соглашение/способ (правый аргумент, затем левый аргумент...), __decl - это еще одно соглашение, которое используется для загрузки функции в стек или регистры.

Если вы используете их, вы даете указание компьютеру использовать этот способ загрузки/выгрузки функции во время связывания и, следовательно, вы не получите несоответствие/сбой.

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

Ответ 9

__ stdcall - это соглашение о вызове, используемое для этой функции. Это сообщает компилятору правила, которые применяются для настройки стека, нажатия аргументов и получения возвращаемого значения. Существует ряд других соглашений о вызовах, таких как __ cdecl, __ thiscall, __ fastcall и __ голый.

__ stdcall - стандартное соглашение о вызове для системных вызовов Win32.

Более подробную информацию можно найти на Wikipedia.