Imaginons que j'écrive une DLL en C++ et que je déclare un objet global d'une classe avec un destructeur non trivial. Le destructeur sera-t-il appelé lorsque la DLL sera déchargée ?
Réponses
Trop de publicités?Dans une DLL Windows C++, tous les objets globaux (y compris les membres statiques des classes) seront construits juste avant l'appel de la DllMain avec DLL_PROCESS_ATTACH, et ils seront détruits juste après l'appel de la DllMain avec DLL_PROCESS_DETACH.
Maintenant, vous devez considérer trois problèmes :
0 - Bien sûr, les objets non-const globaux sont mauvais (mais vous le savez déjà, donc je vais éviter de mentionner le multithreading, les verrous, les objets-dieu, etc.)
1 - L'ordre de construction des objets ou des différentes unités de compilation (i.e. les fichiers CPP) n'est pas garanti, donc vous ne pouvez pas espérer que l'objet A soit construit avant B si les deux objets sont instanciés dans deux CPP différents. Ceci est important si B dépend de A. La solution est de déplacer tous les objets globaux dans le même fichier CPP, car à l'intérieur d'une même unité de compilation, l'ordre d'instanciation des objets sera l'ordre de construction (et l'inverse de l'ordre de destruction).
2 - Il y a des choses qu'il est interdit de faire dans le DllMain. Ces choses sont probablement interdites, aussi, dans les constructeurs. Il faut donc éviter de verrouiller quelque chose. Voir l'excellent blog de Raymond Chen sur le sujet :
- Quelques raisons de ne pas faire quelque chose d'effrayant dans votre DllMain
- Une autre raison de ne pas faire quoi que ce soit d'effrayant dans votre DllMain : Blocage involontaire de l'accès aux données.
- Quelques raisons de ne pas faire quelque chose d'effrayant dans votre DllMain, partie 3
Dans ce cas, l'initialisation paresseuse pourrait être intéressante : Les classes restent dans un état "non initialisé" (les pointeurs internes sont NULL, les booléens sont faux, etc.) jusqu'à ce que vous appeliez une de leurs méthodes, auquel cas elles s'initialisent. Si vous utilisez ces objets à l'intérieur du main (ou d'une des fonctions descendantes du main), tout ira bien car ils seront appelés après l'exécution de DllMain.
3 - Bien sûr, si certains objets globaux de la DLL A dépendent d'objets globaux de la DLL B, vous devez faire très très attention à l'ordre de chargement des DLL, et donc aux dépendances. Dans ce cas, les DLLs avec des dépendances circulaires directes ou indirectes vous causeront une quantité insensée de maux de tête. La meilleure solution est de rompre les dépendances circulaires.
P.S. : Notez qu'en C++, le constructeur peut lancer, et vous ne voulez pas d'une exception au milieu du chargement d'une DLL, donc soyez sûr que vos objets globaux n'utiliseront pas d'exception sans une très, très bonne raison. Comme les destructeurs correctement écrits ne sont pas autorisés à lancer, le déchargement de la DLL devrait être correct dans ce cas.
Cette page de Microsoft entre dans les détails de l'initialisation des DLL et de la destruction des globaux :
http://msdn.microsoft.com/en-us/library/988ye33t.aspx
Si vous voulez voir le code réel qui est exécuté lors de la liaison d'une .dll, jetez un coup d'oeil à %ProgramFiles%\Visual Studio 8\vc\crt\src\dllcrt0.c
.
A partir de l'inspection, les destructeurs seront appelés par l'intermédiaire de _cexit()
lorsque le compte de référence interne maintenu par le dll CRT atteint zéro.
Elle doit être appelée lorsque l'application se termine ou que la DLL est déchargée, selon ce qui se produit en premier. Notez que cela dépend quelque peu du runtime contre lequel vous compilez.
Il faut également se méfier des destructeurs non triviaux, car ils posent des problèmes de synchronisation et d'ordonnancement. Votre DLL peut être déchargée après une DLL sur laquelle repose votre destructeur, ce qui poserait évidemment des problèmes.
Dans Windows, les fichiers d'image binaire avec l'extension *.exe, *.dll sont dans Format PE Ces fichiers ont un point d'entrée. Vous pouvez le visualiser avec un outil dumpbin comme
dumpbin /headers dllname.dll
Si vous utilisez le runtime C de Microsoft, alors votre e *CRTStartup ou *DllMainCRTStartup
Ces fonctions effectuent l'initialisation du runtime c et c++ et délèguent l'exécution à (main, WinMain) ou à DllMain respectivement.
Si vous utilisez le compilateur VC de Microsoft, vous pouvez consulter le code source de ces fonctions dans votre répertoire VC :
- crt0.c
- dllcrt0.c
Le processus DllMainCRTStartup a besoin d'initier/désinitialiser vos variables globales à partir des sections .data dans un scénario normal, lorsqu'il reçoit la notification DLL_PROCESS_DETACH pendant le déchargement de la dll. Par exemple :
- main ou WinMain du fil de démarrage du programme retourne le flux de contrôle
- vous appelez explicitement FreeLibrary et use-dll-counter est zéro
- Réponses précédentes
- Plus de réponses