__cdecl или __stdcall в Windows?
В настоящее время я разрабатываю библиотеку С++ для Windows, которая будет распространяться как DLL. Моя цель - максимизировать двоичную совместимость; более точно, функции в моей DLL должны использоваться из кода, скомпилированного с несколькими версиями MSVС++ и MinGW, без необходимости перекомпилировать DLL. Тем не менее, я смущен тем, какая конвенция лучше всего подходит, cdecl
или stdcall
.
Иногда я слышу такие утверждения, как "соглашение о вызове C - это единственное, гарантированное быть одним и тем же компрометирующим", что контрастирует с такими выражениями, как "Есть некоторые вариации в интерпретации cdecl
, особенно в том, как возвращать значения ". Это, похоже, не останавливает некоторых разработчиков библиотек (например, libsndfile), чтобы использовать соглашение C-вызова в DLL, которые они распространяют, без видимых проблемы.
С другой стороны, соглашение о вызове stdcall
кажется хорошо определенным. Из того, что мне сказали, все компиляторы Windows в основном обязаны следовать за ним, поскольку это соглашение используется для Win32 и COM. Это основано на предположении, что компилятор Windows без поддержки Win32/COM не будет очень полезен. Множество фрагментов кода, размещенных на форумах, объявляет функции как stdcall
, но я не могу найти одно сообщение, которое четко объясняет, почему.
Там слишком много противоречивой информации, и каждый поиск, который я запускаю, дает мне разные ответы, которые на самом деле не помогают мне решить между ними. Я ищу четкое, подробное, аргументированное объяснение, почему я должен выбирать один над другим (или почему они эквивалентны).
Обратите внимание, что этот вопрос относится не только к "классическим" функциям, но также к вызовам функций виртуальных членов, поскольку большинство клиентских кодов будут взаимодействовать с моей DLL через "интерфейсы", чистые виртуальные классы (следующие шаблоны, например, здесь и там).
Ответы
Ответ 1
Я просто провел некоторое тестирование в реальном мире (компиляция DLL и приложений с MSVС++ и MinGW, а затем их микширование). Как оказалось, у меня были лучшие результаты с помощью соглашения о вызове cdecl
.
В частности: проблема с stdcall
заключается в том, что MSVС++ управляет именами в таблице экспорта DLL, даже при использовании extern "C"
. Например, foo
становится [email protected]
. Это происходит только при использовании __declspec(dllexport)
, а не при использовании файла DEF; однако, по моему мнению, файлы DEF - это проблема обслуживания, и я не хочу их использовать.
Управление именами MSVС++ создает две проблемы:
- Использование
GetProcAddress
в DLL становится несколько более сложным;
- MinGW по умолчанию не присваивает украшаемые имена (например, MinGW будет использовать
[email protected]
вместо [email protected]
), что усложняет привязку. Кроме того, он представляет опасность увидеть "не-подчеркивающие версии" DLL и приложений, которые появляются в дикой природе, которые несовместимы с "символами подчеркивания".
Я пробовал соглашение cdecl
: взаимодействие между MSVС++ и MinGW отлично работает, готово, а имена остаются неизмененными в таблице экспорта DLL. Он даже работает для виртуальных методов.
По этим причинам cdecl
явный победитель для меня.
Ответ 2
Самая большая разница в двух соглашениях вызова заключается в том, что "__cdecl" ставит бремя балансировки стека после вызова функции на вызывающем абоненте, что позволяет использовать функции с переменным количеством аргументов. Соглашение "__stdcall" является "более простым" по своей природе, однако менее гибким в этом отношении.
Кроме того, я считаю, что управляемые языки используют стандартное соглашение stdcall по умолчанию, поэтому любой, кто использует P/Invoke на нем, должен будет явно указать соглашение о вызове, если вы перейдете с cdecl.
Итак, если все ваши сигнатуры функций будут статически определены, я, вероятно, склоняюсь к stdcall, если не cdecl.
Ответ 3
В терминах безопасности соглашение __cdecl
является "более безопасным", потому что это вызывающий объект, который должен освободить стек. Что может произойти в библиотеке __stdcall
, так это то, что разработчик мог забыть освободить стеки должным образом, или злоумышленник может ввести некоторый код, повредив стек DLL (например, с помощью API-соединения), который затем не проверяется вызывающим.
У меня нет каких-либо примеров безопасности CVS, которые показывают, что моя интуиция верна.