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 66

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, которые показывают, что моя интуиция верна.