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