Erros em potencial passando por objetos CRT em limites de DLL

Seu código pode ter erros quando você passa objetos CRT (Runtime de C), como identificadores de arquivo, localidades e variáveis de ambiente para dentro ou para fora de uma DLL. As chamadas de função no limite da DLL podem causar um comportamento inesperado se a DLL e quaisquer arquivos que chamam a DLL usarem cópias diferentes das bibliotecas CRT.

Um problema relacionado pode ocorrer ao alocar memória (explicitamente com new ou malloc, ou implicitamente com strdup, strstreambuf::str etc.) e depois passar um ponteiro além do limite da DLL em que é liberado. Esses ponteiros poderão causar uma violação de acesso da memória ou corrupção de heap, se a DLL e seus consumidores estiverem usando cópias diferentes das bibliotecas CRT.

Outro sintoma desse problema é um erro na janela de saída durante a depuração, por exemplo: HEAP[]: Invalid Address specified to RtlValidateHeap(#,#).

Causas

Cada cópia da biblioteca de CRT tem um estado separado e distinto, mantido no armazenamento local de thread por seu aplicativo ou DLL.

Objetos CRT, como identificadores de arquivos, variáveis de ambiente e localidades, só serão válidos para a cópia do CRT no aplicativo ou DLL em que esses objetos foram alocados ou definidos. Quando uma DLL e seus clientes usam cópias diferentes da biblioteca CRT, você não pode esperar que esses objetos CRT sejam usados corretamente quando passados pelo limite de DLL.

Isso vale, especificamente, para as versões de CRT anteriores ao CRT Universal no Visual Studio 2015 e posterior. Havia uma biblioteca de CRT específica de versão para cada versão do Visual Studio, compilada com o Visual Studio 2013 ou anterior. Os detalhes da implementação interna do CRT, como suas estruturas de dados e convenções de nomenclatura, eram diferentes em cada versão. Nunca houve suporte para a vinculação dinâmica do código compilado para uma versão do CRT com outra versão da DLL do CRT. Ocasionalmente, funcionaria, mais por sorte do que por design.

Cada cópia da biblioteca CRT tem seu próprio gerenciador de heap. Isso poderá causar corrupção de heap se você alocar memória em uma biblioteca CRT e passar o ponteiro por um limite de DLL para ser liberado por uma cópia diferente da biblioteca CRT. Se a sua DLL passa objetos CRT pelo limite da DLL, ou aloca memória que é liberada fora da DLL, os clientes da DLL devem usar a mesma cópia da biblioteca CRT que a DLL.

A DLL e seus clientes normalmente usam a mesma cópia da biblioteca de CRT somente se estiverem vinculados no momento do carregamento à mesma versão da DLL do CRT. Como a versão da DLL da biblioteca CRT Universal usada pelo Visual Studio 2015 e posterior agora é um componente do Windows implantado centralmente (ucrtbase.dll), é a mesma para aplicativos criados com o Visual Studio 2015 e versões posteriores. No entanto, mesmo quando o código CRT é idêntico, não é possível fornecer memória alocada em um heap para um componente que use um heap diferente.

Exemplo: passar o identificador de arquivo pelo limite da DLL

Descrição

Este exemplo passa um identificador de arquivo por um limite de DLL.

A DLL e o arquivo .exe são criados com /MD. Portanto, eles compartilham uma única cópia do CRT.

Se você recompilar com /MT para que usem cópias separadas do CRT, a execução do test1Main.exe resultante poderá gerar uma violação de acesso.

Arquivo test1Dll.cpp de origem da 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 );
}

Arquivo test1Main.cpp de origem executável:

// 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

Exemplo: passa as variáveis de ambiente pelo limite da DLL

Descrição

Este exemplo passa as variáveis de ambiente por um limite de DLL.

Arquivo test2Dll.cpp de origem da 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 );
}

Arquivo test2Main.cpp de origem executável:

// 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 você criar os arquivos EXE e DLL usando /MD, para que apenas uma cópia do CRT seja usada, o programa será executado com êxito e produzirá a seguinte saída:

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

Confira também

Arquivos .lib de runtime do C (CRT) e Biblioteca Padrão (STL) do C++