TN058 : Implémentation d'état du module MFC

[!REMARQUE]

La note technique suivante n'a pas été modifiée depuis si c'était première inclus dans la documentation en ligne.Par conséquent, certaines procédures et rubriques peuvent être obsolètes ou incorrects.Pour obtenir les informations les plus récentes, il est recommandé que vous trouviez la rubrique d'intérêt dans l'index de la documentation en ligne.

Cette note technique explique l'implémentation des éléments « état du module » MFC.Une connaissance de l'implémentation d'état du module est critique pour l'utilisation des DLL partagées par MFC à partir d'une DLL (ou dans OLE serveur in-process).

Avant que la lecture de cette remarque, reportez -vous à « gérer des données d'état des modules MFC » dans Créer de nouveaux documents, windows, et vues.Cet article contient des informations et des informations d'ordre général importantes d'utilisation à cette rubrique.

Vue d'ensemble

Il existe trois types d'informations d'état MFC : État du module, de l'état de processus, et état du thread.Parfois ces types d'état peuvent être combinés.Par exemple, les cartes de handles de MFC sont des variables locales du module et des variables locales de thread.Cela permet à deux modules différents des propriétés différentes cartes de chacune de leurs thread.

L'état et l'état des threads de processus sont similaires.Ces éléments de données sont des éléments qui ont traditionnellement été des variables globales, mais offre le besoin d'être spécifiques à un processus ou un thread donné pour la prise en charge appropriée de Win32s ou pour la prise en charge du multithreading appropriée.La catégorie un élément de données donné rentre dans dépend de cet élément et de ses sémantiques souhaitée par rapport à les limites de processus et de threads.

L'état du module est unique parce qu'il peut contenir l'état vraiment globale ou l'état qui est le site web local de processus ou les données locales de thread.En outre, il peut être basculer vers rapidement.

Basculer d'état du module

Chaque thread contient un pointeur vers « actif » ou l'état du module « actif » (logiquement, le pointeur fait partie de l'état local de thread MFC).Ce pointeur est modifié lorsque le thread de l'exécution passe une limite de module, telle qu'une application appelante dans un contrôle OLE ou DLL, ou un contrôle OLE appelant à l'intérieur d'une application.

L'état du module en cours est extrait en appelant AfxSetModuleState.Pour la plupart, vous n'allez traiter jamais directement l'API.MFC, dans de nombreux cas, l'appelle pour vous (à WinMain, à OLE points d'entrée, à AfxWndProc, etc.).Cela s'effectue dans n'importe quel composant que vous écrivez en liant statiquement dans WndProcspécial, et WinMain spécial (ou DllMain) qui ignore quel état du module doit être actuel.Vous pouvez voir ce code en examinant DLLMODUL.CPP ou APPMODUL.CPP dans le répertoire MFC \SRC.

Il est rare que vous souhaitiez définir l'état du module et ne pas le définir ensuite arrière.Le plus souvent vous souhaitez « pousser » votre propre état du module comme planificateur actuel et ensuite, une fois que vous avez fini, « pop sur » en arrière d'origine de contexte.Cela s'effectue par macro AFX_MANAGE_STATE et la classe spéciale AFX_MAINTAIN_STATE.

CCmdTarget a fonctionnalités spéciales pour basculer de prise en charge d'état du module.En particulier, CCmdTarget est la classe racine utilisée pour OLE automation et les points d'entrée OLE COM.Comme tout autre point d'entrée exposé au système, ces points d'entrée doivent définir l'état du module approprié.Comment CCmdTarget donné sait ce qu'elle soit l'état du module « correct » doit ?La réponse est qu'elle « se souvient » ce que l'état du module « actuel » est lorsqu'on construit, de sorte qu'il peut définir l'état du module en cours comme étant celui valeur « retrouvée » lorsqu'il est appelé ultérieurement.Par conséquent, l'état du module auquel un objet donné d' CCmdTarget est associé est l'état du module en cours juste lors de la construction de l'objet.Prenez un exemple simple de charger un serveur INPROC, pour créer un objet, et d'appeler ses méthodes.

  1. La DLL est chargée par OLE à l'aide de LoadLibrary.

  2. RawDllMain est appelé en premier.Il définit l'état du module à l'état du module statique pour la DLL.Pour cette raison RawDllMain est liée statiquement à la DLL.

  3. Le constructeur pour la fabrique de classe associée à notre objet est appelé.COleObjectFactory est dérivé d' CCmdTarget et par conséquent, elle se souvient dans laquelle l'état du module il a été instancié.Cela est important (lorsque la fabrique de classe permet de créer des objets, il sait maintenant quel état du module pour que le actuel.

  4. DllGetClassObject est appelé pour obtenir la fabrique de classe.MFC recherche la liste de fabrique de classe associée à ce module et la retourne.

  5. COleObjectFactory::XClassFactory2::CreateInstance est appelé.Avant de créer l'objet et le retourner, cette fonction définit l'état du module à l'état du module en cours juste à l'étape 3 (celui qui étaient actuels lorsque COleObjectFactory a été instancié).Cela est fait à l'intérieur de METHOD_PROLOGUE.

  6. Lorsque l'objet est créé, il est aussi un dérivé d' CCmdTarget et de la même façon COleObjectFactory retrouvé qui l'état du module était actif, fait ce nouvel objet.Maintenant l'objet connaît quel état du module pour basculer chaque fois qu'il est appelé.

  7. Le client appelle une fonction sur le OLE objet COM qu'elle a reçu de son appel d' CoCreateInstance .Lorsque l'objet est appelé il utilise METHOD_PROLOGUE pour changer l'état du module comme COleObjectFactory fait.

Comme vous pouvez le voir, l'état du module est propagé de l'objet à l'objet à mesure qu'ils sont créés.Il est important d'effectuer définir l'état du module de manière appropriée.Si ce n'est pas définie, la DLL ou objet COM peut interagir problème avec une application MFC qui est appelante il, ou ne peut pas trouver ses propres ressources, ou peut échouer d'autres façons malheureuses.

Notez que certains types de DLL, notamment pour les DLL « d'extension MFC » ne changez pas l'état du module dans leur RawDllMain (en réalité, ils doivent généralement n'ont pas même RawDllMain).Cela est dû au fait qu'ils sont destinés à se comporter comme « if » ils étaient réellement présents dans l'application qui les utilise.Ils sont réellement beaucoup une partie de l'application qui exécute et est leur intention de modifier que l'état global de l'application.

Les contrôles OLE et d'autres DLL sont très différents.Ils ne souhaitez pas modifier l'état de l'application appelante ; l'application qui est appelante il peut même ne pas être une application MFC et de sorte qu'il peut être un état à modifier.c'est la raison pour laquelle basculer d'état du module a été inventé.

Pour les fonctions exportées à partir d'une DLL, tel qu'un qui lance une boîte de dialogue dans la DLL, vous devez ajouter le code suivant au début de la fonction :

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

Cela fait passer l'état du module en cours avec l'état retourné d' AfxGetStaticModuleState jusqu'à la fin de la portée actuelle.

Les problèmes avec les ressources dans les DLL se poseront si la macro d' AFX_MODULE_STATE n'est pas utilisée.Par défaut, MFC utilise le handle de la ressource de l'application principale de charger le modèle de ressource.Ce modèle est stocké en fait dans la DLL.La cause première est que les informations d'état du module MFC n'ont pas été basculées par le d' AFX_MODULE_STATE .Le handle de la ressource est récupérée de l'état du module MFC.Ne pas changer l'état du module provoque le handle de la ressource incorrect à utiliser.

AFX_MODULE_STATE n'a pas besoin d'être placés dans chaque fonction dans la DLL.Par exemple, InitInstance peut être appelé par du code MFC dans l'application sans AFX_MODULE_STATE car les MFC déplace automatiquement l'état du module que InitInstance puis les commutateurs il arrière après qu' InitInstance retourne.Il en va de même pour tous les gestionnaires de table des messages.Les DLL normales liées de manière ont réellement une procédure de fenêtre de page maître spéciale qui bascule automatiquement l'état du module avant de router tout message.

Données locales de processus

Les données locales de processus ne seraient pas concernés une telle grande problème sans la difficulté du modèle de DLL de Win32s.Dans Win32s toutes les DLL partagent leurs données globales, même lorsque chargé par plusieurs applications.Cela s'avère très différent de « true » modèle de données de DLL Win32, 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é, les données allouée sur le tas dans une DLL Win32s est en fait détail de processus (du moins dans la mesure où la propriété disparaît).Considérez les données suivantes et le code :

static CString strGlobal; // at file scope

__declspec(dllexport) 
void SetGlobalString(LPCTSTR lpsz)
{
   strGlobal = lpsz;
}

__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
   StringCbCopy(lpsz, cb, strGlobal);
}

Considérez ce qui se produit si le code ci-dessus est en trouve dans une DLL et cette DLL est chargée par deux processus A et B (il peut, en fait, être deux instances de la même application).Appels SetGlobalString("Hello from A").Par conséquent, la mémoire est allouée pour les données d' CString dans le contexte du processus A un.N'oubliez pas qu' CString lui-même est global et est visible à et à B.Maintenant appels GetGlobalString(sz, sizeof(sz))de B.B pourra voir les données qui A défini.En effet Win32s n'offre aucune protection entre les processus comme Win32 fait.c'est le premier problème ; dans de nombreux cas il n'est pas souhaitable d'avoir de données globale d'impact d'application qui est considérée comme étant possédée par une autre application.

Il existe d'autres problèmes également.Disons que quitte maintenant.Lorsque A fermé, la mémoire utilisée par la chaîne « strGlobal » sont disponibles pour le système - c. autrement dit., toute la mémoire allouée par traitent Un est libérée automatiquement par le système d'exploitation.Il n'est pas libéré parce que le destructeur d' CString est appelé ; il n'a pas été appelé encore.Il est libéré simplement parce que l'application qui l'a allouée reste la scène.Maintenant si B appelle GetGlobalString(sz, sizeof(sz)), il peut ne pas obtenir des données valides.Une autre application peut avoir utilisé cette mémoire pour une autre valeur.

Clairement un problème existe.MFC 3.x utilise une technique appelée stockage local des (TLS) threads.MFC 3.x allouerait un index TLS qui sous Win32s agit réellement comme index de stockage de processus-gens locales, bien qu'il n'est pas appelé que et ensuite référencerait toutes les données selon l'index TLS.Cela revient à l'index TLS qui a été utilisé pour stocker des données locales de thread sur Win32 (voir ci-dessous pour plus d'informations sur cette rubrique).Cela rendait pour utiliser chaque DLL MFC au moins deux index TLS par processus.Lorsque vous présentez charger plusieurs DLL de contrôle OLE (OCXs), il vous manque rapidement d'index TLS (il n'existe qu'de 64 disponibles).En outre, MFC devait placer toutes ces données dans un emplacement, dans une structure unique.Il n'est pas très extensible et n'était pas idéal quant à son utilisation des index TLS.

MFC 4.x adresse cela à un jeu de modèles de classe que vous pouvez « enrouler » autour de les données qui doivent être local de processus.Par exemple, le problème mentionné ci-dessus peut être résolu en écrivant :

struct CMyGlobalData : public CNoTrackObject
{
   CString strGlobal;
};
CProcessLocal<CMyGlobalData> globalData;

__declspec(dllexport) 
void SetGlobalString(LPCTSTR lpsz)
{
   globalData->strGlobal = lpsz;
}

__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
   StringCbCopy(lpsz, cb, globalData->strGlobal);
}

MFC implémente cela en deux étapes.D'abord, il existe une couche sur les API Win32 Tls* (TlsAlloc, TlsSetValue, TlsGetValue, etc.) qui utilisent uniquement deux index TLS par processus, quel que soit le nombre de DLL que vous avez.Ensuite, le modèle d' CProcessLocal est fourni pour accéder à ces données.Elle substitue l'operator-> qui est ce qui permet la syntaxe intuitive qui s'affichent en haut.Tous les objets qui sont encapsulés par CProcessLocal doivent être dérivés d' CNoTrackObject.CNoTrackObject fournit un allocateur plus élémentaire (LocalAlloc/LocalFree) et un destructeur virtuel tels que les MFC peut automatiquement la destruction des objets locaux de processus lorsque le processus est terminé.De tels objets peuvent avoir de destructeur personnalisé si le nettoyage supplémentaire est requis.L'exemple ci-dessus ne requiert pas, étant donné que le compilateur génère un destructeur par défaut pour détruire l'objet incorporé d' CString .

Il existe d'autres avantages intéressants de cette approche.Non seulement tous les objets d' CProcessLocal sont-ils détruits automatiquement, ils ne sont pas construits jusqu'à ce qu'ils soient nécessaires.CProcessLocal::operator-> est-à-dire instancier l'objet associé la première fois qu'il est appelé, et aucun précédemment.Dans l'exemple ci-dessus, cela signifie que la chaîne « strGlobal » ne peut pas être construite tant que la première fois SetGlobalString ou GetGlobalString est appelé.Parfois, cela peut vous aider à réduire le temps de démarrage de la DLL.

Données locaux de thread

Semblable aux données locales de processus, données locales de thread est utilisé lorsque les données doivent être locales à un thread donné.Autrement dit, vous avez besoin d'une instance séparée de données pour chaque thread qui contrôlent l'accès à ces données.Cela peut plusieurs fois être utilisé à la place des mécanismes étendus de synchronisation.Si les données n'ont pas besoin d'être partagé par plusieurs threads, ces mécanismes peuvent être coûteux et inutiles.Supposons que nous avions un objet d' CString (de façon très similaire à l'exemple ci-dessus).Nous pouvons faire des variables locales de thread en les incluant dans un wrapper à un modèle d' CThreadLocal :

struct CMyThreadData : public CNoTrackObject
{
   CString strThread;
};
CThreadLocal<CMyThreadData> threadData;

void MakeRandomString()
{
   // a kind of card shuffle (not a great one)
   CString& str = threadData->strThread;
   str.Empty();
   while (str.GetLength() != 52)
   {
      unsigned int randomNumber;
      errno_t randErr;
      randErr = rand_s( &randomNumber );
      if ( randErr == 0 )
      {
         TCHAR ch = randomNumber % 52 + 1;
         if (str.Find(ch) < 0)
            str += ch; // not found, add it
      }
   }
}

Si MakeRandomString s'appelait de deux threads différents, chacun « brouillerait » la chaîne de différentes façons sans interférer avec l'autre.C'est parce qu'il existe réellement une instance d' strThread par thread au lieu de simplement une instance globale.

Remarque au lieu de la façon dont une référence est utilisée pour capturer l'adresse d' CString une seule fois par itération de boucle.Le code de la boucle peut avoir été entrés pour threadData->strThread partout « str », mais le code serait beaucoup plus lent dans l'exécution.Il est recommandé de mettre en cache une référence aux données lorsque de telles références se produisent dans les boucles.

Le modèle de classe d' CThreadLocal utilise les mêmes mécanismes qu' CProcessLocal effectué et les mêmes techniques d'implémentation.

Voir aussi

Autres ressources

Notes techniques de nombres

Notes techniques de catégorie