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 cdecl
oder 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 stdcall
scheint 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, stdcall
aber 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 .
quelle
Antworten:
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
cdecl
Aufrufkonvention bessere Ergebnisse .Genauer gesagt: Das Problem dabei
stdcall
ist, dass MSVC ++ Namen in der DLL-Exporttabelle auch bei Verwendung entstelltextern "C"
. Zum Beispielfoo
wird_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:
GetProcAddress
in der DLL wird etwas komplizierter.foo@4
anstelle 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
cdecl
Konvention 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
cdecl
ist ein klarer Gewinner für mich.quelle
extern "C"
)?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.
quelle
In Bezug auf die Sicherheit ist die
__cdecl
Konvention "sicherer", da der Aufrufer die Zuordnung des Stapels aufheben muss. In einer__stdcall
Bibliothek 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.quelle