Obecnie pracuję nad biblioteką C ++ dla systemu Windows, która będzie dystrybuowana jako biblioteka DLL. Moim celem jest maksymalizacja interoperacyjności binarnej; dokładniej, funkcje w mojej bibliotece DLL muszą być użyteczne z kodu skompilowanego z wieloma wersjami MSVC ++ i MinGW bez konieczności ponownej kompilacji biblioteki DLL. Jednak nie wiem, która konwencja dzwonienia jest najlepsza cdecl
lub stdcall
.
Czasami słyszę takie stwierdzenia, jak „Konwencja wywoływania w C jest jedyną, która gwarantuje, że będą takie same między kompilatorami”, co kontrastuje ze stwierdzeniami typu „ Istnieją pewne różnice w interpretacji cdecl
, szczególnie w zwracaniu wartości ”. Wydaje się, że nie powstrzymuje to niektórych deweloperów bibliotek (takich jak libsndfile ) przed używaniem konwencji wywoływania C w dystrybuowanych przez nich bibliotekach DLL, bez żadnych widocznych problemów.
Z drugiej strony stdcall
konwencja wywoływania wydaje się być dobrze zdefiniowana. Z tego, co mi powiedziano, wszystkie kompilatory Windows są zasadniczo zobowiązane do przestrzegania tego, ponieważ jest to konwencja używana dla Win32 i COM. Opiera się to na założeniu, że kompilator systemu Windows bez obsługi Win32 / COM nie byłby zbyt przydatny. Wiele fragmentów kodu opublikowanych na forach deklaruje funkcje, stdcall
ale nie mogę znaleźć ani jednego wpisu, który jasno wyjaśnia dlaczego .
Jest tam zbyt wiele sprzecznych informacji, a każde wyszukiwanie, które przeprowadzam, daje mi różne odpowiedzi, co tak naprawdę nie pomaga mi zdecydować między nimi. Szukam jasnego, szczegółowego, uzasadnionego wyjaśnienia, dlaczego powinienem wybrać jedno z nich (lub dlaczego oba są równoważne).
Zwróć uwagę, że to pytanie dotyczy nie tylko „klasycznych” funkcji, ale także wywołań funkcji wirtualnych, ponieważ większość kodu klienta będzie się łączyć z moją biblioteką DLL za pośrednictwem „interfejsów”, czystych klas wirtualnych (według wzorców opisanych np. Tu i tam ).
źródło
Odpowiedzi:
Właśnie przeprowadziłem kilka testów w świecie rzeczywistym (kompilowałem biblioteki DLL i aplikacje z MSVC ++ i MinGW, a następnie mieszałem je). Jak się okazuje, miałem lepsze wyniki z
cdecl
konwencją wywoływania.Dokładniej: problem
stdcall
polega na tym, że MSVC ++ zmienia nazwy w tabeli eksportu DLL, nawet jeśli jest używanyextern "C"
. Na przykładfoo
staje się_foo@4
. Dzieje się tak tylko podczas używania__declspec(dllexport)
, a nie podczas używania pliku DEF; Jednak moim zdaniem pliki DEF są kłopotliwe w utrzymaniu i nie chcę ich używać.Zniekształcenie nazwy MSVC ++ stwarza dwa problemy:
GetProcAddress
z biblioteki DLL staje się nieco bardziej skomplikowane;foo@4
zamiast_foo@4
), co komplikuje linkowanie. Wprowadza również ryzyko zobaczenia „wersji bez podkreślenia” bibliotek DLL i aplikacji, które są niekompatybilne z „wersjami z podkreśleniem”.Wypróbowałem
cdecl
konwencję: interoperacyjność między MSVC ++ i MinGW działa doskonale, zaraz po wyjęciu z pudełka, a nazwy pozostają niezdekorowane w tabeli eksportu DLL. Działa nawet w przypadku metod wirtualnych.Z tych powodów
cdecl
jest dla mnie wyraźnym zwycięzcą.źródło
extern "C"
)?Największą różnicą w tych dwóch konwencjach wywoływania jest to, że " _ _cdecl" nakłada ciężar równoważenia stosu po wywołaniu funkcji na obiekt wywołujący, co pozwala na funkcje ze zmienną ilością argumentów. Konwencja „__stdcall” ma „prostszy” charakter, jednak jest mniej elastyczna pod tym względem.
Uważam również, że języki zarządzane domyślnie używają konwencji stdcall, więc każdy, kto używa na nim P / Invoke, musiałby jawnie określić konwencję wywoływania, jeśli korzystasz z cdecl.
Tak więc, jeśli wszystkie sygnatury funkcji mają być zdefiniowane statycznie, prawdopodobnie skłaniałbym się w stronę stdcall, jeśli nie cdecl.
źródło
Z punktu widzenia bezpieczeństwa,
__cdecl
konwencja jest „bezpieczniejsza”, ponieważ to wywołujący musi zwolnić przydział stosu. W__stdcall
bibliotece może się zdarzyć, że programista mógł zapomnieć o poprawnym zwolnieniu przydziału stosu lub osoba atakująca może wstrzyknąć jakiś kod, uszkadzając stos DLL (np. Przez przechwycenie API), który następnie nie jest sprawdzany przez wywołującego. Nie mam żadnych przykładów bezpieczeństwa CVS, które pokazują, że moja intuicja jest poprawna.źródło