cdecl или stdcall в Windows?
в настоящее время я разрабатываю библиотеку C++ для Windows, которая будет распространяться как DLL. Моя цель-максимизировать двоичную совместимость; точнее, функции в моей DLL должны использоваться из кода, скомпилированного с несколькими версиями MSVC++ и MinGW без необходимости перекомпиляции DLL. Тем не менее, я смущен тем, какое соглашение о вызовах лучше всего,cdecl
или stdcall
.
иногда я слышу такие заявления, как " соглашение о вызове C единственный, который гарантированно будет одним и тем же компилятором accross", который контрастирует с такими утверждениями, как "есть некоторые вариации в интерпретации cdecl
, особенно в том, как возвращать значения". Это, похоже, не останавливает некоторых разработчиков библиотек (например,libsndfile выступает) использовать соглашение о вызовах C в библиотеках DLL, которые они распространяют, без каких-либо видимых проблем.
С другой стороны,stdcall
соглашение о вызове, похоже, четко определено. От чего Мне сказали, что все компиляторы Windows в основном обязаны следовать ему, потому что это соглашение используется для Win32 и COM. Это основано на предположении, что компилятор Windows без поддержки Win32/COM не будет очень полезен. Многие фрагменты кода, размещенные на форумах, объявляют функции как stdcall
но я не могу найти ни одного сообщения, которое ясно объясняет почему.
там слишком много противоречивой информации, и каждый поиск, который я запускаю, дает мне разные ответы, которые на самом деле не помогают мне выбрать между ними. Я ищу ясное, подробное, аргументированное объяснение того, почему я должен выбирать одно над другим (или почему они эквивалентны).
обратите внимание, что этот вопрос относится не только к "классическим" функциям, но и к вызовам виртуальных функций-членов, поскольку большинство клиентских кодов будут взаимодействовать с моей DLL через "интерфейсы", чистые виртуальные классы (следующие шаблоны, описанные, например,здесь и здесь).
3 ответов
Я просто сделал некоторое реальное тестирование (компиляция 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
является явным победителем для меня.
самая большая разница в двух соглашениях о вызовах заключается в том, что"__cdecl " возлагает бремя балансировки стека после вызова функции на вызывающего, что позволяет использовать функции с переменным количеством аргументов. Конвенция" __stdcall "является" более простой " по своему характеру, однако менее гибкой в этом отношении.
кроме того, я считаю, что управляемые языки используют соглашение stdcall по умолчанию, поэтому любой, кто использует P / Invoke на нем, должен будет явно указать соглашение о вызове если вы идете с cdecl.
Итак, если все ваши сигнатуры функций будут статически определены, я, вероятно, склонюсь к stdcall, если не cdecl.
С точки зрения безопасности,__cdecl
соглашение "безопаснее", потому что это вызывающий, который должен освободить стек. Что может произойти в __stdcall
библиотека заключается в том, что разработчик, возможно, забыл правильно освободить стек, или злоумышленник может ввести некоторый код, повредив стек DLL (например, путем подключения API), который затем не проверяется вызывающим.
У меня нет примеров безопасности CVS, которые показывают, что моя интуиция верна.