Partilhar via


Possíveis erros ao passar objetos CRT através dos limites da DLL

Seu código pode ter erros quando você passa objetos C Runtime (CRT), como identificadores de arquivo, localidades e variáveis de ambiente para dentro ou fora de uma DLL. As chamadas de funções através dos limites da DLL podem causar um comportamento inesperado se a DLL e quaisquer ficheiros que a invocam utilizarem cópias diferentes das bibliotecas CRT.

Um problema relacionado pode ocorrer quando você aloca memória (explicitamente com new ou malloc, ou implicitamente com strdup, strstreambuf::stre assim por diante) e, em seguida, passa um ponteiro através de um limite de DLL onde ele é liberado. Esses ponteiros podem causar uma violação de acesso à memória, ou corrupção do heap, se a DLL e os seus consumidores estiverem a usar cópias diferentes das bibliotecas CRT.

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

Causas

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

Objetos CRT, como identificadores de arquivo, variáveis de ambiente e localidades, só são válidos para a cópia do CRT no aplicativo ou DLL onde 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 através do limite da DLL.

É especialmente verdadeiro para versões CRT antes do CRT Universal no Visual Studio 2015 e posterior. Existia uma biblioteca CRT específica para cada versão do Visual Studio que foi desenvolvida com o Visual Studio 2013 ou anterior. Os detalhes de implementação interna do CRT, como estruturas de dados e convenções de nomenclatura, foram diferentes em cada versão. A vinculação dinâmica do código que foi compilado para uma versão do CRT a uma versão diferente da DLL CRT nunca foi suportada. Ocasionalmente funcionava, mas por sorte e não por design.

Cada cópia da biblioteca CRT tem seu próprio gerenciador de heap. Ele pode causar corrupção de pilha se você alocar memória em uma biblioteca CRT e passar o ponteiro através de um limite de DLL para ser liberado por uma cópia diferente da biblioteca CRT. Se sua DLL passa objetos CRT através do 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 como a DLL.

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

Exemplo: Passar o identificador de arquivo através do limite da DLL

Descrição

Este exemplo transfere um identificador de arquivo através da fronteira de uma DLL.

Os arquivos DLL e .exe são criados com /MD, para que compartilhem uma única cópia do CRT.

Se reconstruir com /MT para que utilizem cópias separadas do CRT, executar o test1Main.exe resultante vai resultar numa violação de acesso.

Arquivo de origem 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 );
}

Arquivo executável de origem 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

Exemplo: Passar variáveis de ambiente através do limite da DLL

Descrição

Este exemplo passa variáveis de ambiente através de um limite de DLL.

Arquivo de origem 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 );
}

Arquivo executável de origem 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.

Se criar ambos os ficheiros DLL e EXE usando /MD, de modo a que apenas uma cópia do CRT seja utilizada, o programa será executado com sucesso e produzirá a seguinte saída:

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

Ver também

Ficheiros de tempo de execução de C (CRT) e C++ Standard Library (STL) .lib