Errores potenciales que pasan los objetos de CRT entre los límites de DLL
El código puede tener errores al pasar objetos de C Runtime (CRT), como identificadores de archivo, configuraciones regionales y variables de entorno dentro o fuera de una DLL. Las llamadas de función a través del límite DLL pueden provocar un comportamiento inesperado si la DLL y los archivos que llaman a la DLL usan copias diferentes de las bibliotecas de CRT.
Un problema relacionado puede aparecer cuando asigna memoria (explícitamente con new
o malloc
, o implícitamente con strdup
, strstreambuf::str
, etc.) y después pasar un puntero a través de un límite de DLL, donde se libera. Tales punteros pueden provocar una infracción del acceso a la memoria o daños en el montón si la DLL y sus consumidores usan copias diferentes de las bibliotecas de CRT.
Otro síntoma de este problema es un error en la ventana de salida durante la depuración, como HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)
Causas
Cada copia de la biblioteca de CRT tiene un estado independiente y distinto, que su aplicación o DLL mantiene en el almacenamiento local de subprocesos.
Los objetos de CRT como identificadores de archivos, variables de entorno y configuraciones regionales solo son válidos para la copia de CRT en la aplicación o la DLL donde se asignan o se establecen estos objetos. Cuando una DLL y sus clientes usan copias diferentes de la biblioteca CRT, no se puede esperar que estos objetos de CRT se usen correctamente cuando se pasan a través del límite de DLL.
Es especialmente cierto para las versiones de CRT previas a Universal CRT en Visual Studio 2015 y versiones posteriores. Había una biblioteca de CRT específica para cada una de las versiones de Visual Studio compiladas con Visual Studio 2013 o anterior. Los detalles de implementación internos de CRT, como sus estructuras de datos y las convenciones de nomenclatura, eran diferentes en cada versión. Nunca se ha admitido la vinculación dinámica del código compilado para una versión de CRT a una versión diferente de la DLL de CRT. En ocasiones funcionaría, pero debido a la suerte y no el diseño.
Cada copia de la biblioteca CRT tiene su propio administrador de montón. Puede provocar daños en el montón si asigna memoria en una biblioteca CRT y pasa el puntero a través de un límite DLL para que lo libere una copia diferente de la biblioteca CRT. Si la DLL pasa objetos de CRT a través del límite de la DLL o asigna memoria que se ha liberado fuera de la DLL, los clientes de la DLL deben usar la misma copia de la biblioteca CRT que la DLL.
El archivo DLL y sus clientes usan normalmente la misma copia de la biblioteca CRT solo si ambos están vinculados en tiempo de carga a la misma versión del archivo DLL de CRT. Dado que la versión de DLL de la biblioteca Universal CRT usada por Visual Studio 2015 y posteriores es ahora un componente de Windows implementado de forma centralizada (ucrtbase.dll
), es la misma para las aplicaciones compiladas con Visual Studio 2015 y versiones posteriores. Pero incluso cuando el código de CRT es idéntico, no se puede dar memoria asignada en un montón a un componente que use un montón diferente.
Ejemplo: Paso del manipulador de archivo a través de los límites de DLL
Descripción
En este ejemplo se pasa un identificador de archivo a través de un límite de DLL.
Los archivos DLL y .exe se compilan con /MD
, de modo que comparten una única copia de CRT.
Si recompila con /MT
para que usen copias independientes de CRT, la ejecución del archivo test1Main.exe
resultante produce una infracción de acceso.
Archivo de origen DLL 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 );
}
Archivo de origen ejecutable 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
Ejemplo: Paso de variables de entorno a través de los límites de DLL
Descripción
Este ejemplo pasa variables de entorno a través de un límite de DLL.
Archivo de origen DLL 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 );
}
Archivo de origen ejecutable 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.
Si tanto el archivo DLL como el archivo .exe se compilan con /MD
para usar solo una copia de CRT, el programa se ejecuta correctamente y genera el siguiente resultado:
New MYLIB variable is: c:\mylib;c:\yourlib
Consulte también
Archivos .lib
de tiempo de ejecución de C (CRT) y biblioteca estándar de C++ (STL)