__cdecl o __stdcall su Windows?


84

Attualmente sto sviluppando una libreria C ++ per Windows che verrà distribuita come DLL. Il mio obiettivo è massimizzare l'interoperabilità binaria; più precisamente, le funzioni nella mia DLL devono essere utilizzabili da codice compilato con più versioni di MSVC ++ e MinGW senza dover ricompilare la DLL. Tuttavia, sono confuso su quale convenzione di chiamata sia la migliore, cdeclo stdcall.

A volte sento affermazioni come "la convenzione di chiamata C è l'unica garantita per essere la stessa per tutti i compilatori", che contrasta con affermazioni come " Ci sono alcune variazioni nell'interpretazione di cdecl, in particolare nel modo in cui restituire valori ". Ciò non sembra impedire ad alcuni sviluppatori di librerie (come libsndfile ) di utilizzare la convenzione di chiamata C nelle DLL che distribuiscono, senza problemi visibili.

D'altra parte, la stdcallconvenzione di chiamata sembra essere ben definita. Da quello che mi è stato detto, tutti i compilatori di Windows sono fondamentalmente tenuti a seguirlo perché è la convenzione utilizzata per Win32 e COM. Ciò si basa sul presupposto che un compilatore Windows senza il supporto Win32 / COM non sarebbe molto utile. Molti frammenti di codice pubblicati sui forum dichiarano funzioni come stdcallma non riesco a trovare un singolo post che spieghi chiaramente il motivo .

Ci sono troppe informazioni contrastanti là fuori e ogni ricerca che eseguo mi dà risposte diverse che non mi aiutano davvero a decidere tra i due. Sto cercando una spiegazione chiara, dettagliata e argomentata del motivo per cui dovrei scegliere uno rispetto all'altro (o perché i due sono equivalenti).

Si noti che questa domanda non si applica solo alle funzioni "classiche", ma anche alle chiamate di funzioni membri virtuali, poiché la maggior parte del codice client si interfaccia con la mia DLL tramite "interfacce", classi virtuali pure (seguendo i modelli descritti ad esempio qua e ).


4
"Ci sono alcune variazioni nell'interpretazione di cdecl, in particolare nel modo in cui restituire i valori" - questo significa approssimativamente che diversi sistemi operativi che utilizzano cdecl su x86 potrebbero utilizzare diverse varianti di cdecl, cioè differenti ABI che entrambi chiamano "cdecl". Windows fissa le variazioni, qualsiasi implementazione che gira su Windows e non rispetta le scelte di Windows non sarà in grado di chiamare le funzioni cdecl di Windows, quindi come dici con stdcall, è inutile per la programmazione Windows e ci sono alcuni standard C funzioni sarebbe difficile da implementare.
Steve Jessop

1
La convenzione di chiamata __stdcall viene utilizzata per chiamare le funzioni API Win32. docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Zanna

Risposte:


79

Ho appena eseguito alcuni test nel mondo reale (compilando DLL e applicazioni con MSVC ++ e MinGW, quindi mescolandoli). A quanto pare, ho avuto risultati migliori con la cdeclconvenzione di chiamata.

Più specificamente: il problema stdcallè che MSVC ++ altera i nomi nella tabella di esportazione della DLL, anche quando si utilizza extern "C". Ad esempio foodiventa _foo@4. Questo accade solo quando si utilizza __declspec(dllexport), non quando si utilizza un file DEF; tuttavia, i file DEF sono una seccatura di manutenzione a mio avviso e non voglio usarli.

La modifica del nome MSVC ++ pone due problemi:

  • L'utilizzo GetProcAddresssulla DLL diventa leggermente più complicato;
  • MinGW di default non antepone un undescore ai nomi decorati (es. MinGW userà al foo@4posto di _foo@4), il che complica il collegamento. Inoltre, introduce il rischio di vedere "versioni senza carattere di sottolineatura" di DLL e applicazioni che sono incompatibili con le "versioni di sottolineatura".

Ho provato la cdeclconvenzione: l'interoperabilità tra MSVC ++ e MinGW funziona perfettamente, out-of-the-box, ei nomi rimangono non decorati nella tabella di esportazione della DLL. Funziona anche con metodi virtuali.

Per questi motivi, cdeclè un chiaro vincitore per me.


In particolare, questo non si applica alle DLL a 64 bit, perché __stdcall e __cdecl sono generalmente entrambi ignorati, quindi non si verifica alcuna modifica del nome sulle funzioni C esportate.
jtbr

@jtbr Che ne dici delle funzioni C ++ esportate (cioè no extern "C")?
Ela782

@ Ela782 C ++ avrà sempre qualche modifica del nome, al fine di supportare il sovraccarico delle funzioni. Ma questo non è standardizzato e può quindi variare tra i compilatori.
jtbr

@jtbr Scusa, non volevo dire che il nome storpia. Volevo dire se __stdcall e __cdecl vengono entrambi ignorati anche nelle DLL a 64 bit con funzioni C ++ esportate.
Ela782

1
@ Ela782 Sì; Credo che __stdcall e __cdecl siano ignorati in tutte le compilation x86-64. Vedi wikipedia .
jtbr

20

La più grande differenza nelle due convenzioni di chiamata è che " _ _cdecl" pone l'onere di bilanciare lo stack dopo una chiamata di funzione sul chiamante, il che consente funzioni con quantità variabili di argomenti. La convenzione "__stdcall" è di natura "più semplice", tuttavia meno flessibile a questo riguardo.

Inoltre, credo che i linguaggi gestiti utilizzino la convenzione stdcall per impostazione predefinita, quindi chiunque utilizzi P / Invoke su di esso dovrebbe dichiarare esplicitamente la convenzione di chiamata se si utilizza cdecl.

Quindi, se tutte le firme delle tue funzioni saranno definite staticamente, probabilmente mi orienterei verso stdcall, se non cdecl.


11

In termini di sicurezza, la __cdeclconvenzione è "più sicura" perché è il chiamante che deve deallocare lo stack. Ciò che può accadere in una __stdcalllibreria è che lo sviluppatore potrebbe aver dimenticato di deallocare correttamente lo stack, o un utente malintenzionato potrebbe iniettare del codice corrompendo lo stack della DLL (ad esempio tramite l'API hooking) che non viene quindi controllato dal chiamante. Non ho alcun esempio di sicurezza CVS che dimostri che la mia intuizione è corretta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.