AFX_MANAGE_STATE ist jedem bekannt, der mit DLLs hantiert. Es garantiert bei Verwendung der MFC DLLs, dass die entsprechenden Ressourcen bei den verschiedenen Ladeoperationen, wie z.B. CString::LoadString gefunden werden.
Warum ❓
AfxSetResourceHandle und AfxGetResourceHandle bedienen, eine globale Variable, die in der MFCn.DLL liegt.
Genau genommen ist es keine prozessglobale Variable, dies wird klar wenn man an Threads denkt. Die Variable wird threadlokal gespeichert. Die HINSTANCE für AfxGetResourceHandle liegt in einer Struktur, die AFX_MODULE_STATE heißt. In dieser Struktur werden noch einige andere wichtige threadlokale Daten gespeichert. Das sind z.B. für die Interaktion mit der Managed-World, der Activation Context. Einen Blick auf den Inhalt dieser Struktur zu werfen lohnt sich.
Durch diese Struktur AFX_MOUDLE_STATE hat das Makro AFX_MANAGE_STATE seinen Namen.
Gesetzt den Fall wir haben EXE und DLLs (egal ob Standard oder Extensions DLLs), die alle die MFC DLLs dynamisch binden, dann wird AFX_MANAGE_STATE wichtig. Ruft eine EXE also eine Funktion aus einer DLL auf, hat natürlich die EXE das entsprechende Handle an AfxSetResourceHandle übergeben. Damit nun seinerseits die DLL eigene Ressourcen laden kann, wird durch AFX_MANAGE_STATE die alte AFX_MODULE_STATE Struktur gesichert (genau genommen der Zeiger darauf), und ein Zeiger auf die neue aktuelle AFX_MODULE_STATE Struktur gesetzt. CString::LoadString und CDialog::DoModal finden nun die richtigen Ressourcen in der DLL.
Der Destruktor sorgt nun am Ende der Funktion, dass der alte Zeiger der ursprünglichen AFX_MODULE_STATE Struktur zurückgesetzt wird, auf den Wert vor dem Aufruf.
Soweit mal die Theorie 🙂
Was passiert aber nun unter den folgenden Gegebenheiten:
- Eine DLL ruft einen Dialog oder eine MessageBox auf. In der EXE existieren Fenster, die einen Timer gesetzt haben?
- Oder eine DLL ruft einen Dialog auf, und die EXE hat einige COM-Objekte veröffentlicht, die nun von extern angesprochen werden können.
- Die DLL ruft über einen Mechanismus eine Callback Funktion in der EXE auf.
Das Alles ist kein Problem, solange nicht ihrerseits die Funktionen aus der EXE auf die Idee kommen eine Ressource zu laden. Was würde dann passieren?
Klar, die DLL hat mit AFX_MANAGE_STATE den AFX_MODULE_STATE umgesetzt. Würde die EXE in der Callback-Funktion oder Timer-Funktion nun selbst auf die Idee kommen einen Dialog zu laden oder nur einfach CString::LoadString auszuführen, dann wird evtl. ein String oder Dialog geladen, aber vermutlich nicht der, den man erwartet.
Verwendet man nun Formatierungsfunktionen, wie z.B. Format, wird man manchmal böse Wunder erleben. Oder man lädt einen Dialog mit DoModal. Wenn man Glück hat ist der Dialog mit dieser ID nicht da und DoModal schlägt fehl. Wenn man Pech hat wird der Dialog geladen, aber die entsprechenden Controls die gebunden werden oder mit GetDlgItem gesucht werden sind nicht vorhanden. Und da die meisten Entwickler keine Prüfung auf NULL durchführen (z.B. hier bei GetDlgItem(IDC_MYITEM)->EnableWindow(FALSE)) kracht es an den absonderlichsten Stellen.
Auch in der EXE ist man gut beraten in OnTimer Handlern AFX_MANAGE_STATE zu verwenden, wenn der Timer auch Ressourcen verwendet. Gleiches gilt in COM Interfaces oder IDispatch Interfaces. Glücklicherweise sorgen hier die Wizards für korrektes Verhalten.
Mehr noch: Unglücklicherweise kann theoretisch jeder Windows Handler zu diesem Problem führen, wenn diese Fensterfunktion direkt über die modale Nachrichtenschleife aus einer DLL aufgerufen wird.
Und auch bei mancher Funktion, die als Callback aus einer DLL verwendet wird, kann ein zusätzliches AFX_MANAGE_STATE nicht schaden.
Interessanter Beitrag, vielen Dank dafür!
Leider hier und da etwas schwer lesbar wegen „seltsam“ gesetzten Kommata
Schäm…
Ich bin mal korrigierend kurz über diesen Artikel drüber geflogen. Du hast absolut recht.
Ich habe diesen Artikel irgendwie in den Pausen des Product Launches in FFM geschrieben. Ichhätte doch etwas mehr Konzentration aufwenden müssen.
… ich gestehe, dass Rechtschreibung noch nie meine Stärke war.
Hallo Martin.
Ich schreibe gerade eine Anwendung (shared DLL), die sowohl eigene Dialoge (als Ressource eingebunden) verwendet, als auch (über eine übergebene Funktion) indirekt im Thread einen Thread-fremden Dialog beschreibt.
Verwende ich kein „AFX_MANAGE_STATE(AfxGetStaticModuleState( ))“, kann ich die eigenen Resourcen nicht benutzen – soweit ok. Verwende ich aber AFX_MANAGE_STATE, kann ich zwar die eigene Ressource, aber nicht mehr auf die externe Ressource (des Hauptprogramms) zugreifen.
Was ich also unbedingt bräuchte, wäre ein Rückgängigmachen des AFX_MANAGE_STATE (was ja nach beenden „meiner“ Funktion eh geschieht.)
Ich hab schon vieles versucht – und bin im Internet mehrfach auf Beiträge von dir gestossen – deshalb meine Hoffnung, dass du vielleicht helfen könntest..?!
Dankeschön!
Pack AFX_MANAGE_STATE enfach in eine Klammer Block fpr die Bereiche in denen Du auf die DLL Ressourcen zugreifen willst.
Eigentlich initialisiert AFX_MANAGE_STATE nur ein Objekt, dass das neue Ressource Handle setzt und mit dem Destruktor den alten Wert wieder einträgt.
Es spricht nichts dagegen dieses Objekt mehrfach anzulegen.
Es spricht auch nichts dagegen, selbst AfxSetResourceHandle zu verwenden und den originalen Wert wieder zurück zu setzen…
Kleiner Hinweis:
Bei DLLs lautet der komplette Makro: AFX_MANAGE_STATE(AfxGetStaticModuleState());
In der Exe hingegen:
AFX_MANAGE_STATE(AfxGetAppModuleState());