跨越 DLL 界限傳遞 CRT 物件可能發生的錯誤
當您傳遞 c 執行階段 (CRT) 物件例如檔案控制代碼、 地區設定,以及環境變數進出 DLL (跨越 DLL 界限的函式呼叫),如果 DLL,以及呼叫 DLL 時,檔案使用不同的 CRT 程式庫的複本,就會發生未預期的行為。
當您配置記憶體時,就可能發生的相關的問題 (或是明確地用new或malloc,或隱含的strdup, strstreambuf::str,依此類推),然後將指標傳遞跨越 DLL 界限被釋放。 如果 DLL 和它的使用者使用不同的 CRT 程式庫的複本,這會導致記憶體存取違規或堆積損毀。
這個問題的另一個現象可以是 [輸出] 視窗中的錯誤,例如偵錯期間:
堆積 []: 不正確的位址,指定用來 RtlValidateHeap(#,#)
原因
每一份 CRT 程式庫有分離並區隔的狀態。 因此,CRT 物件例如檔案控制代碼,環境變數,和地區設定才有效,這些物件會配置,或是設定的 CRT 的副本。 DLL 和它的使用者使用時的 CRT 程式庫的不同複本,您不能跨 DLL 界限傳遞這些 CRT 物件又被挑出正確另一端所預期。
此外,每一份 CRT 程式庫有它自己的堆集管理,因為配置一個 CRT 程式庫中的記憶體,並將指標傳遞跨越 DLL 界限來釋出不同的複本,CRT 程式庫是堆積損毀的潛在原因。
如果您設計您的 DLL,讓它跨界限傳遞 CRT 物件或配置記憶體,並預期它被釋放的 DLL 以外,會限制 DLL 峈作為 DLL 中的 CRT 程式庫的相同複本。 DLL 和它的使用者使用 CRT 程式庫的相同複本,只有當兩者都連結的 CRT DLL 相同的版本。 如果您混用建置以 Visual C++ 5.0,對於由 Visual C++ 4.1 或更早版本所建置的 Dll 的應用程式,這可能是問題。 因為 Visual C++ 4.1 所使用的 CRT 程式庫的 DLL 版本是 msvcrt40.dll,而且 Visual 5.0 所使用的是 msvcrt.dll,無法建立您的應用程式,作為這些 Dll 的 CRT 程式庫的相同複本。
但是,沒有例外狀況。 在美式英文版本,以及一些其他當地語系化版本的 Windows 2000 中,例如德文、 法文和捷克文轉寄站的版本 msvcrt40.dll (4.20 版) 在推出。 如此一來,即使該 DLL 是使用 msvcrt40.dll and 其使用者均結合 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 界限,傳遞的檔案控制代碼。
因此它們會共用一份的 CRT DLL 和.exe 檔就是以 /MD,建置。
如果您以重建 /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