Globale Variable in DLL
-
Hallo zusammen,
ich habe den Compiler vom klassischen Borland-Compiler auf den neueren clang-enhanced Compiler umgestellt. Nun beobachte ich ein Verhalten, das ich mir nicht ganz erklären kann. Ich hoffe auf eure Hilfe.
Ausgangspunkt ist eine DLL mit einer Main.c in der ein
std::vector
als globale Variable deklariert ist. In diversen Funktionen der DLL wird auf diese Variable zugegriffen.Im Kern sieht die Main.c ungefähr so aus:
std::vector<Foo> fooList; int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { ... } void __stdcall Add(void) { Foo foo; fooList.push_back(foo); } void __stdcall Finalize(void) { fooList.clear(); }
Die DLL wird per
LoadLibrary()
dynamisch geladen, linkt gegen die dynamische RTL und verwendet die Laufzeit-Packages.Beim Beenden der Anwendung wird der Destruktor von
fooList
nun sofort aufgerufen, sodass beim Aufruf vonFinalize()
bzw. vonDllEntryPoint
mitDLL_PROCESS_DETACH
dieser schon futsch ist. Bei Verwendung des klassischen Compilers war das nicht der Fall...Ich hatte erwartet, dass die globale Variablen einer DLL erst bei Übergabe von
DLL_PROCESS_DETACH
gelöscht werden. Damit lag ich offensichtlich falsch, was bedeutet, dass es bisher schon undefiniertes Verhalten war und nur zufällig funktioniert hat.Liege ich mit meiner Vermutung richtig? Wenn ja, hat jemand eine Idee, wie man das lösen kann? Auf die schnelle habe ich den Vektor als Pointer deklariert, möchte das aber nicht so beibehalten.
Edit
Die SuFu des "neuen" Forums ist brauchbar
statische-variable-in-dllVollständig erhellt bin ich noch nicht, aber mal sehen was sich im Netz findet.
-
Das kommt alles drauf an wie die Toolchain Initialisierung/Finalisierung von statischen Objekten implementiert.
Keine Ahnung wie deine "clang-enhanced" Toolchain das macht.Schreib dir mal ne kleine Klasse die im Konstruktor und Destruktor jeweils irgendwas macht was nicht wegoptimiert wird (z.B. einfach
OutputDebugString()
aufrufen). Von der macht du ein globales Objekt, und dann setzt du in den Konstruktor und den Destruktor jeweils einen Breakpoint.Und dann postest du die Callstacks hier.
-
Beim Kopieren der Call-Stacks ist mir eingefallen, dass ich das Thema schon einmal hatte. Damals habe ich bei stackoverflow (difference between bcc32 and bcc32c object lifetime) so ziemlich die gleiche Frage gestellt. Der Unterschied ist hauptsächlich, dass hier noch eine DLL im Spiel ist.
Die Erkenntnis von damals war, dass statische Objekte, egal ob global oder Member einer Klasse, unabhängig von der Lebensdauer anderer Objekte sind. Mir war bisher nicht klar, dass statische Objekte, die in einer DLL erzeugt werden, im Speicher der Hauptanwendung hängen.
Bedeutet also, ich muss
fooList
aus dem obigen Beispiel als Pointer deklarieren und die Lebensdauer selber managen. Oder?Call-Stacks (clang)
Aufruf des Konstruktors nach
LoadLibrary
::0342C2BD Foo::Foo(this=:0343CE24)
:0342C158 __cxx_global_var_init.22()
:0342C2AC _GLOBAL__sub_I_RecorderPlugin.cpp()Aufruf des Destruktors nach Beenden des Programms (vor
FreeLibrary
)::0342D275 Foo::~Foo(this=:0343CE24)
:0342C2E3 Foo::~Foo(this=:0343CE24)
:0342c188 ; construct<double, {723}...
:32237d88 CC32C270MT.___call_atexit_procs + 0x5cCall-Stacks (Borland)
Aufruf des Konstruktors nach
LoadLibrary
:
:03798DA2 Foo::Foo(this=:037A5544)
:03798C5C STCON0()
:32209025 ; C:\WINDOWS\SysWOW64\CC32230MT.DLL
:32209554 CC32230MT.__wstartupd + 0x90Aufruf des Destruktors nach Beenden des Programms (nach
FreeLibrary
)::03798DC2 Foo::~Foo(this=:037A5544)
:03798C8D STDES0()
:32209043 ; C:\WINDOWS\SysWOW64\CC32230MT.DLL
:32209586 CC32230MT.__wstartupd + 0xc2
:037822a9 ; Teegdiplus
:77905608 ntdll.RtlGetNtSystemRoot + 0x68
:779354c2 ; ntdll.dll
:77935084 ; ntdll.dll
:77915919 ntdll.LdrUnloadDll + 0xe9
:779158b5 ntdll.LdrUnloadDll + 0x85
:76ba05e6 KERNELBASE.FreeLibrary + 0x16
-
Da fehlen wohl einige Debug-Symbole.
Es fehlen interessante Frames, z.B. was bevorCC32C270MT.___call_atexit_procs
kommt, und einige Symbole sind Schrott (Teegdiplus
,C:\WINDOWS\SysWOW64\CC32230MT.DLL
).Naja, egal.
Was du probieren könntest wäre folgendes:- Du machst eine Wrapper-Klasse für
fooList
die sich um die Initialisierung und Cleanup kümmert. - Du verschiebst
fooList
in eine Hilfsfunktion (Meyers' Singleton) - Du rufst die Hilfsfunktion in DllMain auf
Also quasi
struct FooStatics { std::vector<Foo> fooList; FooStatics() { // Replaces the Add() function fooList.push_back(...); } ~FooStatics() { // Replaces the Finalize() function // ... } }; FooStatics& fooStatics() { static FooStatics fs; return fs; } int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { //... case DLL_PROCESS_ATTACH: fooStatics(); //... }
Wenn
DllEntryPoint(DLL_PROCESS_ATTACH)
zu früh läuft kann sein dass dir das um die Ohren fliegt, aber einen Versuch wäre es wert.Und nochwas...
DllEntryPoint
ist ein unüblicher Name für diese Funktion, normalerweise nennt man das DingDllMain
. Verwendest du irgendwelche Linker-Switches damitDllEntryPoint
zum "Entry Point" wird? Falls ja könntest du versuchen den Switch wegzulassen und statt dessen den NamenDllMain
zu verwenden. Was die Implementierungen normalerweise machen ist nämlich der DLL eine Entry Point Funktion zu verpassen die von der Implementierung bereitgestellt wird, und diese ruft dann erst - zum passenden Zeitpunkt - deine DllMain auf. Wenn du das umgehst indem du den Linker zwingst direkt deine Funktion als Entry Point zu verwenden, kann das zu Problemen dieser Art führen.
- Du machst eine Wrapper-Klasse für
-
@hustbaer sagte in Globale Variable in DLL:
Da fehlen wohl einige Debug-Symbole.
Es fehlen interessante Frames, z.B. was bevorCC32C270MT.___call_atexit_procs
kommt, und einige Symbole sind Schrott (Teegdiplus
,C:\WINDOWS\SysWOW64\CC32230MT.DLL
).Verflucht seist du, E.........!
FooStatics& fooStatics() { static FooStatics fs; return fs; }
Leider hat das nicht geholfen. Der Destruktor von
FooStatics
wird fürfs
beim Beenden des Programs aufgerufen.DllEntryPoint
ist ein unüblicher Name für diese Funktion, normalerweise nennt man das DingDllMain
.Der Name wird von der IDE automatisch vergeben. Umbenennen hilft aber auch nicht.
Das Verhalten scheint auch mit dem VCL-Framwork zusammenzuhängen:
Auto-created TForm objects are owned by the global TApplication object. That object is destroyed (thus destroying its owned Forms) after the application's main()/wmain()/WinMain() entry point function has exited. Globals are destroyed during application cleanup.Etwas doof ist das schon, da es bedeutet, dass ich beim "Aufräumen" der Anwendung, nicht auf globale oder statische Variablen zugreifen darf...
-
@Kerem sagte in Globale Variable in DLL:
Leider hat das nicht geholfen. Der Destruktor von FooStatics wird für fs beim Beenden des Programs aufgerufen.
Natürlich wird er das. Die Frage ist wann während des Beendens er aufgerufen wird. Wenn es spät genug passiert, ist das ja kein Problem.
-
@hustbaer sagte in Globale Variable in DLL:
Die Frage ist wann während des Beendens er aufgerufen wird.
Der Aufruf erfolgt sobald die Anwendung geschlossen wird, noch bevor der Destruktor des Hauptfensters aufgerufen wird. Leider ist im Code des Hauptfensters der ganze DLL-Lade- und Entlade-Mechanismus implementiert (Konstrukor:
LoadLibrary
, DestruktorFreeLibrary
).@Kerem sagte in Globale Variable in DLL:
Auto-created TForm objects are owned by the global TApplication object. That object is destroyed (thus destroying its owned Forms) after the application's main()/wmain()/WinMain() entry point function has exited. Globals are destroyed during application cleanup.
Bei VCL-Anwendungen wird das Hauptfenster standardmäßig automatisch erzeugt. Die restliche Anwendungslogik wird dann von dort aus angesprochen. Dies führt in meinem Fall dazu, dass beim Aufruf von
FreeLibrary
im Destruktor des Hauptfensters das "application cleanup" bereits stattgefunden hat und alle statischen Objekte gelöscht wurden.