Потенциальные ошибки при передаче объектов CRT через границы DLLPotential 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.

Связанная с этим проблема может возникнуть при выделении памяти (либо явно с использованием 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, такие как дескрипторы файлов, переменные среды и языковые стандарты, применимы только для той копии 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, вы не можете просто передать 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. Это особенно актуально для версий CRT, выпущенных до появления универсальной CRT в Visual Studio версии 2015 и выше.This is particularly true of CRT versions before the Universal CRT in Visual Studio 2015 and later. В Visual Studio 2013 и более ранних версиях создавались специальные библиотеки CRT для каждой версии Visual Studio.There was a version-specific CRT library for every version of Visual Studio built with Visual Studio 2013 or earlier. Внутренняя реализация библиотек CRT, в том числе структуры данных и соглашения об именовании, могли различаться в разных версиях.Internal implementation details of the CRT, for example, its data structures and naming conventions, were different in each version. Динамически связанный код, скомпилированный для конкретной версии CRT, никогда не поддерживался в других версиях DLL CRT. Иногда он мог успешно работать при переносе, но это происходило скорее случайно.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 ту же копию библиотеки 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. В Visual Studio версии 2015 и выше под управлением Windows 10 версия DLL для универсальной библиотеки CRT развертывается централизованно как компонент 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.

При повторной сборке с /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.

Если и библиотека DLL, и exe-файл создаются с /MD так, что будет использоваться только одна копия 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

Функции библиотеки CRTCRT Library Features