Potencjalne przekazywanie błędów obiektów CRT w poprzek granic DLL
Kod może mieć błędy podczas przekazywania obiektów środowiska C Runtime (CRT), takich jak dojścia plików, ustawień regionalnych i zmiennych środowiskowych do lub poza biblioteką DLL. Wywołania funkcji w granicach bibliotek DLL mogą powodować nieoczekiwane zachowanie, jeśli biblioteka DLL i wszystkie pliki wywołujące bibliotekę DLL używają różnych kopii bibliotek CRT.
Powiązany problem może wystąpić, gdy przydzielasz pamięć (jawnie z elementami new
lub malloc
lub niejawnie za strdup
pomocą metody , strstreambuf::str
itd.), a następnie przekazujesz wskaźnik przez granicę biblioteki DLL, gdzie jest zwalniana. Takie wskaźniki mogą spowodować naruszenie dostępu do pamięci lub uszkodzenie sterty, jeśli biblioteka DLL i jej odbiorcy korzystają z różnych kopii bibliotek CRT.
Innym objawem tego problemu jest błąd w oknie danych wyjściowych podczas debugowania, na przykład HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)
Przyczyny
Każda kopia biblioteki CRT ma oddzielny i odrębny stan przechowywany w magazynie lokalnym wątku przez aplikację lub bibliotekę DLL.
Obiekty CRT, takie jak dojścia plików, zmienne środowiskowe i ustawienia regionalne, są prawidłowe tylko dla kopii CRT w aplikacji lub dll, gdzie te obiekty zostały przydzielone lub ustawione. Gdy biblioteka DLL i jej klienci używają różnych kopii biblioteki CRT, nie można oczekiwać, że te obiekty CRT będą używane poprawnie po przekazaniu przez granicę biblioteki DLL.
Dotyczy to szczególnie wersji CRT przed uniwersalnym CRT w programie Visual Studio 2015 lub nowszym. Dla każdej wersji programu Visual Studio utworzonej przy użyciu programu Visual Studio 2013 lub starszej istniała biblioteka CRT specyficzna dla wersji. Wewnętrzne szczegóły implementacji CRT, takie jak struktury danych i konwencje nazewnictwa, były różne w każdej wersji. Dynamiczne łączenie kodu skompilowanego dla jednej wersji CRT z inną wersją biblioteki DLL CRT nigdy nie było obsługiwane. Od czasu do czasu będzie to działać, ale ze względu na szczęście, a nie projekt.
Każda kopia biblioteki CRT ma własnego menedżera sterty. Może to spowodować uszkodzenie sterty, jeśli przydzielisz pamięć w jednej bibliotece CRT i przekażesz wskaźnik przez granicę biblioteki DLL, która zostanie zwolniona przez inną kopię biblioteki CRT. Jeśli biblioteka DLL przekazuje obiekty CRT przez granicę biblioteki DLL lub przydziela pamięć zwolniona poza biblioteką DLL, klienci biblioteki DLL muszą używać tej samej kopii biblioteki CRT co biblioteka DLL.
Biblioteka DLL i jej klienci zwykle używają tej samej kopii biblioteki CRT tylko wtedy, gdy oba są połączone w czasie ładowania do tej samej wersji biblioteki DLL CRT. Ponieważ wersja biblioteki UNIVERSAL CRT używana przez program Visual Studio 2015 i nowsze wersje biblioteki DLL jest teraz centralnie wdrożonym składnikiem systemu Windows (ucrtbase.dll
), jest taka sama w przypadku aplikacji utworzonych w programie Visual Studio 2015 i nowszych wersjach. Jednak nawet jeśli kod CRT jest identyczny, nie można przydzielić pamięci przydzielonej w jednym stercie do składnika, który używa innego sterty.
Przykład: Przekazywanie dojścia do pliku w granicach bibliotek DLL
opis
W tym przykładzie dojście do pliku przechodzi przez granicę biblioteki DLL.
Pliki DLL i .exe są kompilowane za pomocą /MD
polecenia , dzięki czemu współużytkują pojedynczą kopię CRT.
W przypadku ponownego kompilowania /MT
za pomocą polecenia tak, aby korzystały z oddzielnych kopii CRT, uruchomienie powoduje test1Main.exe
naruszenie dostępu.
Plik test1Dll.cpp
źródłowy biblioteki 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 );
}
Plik test1Main.cpp
źródłowy pliku wykonywalnego:
// 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
Przykład: Przekazywanie zmiennych środowiskowych między granicą biblioteki DLL
opis
W tym przykładzie zmienne środowiskowe przechodzą przez granicę biblioteki DLL.
Plik test2Dll.cpp
źródłowy biblioteki 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 );
}
Plik test2Main.cpp
źródłowy pliku wykonywalnego:
// 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.
Jeśli skompilujesz zarówno pliki DLL, jak i EXE przy użyciu polecenia /MD
, tak aby używana była tylko jedna kopia CRT, program zostanie uruchomiony pomyślnie i wygenerował następujące dane wyjściowe:
New MYLIB variable is: c:\mylib;c:\yourlib