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)