__cdecl или __stdcall в Windows?

84

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

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

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

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

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

Этьен Дешам
источник
4
«Есть некоторые различия в интерпретации cdecl, особенно в том, как возвращать значения» - это приблизительно означает, что разные ОС, использующие cdecl на x86, могут использовать разные варианты cdecl, то есть разные ABI, которые они оба называют «cdecl». Windows фиксирует варианты, любая реализация, которая работает в Windows и не уважает выбор Windows, не сможет вызывать функции Windows cdecl, поэтому, как вы говорите с помощью stdcall, это бесполезно для программирования Windows, и есть некоторые стандартные C функции было бы сложно реализовать.
Steve Jessop
1
Соглашение о вызовах __stdcall используется для вызова функций Win32 API. docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Занна,

Ответы:

79

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

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

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

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

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

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

Этьен Дешам
источник
Примечательно, что это не относится к 64-битным библиотекам DLL, потому что __stdcall и __cdecl обычно оба игнорируются, поэтому в экспортируемых функциях C не происходит искажения имен.
jtbr
@jtbr Как насчет экспортированных функций C ++ (т.е. нет extern "C")?
Ela782 05
@ Ela782 В C ++ всегда будет какое-то искажение имен, чтобы поддерживать перегрузку функций. Но это не стандартизовано и, следовательно, может варьироваться в зависимости от компилятора.
jtbr
@jtbr Извините, я не имел в виду искажение имени. Я имел в виду, игнорируются ли __stdcall и __cdecl в 64-битных библиотеках DLL с экспортированными функциями C ++.
Ela782
1
@ Ela782 Да; Я считаю, что __stdcall и __cdecl игнорируются во всех компиляциях x86-64. См. Википедию .
jtbr
20

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

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

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

Брэндон Моретц
источник
11

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

user2471214
источник