跨 DLL 界限傳遞 CRT 物件時可能發生的錯誤

當您將 C 執行時間 (CRT) 物件傳遞至或移出 DLL 時,您的程式碼可能會發生錯誤,例如檔案控制碼、地區設定和環境變數。 如果 DLL 和任何呼叫 DLL 的檔案使用不同的 CRT 程式庫複本,則跨 DLL 界限的函式呼叫可能會導致非預期的行為。

當您配置記憶體時,可能會發生相關問題(明確使用 newmalloc ,或隱含 strdupstrstreambuf::str 使用 、 等等),然後將指標傳遞至釋放的 DLL 界限。 如果 DLL 及其取用者使用不同的 CRT 程式庫複本,這類指標可能會導致記憶體存取違規或堆積損毀。

此問題的另一個徵兆是偵錯期間輸出視窗中的錯誤,例如 HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)

原因

應用程式或 DLL 在執行緒區域儲存區中保留的每一份 CRT 程式庫複本都有個別且不同的狀態。

CRT 物件,例如檔案控制代碼、環境變數和地區設定,僅適用于配置或設定這些物件之應用程式或 DLL 中的 CRT 複本。 當 DLL 及其用戶端使用不同的 CRT 程式庫複本時,您無法在跨 DLL 界限傳遞時正確使用這些 CRT 物件。

在 Visual Studio 2015 和更新版本中的通用 CRT 之前,CRT 版本尤其如此。 使用 Visual Studio 2013 或更早版本建置的每個 Visual Studio 版本都有一個版本特定 CRT 程式庫。 CRT 的內部實作詳細資料,例如資料結構和命名慣例,在每個版本中都不同。 從未支援將一個 CRT 版本編譯的程式碼動態連結至不同版本的 CRT DLL。 偶爾會運作,但因為運氣而不是設計。

CRT 程式庫的每個複本都有自己的堆積管理員。 如果您在一個 CRT 程式庫中配置記憶體,並將指標傳遞至 DLL 界限,以便由 CRT 程式庫的不同複本釋放,則可能會導致堆積損毀。 如果您的 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 檔案是使用 /MD 所建置,因此它們會共用 CRT 的單一複本。

如果您使用 來重建 /MT ,使其使用 CRT 的個別複本,則執行結果 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.

如果您使用 來建置 DLL 和 EXE 檔案 /MD ,因此只使用 CRT 的一個複本,程式就會成功執行,並產生下列輸出:

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

另請參閱

C 執行時間 (CRT) 和 C++ 標準程式庫 (STL) .lib 檔案