Local Heap, Threads, DLLs und eine Queue-Klasse
-
Hallo,
ich habe eine MFC-MDI-Anwendung, die eine Klasse aus einer DLL instanziert. Innerhalb dieser Klasse wiederum wird eine selbst geschriebene Queue instanziert. Diese Queue an sich ist umfassend getestet und funktioniert auch - bei Verwendung ausschließlich innerhalb der DLL.
Jetzt würde ich gerne per Zeiger auf diese Queue zugreifen und ein Objekt hineinstellen, das in der Anwendung (in CMainFrame) erzeugt wurde. Dass ich dieses Objekt nicht innerhalb der DLL freen darf, weiß ich, weil es außerhalb erzeugt wurde. Daher gebe ich es später an die Anwendung zurück und free es dort.
Das Problem bereitet indes die Queue selbst: Beim einhängen des neuen Objektes wird in der Queue mit malloc oder mit new (beides probiert) ein "Pointerset" erstellt (void * Ante, void * Post, void * Obj). Hier die beiden Funktionen:
void queue::PutIn(void * obj) { // Speicher allozieren workPos = new pointerset; // Neue Objekte in der Queue haben NIE einen "Vorgänger" workPos->ante = NULL; // Nachfolger des Objektes ist das vor dem einfügen erste Objekt in der Queue; // wenn es das einzige Element ist, steht in inPos noch NULL; diese Angabe // ist dann auch korrekt. workPos->post = inPos; // Das zuvor erste Objekt in der Queue rückt jetzt eins weiter; das jetzt ein- // gefügte wird daher dessen Vorgänger // Hier muss abgefragt werden, ob es zuvor überhaupt ein Objekt gab: if(inPos != NULL) inPos->ante = workPos; // Objekt anhängen workPos->object = obj; // inPos aktualisieren inPos = workPos; if (outPos == NULL) outPos = workPos; NrOfObjectsInQueue++; } void * queue::TakeFrom() { // Arbeitsposition setzen: Ausgang der Queue if(!(workPos = (pointerset*)(outPos))) return NULL; // temporären Zeiger für Rückgabe erstellen und belegen void * temp = workPos->object; // Sofern das gerade zu entfernende Objekt einen Vorgänger hat, rückt // dieser an die Position outPos nach: if(workPos->ante != NULL) outPos = workPos->ante; // Wenn es sich um das letzte Objekt handelt, müssen inPos und outPos jetzt // auf NULL gesetzt werden if(workPos->ante == NULL) { inPos = NULL; outPos = NULL; } // Eintrag aus Queue entfernen, Speicher freigeben delete(workPos); workPos = NULL; // Bilanz korrigieren NrOfObjectsInQueue--; // Objekt zurückgeben return temp; }
Wenn ich nun die Funktion PutIn() von CMainFrame aus aufrufe, die Funktion TakeFrom() aber später innerhalb der DLL, dann bekomme ich einen Crash, der vom free(Pointerset) verursacht wird, in der Datei dbgheap.c an folgender Stelle:
#else /* WINHEAP */ /* * Go through the heap regions and see if the pointer lies within one * of the regions of the local heap. * * Pointers from non-local heaps cannot be handled. For example, a * non-local pointer may come from a DLL that has the CRT linked-in. * */ for (i = 0; (base = _heap_regions[i]._regbase) != NULL && i < _HEAP_REGIONMAX; i++) { if (pUserData >= base && pUserData < (void *)(((char *)base)+_heap_regions[i]._currsize)) return TRUE; } return FALSE; #endif /* WINHEAP */ }
Das ist mir nicht ganz klar, denn ich habe ja nur per Pointer auf die Queue zugegriffen. Sehe ich es richtig, dass diese, weil sie in einem anderen Thread lief (CMainFrame), beim allokieren des Speichers für das Pointerset den dortigen Heap verwendet hat??
Was kann ich dagegen tun?
Ciao, Gerry
-
Der Crash deutet darauf hin, dass der Speicher von einer anderen CRT allokiert wurde als er jetzt freigegeben wurde.
Entwder musst Du strikt darauf achten, dass dies nicht vorkommen kann (wo ich aber gerade keinen Fehler in Deinem Code feststellen kann; unter der Annahme, dass die beiden Funtkionen in der DLL implementiert sind und importiert wurden!) oder du verwendest in der EXE und der DLL die gleiche shared CRT; dann gibt es auch keine Probleme.
-
Hallo Jochen,
herzlichen Dank für den Tip. Die Projektmappe besteht aus verschiedenen Einzelprojekten und bei diesen waren unterschiedliche Versionen der Laufzeitbibliothek eingestellt. Nachdem ich sie jetzt alle auf 'Multithreaded-DLL-Debug' gestellt habe, tritt der Fehler nicht mehr auf. Leider/glücklicherweise
förderte das einige viele MemoryLeaks zutage, die in Programmteilen versteckt waren, welche keine Debug-Version der CRT benutzten und mir deshalb bisher nicht angezeigt wurden. Nachdem die auch alle geschlossen sind läuft's jetzt bestens.
anderen CRT allokiert wurde als er jetzt freigegeben wurde.
Entwder musst Du strikt darauf achten, dass dies nicht vorkommen kannWie würde denn ein solcher Fehler aussehen? Mit malloc allokiert und mit delete freigegeben??
Die Funktionen sind beide in der DLL implementiert; mit dem Export bin ich mir nicht so ganz sicher..:
Ich exportiere die Klasse DevComm aus der DLL:// Diese Klasse wird aus DevComm.dll exportiert class DEVCOMM_API CDevComm { public: CDevComm(settings *); ~CDevComm(void); void Trigger(drp*); drp * GetProcessedDRP(void); //... weitere Funktionen und Variablen ...// public: queue * TestQueue }; extern DEVCOMM_API int nDevComm; DEVCOMM_API int fnDevComm(void);
Dabei tauchen laut DumpBin /Exports aber nur die Funktionen auf, die TestQueue wird nicht als Namen exportiert. Ich kann wohl deshalb darauf zugreifen, weil ich den Header ja in der Anwendung einbinde. Ist das falsch so?
Ciao, Gerry
-
Gerry schrieb:
Wie würde denn ein solcher Fehler aussehen? Mit malloc allokiert und mit delete freigegeben??
Nein: Mit malloc oder new in der DLL-1 allokiert und mit delete / free in der DLL-2 (oder EXE) freigegeben... (natürlich nur wenn die DLL / EXE nterschiedliche CRTs verwenden (z.B. eine statische oder mit anderen CRT-gelinket wurden (z.B. VC71 und VC8)))
Gerry schrieb:
Die Funktionen sind beide in der DLL implementiert; mit dem Export bin ich mir nicht so ganz sicher..:
Ich exportiere die Klasse DevComm aus der DLL: [...]Eigentlich müsste es dann passen... hmmm...
Gerry schrieb:
Dabei tauchen laut DumpBin /Exports aber nur die Funktionen auf, die TestQueue wird nicht als Namen exportiert.
Sehr verdächtig... Hast Du die Klasse inline-implementiert?
-
Wenn Du mit "Klasse inline implementiert" den folgenden Aufbau meinst:
class Account { public: Account(double initial_balance) { balance = initial_balance; } double GetBalance(); //...// }; inline double Account::GetBalance() { return balance; }
- nein. Ohne Inline in einer .cpp - ganz "normal".
Ich bin vorhin drüber gestolpert, dass eben nur die _Funktionen_ als Namenssymbole exportiert werden, nicht aber die Variablen, welche als public im header deklariert sind, und als solche ist doch auch die Referenz auf eine Instanz der Klasse queue zu sehen, die im Konstruktor von DevComm schließlich erzeugt wird, oder?
Jetzt habe ich noch folgendes probiert: Ich habe in der Header-Datei der queue-Klasse ebenfalls den Export für DevCommAPI eingebaut:Allerdings spuckt er mir dann eine Reihe
[Linker Tools Warning LNK4049] Exportiertes Symbol "Symbol" wurde auch importiert Das Symbol wurde sowohl aus dem Programm exportiert als auch in das Programm importiert.
und der DumpBin sieht folgendermaßen aus:
Dump of file devcomm.dll File Type: DLL Section contains the following exports for DevComm.dll 00000000 characteristics 43783B88 time date stamp Mon Nov 14 08:23:52 2005 0.00 version 1 ordinal base 33 number of functions 33 number of names ordinal hint RVA name 1 0 000110FF ??0CDevComm@@QAE@PAVsettings@@@Z 2 1 000110B9 ??0queue@@QAE@PAX@Z 3 2 00011479 ??0queue@@QAE@XZ 4 3 00011195 ??1CDevComm@@QAE@XZ 5 4 0001111D ??1queue@@QAE@XZ 6 5 00011091 ??4CDevComm@@QAEAAV0@ABV0@@Z 7 6 00011023 ??4queue@@QAEAAV0@ABV0@@Z 8 7 00011104 ?DispatchADC@CDevComm@@AAE?AW4adcresult@@PAVdrp@@H@Z ... weitere Funktionen der Klasse DevComm... und dann die Funktionen meiner Queue-Klasse: 23 16 0001105F ?PushBack@queue@@QAEXPAX@Z 24 17 0001115E ?PutIn@queue@@QAEXPAX@Z 29 1C 000111D6 ?TakeFrom@queue@@QAEPAXXZ
Aber kein Hinweis auf die Instanz 'TestQueue' der Klasse queue.
Es funktioniert ja jetzt auch ohne den Export der queue-Klasse; ich möchte mir nur keine dirty hacks als Programmierstil aneignen
-
Ist die "queue" ein template??? Wo ist denn die "queue" implementiert? In der DLL? Lt. dumpbin wird sie ja korrekt exportiert, oder? Die Member-Variable "TestQueue" wird nicht exportiert, da diese ja zur Klasse gehört und nicht statisch ist (so wie die Funktionen) und somit mit einem "new" angelegt wird.
-
und nicht statisch ist (so wie die Funktionen)
Achso! Ja klar, leuchtet ein...
Ist die "queue" ein template??? Wo ist denn die "queue" implementiert?
Ne, die Queue ist ein eigenes Projekt in derselben Projektmappe und der Header ist included, sowohl in der DLL, als auch im Projekt, weil sie auch dort verwendet wird (unabhängig von der DLL). Sie ist kein Template.
-
Dan müssen sich aber die Queue-DLL und die CDevComm-DLL die selbe CRT teilen! SOnst geht das net...
-
Neinein, Missverständnis: Die queue ist keine eigene DLL. Sie ist im Header der DevComm included und wird mit in die DevComm-DLL kompiliert.
Gleiches gilt für die Anwendung.
Die gleiche CRT teilen sie sich aber doch, wenn ich das unter Compileroptionen->Laufzeitbib so einstelle.Wie gesagt, es funktioniert ja jetzt auch, ich hab nur verständnishalber nachgefragt, weil Du ja in Deinem ersten Posting geschrieben hattest
unter der Annahme, dass die beiden Funtkionen in der DLL implementiert sind und importiert wurden!)
und mir als ich diese DLL damals schrieb offengestanden nicht so recht klar war, was genau ich tat. Ich hab mich an die Anleitung in VC++6 in 21 Tagen gehalten und war froh, als es funktionierte..
Sehe ich das jetzt so richtig:
* In allen Projekten einer Projektmappe müssen stets dieselben Laufzeitbibs verwendet werden, zumindest dann, wenn Speicher woanders gefreet werden soll als er erzeugt wurde.
* Als Namenssymbole exportiert werden nur statische Elemente, aber keine Instanzen, die erst zur Laufzeit erzeugt werden.
* "Richtiger" wäre es, die Queue als eigene DLL bzw. als eigene exportierte Klasse ist einer "Tools.dll" zu schreiben und sowohl von DevComm.dll als auch von der Anwendung aus darauf zuzugreifen, statt sie in die einzelnen Projekte "hineinzukompilieren".
-
Gerry schrieb:
* In allen Projekten einer Projektmappe müssen stets dieselben Laufzeitbibs verwendet werden, zumindest dann, wenn Speicher woanders gefreet werden soll als er erzeugt wurde.
Genauer gesagt nicht nur die gleichen sondern auch noch die "shared CRT"!
Gerry schrieb:
* Als Namenssymbole exportiert werden nur statische Elemente, aber keine Instanzen, die erst zur Laufzeit erzeugt werden.
Ja.
Gerry schrieb:
* "Richtiger" wäre es, die Queue als eigene DLL bzw. als eigene exportierte Klasse ist einer "Tools.dll" zu schreiben und sowohl von DevComm.dll als auch von der Anwendung aus darauf zuzugreifen, statt sie in die einzelnen Projekte "hineinzukompilieren".
Hab ich jetzt nicht ganz verstanden...