2009-08-20 8 views
48

Mi sono imbattuto in __stdcall molto in questi giorni.
E a mio parere, MSDN non spiega molto chiaramente cosa significa realmente,
quando e perché dovrebbe essere usato, se non del tutto.Qual è il significato e l'uso di __stdcall?

Apprezzerei se qualcuno fornirebbe una spiegazione, preferibilmente con un esempio o due.

Grazie

risposta

45

Tutte le funzioni in C/C++ avere una particolare convenzione di chiamata. Il punto di una convenzione di chiamata è stabilire come vengono trasmessi i dati tra il chiamante e il destinatario e chi è responsabile delle operazioni come la pulizia dello stack di chiamate.

Le convenzioni di chiamata più popolari su finestre sono

  • stdcall
  • cdecl
  • clrcall
  • fastcall
  • thiscall

L'aggiunta di questo identificatore per la dichiarazione di funzione racconta essenzialmente il compilatore che tu desidera che questa particolare funzione abbia questa particolare convenzione di chiamata.

Le convenzioni di chiamata sono documentate qui

Raymond Chen ha fatto anche una lunga serie sulla storia delle varie convenzioni di chiamata (5 parti) a partire qui.

1

Questo è un convenzione di chiamata che funzioni WinAPI devono essere chiamati in modo corretto. Una convenzione di chiamata è un insieme di regole su come i parametri vengono passati alla funzione e su come il valore di ritorno viene passato dalla funzione.

Se il chiamante e il codice chiamato utilizzano convenzioni diverse, si imbattono in comportamenti non definiti (come such a strange-looking crash).

I compilatori C++ non utilizzano __stdcall per impostazione predefinita - utilizzano altre convenzioni. Quindi per chiamare le funzioni WinAPI da C++ è necessario specificare che usano __stdcall - questo di solito viene fatto nei file di intestazione di Windoes SDK e lo si fa anche quando si dichiarano i puntatori di funzione.

3

Specifica una convenzione di chiamata per una funzione. Una convenzione di chiamata è un insieme di regole in cui i parametri vengono passati a una funzione: in quale ordine, per indirizzo o per copia, chi deve pulire i parametri (chiamante o chiamato) ecc.

5

Purtroppo, non c'è una risposta facile per quando usarlo e quando no.

__stdcall significa che gli argomenti di una funzione vengono inseriti nello stack dal primo all'ultimo.Questo è al contrario di __cdecl, il che significa che gli argomenti vengono spinti dall'ultimo al primo e __fastcall, che colloca i primi quattro (penso) argomenti nei registri, e il resto va in pila.

Hai solo bisogno di sapere che cosa si aspetta il callee, o se stai scrivendo una biblioteca, cosa si aspettano probabilmente i tuoi chiamanti e assicurati di documentare la convenzione prescelta.

+2

'__stdcall' e' __cdecl' differiscono solo per la responsabilità per la pulizia dopo il ritorno (e la decorazione). Il passaggio degli argomenti è lo stesso per entrambi (da destra a sinistra). Quello che stai descrivendo è la convenzione di chiamata Pascal. – a3f

6

__stdcall è una convenzione di chiamata: un modo per determinare come i parametri vengono passati a una funzione (in pila o in registri) e chi è responsabile della pulizia dopo il ritorno della funzione (il chiamante o il chiamato).

Raymond Chen ha scritto un blog about the major x86 calling conventions e c'è anche un bel CodeProject article.

Per la maggior parte, non dovresti preoccuparti di loro. L'unico caso in cui dovresti è se stai chiamando una funzione di libreria che usa qualcosa di diverso dal default - altrimenti il ​​compilatore genererà il codice sbagliato e il tuo programma probabilmente si bloccherà.

2

__stdcall indica una convenzione di chiamata (vedere this PDF per alcuni dettagli). Ciò significa che specifica in che modo gli argomenti della funzione vengono inviati e prelevati dallo stack e chi è responsabile.

__stdcall è solo una delle numerose convenzioni di chiamata e viene utilizzata in tutto il WINAPI. È necessario utilizzarlo se si forniscono i puntatori di funzione come callback per alcune di queste funzioni. In generale, non è necessario indicare alcuna convenzione di chiamata specifica nel codice, ma è sufficiente utilizzare l'impostazione predefinita del compilatore, ad eccezione del caso sopra indicato (che fornisce i callback al codice di terze parti).

1

messo semplicemente quando si chiama funzione, viene caricato in stack/registro. __stdcall è una convenzione/via (prima argomento a destra, quindi argomento a sinistra ...), __decl è un'altra convenzione che viene utilizzata per caricare la funzione nello stack o nei registri.

Se li si utilizza, si indica al computer di utilizzare il modo specifico di caricare/scaricare la funzione durante il collegamento e quindi non si otterrebbe una mancata corrispondenza/arresto anomalo.

In caso contrario, la funzione di chiamata e chiamata di funzione potrebbe utilizzare diverse convenzioni che provocano l'arresto anomalo del programma.

33

Tradizionalmente, le chiamate di funzione C vengono eseguite con il chiamante che inserisce alcuni parametri nello stack, chiamando la funzione, e quindi apre lo stack per pulire gli argomenti inseriti.

/* example of __cdecl */ 
push arg1 
push arg2 
push arg3 
call function 
add sp,12 // effectively "pop; pop; pop" 

Nota: la convenzione di default - mostrata sopra - è conosciuta come __cdecl.

L'altra convenzione più popolare è __stdcall. In esso i parametri vengono nuovamente spinti dal chiamante, ma lo stack viene ripulito dal destinatario. È la convenzione standard per le funzioni API Win32 (come definito dalla macro WINAPI in), ed è talvolta chiamata anche convenzione di chiamata "Pascal".

/* example of __stdcall */ 
push arg1 
push arg2 
push arg3 
call function // no stack cleanup - callee does this 

Questo appare come un dettaglio tecnico minore, ma se c'è un disaccordo su come la pila è gestito tra il chiamante e il chiamato, lo stack saranno distrutti in un modo che è improbabile che possa essere recuperato. Poiché __stdcall esegue il cleanup dello stack, il codice (molto piccolo) per eseguire questa attività si trova in una sola posizione, anziché essere duplicato in ogni chiamante come in __cdecl. Ciò rende il codice leggermente più piccolo, sebbene l'impatto sulle dimensioni sia visibile solo nei programmi di grandi dimensioni.

Le funzioni variabili come printf() sono quasi impossibili da ottenere con __stdcall, perché solo il chiamante sa davvero quanti argomenti sono stati passati per pulirli. Il callee può fare alcune buone ipotesi (ad esempio, osservando una stringa di formato), ma la pulizia dello stack dovrebbe essere determinata dalla logica effettiva della funzione, non dal meccanismo della convenzione di chiamata stessa. Quindi solo __cdecl supporta le funzioni variadiche in modo che il chiamante possa eseguire la pulizia.

Decorazioni nome simbolo linker: Come menzionato in un punto elenco sopra, chiamare una funzione con la convenzione "errata" può essere disastroso, quindi Microsoft ha un meccanismo per evitare che ciò accada. Funziona bene, anche se può essere esasperante se non si conoscono le ragioni. Hanno scelto di risolvere questo codificando la convenzione di chiamata nei nomi di funzione di basso livello con caratteri aggiuntivi (che sono spesso chiamati "decorazioni"), e questi sono trattati come nomi non collegati dal linker. La convenzione di chiamata predefinita è __cdecl, ma ognuno può essere richiesto esplicitamente con/G? parametro per il compilatore.

__cdecl (cl/Gd ...)

Tutti i nomi delle funzioni di questo tipo sono preceduti dal prefisso un carattere di sottolineatura, e il numero di parametri non importa, perché il chiamante è responsabile per la messa a punto dello stack e lo stack di pulizia. È possibile che un chiamante e un chiamato si confondano sul numero di parametri effettivamente passati, ma almeno la disciplina dello stack viene mantenuta correttamente.

__stdcall (cl/Gz ...)

Questi nomi di funzione sono prefissati con una sottolineatura e aggiunti con @ più il numero di byte di parametri passati. Con questo meccanismo, non è possibile chiamare una funzione con il tipo "sbagliato" o anche con un numero errato di parametri.

__fastcall (cl/Gr ...)

Questi i nomi delle funzioni iniziano con un segno @ e hanno come suffisso il conteggio @parameter, proprio come __stdcall.

Esempi:

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);   -----------------------> [email protected] 

void __stdcall foo(int a);   -----------------------> [email protected] 

void __stdcall foo(int a, int b); -----------------------> [email protected] 

void __fastcall foo(void);   -----------------------> @[email protected] 

void __fastcall foo(int a);  -----------------------> @[email protected] 

void __fastcall foo(int a, int b); -----------------------> @[email protected] 
1

__stdcall è la convenzione di chiamata utilizzata per la funzione. Questo dice al compilatore le regole che si applicano per l'impostazione dello stack, spingendo gli argomenti e ottenendo un valore di ritorno. Ci sono una serie di altre convenzioni di chiamata come __cdecl, __thiscall, __fastcall e __naked.

__stdcall è la convenzione di chiamata standard per le chiamate di sistema Win32.

Maggiori dettagli possono essere trovati su Wikipedia.