Potenzielle Fehler bei der Übergabe von CRT-Objekten über DLL-Grenzen

Ihr Code hat möglicherweise Fehler, wenn Sie C-Runtime-Objekte (CRT) wie Dateihandles, Gebietsschemas und Umgebungsvariablen an oder aus einer DLL übergeben. Funktionsaufrufe über die DLL-Grenze hinweg können zu unerwartetem Verhalten führen, wenn die DLL und alle Dateien, die in die DLL aufrufen, verschiedene Kopien der CRT-Bibliotheken verwenden.

Ein verwandtes Problem kann auftreten, wenn Sie Arbeitsspeicher (entweder explizit mit oder malloc, oder implizit mit strdupnew , strstreambuf::strusw.) zuweisen und dann einen Zeiger über eine DLL-Grenze übergeben, an die er freigegeben wird. Solche Zeiger können zu einer Speicherzugriffsverletzung oder Heap-Beschädigung führen, wenn die DLL und ihre Consumer unterschiedliche Kopien der CRT-Bibliotheken verwenden.

Ein weiteres Symptom dieses Problems ist ein Fehler im Ausgabefenster beim Debuggen, z. B. HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

Ursachen

Jede Kopie der CRT-Bibliothek verfügt über einen eigenen Status, der von Ihrer App oder DLL im lokalen Threadspeicher gespeichert wird.

CRT-Objekte wie Dateihandles, Umgebungsvariablen und Gebietsschemas sind nur für die Kopie des CRT in der App oder DLL gültig, für die diese Objekte zugewiesen oder festgelegt wurden. Wenn eine DLL und ihre Clients unterschiedliche Kopien der CRT-Bibliothek verwenden, können Sie nicht erwarten, dass diese CRT-Objekte ordnungsgemäß verwendet werden, wenn sie über die DLL-Grenze übergeben werden.

Dies gilt insbesondere für CRT-Versionen vor dem universellen CRT in Visual Studio 2015 und höher. Für jede mit Visual Studio 2013 oder früher erstellte Version von Visual Studio gab es eine versionsspezifische CRT-Bibliothek. Interne Implementierungsdetails des CRT, z. B. Datenstrukturen und Benennungskonventionen, waren in jeder Version unterschiedlich. Dynamisches Verknüpfen von Code, der für eine Version von CRT mit einer anderen Version der CRT-DLL kompiliert wurde, wurde nie unterstützt. Gelegentlich würde es funktionieren, aber aufgrund des Glücks anstatt des Designs.

Jede Kopie der CRT-Bibliothek verfügt über einen eigenen Heap-Manager. Dies kann zu Heapbeschädigungen führen, wenn Sie Speicher in einer CRT-Bibliothek zuweisen und den Zeiger über eine DLL-Grenze übergeben, die von einer anderen Kopie der CRT-Bibliothek freigegeben werden soll. Wenn Ihre DLL CRT-Objekte über die DLL-Grenze übergibt oder Speicher zuweist, der außerhalb der DLL freigegeben wird, müssen Clients der DLL dieselbe Kopie der CRT-Bibliothek wie die DLL verwenden.

Die DLL und die DLL-Clients verwenden normalerweise nur dann die gleiche Kopie der CRT-Bibliothek, wenn beide zur Ladezeit mit der gleichen Version der CRT-DLL verknüpft sind. Da die DLL-Version der universellen CRT-Bibliothek, die von Visual Studio 2015 und höher verwendet wird, jetzt eine zentral bereitgestellte Windows-Komponente (ucrtbase.dll) ist, ist sie für Apps identisch, die mit Visual Studio 2015 und höheren Versionen erstellt wurden. Auch wenn der CRT-Code identisch ist, können Sie einer Komponente, die einen anderen Heap verwendet, keinen Speicher in einem Heap zuweisen.

Beispiel: Übergeben des Dateihandles über DIE DLL-Grenze

Beschreibung

Dieses Beispiel übergibt ein Dateihandle über eine DLL-Grenze.

Die DLL- und EXE-Dateien werden mit /MDerstellt, sodass sie eine einzelne Kopie der CRT teilen.

Wenn Sie neu /MT erstellen, sodass sie separate Kopien des CRT verwenden, führen Sie die resultierenden test1Main.exe Ergebnisse in einer Zugriffsverletzung aus.

DLL-Quelldatei test1Dll.cpp:

// test1Dll.cpp
// compile with: cl /EHsc /W4 /MD /LD test1Dll.cpp
#include <stdio.h>
__declspec(dllexport) void writeFile(FILE *stream)
{
   char   s[] = "this is a string\n";
   fprintf( stream, "%s", s );
   fclose( stream );
}

Ausführbare Quelldatei test1Main.cpp:

// test1Main.cpp
// compile with: cl /EHsc /W4 /MD test1Main.cpp test1Dll.lib
#include <stdio.h>
#include <process.h>
void writeFile(FILE *stream);

int main(void)
{
   FILE  * stream;
   errno_t err = fopen_s( &stream, "fprintf.out", "w" );
   writeFile(stream);
   system( "type fprintf.out" );
}
this is a string

Beispiel: Übergeben von Umgebungsvariablen über DLL-Grenzen hinweg

Beschreibung

Dieses Beispiel übergibt Umgebungsvariablen über eine DLL-Grenze hinweg.

DLL-Quelldatei test2Dll.cpp:

// test2Dll.cpp
// compile with: cl /EHsc /W4 /MT /LD test2Dll.cpp
#include <stdio.h>
#include <stdlib.h>

__declspec(dllexport) void readEnv()
{
   char *libvar;
   size_t libvarsize;

   /* Get the value of the MYLIB environment variable. */
   _dupenv_s( &libvar, &libvarsize, "MYLIB" );

   if( libvar != NULL )
      printf( "New MYLIB variable is: %s\n", libvar);
   else
      printf( "MYLIB has not been set.\n");
   free( libvar );
}

Ausführbare Quelldatei test2Main.cpp:

// test2Main.cpp
// compile with: cl /EHsc /W4 /MT test2Main.cpp test2dll.lib
#include <stdlib.h>
#include <stdio.h>

void readEnv();

int main( void )
{
   _putenv( "MYLIB=c:\\mylib;c:\\yourlib" );
   readEnv();
}
MYLIB has not been set.

Wenn Sie sowohl die DLL- als auch die EXE-Dateien verwenden /MD, damit nur eine Kopie des CRT verwendet wird, wird das Programm erfolgreich ausgeführt und erzeugt die folgende Ausgabe:

New MYLIB variable is: c:\mylib;c:\yourlib

Siehe auch

C-Runtime-Dateien (CRT) und C++ Standard Library (STL) .lib