Potenziali errori di passaggio di oggetti CRT attraverso i limiti DLL
Il codice può avere errori quando si passano oggetti C Runtime (CRT), ad esempio handle di file, impostazioni locali e variabili di ambiente all'interno o all'esterno di una DLL. Le chiamate di funzione oltre il limite della DLL possono causare un comportamento imprevisto se la DLL e tutti i file che chiamano nella DLL usano copie diverse delle librerie CRT.
Un problema correlato può verificarsi quando si alloca memoria (in modo esplicito con new
o malloc
oppure in modo implicito con strdup
, strstreambuf::str
e così via) e quindi si passa un puntatore attraverso un limite di DLL in cui viene liberato. Tali puntatori possono causare una violazione dell'accesso alla memoria o un danneggiamento dell'heap, se la DLL e i relativi consumer usano copie diverse delle librerie CRT.
Un altro sintomo di questo problema è un errore nella finestra di output durante il debug, ad esempio HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)
Cause
Ogni copia della libreria CRT ha uno stato separato e distinto, che viene mantenuto dall'app o dalla DLL nella memoria locale dei thread.
Gli oggetti CRT, ad esempio handle di file, variabili di ambiente e impostazioni locali, sono validi solo per la copia di CRT nell'app o nella DLL in cui questi oggetti sono stati allocati o impostati. Quando una DLL e i relativi client usano copie diverse della libreria CRT, non è possibile prevedere che questi oggetti CRT vengano usati correttamente quando vengono passati oltre il limite della DLL.
È particolarmente vero per le versioni CRT prima di Universal CRT in Visual Studio 2015 e versioni successive. È presente una libreria CRT specifica della versione per ogni versione di Visual Studio compilata con Visual Studio 2013 o versioni precedenti. I dettagli di implementazione interni di CRT, ad esempio le strutture di dati e le convenzioni di denominazione, erano diversi in ogni versione. Il collegamento dinamico del codice compilato per una versione di CRT a una versione diversa della DLL CRT non è mai stato supportato. A volte funzionava, ma a causa della fortuna piuttosto che della progettazione.
Ogni copia della libreria CRT ha un proprio gestore heap. Può causare il danneggiamento dell'heap se si alloca memoria in una libreria CRT e si passa il puntatore attraverso un limite dll per essere liberato da una copia diversa della libreria CRT. Se la DLL passa oggetti CRT oltre il limite della DLL o alloca memoria liberata all'esterno della DLL, i client della DLL devono usare la stessa copia della libreria CRT della DLL.
La DLL e i relativi client usano in genere la stessa copia della libreria CRT solo se sono collegati al momento del caricamento alla stessa versione della DLL CRT. Poiché la versione DLL della libreria CRT universale usata da Visual Studio 2015 e versioni successive è ora un componente Windows distribuito centralmente (ucrtbase.dll
), è lo stesso per le app compilate con Visual Studio 2015 e versioni successive. Tuttavia, anche quando il codice CRT è identico, non è possibile assegnare memoria allocata in un heap a un componente che usa un heap diverso.
Esempio: Passare l'handle di file attraverso il limite della DLL
Descrizione
In questo esempio viene passato un handle di file oltre i limiti di una DLL.
La DLL e i file .exe vengono compilati con /MD
, in modo da condividere una singola copia di CRT.
Se si ricompila con /MT
in modo che usino copie separate di CRT, l'esecuzione dei risultati comporta test1Main.exe
una violazione di accesso.
File test1Dll.cpp
di origine 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 );
}
File di origine test1Main.cpp
eseguibile:
// 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
Esempio: Passare variabili di ambiente attraverso il limite della DLL
Descrizione
In questo esempio vengono passate le variabili di ambiente oltre i limiti di una DLL.
File test2Dll.cpp
di origine 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 );
}
File di origine test2Main.cpp
eseguibile:
// 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.
Se si compilano entrambi i file DLL e EXE usando /MD
, in modo che venga usata una sola copia di CRT, il programma viene eseguito correttamente e produce l'output seguente:
New MYLIB variable is: c:\mylib;c:\yourlib