__cdecl oder __stdcall unter Windows?

83

Ich entwickle derzeit eine C ++ - Bibliothek für Windows, die als DLL verteilt wird. Mein Ziel ist es, die binäre Interoperabilität zu maximieren. Genauer gesagt müssen die Funktionen in meiner DLL aus Code verwendet werden können, der mit mehreren Versionen von MSVC ++ und MinGW kompiliert wurde, ohne dass die DLL neu kompiliert werden muss. Ich bin jedoch verwirrt darüber, welche Anrufkonvention am besten ist cdecloder stdcall.

Manchmal höre ich Aussagen wie "Die C-Aufrufkonvention ist die einzige, von der garantiert wird, dass sie für alle Compiler gleich ist", was im Gegensatz zu Aussagen wie " Es gibt einige Unterschiede bei der Interpretation von cdecl, insbesondere bei der Rückgabe von Werten " steht. Dies scheint bestimmte Bibliotheksentwickler (wie libsndfile ) nicht davon abzuhalten, die C-Aufrufkonvention in den von ihnen verteilten DLLs ohne sichtbare Probleme zu verwenden.

Andererseits stdcallscheint die Aufrufkonvention klar definiert zu sein. Nach allem, was mir gesagt wurde, müssen grundsätzlich alle Windows-Compiler diese befolgen, da dies die für Win32 und COM verwendete Konvention ist. Dies basiert auf der Annahme, dass ein Windows-Compiler ohne Win32 / COM-Unterstützung nicht sehr nützlich wäre. Viele in Foren veröffentlichte Codefragmente deklarieren Funktionen als, stdcallaber ich kann anscheinend keinen einzigen Beitrag finden, der klar erklärt, warum .

Es gibt zu viele widersprüchliche Informationen, und jede Suche, die ich durchführe, gibt mir unterschiedliche Antworten, was mir nicht wirklich hilft, mich zwischen den beiden zu entscheiden. Ich suche nach einer klaren, detaillierten und argumentierten Erklärung, warum ich eine über die andere wählen sollte (oder warum die beiden gleichwertig sind).

Beachten Sie, dass diese Frage nicht nur für "klassische" Funktionen gilt, sondern auch für Funktionsaufrufe virtueller Mitglieder, da der größte Teil des Client-Codes über "Schnittstellen", reine virtuelle Klassen (gemäß den hier und da beschriebenen Mustern) mit meiner DLL verbunden ist .

Etienne Dechamps
quelle
4
"Es gibt einige Unterschiede bei der Interpretation von cdecl, insbesondere bei der Rückgabe von Werten" - dies bedeutet ungefähr, dass verschiedene Betriebssysteme, die cdecl unter x86 verwenden, möglicherweise unterschiedliche Varianten von cdecl verwenden, dh unterschiedliche ABIs, die beide "cdecl" nennen. Windows legt die Variationen fest. Jede Implementierung, die unter Windows ausgeführt wird und die Auswahlmöglichkeiten von Windows nicht berücksichtigt, kann keine Windows-Cdecl-Funktionen aufrufen. Wie Sie mit stdcall sagen, ist dies für die Windows-Programmierung nutzlos, und es gibt einige Standard-C-Funktionen Funktionen wäre es schwierig zu implementieren.
Steve Jessop
1
Die Aufrufkonvention __stdcall wird zum Aufrufen von Win32-API-Funktionen verwendet. docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Zanna

Antworten:

79

Ich habe gerade einige reale Tests durchgeführt (DLLs und Anwendungen mit MSVC ++ und MinGW kompilieren und dann mischen). Wie es scheint, hatte ich mit der cdeclAufrufkonvention bessere Ergebnisse .

Genauer gesagt: Das Problem dabei stdcallist, dass MSVC ++ Namen in der DLL-Exporttabelle auch bei Verwendung entstellt extern "C". Zum Beispiel foowird _foo@4. Dies geschieht nur bei Verwendung __declspec(dllexport), nicht bei Verwendung einer DEF-Datei. DEF-Dateien sind meiner Meinung nach jedoch ein Wartungsproblem, und ich möchte sie nicht verwenden.

Das Mangeln von MSVC ++ - Namen wirft zwei Probleme auf:

  • Die Verwendung GetProcAddressin der DLL wird etwas komplizierter.
  • MinGW stellt den dekorierten Namen standardmäßig keinen Undescore voran (z. B. wird MinGW foo@4anstelle von verwendet _foo@4), was die Verknüpfung erschwert. Außerdem besteht das Risiko, dass "Nicht-Unterstrich-Versionen" von DLLs und Anwendungen in freier Wildbahn angezeigt werden, die mit den "Unterstrich-Versionen" nicht kompatibel sind.

Ich habe die cdeclKonvention ausprobiert : Die Interoperabilität zwischen MSVC ++ und MinGW funktioniert einwandfrei, sofort einsatzbereit, und Namen bleiben in der DLL-Exporttabelle undekoriert. Es funktioniert sogar für virtuelle Methoden.

Aus diesen Gründen cdeclist ein klarer Gewinner für mich.

Etienne Dechamps
quelle
Dies gilt insbesondere nicht für 64-Bit-DLLs, da __stdcall und __cdecl im Allgemeinen beide ignoriert werden, sodass bei exportierten C-Funktionen keine Namensverfälschung auftritt.
19.
@jtbr Wie wäre es mit exportierten C ++ - Funktionen (dh nein extern "C")?
Ela782
@ Ela782 C ++ hat immer eine Namensverfälschung, um das Überladen von Funktionen zu unterstützen. Dies ist jedoch nicht standardisiert und kann daher zwischen den Compilern variieren.
19.
@jtbr Sorry, ich meinte nicht den Namen Mangling. Ich meinte, ob __stdcall und __cdecl auch in 64-Bit-DLLs mit exportierten C ++ - Funktionen ignoriert werden.
Ela782
1
@ Ela782 Ja; Ich glaube, __stdcall und __cdecl werden in allen x86-64-Kompilierungen ignoriert. Siehe Wikipedia .
24.
20

Der größte Unterschied zwischen den beiden Aufrufkonventionen besteht darin, dass " _ _cdecl" die Last des Ausgleichs des Stapels nach einem Funktionsaufruf auf den Aufrufer legt, wodurch Funktionen mit variablen Argumentmengen möglich sind. Die Konvention "__stdcall" ist "einfacher", in dieser Hinsicht jedoch weniger flexibel.

Ich glaube auch, dass verwaltete Sprachen standardmäßig die stdcall-Konvention verwenden, sodass jeder, der P / Invoke verwendet, die aufrufende Konvention explizit angeben muss, wenn Sie sich für cdecl entscheiden.

Wenn also alle Ihre Funktionssignaturen statisch definiert werden sollen, würde ich mich wahrscheinlich zu stdcall neigen, wenn nicht zu cdecl.

Brandon Moretz
quelle
11

In Bezug auf die Sicherheit ist die __cdeclKonvention "sicherer", da der Aufrufer die Zuordnung des Stapels aufheben muss. In einer __stdcallBibliothek kann es vorkommen, dass der Entwickler vergessen hat, die Zuordnung des Stapels ordnungsgemäß aufzuheben, oder dass ein Angreifer Code einfügt, indem er den Stapel der DLL beschädigt (z. B. durch API-Hooking), der dann vom Aufrufer nicht überprüft wird. Ich habe keine CVS-Sicherheitsbeispiele, die zeigen, dass meine Intuition korrekt ist.

user2471214
quelle