跨 DLL 边界传递 CRT 对象时可能的错误Potential Errors Passing CRT Objects Across DLL Boundaries

当你将 C 运行时 (CRT) 对象(例如,文件句柄、区域设置和环境变量)传入或传出 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.

如果你分配内存(使用 newmalloc 显式分配,或使用 strdupstrstreambuf::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 对象(例如,文件句柄、环境变量和区域设置)仅对在其中分配或设置这些对象的 CRT 的应用或 DLL 的副本有效。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 库副本时,你无法跨 DLL 边界传递这些 CRT 对象,也无法期望在另一侧正确地选取它们。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 版本之前的 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 库中的内存并跨 DLL 边界传递由 CRT 库的不同副本释放的指针可能是导致堆损坏的原因。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 库副本。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.

使用 /MD 生成 DLL 和 .exe 文件,二者共享单个 CRT 副本。The DLL and .exe file are built with /MD, so they share a single copy of the CRT.

如果使用 /MT 重新生成它们以使其使用单独的 CRT 副本,则运行生成的 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.  

如果使用 /MD 同时生成 DLL 和 .exe 文件以便仅使用一个 CRT 副本,则此程序将成功运行并产生以下输出: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