Cette note technique décrit la mise en œuvre de constructions de le « état de module"MFC. Une compréhension de la mise en œuvre de module État est critique pour utiliser le MFC partagés dll à partir d'une DLL (ou OLE dans le processus serveur).
Avant de lire cette note, veuillez vous référer à la « Gestion de l'état de MFC Modules de données » dans la création de nouveaux Documents, les fenêtres et les vues dans le Guide du programmeur Visual C++. Cet article contient des informations sur l'utilisation importante et informations de présentation sur le sujet.
Vue d'ensemble
Il y a trois sortes d'informations sur l'État MFC : Module État, État du processus et état de Thread. Parfois, ces types d'État peuvent être combinés. Par exemple, les cartes de poignée de MFC sont module local et thread local. Cela permet aux deux modules différents d'avoir différentes cartes dans chacune de leurs fils.
Processus et état de Thread sont similaires. Ces éléments de données sont des choses qui ont traditionnellement été des variables globales, mais ont besoin être spécifique à un processus donné ou thread pour Win32s appropriées de soutien ou d'appui multithreading appropriée. Quelle catégorie un élément de données correspond au dépend de cet élément et de sa sémantique souhaitée à l'égard des limites de processus et de thread.
État de module est unique en ce qu'il peut contenir soit véritablement mondiale État ou local de processus ou thread local. Aussi il peut être changé rapidement.
État de module de commutation
Chaque thread contient un pointeur vers l'État du module « courant » ou « active » (n'est pas surprenant, le pointeur est partie de l'État local de thread MFC). Ce pointeur est modifié lorsque le thread d'exécution passe une frontière de module, comme une demande d'appel dans un contrôle OLE ou DLL ou un contrôle OLE rappel dans une application.
L'état actuel du module est commuté en appelant AfxSetModuleState. Dans la plupart des cas, vous ne traitera jamais directement avec l'API. MFC, dans de nombreux cas, appellerai pour vous (à WinMain, OLE-points d'entrée AfxWndProc, etc.). Cela se fait dans n'importe quel composant vous écrire en la liant statiquement dans une spéciale WndProcet un spécial WinMain (ou DllMain) qui sait quel État module doit être actuel. Vous pouvez voir ce code en prenant un coup d'oeil à DLLMODUL.RPC ou APPMODUL.RPC dans le répertoire MFC\SRC.
Il est rare que vous souhaitez définir l'État du module et ensuite pas définie arrière. La plupart du temps que vous voulez « pousser » votre propre module d'État comme l'actuel et ensuite, après que vous avez terminé, « pop » le contexte original de retour. Cela se fait par la macro AFX_MANAGE_STATE et la classe spéciale AFX_MAINTAIN_STATE.
CCmdTarget a des caractéristiques spéciales de support module État de commutation. En particulier, un CCmdTarget est que la classe racine utilisé pour l'automation OLE et COM OLE des points d'entrée. Comme n'importe quel autre point d'entrée exposée dans le système, ces points d'entrée doivent définir l'état de module correct. Comment une donnée CCmdTarget sait ce que devrait être l'État du module « correcte » ? La réponse est qu'il « souvient » quel est l'état de module « actuel » lorsqu'il est construit, telle qu'elle peut définir l'état actuel du module à celle « rappeler » valeur lorsqu'il est plus tard appelée. En conséquence, l'État du module qui est associé à un objet donné de CCmdTarget est l'État du module qui était en cours lorsque l'objet a été construit. Prenons un exemple simple de charger un serveur INPROC, création d'un objet et appeler ses méthodes.
Comme vous pouvez le voir, l'État du module est propagé d'objet à objet car ils sont créés. Il est important d'avoir l'État module défini convenablement. Si il n'est pas défini, votre objet COM ou DLL peut-être interagir mal avec une application MFC qui appelle, peut être impossible de trouver ses propres ressources ou peut échouer par d'autres moyens misérable.
Note que certains types de DLL, plus précisément « Extension MFC » DLL ne pas basculer l'État du module en leur RawDllMain fournie (en fait, ils habituellement n'ont même un RawDllMain fournie). C'est parce qu'ils sont censés se comporter « comme si » ils étaient effectivement présents dans l'application qui les utilise. Ils sont très bien une partie de l'application qui s'exécute et c'est leur intention de modifier l'état global de cette application.
OLE contrôles et autres DLL est très différents. Ils ne veulent pas de modifier l'état de l'application appelant ; l'application qui fait appel à eux n'ont même pas une application MFC, et donc il ne peut y avoir aucun État à modifier. C'est la raison que le module État de commutation a été inventé.
Pour les fonctions exportées d'une DLL, comme celui qui lance une boîte de dialogue dans votre DLL, vous devez ajouter le code suivant au début de la fonction:
AFX_MANAGE_STATE (partir de AfxGetStaticModuleState ())
L'actuel État de module par l'état retourné à partir de AfxGetStaticModuleState jusqu'à la fin de la portée actuelle.
Problèmes de ressources dans des dll se produira si la macro AFX_MODULE_STATE n'est pas utilisée. Par défaut, MFC utilise le handle de ressource de l'application principale pour charger le modèle de ressource. Ce modèle est réellement stocké dans la DLL. La cause est que des informations d'État module MFC n'ont pas été commutées par la macro AFX_MODULE_STATE . Le handle de ressource est récupéré de l'état de module de MFC. L'État du module de commutation ne provoque le handle de ressource mal à utiliser.
AFX_MODULE_STATE n'a pas besoin d'être mis dans chaque fonction dans la DLL. Par exemple, InitInstance peut être appelée par le code MFC dans l'application sans AFX_MODULE_STATE car MFC décale automatiquement l'État du module avant InitInstance et ensuite les commutateurs it back après InitInstance renvoie. Il en va de même pour tous les gestionnaires de la carte de message. DLL normales ont effectivement une procédure spéciale fenêtre maître qui bascule automatiquement de l'État du module avant le routage d'un message.
Données de processus locales
Traiter les données locales ne seraient pas une telle grande préoccupation n'avait pas pour la difficulté du modèle DLL Win32s. Dans Win32s toutes les DLL partagent leurs données mondiales, même lorsque chargée par plusieurs applications. C'est totalement différent du modèle de données Win32 DLL « réel », où chaque DLL obtient une copie distincte de son espace de données dans chaque processus qui s'attache à la DLL. Pour ajouter à la complexité, données allouées sur le tas dans une DLL Win32s sont en fait processus spécifiques (au moins autant que la propriété goes). Examiner le code et les données suivantes:
statique CString strGlobal ; et à la portée du fichier
__declspec (dllexport) void SetGlobalString(LPCTSTR lpsz)
{
strGlobal = lpsz ;
}
__declspec (dllexport)
VOID GetGlobalString (int cb LPCTSTR lpsz)
{
lstrcpyn (lpsz, strGlobal, cb) ;
}
Tenir compte de ce qui se passe si le code ci-dessus est situé dans une DLL et que la DLL est chargée par deux processus a et B (il pourrait, en fait, être deux instances de la même application). Un appel SetGlobalString("Hello from A") . Par conséquent, la mémoire est allouée pour les données de CString dans le contexte du processus a. Gardez à l'esprit que le CString elle-même est mondial et est visible à la fois a et b. B appelle maintenant GetGlobalString(sz, sizeof(sz)) . B seront en mesure de voir les données qu'un ensemble. C'est parce que Win32s n'offre aucune protection entre les processus comme Win32. C'est le premier problème ; dans de nombreux cas, il n'est pas souhaitable d'avoir une application affectent les données globales qui sont censées être détenue par une autre application.
Mais l'attente — il y a plus de problèmes. Disons qu'un maintenant sorties. Lorsque a quitte, la mémoire utilisée par le ' strGlobal ' chaîne est rendu disponible pour le système — c'est toute la mémoire allouée par le processus a est libérée automatiquement par le système d'exploitation. Il n'est pas libéré parce que le destructeur de CString est appelé ; Il n'a pas été appelé encore. Il est libéré de tout simplement parce que l'application qui les a attribuées, il a quitté les lieux. Maintenant si b appelé GetGlobalString(sz, sizeof(sz)) , il ne peut pas obtenir des données valides. Une autre application peut avoir utilisé cette mémoire pour autre chose.
Il y a clairement un problème ici. MFC 3.x utilisé une technique appelée thread local storage (TLS). MFC 3.x attribuerait un index TLS sous Win32s agit vraiment comme un indice du processus local de stockage, même si cela ne s'appelle qu'et puis aurait référence toutes les données de base sur cet index TLS. Ceci est similaire à l'index TLS qui a été utilisé pour stocker les données de thread local sur Win32 (voir ci-dessous pour plus d'informations à ce sujet). Cela a causé chaque DLL MFC utiliser au moins deux indices TLS par processus. Lorsque vous tenir compte de nombreux contrôle DLL OLE (OCX) de chargement, vous exécutez rapidement des indices TLS (il y a seulement 64 disponible). En outre, MFC a dû placer toutes ces données en un seul endroit, dans une structure unique. Il n'était pas très extensible et n'était pas idéal à l'utilisation des indices TLS.
MFC 4.x traite cela avec un ensemble de modèles de classe vous pouvez « habiller » les données qui devraient être des processus locaux. Par exemple, le problème mentionné ci-dessus pourrait être fixé par écrit:
struct CMyGlobalData : public CNoTrackObject
{
CString strGlobal ;
};
CProcessLocallt ;CMyGlobalData > globalData ;
__declspec (dllexport) void SetGlobalString(LPCTSTR lpsz)
{
globalData - > strGlobal = lpsz ;
}
__declspec (dllexport)
VOID GetGlobalString (int cb LPCTSTR lpsz)
{
lstrcpyn (lpsz, globalData - > strGlobal, cb) ;
}
MFC implémente cela en deux étapes. Tout d'abord, il y a une couche au dessus de la Win32 Tls * API (TlsAlloc, TlsSetValue, Tls&GetValue, etc.) qui utilisent seulement deux index TLS par processus, quel que soit les DLL combien vous avez. Deuxièmement, la CProcessLocal modèle est fourni pour accéder à ces données. Il substitue l'opérateur-gt ; qui est ce qui permet la syntaxe intuitive que vous voyez ci-dessus. Tous les objets qui sont enveloppés par CProcessLocal doit être dérivé de CNoTrackObject . CNoTrackObject fournit un allocateur de niveau inférieur (LocalAllocetLocalFree) et un destructeur virtuel tel que MFC peut détruire automatiquement les objets locaux de processus lorsque le processus est terminé. Ces objets peuvent avoir un destructeur personnalisé si nettoyage supplémentaire est nécessaire. L'exemple ci-dessus ne nécessite pas un, puisque le compilateur générera un destructeur par défaut pour détruire l'objet CString incorporé.
Il y a d'autres avantages intéressants à cette approche. Sont non seulement tous les CProcessLocal objets détruits automatiquement, elles ne sont pas construites jusqu'à ce qu'ils sont nécessaires. CProcessLocal::operator-gt; instancie l'objet associé à la première fois, elle est appelée et pas plus tôt. Dans l'exemple ci-dessus, cela signifie que le ' str&Global ' chaîne ne sera pas construit jusqu'à la première fois, SetGlobalString ou GetGlobalString est appelée. Dans certains cas, cela peut aider à diminuer les temps de démarrage de DLL.
Données locales de thread
Semblables pour traiter les données locales, les données locales de thread sont utilisées lorsque les données doivent être locales pour un thread donné. Vous avez besoin c'est une instance séparée des données pour chaque thread qui accède à ces données. Cela plusieurs fois permet au lieu de mécanismes de synchronisation vaste. Si les données n'a pas besoin d'être partagé par plusieurs threads, ces mécanismes peuvent être coûteux et inutiles. Supposons que nous avons eu un objet CString (comme l'exemple ci-dessus). Nous pouvons faire ce thread local en enroulant avec une CThreadLocal modèle:
struct CMyThreadData : public CNoTrackObject
{
CString strThread ;
};
CThreadLocallt ;CMyThreadData > threadData ;
VOID MakeRandomString()
{
/ / une sorte de remaniement de la carte (pas une merveille)
CString & str = threadData - > strThread ;
Str.Empty() ;
alors que (str.GetLength()! = 52)
{
TCHAR ch = ALEA() % 52 + 1 ;
Si (str.Find(ch) < 0)
STR += ch ; / / pas trouvée, ajouter
}
}
Si MakeRandomString a été appelée de deux threads différents, chacun serait « shuffle » la chaîne de différentes manières sans interférer avec les autres. C'est parce qu'il n'y a effectivement une strThread instance par thread au lieu d'une seule instance globale.
Notez comment une référence est utilisée pour capturer l'adresse CStrin&g une fois au lieu d'une fois par itération de boucle. Le code de la boucle aurait pu être écrit avec threadData-gt;strThread partout ' str ' est utilisé, mais le code serait beaucoup plus lent dans l'exécution. Il est préférable de mettre en cache une référence aux données lorsque ces références se produisent dans les boucles.
Le CThreadLocal modèle de classe utilise les mêmes mécanismes qui CProcessLocal les mêmes techniques de mise en œuvre et les.
&Notes techniques par le numéro |nbsp ; Notes techniques par catégorie