MFC in einer DLL
-
Ich schreibe derzeit eine reguläre MFC-DLL, die von meinem Programm geladen wird. Alle Beispiele und Tutorials, die ich dazu auf die Schnelle gefunden habe, gehen davon aus, dass man in der DLL eine CWinApp-Klasse hat. Mein Problem ist aber, dass ich das gerade nicht mache, sondern lediglich eine von CWnd abgeleitete Klasse benutze. Das Problem entsteht dadurch, dass DllMain() bei diesem Typ DLL schon vordefiniert ist, und automatisch InitInstance() der Applikation aufruft. Weil ich aber keine Applikations-Klasse in der DLL habe, wird stattdessen anscheinend nochmal die entsprechende Methode der Klasse des ladenden Programms aufgerufen. Und das ist natürlich falsch, weil da ja zweimal initialisiert wird. Zum Beispiel initialisiere ich da OLE, und sobald ich zur Laufzeit meine DLL lade, gibt es da die Fehlermeldung, dass zweimalige Initialisierung verboten ist.
Jetzt könnte ich zwar am Anfang von InitInstance() nachschauen, ob die Methode schonmal aufgerufen wurde, aber das Problem bleibt eigentlich bestehen. Ich würde die Sache also gerne irgendwie tieferlegend beseitigend, zumal ich vorhabe, noch weitere DLLs dieser Art zu schreiben. Habe ich vielleicht die falsche Art von MFC-DLL für meinen Zweck gewählt? Es gibt ja MFC-Extension-DLLs und reguläre DLLs. Nun habe ich mit ersteren keine besondere Erfahrung, und die reguläre Methode schien mir keine besonderen Nachteile zu haben. Irre ich mich da?
-
Was willst Du nun? Mit der MFC eine Regualr DLL bauen oder nicht?
Ich versehe auch nciht was nun Dein Problem ist.
1. Du kanst DllMain in diesem Fall nicht ersetzen
2. Jedes MFC Projekt (außer Extension DLLs) benötigt ein CWinApp Objekt.
Das heißt auch Deine DLL hat ein CWinApp! Auch in Deiner DLl wird CWinApp::InitInstance aufegrufen:
3. Du kannst CWnd nicht ohne ein threadlokales/modullokales CWinApp Objekt nutzen. Es werden Funktionen im FRamework benutzt, de diese Klassen koppeln.
4. Eine DLL sollte nie AfxOleInit aufrufen, denn eine DLL kan aus jedem Thread-Kontext geladen werden. Nur der Thread-Owner (also die Applikatin) sollte dies tun.
-
Ob ich eine reguläre DLL bauen will oder nicht, das versuche ich gerade herauszufinden. In erster Linie will ich eine funktionierende DLL schreiben, die nur ein CWnd-abgeleitete Klasse enthält, die dann mittels des DLL-Interface vom ladenden Programm instanziert werden kann.
1. Klar, das ist Teil des Problems.
2. Aha, mir war nicht klar, dass ein CWinApp-Objekt in einer DLL unbedingt benötigt ist. Das hatte ich nämlich bis jetzt nirgendwo gelesen. Ich hatte das dann also aus dem erzeugten Standard-Code gelöscht.
3. Gibt es für diesen Fall nicht das Makro AFX_MANAGE_STATE(AfxGetStaticModuleState()), wenn das Framework schon im ladenden Programm läuft?
4. Das ist ein Missverständnis. AfxOleInit() wird in meinem Hauptprogramm in InitInstance() aufgerufen, nicht in der DLL. Es ist halt einfach das Problem, dass beim Laden der DLL diese Methode nochmal aufgerufen wird.
Jetzt heißt es also, dass ich eine Extension-DLL schreiben muss, wenn ich keine Applikations-Klasse in meiner DLL haben will? Dann scheine ich ja die Lösung zu haben. Ich werde mich nochmal in diese Richtung informieren.
Aber mich wundert es ein wenig, wozu eine DLL mit einer ganzen MFC-Applikation darin gut sein soll?
-
Du irrst. CWinApp ist keine ganze Applikation!
CWinApp ist nur ein Wrapper für CWinThread ist in diesem Fall einfach ein Objekt, dass die Daten der aktuellen MFC Instanz (also Deines DLL Modules) hält.
Wieso denkst Du das mit CWinApp "viel" verbunden ist. Das Objekt ist gerade mal ein paar 100 Bytes groß. Jeder Thread in der MFC erzeugt ein äquivalentes CWinThread Objekt.CWinApp::InitInstance ist in diesem Sinne in einer regulären DLL nichts anderes als ein Wrapper für DllMain (an alle Spezialisten: Ich weiß das der Vergleich hinkt und es Unterschiede gibt).
Das Löschen des CWinApp Objektes war ein Fehler

MFC Extension DLLs können nur aus anderen Programmen benutzt werden, die auch MFC Programme sind auch und garantiert die gleiche MFC Version als shared DLL verwenden. Dies ist eine Einschränkung über die Du dir klar sein musst.
HTH
-
Ich habe meine DLL also jetzt als Extension angelegt. Allerdings tritt jetzt beim Laden der DLL ein anderes Problem auf. Es gibt in einer Header-Datei im ausgeführten Programm eine Deklaration einer Instanz meiner CWinApp-Klasse mittels extern. Und in der dazugehörigen cpp-Datei wird diese dann definiert. Aber wenn ich jetzt die DLL lade, dann gibt es einen Fehler im Konstruktor von CWinApp bei ASSERT(AfxGetThread() == NULL). Der Call-Stack sagt aus, dass wir uns in der DLL befinden. Es wird also beim Laden der DLL von dort aus noch einmal meine globale App-Instanz konstruiert. Wie umgehe ich denn dieses Problem jetzt? Eigentlich ich der Header, in dem das Objekt deklariert wird, vom Code meiner DLL aus gar nicht sichtbar. Ich bin also etwas überrascht, dass so etwas passiert.
-
Nein! Eine Extension DLL hat kein CWinApp Objekt. Hast Du das evtl. nicht entfernt?
-
Sorry, da liegt wohl wieder ein Missverständnis vor. Ich habe mein DLL-Projekt mit den entsprechenden Einstellungen komplett neu angelegt, also gibt es da keine Altlasten. Es gibt auch kein CWinApp-Objekt in meiner DLL. Ich sprach von der Instanz der CWinApp-abgeleiteten Klasse in der EXE, die die DLL lädt. Der Fehler tritt beim Laden der DLL auf, weil dieses Objekt erneut konstruiert wird, allerdings aus der DLL heraus (der Call-Stack zeigt den Namen der DLL vor dem Funktionsnamen, nicht den der EXE).
-
Das kann nicht sein. Die Extension DLL erzeugt kein CWinApp Objekt und instanziiert auch kein neues.
Wie sieht der Callstack aus?
Benutzt Du in einem Konstruktor evtl. schon Objekte, die in der EXE noch nicht instanziiert sind?Du kannst nicht gleich ein CWnd Objekt anlegen in einem KOnstruktor einens statischen Objektes, denn InitInstance in der EXE ist evtl. ja nicht abgelaufen...
-
Das Laden der DLLs, bei dem der Absturz erfolgt, wird in der Tat in InitInstance() der EXE gemacht, also bevor InitInstance() fertig ist. Allerdings wird dabei weder ein CWnd-Objekt, noch irgend etwas anderes benutzt, das mit MFC zu tun hat. Außerdem habe ich die Methode zum Testen mal aus InitInstance() herausbewegt, und der Fehler tritt immer noch genau so wie vorher auf.
Der Callstack sieht in Auszügen so aus:
mfc80ud.dll!CWinApp::CWinApp(...) Plugin.dll!CExeApp::CExeApp(...) Plugin.dll!`dynamic initializer for 'theApp''() ... Plugin.dll!_DllMainCRTStartup(...) ... Application.exe!PluginLoader::loadPlugins(...) Application.exe!CExeApp::InitInstance() ...Dabei ist Plugin.dll die DLL, die geladen werden soll. Application.exe ist das Programm, das die DLL laden soll. CExeApp ist die CWinApp-Klasse in meiner EXE, und theApp ist die Instanz dieser Klasse, von der ich schon sprach. Was eben merkwürdig ist, ist dass dieses Objekt (das sich eigentlich in der EXE befindet) aus der DLL nochmal konstruiert wird.
-
Ich entschuldige mich für den Doppelpost. Ich glaube aber, dass ich die Sache vielleicht ein wenig aufklären kann.
Das Problem scheint mir nach einiger Recherche mittlerweile folgendes zu sein: Beim Laden der DLL durch die EXE wird (wie im Call Stack zu erkennen ist) das globale CWinApp-Objekt im EXE-Code aus der DLL heraus noch einmal dynamisch initialisiert. Und so wie ich das sehe ist die Lösung des Problems, das zu verhindern.
Ich kenne allerdings nicht die Ursache für dieses Verhalten. Die DLL sollte mit globalen Objekte der EXE nichts zu tun haben. Ich kann hier nur Vermutungen anstellen und darum bitten, dass jemand mit mehr Wissen vielleicht hilft.
Mir ist allerdings wieder aufgefallen, dass ich meine gesamte Applikation (also die EXE) auch noch als LIB kompliliere und statisch in die DLL linke, weil es sonst zu nicht aufgelösten externen Symbolen beim Kompilieren der DLL kommt. Ich bin mir nicht sicher, ob das die richtige Vorgehenweise ist. Möglicherweise kommt hierbei das globale Objekt der EXE fälschlicherweise in die DLL?
-
Der Callstack zeigt doch eindeutig, dass Du ein Objekt mit dem Namen theApp in Deiner DLL definiert hast.
Was hast Du denn in einem Deiner Header evtl. stehen?
-
Ich habe in keiner Datei, die zur DLL gehört, theApp definiert. Eine Suche im gesamten DLL-Projekt bringt wie erwartet keine Ergebnisse. Wenn ich außerdem im Call Stack zum Ort des "dynamic initializer" springe, dann komme ich zur Definition von theApp in einer CPP-Datei mit Code für die EXE, der eigentlich nie von der DLL aufgerufen werden soll. So wie ich das sehe, gibt es kein theApp in meiner DLL. Ich weiß nicht, wie die DLL an theApp kommt. Das ist nicht beabsichtigt.