DLL 경계를 넘어 CRT 개체를 전달할 때 발생할 수 있는 오류Potential Errors Passing CRT Objects Across DLL Boundaries

파일 핸들, 로캘 및 환경 변수와 같은 CRT(C 런타임) 개체를 DLL 내외로 전달하면(DLL 경계를 넘어선 함수 호출) DLL과 DLL로 호출되는 파일에 다른 CRT 라이브러리 복사본이 사용될 경우 예기치 않은 동작이 발생할 수 있습니다.When you pass C Run-time (CRT) objects such as file handles, locales, and environment variables into or out of a DLL (function calls across the DLL boundary), unexpected behavior can occur if the DLL, as well as the files calling into the DLL, use different copies of the CRT libraries.

메모리를 할당(new 또는 malloc를 사용하여 명시적으로나 strdup, strstreambuf::str 등을 사용하여 암시적으로)한 다음 해제되도록 DLL 경계를 넘어 포인터를 전달하면 관련 문제가 발생할 수 있습니다.A related problem can occur when you allocate memory (either explicitly with new or malloc, or implicitly with strdup, strstreambuf::str, and so on) and then pass a pointer across a DLL boundary to be freed. DLL 및 해당 사용자가 다른 CRT 라이브러리 복사본을 사용할 경우 이로 인해 메모리 액세스 위반 또는 힙 손상이 발생할 수 있습니다.This can cause a memory access violation or heap corruption if the DLL and its users use different copies of the CRT libraries.

또한 이 문제로 인해 디버깅 중 출력 창에 다음과 같은 오류가 발생할 수 있습니다.Another symptom of this problem can be an error in the output window during debugging such as:

HEAP[]: RtlValidateHeap(#,#)에 잘못된 주소가 지정되었습니다.HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

원인Causes

CRT 라이브러리 복사본마다 별도의 고유한 상태가 앱 또는 DLL에 의해 스레드 로컬 저장소에 유지됩니다.Each copy of the CRT library has a separate and distinct state, kept in thread local storage by your app or DLL. 따라서 파일 핸들, 환경 변수 및 로캘과 같은 CRT 개체가 이러한 개체가 할당 또는 설정된 앱 또는 DLL의 CRT 복사본에 대해서만 유효합니다.As such, CRT objects such as file handles, environment variables, and locales are only valid for the copy of the CRT in the app or DLL where these objects are allocated or set. DLL 및 해당 앱 클라이언트가 다른 CRT 라이브러리 복사본을 사용하면 이러한 CRT 개체를 DLL 경계를 넘어 전달할 때 해당 개체가 반대쪽에서 올바르게 수신되지 않을 수 있습니다.When a DLL and its app clients use different copies of the CRT library, you cannot pass these CRT objects across the DLL boundary and expect them to be picked up correctly on the other side. Visual Studio 2015 이상의 유니버설 CRT 이전 버전에서 이러한 현상이 특히 두드러집니다.This is particularly true of CRT versions before the Universal CRT in Visual Studio 2015 and later. Visual C++ 2013 또는 이전 버전으로 빌드된 모든 버전의 Visual Studio에 대해 버전별 CRT 라이브러리가 있습니다.There was a version-specific CRT library for every version of Visual Studio built with Visual C++ 2013 or earlier. 해당 데이터 구조 및 명명 규칙과 같은 CRT의 내부 구현 세부 사항은 버전마다 다릅니다.Internal implementation details of the CRT, for example, its data structures and naming conventions, were different in each version. 한 버전의 CRT에 대해 컴파일된 코드를 다른 버전의 CRT DLL로 동적으로 연결하는 방식은 절대 지원되지 않았습니다. 가끔씩 이러한 경우가 있더라도 우연에 의한 것이엇습니다.Dynamically linking code compiled for one version of the CRT to a different version of the CRT DLL has never been supported, though occasionally it would work, more by luck than by design.

또한 CRT 라이브러리 복사본마다 자체 힙 관리자가 있기 때문에 한 CRT 라이브러리에서 메모리를 할당한 다음 다른 CRT 라이브러리 복사본에 의해 해제되도록 DLL 경계를 넘어 포인터를 전달하는 것은 힙 손상의 잠재적 원인이 될 수 있습니다.Also, because each copy of the CRT library has its own heap manager, allocating memory in one CRT library and passing the pointer across a DLL boundary to be freed by a different copy of the CRT library is a potential cause for heap corruption. DLL이 경계를 넘어 CRT 개체를 전달하거나 메모리를 할당한 다음 DLL 외부에서 해제되도록 DLL을 디자인하면 DLL과 동일한 CRT 라이브러리 복사본을 사용하도록 DLL의 앱 클라이언트를 제한하게 됩니다.If you design your DLL so that it passes CRT objects across the boundary or allocates memory and expects it to be freed outside the DLL, you restrict the app clients of the DLL to use the same copy of the CRT library as the DLL. DLL 및 해당 클라이언트는 일반적으로 로드 타임에 둘 다 동일한 버전의 CRT DLL에 연결된 경우에만 동일한 CRT 라이브러리 복사본을 사용합니다.The DLL and its clients normally use the same copy of the CRT library only if both are linked at load time to the same version of the CRT DLL. Windows 10의 Visual Studio 2015 이상에서 사용되는 유니버설 CRT 라이브러리의 DLL 버전은 이제 중앙에서 배포된 Windows 구성 요소인 ucrtbase.dll이므로 Visual Studio 2015 및 이상 버전으로 빌드한 앱에 대해 동일합니다.Because the DLL version of the Universal CRT library used by Visual Studio 2015 and later on Windows 10 is now a centrally deployed Windows component, ucrtbase.dll, it is the same for apps built with Visual Studio 2015 and later versions. 그러나 CRT 코드가 동일하더라도 한 힙에 할당된 메모리를 다른 힙을 사용하는 구성 요소로 전달할 수 없습니다.However, even when the CRT code is identical, you can't hand off memory allocated in one heap to a component that uses a different heap.

Example

설명Description

이 예제에서는 DLL 경계를 넘어 파일 핸들을 전달합니다.This example passes a file handle across a DLL boundary.

DLL 및 .exe 파일은 /MD를 사용하여 빌드되므로 단일 CRT 복사본을 공유합니다.The DLL and .exe file are built with /MD, so they share a single copy of the CRT.

서로 다른 CRT 복사본을 사용하도록 /MT를 사용하여 다시 빌드할 경우 초래되는 test1Main.exe 결과를 실행하면 액세스 위반이 발생합니다.If you rebuild with /MT so that they use separate copies of the CRT, running the resulting test1Main.exe results in an access violation.

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

Example

설명Description

이 예제에서는 DLL 경계를 넘어 환경 변수를 전달합니다.This example passes environment variables across a DLL boundary.

// 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  
// 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 복사본이 하나만 사용되도록 /MD를 사용하여 DLL과 .exe 파일을 모두 빌드하면 프로그램이 실행되고 다음과 같은 출력이 생성됩니다.If both the DLL and .exe file are built with /MD so that only one copy of the CRT is used, the program runs successfully and produces the following output:

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

참고 항목See Also

CRT 라이브러리 기능CRT Library Features