Diese technische Hinweis beschreibt die Implementierung der MFC "Modulzustand" Konstrukte. Ein Verständnis für das Modulimplementierung ist entscheidend für die Laufzeit-DLLs aus einer DLL mit die MFC (oder in-Process-OLE-Server).
Bevor Sie diesen Hinweis lesen, finden Sie zu "Verwalten der Statusdaten von MFC-Modulen" in Anlegen neuer Dokumente, Windows, und Ansichten in der Visual C++ Programmer's Guide. Dieser Artikel enthält wichtige Informationen und Überblick zu diesem Thema.
Übersicht
Es gibt drei Arten von MFC-Zustandsinformationen: Modulstatus, Prozessstatus und Zustand des Threads. Manchmal können diese Statustypen kombiniert werden. MFC Handlezuordnungen sind beispielsweise beide Module lokale und Thread lokale. Auf diese Weise können zwei verschiedene Module zu verschiedene Karten in jedem ihrer threads.
Prozess und Thread-Staat sind ähnlich. Diese Datenelemente sind Dinge, die seit jeher globale Variablen, aber spezifisch für einen bestimmten Prozess oder thread für ordnungsgemäße Win32s unterstützt oder für angemessene Unterstützung von multithreading haben müssen. Welche Kategorie ein bestimmtes Datenelement passt hängt das Element und seine gewünschte Semantik hinsichtlich Prozess- und Thread-Grenzen.
Modulstatus ist einzigartig, da es entweder wirklich globalen Status oder Zustand, lokalen Prozess oder Thread lokale ist, enthalten kann. Auch kann es schnell umgeschaltet werden.
Modulstatus zu wechseln
Jeder Thread enthält einen Zeiger auf den Modulstatus "aktuell" oder "aktiv" (nicht überraschend, der Zeiger ist Teil von MFC Thread lokalen Zustand). Dieser Zeiger wird geändert, wenn der Thread der Ausführung übergibt eine Modul-Grenze, z. B. eine Anwendung in ein OLE-Steuerelement oder ein OLE-Steuerelement aufrufen zurück in eine Anwendung oder DLL aufrufen.
Der aktuellen Modulstatus geschaltet ist, indem Sie AfxSetModuleStateaufrufen. Zum größten Teil, werden Sie nie direkt mit der API beschäftigen. MFC, in vielen Fällen wird es für Sie aufrufen (bei WinMain, OLE-Einstiegspunkte, AfxWndProc, usw.). Dies geschieht in einer beliebigen Komponente, die Sie schreiben, indem die statische Verknüpfung in einem speziellen WndProcund einer speziellen WinMain (oder DllMain) weiß, dass die Modulstatus aktuell sein sollte. Sie können diesen Code sehen, indem Sie einen Blick auf DLLMODUL.CPP oder APPMODUL.CPP im Verzeichnis MFC\SRC.
Es ist selten, dass den Modulstatus festgelegt und dann nicht wieder gesetzt werden sollen. Die meiste Zeit, die Sie möchten "Ihre eigenen Modul push" Zustand wie der aktuelle Enumerator und dann, nachdem Sie fertig sind, "pop" den ursprünglichen Kontext zurück. Dies geschieht durch das AFX_MANAGE_STATE -Makro und die besondere Klasse AFX_MAINTAIN_STATE.
CCmdTarget verfügt über spezielle Features für die Unterstützung von Modul Zustand wechseln. Einen CCmdTarget ist insbesondere, dass die Stammklasse für OLE-Automatisierung und OLE COM Einstiegspunkte verwendet. Wie alle anderen Einstiegspunkt für das System verfügbar gemacht, müssen diese Einstiegspunkte korrekten Modulzustand festlegen. Woher weiß ein bestimmtes CCmdTarget was der "richtige" Modulzustand sein sollte? Die Antwort ist, dass es "merkt", was der "aktuellen" Modulzustand ist, wenn es erstellt wird, sodass sie festlegen kann der aktuellen Modulstatus, "erinnerten" Wert, wenn es später wird aufgerufen. Infolgedessen ist der Modulstatus, dem ein bestimmtes CCmdTarget -Objekt zugeordnet ist den Modulstatus, die aktuell sind, wenn das Objekt erstellt wurde. Nehmen wir ein einfaches Beispiel für einen INPROC-Server laden, erstellen ein Objekt und seine Methoden aufrufen.
Wie Sie sehen können, ist der Modulstatus vom Objekt-Objekt weitergegeben, wie sie erstellt werden. Es ist wichtig, den Modulstatus entsprechend gesetzt haben. Wenn sie nicht festgelegt ist, kann Ihre DLL oder COM-Objekt schlecht mit einer MFC-Anwendung interagieren, die möglicherweise nicht in der Lage, ihre eigenen Ressourcen zu finden ist, nannte es, oder möglicherweise nicht auf andere Weise unglücklich.
Beachten Sie, dass bestimmte Arten von DLLs, speziell "MFC-Erweiterungs-" DLLs nicht den Modulstatus in ihre RawDllMain wechseln (tatsächlich, sie normalerweise haben nicht einmal eine RawDllMain). Dies ist, weil sie Verhalten sich, "als ob" sie in der Anwendung tatsächlich vorhanden waren, die sie verwendet werden sollen. Sie sind sehr viel ein Teil der Anwendung, die ausgeführt wird, und es ist ihre Absicht, diese globalen Anwendungsstatus ändern.
OLE-Steuerelemente und andere DLLs sehr verschieden sind. Sie wollen nicht die aufrufende Anwendung Status ändern; die Anwendung, die sie aufrufen, wird möglicherweise gar keiner MFC-Anwendung und also kann es kein Zustand zu ändern. Dies ist der Grund, dass Modul Zustand umschalten erfunden wurde.
Für exportierte Funktionen aus einer DLL, z. B. eine, die in Ihrer DLL ein Dialogfeld startet, müssen Sie fügen Sie den folgenden Code an den Anfang der Funktion:
AFX_MANAGE_STATE (AfxGetStaticModuleState ())
Diese tauscht den aktuellen Modulstatus mit dem Status von AfxGetStaticModuleState bis zum Ende des aktuellen Bereichs zurückgegeben.
Probleme mit Ressourcen in DLLs werden auftreten, wenn das AFX_MODULE_STATE -Makro nicht verwendet wird. Standardmäßig verwendet MFC das Ressourcenhandle der Hauptanwendung, um die Ressourcenvorlage zu laden. Diese Vorlage ist tatsächlich in der DLL gespeichert. Die Ursache ist, dass MFC Modul Zustandsinformationen nicht durch das AFX_MODULE_STATE -Makro gewechselt wurde. Das Ressourcenhandle wird vom MFC Modulstatus wiederhergestellt. Nicht wechseln den Modulstatus bewirkt, dass das falsche Ressourcenhandle verwendet werden.
AFX_MODULE_STATE muss nicht in jeder Funktion in der DLL abgelegt werden. Z. B. InitInstance von den MFC-Code in der Anwendung ohne AFX_MODULE_STATE aufgerufen werden kann, da MFC automatisch verschiebt den Modulstatus vor InitInstance und dann schaltet es zurück nach InitInstance zurückgibt. Das gleiche gilt für alle Meldungshandler Karte. Reguläre DLLs verfügen tatsächlich eine spezielle master-Fenster-Prozedur, die automatisch den Modulstatus wechselt vor dem Weiterleiten einer Nachricht.
Lokale Daten
Lokale Prozessdaten wäre es nicht für die Schwierigkeit, die Win32s DLL-Modell wurde nicht von so großer Bedeutung. In Win32s teilen alle DLLs ihrer globalen Daten, selbst wenn mehrere Anwendungen geladen. Dies ist völlig anders als die "echten" Win32-DLL-Datenmodell, wo jede DLL Ruft eine separate Kopie der seinen Platz in jedem Prozess für Daten, die an die DLL angefügt wird. Um die Komplexität hinzuzufügen, ist Daten auf den Heap in eine Win32s DLL reserviert in der Tat (zumindest so weit wie Eigenverantwortung geht). Berücksichtigen Sie die folgenden Daten und code:
statische CString StrGlobal; / / im Dateigültigkeitsbereich
__declspec(dllexport) void SetGlobalString(LPCTSTR lpsz)
{
StrGlobal = Lpsz;
}
__declspec(dllexport)
privatevoid GetGlobalString (LPCTSTR Lpsz, Int-Cb)
{
Lstrcpyn (Lpsz, StrGlobal, Cb);
}
Bedenken Sie, was geschieht, wenn die oben genannten Code in einer DLL befindet, und die DLL durch zwei Prozesse A und b geladen wird (es könnte in der Tat, werden zwei Instanzen derselben Anwendung). Eine ruft SetGlobalString("Hello from A") . Infolgedessen wird Arbeitsspeicher für die CString -Daten im Kontext des Prozesses A. halten Sie daran, dass die CString selbst global ist und sichtbar auf beide a ist und b. Jetzt b ruft GetGlobalString(sz, sizeof(sz)) . B werden können um die Daten anzuzeigen, die eine Reihe. Deshalb, weil der Win32s bietet keinen Schutz zwischen Prozessen wie Win32. Also das ist das erste Problem; in vielen Fällen ist es nicht wünschenswert, damit eine Anwendung globale Daten betreffen, die im Besitz einer anderen Anwendungs als.
Aber Moment – gibt es weitere Probleme. Nehmen wir an, dass eine jetzt beendet. Wenn a beendet wird, den verwendete Speicher der ' strGlobal ' Zeichenfolge für das System zur Verfügung gestellt – d. h. von Prozess A alle Arbeitsspeicher vom Betriebssystem automatisch freigegeben ist. Es wird nicht freigegeben, weil der CString -Destruktor aufgerufen wird; Es hat nicht noch aufgerufen. Es ist frei, einfach weil die Anwendung, die Sie die Szene verlassen hat. Nun, wenn b genannt GetGlobalString(sz, sizeof(sz)) , es kann nicht gültige Daten erhalten. Eine andere Anwendung kann, dass der Speicher für etwas anderes verwendet haben.
Klar gibt es hier ein Problem. MFC 3.x verwendet eine Technik namens lokalen Threadspeicher (TLS). MFC 3.x würde einen TLS-Index, der unter Win32s wirklich als lokalen Prozess-Speicher-Index fungiert reservieren, obwohl es heißt nicht, und dann alle Daten auf der Grundlage, dass TLS-Index verweisen würde. Dies ist vergleichbar mit der TLS-Index, der zum Speichern von lokalen Thread-Daten auf Win32 verwendet wurde (siehe unten für weitere Informationen zu diesem Thema). Dies verursacht jede MFC-DLL, mindestens zwei TLS Indizes pro Prozess zu nutzen. Wenn Sie viele OLE Control DLLs geladen (OCXs) entfallen, laufen Sie schnell von TLS Indizes (es gibt nur 64 erhältlich). Darüber hinaus hatte MFC, alle diese Daten an einem Ort, in einer einzigen Struktur zu platzieren. Es war nicht sehr erweiterbar und war nicht ideal im Zusammenhang mit der Verwendung von TLS-Indizes.
MFC 4.x wird dies mit einem Satz von Class-Vorlagen Sie können "wrap" um die Daten, die lokalen Prozess sein sollte. Beispielsweise könnte das oben erwähnte Problem schriftlich festgesetzt:
struct CMyGlobalData: öffentliche CNoTrackObject
{
CString StrGlobal;
};
CProcessLocallt;CMyGlobalData > GlobalData;
__declspec(dllexport) void SetGlobalString(LPCTSTR lpsz)
{
GlobalData - > StrGlobal = Lpsz;
}
__declspec(dllexport)
privatevoid GetGlobalString (LPCTSTR Lpsz, Int-Cb)
{
Lstrcpyn (Lpsz, GlobalData - > StrGlobal, Cb);
}
MFC implementiert dies in zwei Schritten. Einerseits gibt es eine Schicht über die Win32 Tls * APIs (TlsAlloc, Tls&GetValue, TlsSetValue, etc.), die nur zwei TLS Indizes pro Prozess, unabhängig davon, wie viele DLLs verwenden, die Sie haben. Zweitens, die CProcessLocal Vorlage bereitgestellt wird, um auf diese Daten zugreifen. Es überschreibt Operator-Gt; Das ist, was ermöglicht die intuitive Syntax, die, der Sie oben sehen. Alle Objekte, die vom eingebunden sind CProcessLocal von abgeleitet werden CNoTrackObject . CNoTrackObject bietet eine Low-Level-Zuweisung (LocalAlloc/LocalFree) und einen virtuellen Destruktor, dass MFC automatisch der Prozess lokale Objekte zerstören kann, wenn der Prozess beendet ist. Solche Objekte können einen benutzerdefinierten Destruktor haben, wenn eine zusätzliche Bereinigung erforderlich ist. Das obige Beispiel erforderlich nicht, ein, da der Compiler einen Standard-Destruktor generiert, das eingebettete CString -Objekt zu zerstören.
Es gibt andere interessante Vorteile dieses Ansatzes. Sind nicht nur alle CProcessLocal Objekte automatisch zerstört, sie sind nicht gebaut, bis diese benötigt werden. CProcessLocal::operator-gt; wird instanziiert das Objekt zugeordneten zum ersten Mal es heißt, und nicht früher. Im obigen Beispiel, das bedeutet, dass die ' str&Global ' Zeichenfolge wird nicht erstellt werden, bis das erste Mal SetGlobalString oder GetGlobalString aufgerufen. In einigen Fällen kann dies verringern DLL-Startzeit.
Lokalen Thread-Daten
Ähnlich wie bei lokale Daten zu verarbeiten, werden Thread lokale Daten verwendet, wenn die Daten für einen bestimmten Thread lokal sein müssen. Das heißt, benötigen Sie eine separate Instanz der Daten für jeden Thread, der auf die Daten zugreift. Dies kann oft anstelle von umfangreichen Synchronisierungsmechanismen verwendet werden. Wenn die Daten nicht von mehreren Threads gemeinsam genutzt werden müssen, kann solche Mechanismen teuer und unnötig. Genommen Sie an, wir hatten ein CString -Objekt (ähnlich wie im Beispiel oben). Wir machen es thread lokale durch Wrappen es mit einer CThreadLocal Vorlage:
struct CMyThreadData: öffentliche CNoTrackObject
{
CString StrThread;
};
CThreadLocallt;CMyThreadData > ThreadData;
void MakeRandomString()
{
/ / eine Art Karte shuffle (kein großer ein)
CString & str = ThreadData - > StrThread;
Str.Empty();
während (str.GetLength()! = 52)
{
TCHAR ch = rand() % 52 + 1;
Wenn (str.Find(CH) < 0)
Str += ch; / / nicht gefunden, es hinzufügen
}
}
Wenn MakeRandomString wurde von zwei verschiedenen Threads, jeder würde "shuffle" die Zeichenfolge auf unterschiedliche Weise ohne zu stören die anderen. Dies ist weil es ist eigentlich ein strThread Instanz pro Thread statt nur eine globale Instanz.
Beachten Sie, wie ein Verweis verwendet wird, um die CStrin&g -Adresse einmal statt einmal pro Schleifeniteration der zu erfassen. Der Schleife Code hätte mit geschrieben threadData-gt;strThread überall ' str ' wird verwendet, aber der Code wäre viel langsamer in der Ausführung. Es empfiehlt sich, einen Verweis auf die Daten zwischengespeichert werden, wenn solche Verweise in Schleifen auftreten.
Die CThreadLocal Vorlage-Klasse verwendet die gleichen Mechanismen, die CProcessLocal bedeutet und die gleiche Implementierungstechniken.
Technische Hinweise von &Nummer |nbsp; Technische Hinweise nach Kategorie