__cdecl или __stdcall в Windows? - PullRequest
72 голосов
/ 28 июня 2011

В настоящее время я разрабатываю библиотеку C ++ для Windows, которая будет распространяться в виде DLL. Моя цель - максимизировать бинарную совместимость; более точно, функции в моей DLL должны быть пригодны для использования из кода, скомпилированного с несколькими версиями MSVC ++ и MinGW, без необходимости перекомпиляции DLL. Однако я не совсем понимаю, какое соглашение о вызовах лучше: cdecl или stdcall.

Иногда я слышу утверждения типа «Соглашение о вызове C - единственное гарантированное совпадение в разных компиляторах», что отличается от утверждений типа «. Существуют некоторые различия в интерпретации cdecl, особенно в том, как вернуть значения". Похоже, это не мешает некоторым разработчикам библиотек (например, libsndfile ) использовать соглашение о вызовах C в распространяемых ими библиотеках DLL без видимых проблем.

С другой стороны, соглашение о вызовах stdcall представляется вполне определенным. Из того, что мне сказали, все компиляторы Windows в основном обязаны следовать ему, потому что это соглашение используется для Win32 и COM. Это основано на предположении, что компилятор Windows без поддержки Win32 / COM не будет очень полезным. Многие фрагменты кода, размещенные на форумах, объявляют функции как stdcall, но я не могу найти ни одного поста, который бы четко объяснял , почему .

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

Обратите внимание, что этот вопрос относится не только к "классическим" функциям, но также и к вызовам виртуальных функций-членов, так как большая часть кода клиента будет взаимодействовать с моей DLL через "интерфейсы", чисто виртуальные классы (в соответствии с описанными здесь схемами, например, и там ).

Ответы [ 3 ]

71 голосов
/ 09 июля 2011

Я только что провел некоторое реальное тестирование (компилирование DLL и приложений с использованием MSVC ++ и MinGW, а затем их смешивание).Как оказалось, у меня были лучшие результаты с соглашением о вызовах cdecl.

В частности: проблема с stdcall заключается в том, что MSVC ++ искажает имена в таблице экспорта DLL, даже при использовании extern "C".Например, foo становится _foo@4.Это происходит только при использовании __declspec(dllexport), а не при использовании файла DEF;однако, файлы DEF, на мой взгляд, являются хлопотами по обслуживанию, и я не хочу их использовать.

Из-за искажения имен MSVC ++ возникают две проблемы:

  • Использование GetProcAddress в DLL немного усложняется;
  • MinGW по умолчанию не добавляет префикс undescore к оформленным именам (например, MinGW будет использовать foo@4 вместо _foo@4), что усложняет компоновку,Кроме того, это создает риск появления «не подчеркивающих версий» библиотек DLL и приложений, всплывающих в дикой природе, которые несовместимы с «версиями подчеркивания».

Я пробовал cdeclСоглашение: функциональная совместимость между MSVC ++ и MinGW работает отлично, из коробки, и имена остаются неукрашенными в таблице экспорта DLL.Это даже работает для виртуальных методов.

По этим причинам cdecl является для меня явным победителем.

17 голосов
/ 28 июня 2011

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

Кроме того, я считаю, что в управляемых языках по умолчанию используется соглашение stdcall, поэтому любой, кто использует P / Invoke на нем, должен явно указать соглашение о вызовах, если вы используете cdecl.

Итак, если все ваши сигнатуры функций будут определены статически, я бы, вероятно, склонился к stdcall, если не к cdecl.

9 голосов
/ 02 января 2014

С точки зрения безопасности, соглашение __cdecl является "более безопасным", поскольку именно вызывающая сторона должна освободить стек.Что может случиться в библиотеке __stdcall, так это то, что разработчик, возможно, забыл правильно освободить стек, или злоумышленник может внедрить некоторый код, повредив стек DLL (например, перехватом API), который затем не проверяется вызывающей стороной.У меня нет примеров безопасности CVS, показывающих, что моя интуиция верна.

...