Je développe actuellement une bibliothèque C++ pour Windows qui sera distribuée sous forme de DLL. Mon objectif est de maximiser l'interopérabilité binaire; plus précisément, les fonctions de mon DLL doivent être utilisables à partir de code compilé avec plusieurs versions de MSVC++ et MinGW sans avoir à recompiler la DLL. Cependant, je ne sais pas quelle convention d'appel est la meilleure, cdecl
ou stdcall
.
Parfois, j'entends des déclarations comme "la convention d'appel C est la seule garantie d'être les mêmes entre les compilateurs", ce qui contraste avec des déclarations comme " Il y a quelques variations dans l'interprétation de cdecl
, en particulier dans comment renvoyer des valeurs ". Cela ne semble pas empêcher certains développeurs de bibliothèques (comme libsndfile ) d'utiliser la convention d'appel C dans les DLL qu'ils distribuent, sans aucun problème visible.
D'un autre côté, la convention d'appel stdcall
semble être bien définie. D'après ce qu'on m'a dit, tous les compilateurs Windows sont fondamentalement tenus de le suivre car c'est la convention utilisée pour Win32 et COM. Ceci est basé sur l'hypothèse qu'un compilateur Windows sans prise en charge Win32/COM ne serait pas très utile. De nombreux extraits de code publiés sur les forums déclarent les fonctions comme stdcall
mais je n'arrive pas à trouver un seul message qui explique clairement pourquoi .
Il y a trop d'informations contradictoires et chaque recherche que je lance me donne des réponses différentes, ce qui ne m'aide pas vraiment à choisir entre les deux. Je cherche une explication claire, détaillée et argumentée pour expliquer pourquoi je devrais choisir l'un plutôt que l'autre (ou pourquoi les deux sont équivalents).
Notez que cette question s'applique non seulement aux fonctions "classiques", mais aussi aux appels de fonction de membre virtuel, car la plupart du code client s'interface avec mes DLL via les "interfaces", classes virtuelles pures (modèles suivants décrit par exemple ici et là ).
Je viens de faire des tests dans le monde réel (compiler des DLL et des applications avec MSVC++ et MinGW, puis les mélanger). Comme il apparaît, j'ai eu de meilleurs résultats avec la convention d'appel cdecl
.
Plus précisément: le problème avec stdcall
est que MSVC++ modifie les noms dans la table d'exportation DLL, même en utilisant extern "C"
. Par exemple foo
devient _foo@4
. Cela ne se produit que lorsque vous utilisez __declspec(dllexport)
, pas lorsque vous utilisez un fichier DEF; cependant, les fichiers DEF sont un problème de maintenance à mon avis, et je ne veux pas les utiliser.
Le changement de nom MSVC++ pose deux problèmes:
GetProcAddress
sur le DLL devient légèrement plus compliqué;foo@4
Au lieu de _foo@4
), Ce qui complique la liaison. En outre, il présente le risque de voir apparaître des "versions non soulignées" des DLL et applications qui sont incompatibles avec les "versions soulignées".J'ai essayé la convention cdecl
: l'interopérabilité entre MSVC++ et MinGW fonctionne parfaitement, prête à l'emploi, et les noms restent non décorés dans la table d'exportation DLL. Elle a même fonctionne pour les méthodes virtuelles.
Pour ces raisons, cdecl
est clairement un gagnant pour moi.
La plus grande différence entre les deux conventions d'appel est que "_ _ cdecl" fait peser la charge de l'équilibrage de la pile après un appel de fonction sur l'appelant, ce qui permet des fonctions avec des quantités variables d'arguments. La convention "__stdcall" est de nature "plus simple", mais moins flexible à cet égard.
De plus, je crois que les langages gérés utilisent la convention stdcall par défaut, donc toute personne utilisant P/Invoke dessus devrait indiquer explicitement la convention d'appel si vous utilisez cdecl.
Donc, si toutes vos signatures de fonction vont être définies statiquement, je pencherais probablement vers stdcall, sinon cdecl.
En termes de sécurité, le __cdecl
la convention est "plus sûre" car c'est l'appelant qui doit désallouer la pile. Que peut-il arriver dans un __stdcall
bibliothèque est que le développeur a peut-être oublié de désallouer la pile correctement, ou qu'un attaquant peut injecter du code en corrompant la pile de la DLL (par exemple par le raccordement d'API) qui n'est alors pas vérifiée par l'appelant. Je n'ai pas d'exemples de sécurité CVS qui montrent que mon intuition est correcte.