Nicht primitiven C ++ - Typ aus einer DLL-Funktion zurückgeben, die mit einer statischen Laufzeit verbunden ist (/ MT oder / MTd)

8

Beachten Sie, dass wir eine dynamische Bibliothek ( "HelloWorld.dll" ) haben, die mit Microsoft Visual Studio 2010 aus dem folgenden Quellcode kompiliert wird:

%Vor%

Und wir haben auch eine ausführbare Datei ( "LoadLibraryExample.exe" ), die diese DLL dynamisch mit der LoadLibrary WINAPI-Funktion lädt:

%Vor%

Dies funktioniert gut, wenn Sie mit einer dynamischen Laufzeitbibliothek verknüpft sind ( / MD oder / MDd -Schalter).

Das Problem tritt auf, wenn ich sie (die Bibliothek und die ausführbare Datei) mit einer Debug-Version der statischen Laufzeitbibliothek ( / MTd switch) verknüpfe. Das Programm scheint zu funktionieren ( "Hallo, Welt!" wird im Konsolenfenster angezeigt), stürzt dann aber mit der folgenden Ausgabe ab:

%Vor%

Das Problem tritt bei einer Release-Version der statischen Laufzeitbibliothek ( / MT switch) magisch nicht auf. Meine Annahme ist, dass die Release-Version den Fehler nicht sehen, aber es ist immer noch da.

Nach einer kleinen Recherche fand ich diese Seite MSDN, die Folgendes angibt:

  

Wenn Sie die statisch verknüpfte CRT verwenden, bedeutet dies, dass alle von der C-Laufzeitbibliothek gespeicherten Statusinformationen lokal für diese Instanz des CRT sind.
  Da eine DLL, die durch Verknüpfung mit einem statischen CRT erstellt wird, über einen eigenen CRT-Status verfügt, wird die statische Verknüpfung mit dem CRT in einer DLL nicht empfohlen, es sei denn, die Konsequenzen sind ausdrücklich erwünscht und verständlich.

So haben die Bibliothek und die ausführbare Datei ihre eigenen Kopien von CRT, die ihre eigenen Zustände haben. Eine Instanz von std :: string wird in der Bibliothek erstellt (wobei einige interne Speicherzuordnungen vom CRT der Bibliothek vorgenommen werden) und dann an die ausführbare Datei zurückgegeben. Die ausführbare Datei zeigt sie an und ruft dann ihren Destruktor auf (was zur Freigabe des internen Speichers durch die CRT der ausführbaren Datei führt). Wie ich verstehe, tritt hier ein Fehler auf: Der zugrunde liegende Speicher von std :: string wird mit einer CRT belegt und versucht, mit einer anderen aufgehoben zu werden.

Das Problem tritt nicht auf, wenn wir einen primitiven Typ (int, char, float usw.) oder einen Zeiger aus der DLL zurückgeben, da es in diesen Fällen keine Speicherzuweisungen oder -zuordnungen gibt. Ein Versuch, den zurückgegebenen Zeiger in der ausführbaren Datei zu löschen führt jedoch zu demselben Fehler (und das Löschen des Zeigers führt offensichtlich zu einem Speicherverlust).

Die Frage ist also: Ist es möglich, dieses Problem zu umgehen?

PS .: Ich möchte wirklich keine Abhängigkeit von MSVCR100.dll haben und die Benutzer meiner Anwendung dazu bringen, weitervertreibbare Pakete zu installieren.

P.P.S: Der obige Code erzeugt die folgende Warnung:

%Vor%

kann aufgelöst werden, indem extern "C" aus der Deklaration der Bibliotheksfunktion entfernt wird:

%Vor%

und ändern Sie den Aufruf GetProcAddress wie folgt:

%Vor%

(Funktionsname wird vom C ++ - Compiler eingerichtet, der tatsächliche Name kann mit dem Dienstprogramm dumpbin.exe abgerufen werden). Die Warnung ist dann weg, aber das Problem bleibt bestehen.

P.P.S: Ich sehe eine mögliche Lösung darin, für jede Situation ein Paar Funktionen in der Bibliothek bereitzustellen: eine, die einen Zeiger auf einige Daten zurückgibt, und die andere, die einen Zeiger auf diese Daten löscht. In diesem Fall wird der Speicher mit derselben CRT zugewiesen und freigegeben. Aber diese Lösung scheint sehr hässlich und unfreundlich zu sein, da wir immer mit Zeigern arbeiten müssen und außerdem muss ein Programmierer immer daran denken, eine spezielle Bibliotheksfunktion aufzurufen, um einen Zeiger zu löschen, anstatt einfach ein delete Schlüsselwort zu verwenden.

    
Fedor Chunovkin 15.10.2011, 16:04
quelle

1 Antwort

11

Ja, das ist der Hauptgrund dafür, dass / MD überhaupt existiert. Wenn Sie das DLL mit / MT erstellen, wird es eine eigene Kopie des eingebetteten CRT erhalten. Das erstellt einen eigenen Heap, von dem aus zugeordnet werden soll. Das von Ihnen zurückgegebene Objekt std :: string wird auf diesem Heap zugeordnet.

Wenn der Clientcode versucht, das Objekt freizugeben, gehen die Dinge schief. Es ruft den Löschoperator auf und versucht, den Speicher auf seinem eigenen Heap freizugeben . Unter Vista und Win7 bemerkt der Windows-Speichermanager, dass er aufgefordert wird, einen Heap-Block freizugeben, der nicht Teil des Heapspeichers ist und an den ein Debugger angehängt ist. Es generiert einen automatischen Debugger-Break und eine Diagnosemeldung, um Sie über das Problem zu informieren. Sehr schön übrigens.

Clearly / MD löst das Problem, sowohl Ihre DLL als auch der Client-Code verwenden die gleiche Kopie der CRT und damit den gleichen Heap. Es ist keine sichere Lösung, Sie werden immer noch Probleme mit der DLL haben wird gegen eine andere Version des CRT gebaut. Wie msvcr90.dll anstelle von msvcr100.dll.

Die einzige vollständig fehlerfreie Lösung besteht darin, die API einzuschränken, die Sie von der DLL bereitstellen. Geben Sie keine Zeiger auf Objekte zurück, die vom Clientcode freigegeben werden müssen. Weisen Sie dem Modul, das es erstellt hat, den Besitz von Objekten zu. Referenzzählung ist eine gebräuchliche Lösung. Wenn Sie dann einen Heap verwenden müssen, der von allen Codes in einem Prozess gemeinsam genutzt wird, sind entweder der Standardprozess-Heap (GlobalAlloc) oder der COM-Heap (CoTaskMemAlloc) zulässig. Lassen Sie auch keine Ausnahmen die Barriere passieren, dasselbe Problem. Die COM-Automatisierung abi ist ein gutes Beispiel.

    
Hans Passant 15.10.2011, 16:36
quelle

Tags und Links