#c #winapi #calling-convention
#c #winapi #соглашение о вызове
Вопрос:
Насколько я понимаю, вызывающий и вызываемый должны иметь одинаковое соглашение о вызовах. В противном случае стек может быть поврежден.
WinMain
объявляется с __stdcall
помощью и вызывает все функции, которые я определил. Означает ли это, что все функции, которые я определяю, должны использовать stdcall
соглашение о вызовах?
Я пробовал не использовать __stdcall
, и ничего плохого не произошло. Я также видел, что известные библиотеки GUI, поддерживающие Windows, не используются stdcall
. Почему стек не повреждается?
Комментарии:
1. Вызывающий и вызываемый абонент должны согласовать соглашение о вызове вызываемого абонента. Обычно вызывающий и вызываемый имеют разные соглашения. Вы, вероятно, уже делаете это сами: все функции в стандартной библиотеке C являются cdecl, но вы можете вызывать их просто отлично из WinMain.
2. Соглашения о вызовах — это контракт между вызывающими и вызываемыми. Если вы вызываете библиотеки (например, Windows API), вы должны следовать контрактам библиотек. Если вы реализуете библиотеку, вы сами решаете, какое соглашение о вызовах использовать. Если вы нацелены на x64, все менее сложно: существует только одно соглашение о вызове.
Ответ №1:
WinMain объявляется с
__stdcall
помощью и вызывает все функции, которые я определил. Означает ли это, что все функции, которые я определяю, должны использоватьstdcall
соглашение о вызовах?
Нет. Соглашения о вызовах обрабатываются на основе вызова каждой функции прямо на сайте вызова. Соглашение определяет, как вызывающий и вызываемый объект управляют стеком вызовов — как передаются параметры, в каком порядке, кто очищает стек и т.д. Пока вызывающий и вызываемый соглашаются использовать одно и то же соглашение о вызове при каждом отдельном вызове функции, для stdcall
функции совершенно безопасно вызывать функцию, которая использует другое соглашение, например cdecl
, и наоборот. Соглашение о вызове функции применяется только тогда, когда:
- функция вводится новым вызывающим.
- функция возвращается обратно к этому вызывающему.
- функция обращается к своим собственным параметрам.
Помимо этого, то, что функция выполняет внутренне, не имеет ничего общего с ее собственным соглашением о вызовах.
Например, допустим, что WinMain()
stdcall
функция хочет вызвать cdecl
функцию.
Это не имеет никакого значения, что WinMain()
само по себе является stdcall
функцией. Пока код выполняется внутри WinMain()
, он может делать все, что захочет. WinMain()
stdcall
Соглашение применяется только при входе и выходе из WinMain()
самого себя. Это контракт WinMain()
с вызывающим ЕГО абонентом.
Важно то, что WinMain()
необходимо следовать правилам cdecl
при настройке стека вызовов для cdecl
функции, которую она собирается вызвать, и очистке стека вызовов, когда эта функция возвращается обратно WinMain()
.
То же самое относится к любому вызову функции любого соглашения о вызовах.
Я пробовал не использовать
__stdcall
, и ничего плохого не произошло. Я также видел, что известные библиотеки GUI, поддерживающие Windows, не используютсяstdcall
. Почему стек не повреждается?
Поскольку стек вызовов управляется правильно при каждом вызове и возврате функции, поэтому несбалансированная очистка не приводит к повреждению стека.
Комментарии:
1. Что заставляет соглашения о вызовах согласовываться друг с другом? Сначала я думал, что они соглашаются только тогда, когда у них одинаковое соглашение о вызовах, но, видимо, нет.
2. » Я изначально думал, что они соглашаются только тогда, когда у них одинаковое соглашение о вызовах » — это совершенно верно. Если в объявлении вызывающей функции для данной функции используется то же соглашение о вызове, что и вызываемый абонент в реализации функции, тогда все хорошо. Если вызывающий использует другое соглашение, вы сталкиваетесь с проблемами. » но, по-видимому, нет » — почему вы так думаете?
3. stdcall и cdecl — это разные соглашения о вызовах, но они по-прежнему согласуются друг с другом. Я понимаю, что они по-прежнему согласны, когда у них одинаковое соглашение о вызовах, но я не понимаю, почему cdecl и stdcall согласуются друг с другом, хотя они оба разные.
4. » stdcall и cdecl … все еще согласны друг с другом » — нет, они этого не делают, потому что у них разные правила очистки.
stdcall
Вызываемый объект очищает стек вызовов. Вcdecl
вызывающий выполняет очистку. Вы слишком много внимания уделяете тому, какое соглашение вызывающий использует для СВОЕГО СОБСТВЕННОГО фрейма стека, а не тому, какое соглашение вызывающий использует для ВЫЗОВА СЛЕДУЮЩЕЙ ФУНКЦИИ. Я обновил свой ответ, чтобы отразить это.5.@kurupreo: Вы, кажется, думаете, что «
WinMain
естьstdcall
,printf
естьcdecl
, поэтому еслиWinMain
вызываетprintf
, то вызывающий =stdcall
и вызываемый =cdecl
» и пытаетесь заменить их в заявлении Реми о том, что «вызывающий и вызываемый соглашаются» получить «stdcall
иcdecl
соглашаются». Соглашения о вызовах работают не так.WinMain
beingstdcall
влияет наreturn
операторы внутриWinMain
. Это не влияет на остальную часть тела функции. Это также влияет на вызываемую функцию (предоставляемую набором инструментов)WinMain
.