DLL 경계를 넘어 CRT 개체를 전달할 때 발생할 수 있는 오류

파일 핸들, 로캘 및 환경 변수와 같은 CRT(C 런타임) 개체를 DLL로 들어오거나 나가는 경우 코드에 오류가 발생할 수 있습니다. DLL 경계를 가로지르는 함수 호출은 DLL 및 DLL을 호출하는 모든 파일이 CRT 라이브러리의 다른 복사본을 사용하는 경우 예기치 않은 동작을 일으킬 수 있습니다.

메모리를 명시적으로 또는 암시적으로 newstrdupstrstreambuf::str할당한 malloc다음 해제된 DLL 경계에 포인터를 전달할 때 관련 문제가 발생할 수 있습니다. DLL과 해당 소비자가 CRT 라이브러리의 다른 복사본을 사용하는 경우 이러한 포인터로 인해 메모리 액세스 위반 또는 힙 손상이 발생할 수 있습니다.

이 문제의 또 다른 증상은 다음과 같이 디버깅하는 동안 출력 창의 오류입니다. HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

원인

CRT 라이브러리 복사본마다 별도의 고유한 상태가 앱 또는 DLL에 의해 스레드 로컬 스토리지에 유지됩니다.

파일 핸들, 환경 변수 및 로캘과 같은 CRT 개체는 이러한 개체가 할당되거나 설정된 앱 또는 DLL의 CRT 복사본에만 유효합니다. DLL 및 해당 클라이언트가 CRT 라이브러리의 다른 복사본을 사용하는 경우 DLL 경계를 넘어 전달될 때 이러한 CRT 개체가 올바르게 사용될 것으로 기대할 수 없습니다.

특히 Visual Studio 2015 이상에서 유니버설 CRT 이전의 CRT 버전에 해당합니다. Visual Studio 2013 또는 이전 버전으로 빌드된 모든 버전의 Visual Studio에 대해 버전별 CRT 라이브러리가 있습니다. 데이터 구조 및 명명 규칙과 같은 CRT의 내부 구현 세부 정보는 각 버전에서 달랐습니다. CRT의 한 버전에 대해 컴파일된 코드를 다른 버전의 CRT DLL에 동적으로 연결하는 것은 지원되지 않습니다. 때로는 작동하지만 디자인보다는 행운 때문에 작동합니다.

CRT 라이브러리의 각 복사본에는 고유한 힙 관리자가 있습니다. 한 CRT 라이브러리에 메모리를 할당하고 CRT 라이브러리의 다른 복사본에 의해 해제되도록 DLL 경계를 넘어 포인터를 전달하는 경우 힙이 손상될 수 있습니다. DLL이 DLL 경계를 넘어 CRT 개체를 전달하거나 DLL 외부에서 해제된 메모리를 할당하는 경우 DLL의 클라이언트는 DLL과 동일한 CRT 라이브러리 복사본을 사용해야 합니다.

DLL 및 해당 클라이언트는 일반적으로 로드 타임에 둘 다 동일한 버전의 CRT DLL에 연결된 경우에만 동일한 CRT 라이브러리 복사본을 사용합니다. Visual Studio 2015 이상에서 사용하는 유니버설 CRT 라이브러리의 DLL 버전은 이제 중앙에서 배포된 Windows 구성 요소()ucrtbase.dll이므로 Visual Studio 2015 이상 버전으로 빌드된 앱과 동일합니다. 그러나 CRT 코드가 동일한 경우에도 한 힙에 할당된 메모리를 다른 힙을 사용하는 구성 요소에 제공할 수 없습니다.

예: DLL 경계를 넘어 파일 핸들 전달

설명

이 예제에서는 DLL 경계를 넘어 파일 핸들을 전달합니다.

DLL 및 .exe 파일은 CRT의 단일 복사본을 공유할 수 있도록 빌드 /MD됩니다.

CRT의 별도 복사본을 사용하도록 다시 /MT 빌드하면 액세스 위반이 발생합니다 test1Main.exe .

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 );
}

실행 파일 원본 파일 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

예: DLL 경계를 넘어 환경 변수 전달

설명

이 예제에서는 DLL 경계를 넘어 환경 변수를 전달합니다.

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 );
}

실행 파일 원본 파일 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.

CRT의 복사본 하나만 사용되도록 DLL 및 EXE 파일을 /MD모두 빌드하면 프로그램이 성공적으로 실행되고 다음 출력이 생성됩니다.

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

참고 항목

C 런타임(CRT) 및 C++ STL(표준 라이브러리) .lib 파일