Должна ли каждая функция в приложении с графическим интерфейсом Windows использовать stdcall?

#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 being stdcall влияет на return операторы внутри WinMain . Это не влияет на остальную часть тела функции. Это также влияет на вызываемую функцию (предоставляемую набором инструментов) WinMain .