AV bei Aufruf von LoadLibraryW



  • Hallo zusammen,

    seit neuestem erhalte ich beim Laden einer bestimmten DLL(eigene) folgende Fehlermeldung:

    ---------------------------
    Benachrichtigung über Debugger-Exception
    ---------------------------
    Im Projekt Moman6.exe ist eine Exception der Klasse $C0000005 mit der Meldung 'access violation at 0x21494f4d: read of address 0x00000010' aufgetreten.
    ---------------------------
    Anhalten Fortsetzen Hilfe
    ---------------------------

    Die DLL wird über eine Hilfsfunktion, die sich in einem Namespace befindet, aufgerufen:

    HINSTANCE LoadDll(String FileName)
    {
    	HINSTANCE Plugin = NULL;
    	if (FileExists(FileName)) {
    			Plugin = LoadLibraryW(FileName.w_str());
    			if (Plugin == NULL) {
    				String Msg;
    				Msg.sprintf(LoadStr(ERROR_LOADPLUGIN).c_str(), FileName.c_str());
    				MessageDlg(Msg, mtError, TMsgDlgButtons() << mbOK, 0);
    			}
    	}
    	else {
    		String Msg;
    		Msg.sprintf(LoadStr(ERROR_FILENOTFOUND).c_str(), FileName.c_str());
    		MessageDlg(Msg, mtError, TMsgDlgButtons() << mbOK, 0);
    	}
    	return Plugin;
    }
    

    Wenn ich in der Fehlermeldung auf "Anhalten" klicke, sehe ich folgende Aufruf-Stack:

    :21494f4d BORLNDMM.@Borlndmm@SysGetMemqqri + 0x2d :06709889 ; \_\_\_CRTL\_MEM_Revector :067097dd _malloc + 0xD :0670d4f0 __unlockLocale + 0x14 :06712807 ; __startupd :066f1c14 ; Pagecontroldff :77d7c23d ; ntdll.dll :77d7aeb5 ; ntdll.dll :77d7afcc ntdll.LdrLoadDll + 0x7b :76b32ca2 ; C:\\Windows\\syswow64\\KERNELBASE.dll :76f948dc kernel32.LoadLibraryW + 0x11 :054DAC17 pluginhlp::LoadDll(FileName={ u"D:\\\Arbeit_SVN\\\Moman XE6\\\trunk\\\Build\\\Win32\\\Debug\\\Plugin\\\Cockpit.dll" }) :054DAE14 pluginhlp::CreatePlugin(FileName={ u"D:\\\Arbeit_SVN\\\Moman XE6\\\trunk\\\Build\\\Win32\\\Debug\\\Plugin\\\Cockpit.dll" }, Handle=:02E794BC, Owner=:02E79040) :054DC474 TFormController::ActionCockpitExecute(this=:02E79040, Sender=:0209B160) :50176e96 rtl200.@System@Classes@TBasicAction@Executeqqrv + 0x12
    :504f2833 vcl200.@Vcl@Controls@TControl@Performqqruiuii+0x27:504f700cvcl200.@Vcl@Controls@TWinControl@IsControlMouseMsgqqruiuii + 0x27 :504f700c vcl200.@Vcl@Controls@TWinControl@IsControlMouseMsgqqrr24Winapi@Messages@TWMMouse + 0xb0
    :504f6d83 vcl200.@Vcl@Controls@TWinControl@MainWndProcqqrr24Winapi@Messages@TMessage+0x2f:50177b8artl200.@System@Classes@TDataModule@WriteHeightqqrr24Winapi@Messages@TMessage + 0x2f :50177b8a rtl200.@System@Classes@TDataModule@WriteHeightqqrp22System@Classes@TWriter + 0x22
    :76b862fa ; C:\Windows\syswow64\USER32.dll
    :76b86d3a USER32.GetThreadDesktop + 0xd7
    :76b877c4 ; C:\Windows\syswow64\USER32.dll
    :76b8788a USER32.DispatchMessageW + 0xf
    :506430e4 vcl200.@Vcl@Forms@TApplication@ProcessMessage$qqrr6tagMSG + 0xf8

    Wie man sehen kann, scheint es ein Problem mit dem Borland Speichermanager(borlndmm) zu geben. Das Interessante bzw. Unverständliche ist jedoch, dass die DLL garnicht erst geladen wird. Setzte ich einen Breakpoint in der Zeile in der LoadLibraryW() aufgerufen wird, lande ich mit F7 in der Datei ustring.h und erhalte dort beim nächsten step in die AV beim Aufruf von

    WideChar* w_str() const   { return (Data)? Data: const_cast<WideChar*>(L"");}
    

    in Zeile 179...

    Dynamisches Linken der RTL ist in allen Projekten deaktiviert. Alles neu erzeugen, Rechner neustarten und andere Verzweiflungstaten haben nicht zum Erfolg geführt. Ich bin leider nicht sehr gut im Interpretieren vom Aufruf-Stack und das Suchen nach den Meldungen in diesem hat auch keine Ergbenisse geliefert.

    Hat jemand eine Idee?

    VG Kerem



  • Das Probem tritt jetzt nicht immer auf. Es scheint mit einem Formular das ich dem Projekt hinzugefügt habe zusammenzuhängen. Das Formular enthält eine Komponente für die einige Includes in der Projektdatei generiert werden.
    Bin dran und werden meine Ergebnisse mit euch teilen.



  • In bezug auf DLLs gilt folgendes:

    Wenn du und C++-Objekte zwischen der DLL und der Anwendung austauschst, mußt du mit der dynamischen RTL linken.

    Wenn du Delphi-Strings ( String , AnsiString , UnicodeString ) zwischen der DLL und der Anwendung austauschst, mußt du mit borlndmm.dll linken (dazu memmgr.lib zum Projekt hinzufügen).

    Wenn du Delphi-Klassen (alles, was von TObject ableitet, also Formulare, Frames, Komponenten etc.) zwischen der DLL und der Anwendung herumreichst, mußt du mit Laufzeitpackages linken und die dynamische RTL verwenden.

    Alles andere führt zu undefiniertem Verhalten, und diese führt, wie du gerade feststellst, nicht selten zu Zugriffsverletzungen, und das auch nur, wenn du Glück hast. Im schlimmsten Fall werden irgendwelche Daten korrumpiert, und du merkst es erst, wenn obskure Bugreports von deinen Kunden kommen.

    Edit: Nur falls das nicht klar ist, die oben genannten Anforderungen gelten sowohl für die Anwendung als auch für die DLL. Es müssen also jeweils beide mit der dynamischen RTL, mit borlndmm.dll bzw. mit Laufzeitpackages gelinkt werden.



  • Ich hatte mich einst kurz mit diesem Thema auseinandergesetzt, bin dann
    aber zu dem Entschluss gekommen in allen DLLs, BPLs und der EXE das dynamische Linken der RTL wegzulassen. Ansonsten erhalte ich AVs beim Beenden der Anwendung, wenn in den DLLs Funktionen der STL verwendet werden.

    https://www.c-plusplus.net/forum/327344

    Deine Antwort verwirrt mich jetzt ein wenig, wobei ich dein Wissen über den Builder nicht in Frage stelle. Ich werde dem nochmal nachgehen.



  • D.h., du linkst zurzeit mit Laufzeitpackages, aber ohne dynamische RTL? Soweit ich weiß, wird diese Konfiguration nicht unterstützt. Kann natürlich sein, daß es trotzdem funktioniert, aber wie es aussieht, ja eher nicht.

    Versuche mal, die dynamische RTL zu verwenden und zugleich in allen Projekten memmgr.lib einzubinden (das sollte in Package-Projekten nicht nötig sein, aber für DLLs anscheinend schon, auch wenn diese mit Laufzeitpackages gelinkt werden – da bin ich nicht ganz sicher) und schau, ob der LoadLibrary -Aufruf dann funktioniert.

    Das stringstream -Problem, das du im anderen Thread ansprichst, ist mir auch schon begegnet. Das ist ein Bug in der RTL, und für den wirst du einen Workaround suchen müssen. Die dynamische RTL wird ja im Quelltext mitgeliefert, so daß du sie selbst bauen und eben auch kritische Probleme beheben kannst.

    Ich schau mal, ob ich das Problem in C++Builder XE auch habe und ob ich einen Weg finde, es zu umgehen.



  • Ganz genau. Laufzeitpackages ja; Linken mit dyn. RTL nein.
    Dass das nicht zulässig ist wusste ich nicht. Könntest du das kurz erläutern?

    Ich habe jetzt folgende Konstellation vorliegen und LoadLibrary scheint wieder zu Funktionieren:

    EXE: dyn. RTL, Laufzeitpackages, memmgr.lib
    BPL: dyn. RTL, Laufzeitpackages
    DLL: dyn. RTL, Laufzeitpackages, memmgr.lib

    EXE läd BPL, BPL läd DLL

    Da der Fehler aber vorher auch nicht immer aufgetreten ist, werden ich wohl etwas abwarten müssen.



  • Die Regeln, die ich oben angegeben habe, haben ihre Ursache darin, daß die C++-RTL, die Delphi-RTL und die VCL nicht zustandsfrei sind:

    - für dynamische Speicheranforderungen legt der Memory-Manager einen Heap an; wenn du in A.dll c = malloc() aufrufst und in B.dll free(c) , dann ist es wichtig, daß malloc() in A.dll und free() in B.dll sich auf denselben Heap beziehen. Wenn du beide mit der statischen RTL linkst, wissen sie nichts voneinander und haben beide ihren eigenen Heap => UB.

    - die Sache ist bei der Delphi-RTL nicht anders. Delphi-Strings beispielsweise werden vom Memory-Manager der Delphi-RTL verwaltet, und wenn verschiedene Module jeweils ihre eigene Instanz vom Memory-Manager mitbringen, führt der Austausch von Strings über Modulgrenzen auch zu UB. Dem kann man abhelfen, indem man mit Laufzeitpackages linkt und Packages statt DLLs benutzt (denn Packages sind auch nur DLLs), oder eben durch explizite Verwendung eines gemeinsamen Memory-Manager-Moduls ( memmgr.lib ).

    - natürlich gibt es in der Delphi-RTL und insbesondere der VCL zahlreiche globale Variablen wie Application , bei denen relativ klar ist, daß es keine gute Idee ist, mehrere davon in einem Programm zu haben, und insbesondere, sie zu vermischen, also ein Formular aus dem einen Modul mit Application.CreateForm() des anderen Moduls zu erstellen, einer Komponente aus dem einen ein Parent aus dem anderen Modul zu geben oder so. Die Lösung dafür sind Laufzeitpackages.

    - es gibt auch weniger offensichtliche Zustände in den RTLs, z.B. die Locales in der C++-RTL, den Exception-Mechanismus in der Delphi-RTL oder den Startup- und Threading-Code. Der gebildete Leser weiß, daß man in C- und C++-Anwendungen _beginthread() statt CreateThread() verwenden sollte, damit die C++-RTL ihren thread-lokalen Zustand richtig verwalten kann; in C++Builder geht natürlich auch TThread (was wiederum erfordert, daß die C++-RTL sich bei der Delphi-RTL registriert, damit sie Threaderstellung und -freigabe mitbekommt). All das kann auch auf subtile Weise kaputtgehen, wenn man mehrere RTLs mischt.

    - der wichtigste Grund, warum man Laufzeitpackages benutzen muß, ist allerdings ein anderer, nämlich die Typidentität. Für jede Delphi-Klasse (ebenso für jede C++-Klasse mit virtuellen Funktionen) wird eine VMT nebst Metadaten zur Klasse angelegt, und diese werden für dynamische Casts, RTTI und desgleichen herangezogen. Wenn A.dll und B.dll statisch mit der Delphi-RTL gelinkt werden, gibt es für alle Typen aus der RTL (also TObject , Exception , TPersistent , ...) und ggf. der VCL ( TComponent , TForm , ...) zwei VMTs. Dann kann es z.B. sein, daß dynamische Casts willkürlich fehlschlagen, weil ein Objekt aus der einen Hierarchie nicht mit denen aus der anderen Hierarchie verwandt ist.

    - Typidentität ist auch in C++ ein Problem, allerdings eines, das über die Modulproblematik hinausgeht, weil man auch innerhalb eines Moduls Typduplikationen verursachen kann (mit Templates oder inline definierten Konstruktoren), die der Linker nicht immer beseitigt. Die Implementation von dynamic_cast<>() hat deshalb einen Fallback-Mechanismus, der nicht die VMTs, sondern die Typnamen vergleicht, also typeid(T).name() . U.a. deshalb kommt man mit der statischen C++-RTL öfter ungestraft davon; aber richtig ist es deshalb noch nicht.

    Kerem schrieb:

    Ganz genau. Laufzeitpackages ja; Linken mit dyn. RTL nein.
    Dass das nicht zulässig ist wusste ich nicht. Könntest du das kurz erläutern?

    Dazu habe ich keine konkrete technische Begründung, das ist nur ein Detail aus einem Mail- oder Newsgroupaustausch mit einem Embarcadero-Angestellten. Er sagte sinngemäß "linking against the static RTL while using runtime packages is not something we support". Ich weiß nicht, ob dieser Sachverhalt auch ordentlich dokumentiert ist. Aber eingedenk der obigen Probleme kann man vermuten, daß es damit zusammenhängt, daß die C++-RTL an ein paar Stellen (Startup, Threading, Exceptions) mit der Delphi-RTL interagieren muß, und daß unklar ist, was passiert, wenn sich mehrere statisch gelinkte Instanzen der C++-RTL bei der Delphi-RTL registrieren (und, noch schlimmer, beim Entladen von Modulen wieder deregistrieren).



  • Vielen Dank für diese ausführliche Beschreibung 👍

    Dann kann es z.B. sein, daß dynamische Casts willkürlich fehlschlagen, weil ein Objekt aus der einen Hierarchie nicht mit denen aus der anderen Hierarchie verwandt ist.

    In einem anderen Modul hatte ich vermutlich genau dieses Problem beim Casten eines Frames. Hat mich fast verrückt gemacht...

    Ich sehe jetzt ein, dass ich die RTL dyn. linken muss. Leider besteht jetzt wie gesagt das Problem mit der STL, insbesondere stringstream, die ich teilweise in meinen DLLs verwende. Sofern ich einen Wokraround finde, werde ich diesen posten. Ansonsten wird stingstream wahrscheinlich vorerst rausfliegen. Dann kann ich nur hoffen, dass nicht auch andere Komponenten aus der STL dieses Problem verursachen.



  • Ich habe einen Workaround für die DLL-RTL-Geschichte gefunden:

    https://www.c-plusplus.net/forum/p2454897#2454897

    Gruß
    Kerem


Anmelden zum Antworten