Поделиться через


Потенциальные ошибки при передаче объектов CRT через границы DLL

При передаче объектов среды выполнения C, таких как дескрипторы файлов, языковые стандарты и переменные среды, в библиотеку DLL или из нее (вызовы функций через границы DLL), может возникнуть непредвиденное поведение, если библиотека DLL, а также файлы вызывающие функции из DLL, используют различные копии библиотек CRT.

Связанная проблема может возникнуть при выделении памяти (явно с использованием new или malloc или неявно с использованием strdup, strstreambuf::str и т. д.) и последующей передаче указателя через границу библиотеки DLL для освобождения. Это может вызвать повреждение кучи или нарушение доступа к памяти, если библиотека DLL и ее пользователи используют различные копии библиотек CRT.

Другим признаком этой проблемы может быть ошибка в окне вывода во время отладки, например:

HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

Причины

Каждая копия библиотеки CRT имеет отдельное и отличное от других состояние. В этом случае объекты CRT, такие как дескрипторы файлов, переменные среды и языковые стандарты, допустимы только для той копии CRT, в которой эти объекты были размещены или установлены. Когда библиотека DLL и ее пользователи используют различные копии библиотеки CRT, нельзя передавать эти CRT объекты через границы DLL и ожидать, что они будут корректно приняты на другой стороне.

Кроме того, поскольку все копии библиотеки CRT имеют свой собственный менеджер кучи, распределяя память в одной библиотеке CRT и передавая указатель через границу DLL для освобождения другой копией библиотеки CRT, можно вызвать повреждение кучи.

Если вы разрабатываете библиотеку DLL так, что она передает объекты CRT через границы или распределяет память и ожидает ее освобождения вне DLL, вы ограничиваете пользователям возможность использовать ту же копию библиотеки CRT, что использует DLL. Библиотека DLL и ее пользователи используют одну и ту же копию библиотеки CRT, только если они связаны с одной и той же версией DLL CRT. Это может стать проблемой, если вы совмещаете приложения, созданные с помощью Visual C++ 5.0 с DLL, которые созданы с помощью версии Visual C++ 4.1 или более ранних. Поскольку версия DLL библиотеки CRT, используемая Visual C++ 4.1, — msvcrt40.dll, а версия, используемая 5.0, — msvcrt.dll, нельзя создать приложение, которое будет использовать ту же копию библиотеки CRT, что и эти DLL.

Однако существует исключение. C американской английской версией и некоторыми другими локализованными версиями Windows 2000, например немецкой, французской и чешской, поставляется версия msvcrt40.dll с пересылкой (версия 4.20). В результате, даже если библиотека DLL связана с msvcrt40.dll и ее пользователь связан с msvcrt.dll, можно по-прежнему использовать ту же копию библиотеки CRT, поскольку все вызовы, выполняемые в msvcrt40.dll переадресуются в msvcrt.dll.

Однако эта версия msvcrt40.dll с пересылкой недоступна в некоторых локализованных версиях Windows 2000 (например, японский, корейской и китайской). Таким образом, если приложение нацелено на эти операционные системы, то необходимо либо получить обновленную версию библиотеки DLL, не зависящую от msvcrt40.dll, либо изменить приложение так, чтобы оно не полагалось на использование одной копии библиотек CRT. Если вы разработали библиотеку DLL, нужно пересоздать ее с использованием Visual C++ 4.2 или более поздней версии. Если это сторонняя библиотека DLL, нужно связаться с поставщиком для обновления.

Обратите внимание на то, что эта версия библиотеки DLL с пересылкой msvcrt40.dll (версия 4.20) не может распространяться.

Пример

Описание

В этом примере дескриптор файла передается через границу библиотеки DLL.

Библиотека DLL и exe-файл создаются с /MD, поэтому они совместно используют один экземпляр CRT.

При повторном создании с /MT таким образом, чтобы в них использовались отдельные копии CRT, запуск получившегося test1Main.exe вызывает нарушение доступа.

Код

// test1Dll.cpp
// compile with: /MD /LD
#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: /MD 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" );
}

Output

this is a string

Пример

Описание

В этом примере переменные среды передаются через границу библиотеки DLL.

Код

// test2Dll.cpp
// compile with: /MT /LD
#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: /MT /link test2dll.lib
#include <stdlib.h>
#include <stdio.h>

void readEnv();

int main( void )
{
   _putenv( "MYLIB=c:\\mylib;c:\\yourlib" );
   readEnv();
}

Output

MYLIB has not been set.

Если библиотека DLL и exe-файл создаются с /MD так, что будет использоваться только одна копия CRT, программа выполняется успешно и выдает следующий результат:

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

См. также

Ссылки

Функции библиотеки CRT