Freigeben über


TN058: MFC-Modulzustands-Implementierung

HinweisHinweis

Im Folgenden technischen Hinweis ist nicht aktualisiert wurde, seitdem er erstmals in der Onlinedokumentation enthalten waren.Folglich können mehrere Prozeduren und Themen veraltet oder falsch.Die aktuellsten Informationen wird empfohlen, zum Thema Onlinedokumentations im Index finden.

In diesem technischen Hinweis beschreibt die Implementierung von MFC- "Modulzustands" - Konstrukten.Ein Verständnis der Implementierung Modulzustands ist für die Verwendung der MFC-gemeinsamen genutzten DLLs aus einem prozessinternen wichtig Server DLL (oder OLE).

Bevor Sie diesen Hinweis lesen finden Sie unter "Verwalten von Zustandsdaten von MFC-Modulen" in Die neue Dokumente, Windows und Ansichten erstellen an.Dieser Artikel enthält wichtige Verwendungsinformationen und Übersichtsinformationen zu diesem Thema.

Übersicht

Es gibt drei Arten MFC-Zustandsinformationen: Modulzustand, Prozess-Zustand und Thread-Zustand.Manchmal können diese Typen Zustände zusammengefasst werden.Beispielsweise sind Handle von Namespacezuordnungen MFC Modul lokale Variablen und Threads lokale Variable.Dadurch können zwei unterschiedlichen Modulen, um verschiedene Zuordnungen in einem ihrer Threads verfügen.

Prozesszustand und Thread-Zustand sind ähnlich.Diese Datenelemente sind die Punkte traditionsgemäß globale Variablen haben, verfügen jedoch erforderlich, auf einen bestimmten Prozess oder einen Thread für eine ordnungsgemäße Win32s-Unterstützung Multithreadingunterstützung für eine ordnungsgemäße oder festgelegt werden soll.Welche Kategorie ein bestimmtes Datenelement in passt, hängt von diesem Element und die gewünschte Semantik hinsichtlich der Prozess- und Threads hinweg ab.

Modulzustand ist insofern einmalig, dass es sich um globale Zustände enthalten oder angeben kann, der die lokale Variable oder Thread lokale Variable ist.Außerdem kann er schnell umgeschaltet werden.

Modulzustands-umschalten

Jeder Thread enthält einen Zeiger auf das "aktuellen Modulzustand" Aktiv" oder "(erwartungsgemäß, ist der Zeiger Teil von lokalem Zustand des Threads MFC).Dieser Zeiger wird, wenn der Ausführungsthread eine gebundene Modul in eine Anwendung führt ein Aufruf von OLE-Steuerelement oder DLL-Datei geändert oder in ein OLE-Steuerelement Aufruf zurück in eine Anwendung.

Der aktuellen Modulzustand wird umgeschaltet, indem AfxSetModuleState aufruft.In den meisten Fällen arbeiten Sie nie direkt die API.MFC in vielen Fällen ruft es für Sie an, OLE-Einstiegspunkten WinMain (ON AfxWndProc an, an usw.).Dies ist in jeder Komponente, die Sie schreiben, indem Sie statisch in speziellen WndProc verknüpfen und spezielles WinMain ausgeführt (oder die) DllMain weiß, welcher Modulzustand aktuell sein soll.Sie können diesen Code überprüfen, indem Sie DLLMODUL.CPP oder APPMODUL.CPP in MFC- \ SRC-Verzeichnis berücksichtigen.

Es empfiehlt sich, dass Sie den Modulzustand festlegen und dann wieder festlegen möchten.In den meisten Fällen möchten Sie einen eigenen Modulzustand als aktuelles "und dann" drücken, nachdem Sie fertig sind, "bringen" die ursprüngliche Kontext wieder.Dies ist von Makro- AFX_MANAGE_STATE und die spezielle Klasse AFX_MAINTAIN_STATE durchgeführt.

CCmdTarget verfügt über spezielle Funktionen zum Sichern des Modulzustands umschaltens.Insbesondere ist CCmdTarget die Stammklasse für die OLE-Automatisierung und Einstiegspunkte OLE COM verwendet wird.Wie jeder andere Einstiegspunkt, der für das System verfügbar gemacht wird, müssen diese Einstiegspunkte den korrekten Modulzustand festlegen.Wie kennt angegebenes CCmdTarget, was der "richtige" Modulzustand werden soll?Die Antwort ist, dass sie sich "erinnert" welcher "aktuelle" Modulzustand, wenn es erstellt wird. Daher besteht darin, dass es den aktuellen Modulzustand auf den Wert" erinnerter "festlegen kann, wenn es später aufgerufen wird.Daher ist der Modulzustand, ob ein angegebenes CCmdTarget-Objekt zugeordnet ist, der Modulzustand, der aktuell war, als das Objekt erstellt wurde.Nehmen Sie ein einfaches Beispiel für das Laden eines INPROC-Servers, des Erstellens eines Objekts und des Aufrufs der Methoden.

  1. Die DLL wird von OLE mit LoadLibrary geladen.

  2. RawDllMain wird zuerst aufgerufen.Er legt den Modulzustand dem bekannten statischen Modulzustand für die DLL fest.Aus diesem Grund wird RawDllMain statisch mit der DLL verknüpft.

  3. Der Konstruktor für die Klassenfactory, die mit unserem Objekt zugeordnete aufgerufen wird.COleObjectFactory ist von CCmdTarget abgeleitet und infolgedessen, speichert es in der Modulzustand er instanziiert wurde.Dies ist wichtig, wenn die Klassenfactory aufgefordert wird, Objekte zu erstellen, weiß er jetzt der aktuellen Modulzustand, um zu machen.

  4. DllGetClassObject wird aufgerufen, um die Klassenfactory zu erhalten.MFC sucht in der Liste Klassenfactory diesem Modul zugeordnet ist, und gibt sie zurück.

  5. COleObjectFactory::XClassFactory2::CreateInstance aufgerufen wird.Vor dem das Objekt erstellt und zurückgibt, wird diese Funktion den Modulzustand um Modulzustand fest, der in Schritt 3 (der aktuelle Datensatz war der aktuelle Datensatz war, als COleObjectFactory instanziiert wurde).Dies ist in der METHOD_PROLOGUE erfolgt.

  6. Wenn das Objekt erstellt wird, ist es außerdem eine Ableitung CCmdTarget auf und führt COleObjectFactory, das gespeichert wird, aktiv war, Modulzustand, die so das neue Objekt.Nun weiß das Objekt, welcher Umschalten Modulzustand, wenn sie aufgerufen wird.

  7. Der Client ruft eine Funktion auf dem OLE-COM-Objekt an, das vom CoCreateInstance Aufruf empfangen hat.Wenn das Objekt aufgerufen wird, verwendet er METHOD_PROLOGUE, um den Modulzustand wechseln, ebenso wie COleObjectFactory ausführt.

Wie Sie sehen, wird der Modulzustand von Objekt zu Objekt weitergegeben, wenn sie erstellt wurden.Es ist wichtig, den Modulzustand festlegen, geeignet.Wenn es nicht festgelegt wurde, schlagen möglicherweise die DLL möglicherweise schlecht oder COM-Objekt die Interaktion mit einer MFC-Anwendung, die das aufrufende oder ist möglicherweise nicht in der Lage, die eigenen Ressourcen zu suchen oder auf andere Weise elende aus.

Beachten Sie, dass bestimmte Arten von DLLs ", insbesondere MFC-Erweiterung" Modulzustand die DLL nicht in ihrem RawDllMain wechseln (tatsächlich haben sie normalerweise nicht einmal RawDllMain).Der Grund hierfür ist, dass sie bestimmt sind sich entsprechend verhalten "als" ob sie in der Anwendung vorhanden waren, die sie verwendet.Sie sind sehr viel Teil der Anwendung, der ausgeführt wird und deren Zweck ist zu ändernden dass der globalen Zustand der Anwendung.

OLE-Steuerelemente und andere DLL ist sehr unterschiedlich.Sie möchten nicht den aufrufenden Zustand der Anwendung ändern. die aufrufende Anwendung, die sie kann, liegt dies unter Umständen nicht einmal eine MFC-Anwendung und sodass möglicherweise kein Zustand zu ändern.Dies ist der Grund, dass das Modulzustands wechseln frei erfunden wurde.

Eine exportierte Funktionen aus einer DLL (z. B. ein, das ein Dialogfeld in der DLL startet, müssen Sie folgenden Code am Anfang der Funktion hinzugefügt werden:

AFX_MANAGE_STATE(AfxGetStaticModuleState( ))

Dieses lagert den Modulzustand mit dem Zustand, der von AfxGetStaticModuleState bis zum Ende des aktuellen Bereichs zurückgegeben wurde.

Probleme mit Ressourcen in DLLs treten auf, wenn das AFX_MODULE_STATE Makro nicht verwendet wird.Standardmäßig verwendet MFC das Ressourcenhandle der Hauptanwendung, die Ressourcenvorlage zu laden.Diese Vorlage wird eigentlich in der DLL gespeichert.Der Grund liegt darin, dass Informationen Modulzustands MFC nicht über das Makro AFX_MODULE_STATE beendet wurden.Das Ressourcenhandle wird von MFC Modulzustand wiederhergestellt.Bewirkt, dass das Umschalten des nicht Modulzustandes das falsche Ressourcenhandle vorgesehen.

AFX_MODULE_STATE muss nicht in jede Funktion in der DLL abgelegt werden soll.Beispielsweise kann InitInstance durch den MFC-Code in der Anwendung ohne AFX_MODULE_STATE aufgerufen werden, da MFC automatisch den Modulzustand vor InitInstance Schaltern und anschließend wieder richtet sich nach InitInstance zurückkehrt.Dies gilt auch für alle Meldungszuordnungs Klassenhandler true.Reguläre DLL verfügt tatsächlich eine besondere Vorlagen fensterprozedur, die automatisch den Modulzustand wechseln, bevor eine Nachricht weiterleitet.

Lokale ProzeßBezugspunkte

Lokale ProzeßBezugspunkte werden nicht von solche umfangreichen Sorge ist es nicht für die Schwierigkeiten des Modells DLL Win32 worden sein.In Win32 gibt alle DLLs ihrer globalen Daten geladen, auch wenn gemeinsam von mehreren Anwendungen.Dies entspricht dem "echten" Win32 Datenmodell DLL sehr unterschiedlich, wobei jedes DLL eine separate Kopie des Datenbereichs in einem Prozess abruft, der dem DLL angefügt werden.Um Komplexität hinzufügen, sind die Daten, die in einem auf dem Heap der DLL verknüpft sind besondere Prozess der Tat insoweit Besitz geht (mindestens).Berücksichtigen Sie die folgenden Daten und den folgenden 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);
}

Berücksichtigen Sie, was geschieht, wenn der obige Code befindet sich in einer DLL, und diese DLL durch zwei Prozesse A und B geladen wird (es könnte zwei Instanzen der gleichen Anwendung tatsächlich sein).Aufrufe SetGlobalString("Hello from A").Folglich wird Arbeitsspeicher für die CString Daten im Rahmen des Prozesses A verknüpft.Beachten Sie, dass CString auch global ist und A und B sichtbar ist.Nun B ruft GetGlobalString(sz, sizeof(sz)).B besteht darin, die Daten zu finden, die A festgelegt.Dies liegt daran, dass keinen Schutz von Win32 Funktion zwischen Prozessen wie Win32 bietet.Das ist das erste Problem. In vielen Fällen ist es nicht wünschenswert, die globalen eine Anwendung mit Daten affekten, das als Besitzer von einer anderen Anwendung betrachtet wird.

Es gibt weitere Probleme.Sehen Sie sich sagen, dass jetzt A beendet wird.Wenn a-Beendigungen, der Arbeitsspeicher, der von der strGlobal" String "wird für das System, also aller Arbeitsspeicher bereitgestellt wird, der durch zugeordnet ist, verarbeiten Sie A wird vom Betriebssystem automatisch freigegeben.Es wird erst freigegeben, da der CString Destruktor aufgerufen wird. Es wurde noch nicht aufgerufen.Es wird einfach freigegeben, da die Anwendung, die sie zugeordnet, die Szene verlassen hat.Jetzt, wenn B GetGlobalString(sz, sizeof(sz)) aufgerufen hat, ruft es möglicherweise keine gültige Daten ab.Beliebige andere Anwendung verwendet möglicherweise etwas Anderes für diesen Speicher.

Offenbar vorhanden ist.MFC 3.x verwendet eine Technik, die lokalen Threadspeicher (TLS) aufgerufen wurde.MFC 3.x würde einen TLS-Index zuordnen, als tatsächlich mit der Win32 Variable-Speicher PROCESS ein Index auftritt, obwohl er wird nicht aufgerufen, und der würde dann alle Daten auf der Grundlage dieses TLS-Index verweisen.Dies entspricht dem TLS-Index, der verwendet wurde, um threadlokale Daten auf Win32 zu speichern (siehe unten weitere Informationen zu diesem Thema.)Dies verursacht hat jede MFC-DLLs mindestens zwei TLS-Indizes pro Prozess verwendet werden soll.Wenn Sie das Laden viel OLE-Steuerelement OCX (DLL) erläutert wird, haben Sie schnell mehr (es gibt kein TLS-Indizes nur 64 verfügbar).Außerdem musste MFC alle Daten platziert diese in einem Abstand in einer einzigen Struktur.Es war nicht sehr erweiterbar und was not hinsichtlich ihres Verwendung von TLS-Indizes geeignet.

MFC 4.x behandelt dies mit einem Satz von Klassenvorlagen, die Sie einschließen können die Daten "Prozess" die lokale Variable sein sollen.Zum Beispiel könnte das Problem, das oben erwähnt wurde behoben werden, indem geschrieben wurde:

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 implementiert dieses in zwei Schritten.Anfänglich befindet sich eine Ebene auf Win32 Tls* API (TlsAlloc, TlsSetValue, TlsGetValue usw.), die nur zwei TLS-Indizes pro Prozess verwenden, unabhängig davon, wie viele DLLs Sie hat.Zweitens wird die CProcessLocal Vorlage bereitgestellt, um diese Daten zuzugreifen.Überschreiben Sie operator->, der die intuitive Syntax ermöglicht, die Sie oben angezeigt wird.Alle Objekte, die von CProcessLocal umschlossen werden, müssen von CNoTrackObject abgeleitet werden.CNoTrackObject stellt eine Belegungsfunktion auf niedrigerer Ebene (LocalAlloc/LocalFree) und einen virtuellen Destruktor so, dass MFC die lokalen ProzeßObjekte automatisch zerstören kann, wenn der Prozess beendet wird.Solche Objekte können ein benutzerdefinierter Destruktor verfügen, wenn zusätzlicher Bereinigung erforderlich ist.Das obige Beispiel erfordert nicht, da der Compiler einen Destruktor generiert, um das eingebettete CString-Objekt zu zerstören.

Es gibt weitere interessante Vorteile dieses Ansatzes.Nicht alle CProcessLocal werden nur Objekte automatisch zerstört, werden sie erst dann erstellt, wenn sie benötigt werden.CProcessLocal::operator-> instanziiert das zugeordnete Objekt beim ersten Aufrufen und kein.Im Beispiel oben, dies bedeutet, dass die Zeichenfolge" "strGlobal nicht bis zum ersten Mal erstellt wird, oder SetGlobalStringGetGlobalString aufgerufen wird.In einigen Fällen kann es helfen, DLL-Start Zeit zu verringern.

Daten der Thread-lokalen Variable

Ähnlich wie den lokalen ProzeßBezugspunkten, lokale Daten des Threads wird verwendet, wenn die Daten in einen angegebenen Thread lokal sein müssen.Das heißt, benötigen Sie eine separate Instanz der Daten für jeden Thread, der diese Daten zugreift.Dies kann viele Male anstelle von umfangreichen Synchronisierungsmechanismen verwendet werden.Wenn die Daten nicht von mehreren Threads gemeinsam genutzt werden, können solche Mechanismen aufwändig und nicht erforderlich sein.Angenommen, wird ein CString-Objekt hätten (ähnlich wie das Beispiel oben).Es können jedoch Threads lokale Variable machen, indem wir sie mit einer CThreadLocal Vorlage einschließen:

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
      }
   }
}

Wenn MakeRandomString von zwei verschiedenen Threads aufgerufen wurde, wird entweder die Zeichenfolge "auf verschiedene Arten mischen" ohne das andere zu beeinflussen.Dies liegt daran, dass es sich um eine strThread-Instanz pro Thread nicht nur einer globalen Instanz vorhanden ist.

Anstatt Beachten Sie, wie ein Verweis wird verwendet, um einmal die Ablaufverfolgung CString Adresse einmal pro Schleifeniteration.Der Code mit Schleifen kann überall threadData->strThread "str" geschrieben worden sein, jedoch wird der Code wird in der Ausführung viel langsamer sein.Es empfiehlt sich, einen Verweis auf die Daten zwischenzuspeichern, wenn diese Verweise in Schleifen auftreten.

Die CThreadLocal-Klassenvorlage verwendet die gleichen Mechanismen, dass CProcessLocal und dieselben Implementierungen techniken.

Siehe auch

Weitere Ressourcen

Technische Hinweise durch Zahl

Technische Hinweise nach Kategorie