Sdílet prostřednictvím


Možné chyby při předávání objektů CRT přes hranice knihovny DLL

Kód může mít chyby při předávání objektů C Runtime (CRT), jako jsou popisovače souborů, národní prostředí a proměnné prostředí do nebo mimo knihovnu DLL. Volání funkce přes hranice knihovny DLL může způsobit neočekávané chování, pokud knihovna DLL a všechny soubory, které do knihovny DLL volají, používají různé kopie knihoven CRT.

Související problém může nastat, když přidělíte paměť (explicitně s new nebo mallocnebo implicitně s strdupstrstreambuf::str, atd.) a pak předáte ukazatel přes hranici knihovny DLL, kde je uvolněna. Tyto ukazatele můžou způsobit narušení přístupu k paměti nebo poškození haldy, pokud knihovna DLL a její příjemci používají různé kopie knihoven CRT.

Dalším příznakem tohoto problému je chyba ve výstupním okně během ladění, například HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

Příčiny

Každá kopie knihovny CRT má samostatný a odlišný stav uložený v místním úložišti vlákna vaší aplikací nebo knihovnou DLL.

Objekty CRT, jako jsou popisovače souborů, proměnné prostředí a národní prostředí, jsou platné pouze pro kopii CRT v aplikaci nebo knihovně DLL, kde byly tyto objekty přiděleny nebo nastaveny. Pokud knihovna DLL a její klienti používají různé kopie knihovny CRT, nemůžete očekávat, že se tyto objekty CRT po předání přes hranici knihovny DLL správně použijí.

Platí to zejména pro verze CRT před univerzálním CRT v sadě Visual Studio 2015 a novějším. Pro každou verzi sady Visual Studio vytvořenou se sadou Visual Studio 2013 nebo starší verzí byla knihovna CRT specifická pro jednotlivé verze. Interní podrobnosti implementace CRT, jako jsou datové struktury a zásady vytváření názvů, se v jednotlivých verzích liší. Dynamicky propojující kód zkompilovaný pro jednu verzi CRT na jinou verzi knihovny CRT DLL nebyl nikdy podporován. Občas by to fungovalo, ale kvůli štěstí místo návrhu.

Každá kopie knihovny CRT má vlastního správce haldy. Může způsobit poškození haldy, pokud přidělíte paměť v jedné knihovně CRT a předáte ukazatel přes hranici knihovny DLL, aby byla uvolněna jinou kopií knihovny CRT. Pokud knihovna DLL předává objekty CRT přes hranici knihovny DLL nebo přidělí paměť, která je uvolněna mimo knihovnu DLL, klienti knihovny DLL musí používat stejnou kopii knihovny CRT jako knihovna DLL.

Knihovna DLL a její klienti obvykle používají stejnou kopii knihovny CRT pouze v případě, že jsou obě propojeny v době načítání do stejné verze knihovny CRT DLL. Vzhledem k tomu, že verze knihovny DLL univerzální knihovny CRT používaná sadou Visual Studio 2015 a novější je teď centrálně nasazená komponenta Systému Windows (ucrtbase.dll), je stejná pro aplikace vytvořené pomocí sady Visual Studio 2015 a novějších verzí. I když je však kód CRT stejný, nemůžete dát paměť přidělenou v jedné haldě komponentě, která používá jinou haldu.

Příklad: Předání popisovače souboru přes hranice knihovny DLL

Popis

Tento příklad předá popisovač souboru přes hranici knihovny DLL.

Soubory DLL a .exe jsou sestaveny s /MD, takže sdílejí jednu kopii CRT.

Pokud znovu sestavíte /MT tak, aby používaly samostatné kopie CRT, spuštění výsledného test1Main.exe výsledku způsobí porušení přístupu.

Zdrojový soubor test1Dll.cppknihovny DLL:

// 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 );
}

Spustitelný zdrojový soubor 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

Příklad: Předání proměnných prostředí napříč hranicemi knihovny DLL

Popis

Tento příklad předává proměnné prostředí přes hranice knihovny DLL.

Zdrojový soubor test2Dll.cppknihovny DLL:

// 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 );
}

Spustitelný zdrojový soubor 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.

Pokud vytváříte soubory DLL i EXE pomocí /MD, aby se použila pouze jedna kopie CRT, program se úspěšně spustí a vytvoří následující výstup:

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

Viz také

Soubory C runtime (CRT) a standardní knihovny C++ (STL) .lib