Я багато що стикався __stdcall
з цими днями.
MSDN не дуже чітко пояснює, що це насправді означає, коли і навіщо його використовувати, якщо взагалі.
Буду вдячний, якщо хтось надасть пояснення, бажано із прикладом чи двома.
Відповіді:
Усі функції в C / C ++ мають певний принцип виклику. Суть домовленості про дзвінки полягає у встановленні способу передачі даних між абонентом та абонентом, а також тим, хто відповідає за такі операції, як очищення стека викликів.
Найпопулярнішими умовами дзвінків у Windows є
__stdcall
, Виштовхує параметри в стек, у зворотному порядку (справа наліво)__cdecl
, Виштовхує параметри в стек, у зворотному порядку (справа наліво)__clrcall
, Завантажте параметри в стек виразів CLR по порядку (зліва направо).__fastcall
, Зберігається в реєстрах, потім натискається на стек__thiscall
, Натиснуто на стек; цей покажчик зберігається в ECXДодавання цього специфікатора до оголошення функції по суті повідомляє компілятору, що ви хочете, щоб ця конкретна функція мала саме цю умову виклику.
Конвенції про дзвінки задокументовані тут
Раймонд Чень також зробив довгу серію з історії різних конвенцій (5 частин), починаючи з цього.
Традиційно виклики функції C здійснюються з викликом абонента, натискаючи деякі параметри на стек, викликаючи функцію, а потім висуваючи стек, щоб очистити ці аргументи.
/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"
Примітка: Конвенція за замовчуванням - показана вище - відома як __cdecl.
Інша найпопулярніша конвенція - __stdcall. У ньому параметри знову натискаються абонентом, але стек очищається абонентом. Це стандартне домовленість для функцій API Win32 (як визначено макросом WINAPI у), а також його іноді називають умовою викликів "Паскаль".
/* example of __stdcall */
push arg1
push arg2
push arg3
call function // no stack cleanup - callee does this
Це виглядає як незначна технічна деталь, але якщо є розбіжності в тому, як керувати стеком між абонентом і абонентом, стек буде знищений таким чином, що навряд чи вдасться відновити. Оскільки __stdcall виконує очищення стека, (дуже крихітний) код для виконання цього завдання знаходиться лише в одному місці, а не дублюється в кожному абоненті, як у __cdecl. Це робить код дуже трохи меншим, хоча вплив розміру видно лише у великих програмах.
Варіадичні функції, такі як printf (), майже неможливо правильно отримати за допомогою __stdcall, оскільки лише той, хто викликає, насправді знає, скільки аргументів було передано для їх очищення. Викликаний може зробити кілька гарних здогадок (скажімо, переглянувши рядок форматування), але очищення стека повинна визначатися фактичною логікою функції, а не самим механізмом узгодження викликів. Отже, лише __cdecl підтримує різні функції, щоб абонент міг виконати очищення.
Прикраси імен символів лінкера: Як уже згадувалося в маркуванні вище, виклик функції за “неправильною” конвенцією може бути згубним, тому Microsoft має механізм, щоб цього не сталося. Це працює добре, хоча може звести з розуму, якщо хтось не знає, у чому причини. Вони вирішили вирішити цю проблему, закодувавши домовленість про виклик у імена функцій низького рівня з додатковими символами (які часто називають "прикрасами"), і лінкер їх розглядає як не пов'язані імена. Типовою умовою виклику є __cdecl, але кожна з них може бути явно запитана за допомогою / G? параметр до компілятора.
__cdecl (cl / Gd ...)
Всі імена функцій цього типу мають префікс з підкресленням, і кількість параметрів насправді не має значення, оскільки абонент відповідає за налаштування стека та очищення стека. Можна викликати плутанину між абонентом і абонентом щодо кількості фактично переданих параметрів, але принаймні дисципліна стека підтримується належним чином.
__stdcall (cl / Gz ...)
Ці імена функцій мають префікс підкреслення та додаються до знака @ плюс кількість байт переданих параметрів. За цим механізмом неможливо викликати функцію з «неправильним» типом або навіть з неправильною кількістю параметрів.
__швидкий дзвінок (cl / Gr ...)
Ці імена функцій починаються зі знака @ та суфіксом із числом @parameter, подібно до __stdcall.
Приклади:
Declaration -----------------------> decorated name
void __cdecl foo(void); -----------------------> _foo
void __cdecl foo(int a); -----------------------> _foo
void __cdecl foo(int a, int b); -----------------------> _foo
void __stdcall foo(void); -----------------------> _foo@0
void __stdcall foo(int a); -----------------------> _foo@4
void __stdcall foo(int a, int b); -----------------------> _foo@8
void __fastcall foo(void); -----------------------> @foo@0
void __fastcall foo(int a); -----------------------> @foo@4
void __fastcall foo(int a, int b); -----------------------> @foo@8
__stdcall - це умова виклику: спосіб визначення способу передачі параметрів функції (у стеку або в регістрах) і хто відповідає за очищення після повернення функції (абонент або абонент).
Реймонд Чен написав блог про основні правила виклику x86 , і там також є чудова стаття CodeProject .
Здебільшого вам не слід турбуватися про них. Єдиний випадок, коли вам слід це зробити, це якщо ви викликаєте функцію бібліотеки, яка використовує щось інше, ніж за замовчуванням - інакше компілятор згенерує неправильний код, і ваша програма, можливо, вийде з ладу.
На жаль, немає простої відповіді, коли його використовувати, а коли ні.
__stdcall означає, що аргументи функції переміщуються в стек від першого до останнього. Це на відміну від __cdecl, що означає, що аргументи переносяться з останнього в перший, і __fastcall, який поміщає перші чотири (я думаю) аргументи в регістри, а решта йдуть у стек.
Вам просто потрібно знати, чого очікує абонент, або якщо ви пишете бібліотеку, що, ймовірно, очікують ваші абоненти, і переконайтеся, що ви задокументували обрану вами угоду.
__stdcall
і __cdecl
різняться лише відповідальністю за прибирання після повернення (та оздоблення). Передача аргументів однакова для обох (справа наліво). Те, що ви описуєте, - це Конвенція про дзвінки Паскаля.
__stdcall означає домовленість про дзвінки (див. цей PDF для отримання детальної інформації). Це означає, що він визначає, як аргументи функції виштовхуються та вискакуються зі стеку, і хто відповідає.
__stdcall - це лише одна з кількох конвенцій викликів, і вона використовується у всьому WINAPI. Ви повинні використовувати його, якщо ви надаєте покажчики функцій як зворотні виклики для деяких з цих функцій. Як правило, вам не потрібно позначати будь-які конкретні правила викликів у вашому коді, а просто використовуйте компілятор за замовчуванням, за винятком випадку, зазначеного вище (надання зворотних викликів сторонньому коду).
Це умова викликів, згідно з якою функції WinAPI потрібно викликати правильно. Конвенція виклику - це набір правил щодо того, як параметри передаються у функцію та як передається повертане значення з функції.
Якщо абонент і викликаний код використовують різні правила, ви стикаєтесь із невизначеною поведінкою (як такий дивний на вигляд збій ).
Компілятори C ++ не використовують __stdcall за замовчуванням - вони використовують інші домовленості. Отже, для виклику функцій WinAPI із С ++ потрібно вказати, що вони використовують __stdcall - це зазвичай робиться у заголовках файлів SDK Windoes, і ви також робите це при оголошенні покажчиків на функції.
простіше кажучи, коли ви викликаєте функцію, вона завантажується в стек / реєстр. __stdcall - це одна умова / спосіб (спочатку правий аргумент, потім лівий аргумент ...), __decl - інша умова, яка використовується для завантаження функції в стек або регістри.
Якщо ви використовуєте їх, ви доручаєте комп’ютеру використовувати саме такий спосіб завантаження / вивантаження функції під час зв’язування, і, отже, ви не отримаєте невідповідність / збій.
В іншому випадку виклик функцій та виклик функцій можуть використовувати різні правила, що спричиняють збій програми.
__stdcall - це виклик, який використовується для функції. Це повідомляє компілятору правила, які застосовуються для налаштування стека, проштовхування аргументів та отримання поверненого значення. Існує ряд інших домовленостей про виклики, таких як __cdecl , __thiscall , __fastcall та __naked .
__stdcall - це стандартна умова викликів для системних дзвінків Win32.
Більше деталей можна знайти у Вікіпедії .