__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, не зможе викликати функції cdecl Windows, тому, як ви говорите зі 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

@ Ela782 C ++ завжди матиме певне ім'я, яке керує іменами, щоб підтримувати перевантаження функції. Але це не стандартизовано і, отже, може варіюватися у різних компіляторів.
jtbr

@jtbr Вибачте, я не мав на увазі ім'я, яке перекручує. Я мав на увазі, чи ігноруються __stdcall та __cdecl у 64-розрядних бібліотеках DLL з експортованими функціями C ++.
Ela782,

1
@ Ela782 Так; Я вважаю, що __stdcall та __cdecl ігноруються у всіх компіляціях x86-64. Дивіться wikipedia .
jtbr

20

Найбільша різниця між двома умовами виклику полягає в тому, що " _ _cdecl" покладає тягар балансування стека після виклику функції на абонента, що дозволяє використовувати функції із змінною кількістю аргументів. Конвенція "__stdcall" має "простіший" характер, проте менш гнучка в цьому відношенні.

Крім того, я вважаю, що керовані мови використовують конвенцію stdcall за замовчуванням, тому кожен, хто використовує P / Invoke, повинен чітко вказати конвенцію про виклики, якщо ви використовуєте cdecl.

Отже, якщо всі ваші підписи функцій будуть статично визначеними, я, мабуть, схиляюся до stdcall, якщо не cdecl.


11

З точки зору безпеки, __cdeclконвенція є "безпечнішою", оскільки саме абонент повинен звільнити стек. Що може трапитися в __stdcallбібліотеці, так це те, що розробник, можливо, забув правильно вивільнити стек, або зловмисник може ввести якийсь код, пошкодивши стек DLL (наприклад, підключенням API), який потім не перевіряє абонент. У мене немає жодного прикладу безпеки CVS, який би свідчив про правильність моєї інтуїції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.