跨越DLL邊界傳遞CRT對象潛在的錯誤

跨越DLL邊界傳遞CRT對象潛在的錯誤

翻譯:magictong(童磊)20135

版權:microsoft 

原文地址:http://msdn.microsoft.com/en-us/library/ms235460(v=vs.80).aspx


簡介

當你把C運行時(CRT)對象(譬如文件句柄、語言環境和環境變量等等)傳入傳出DLL時(通過調用DLL裏面暴露的一些函數),如果這個DLL加載了一份(與可執行文件)不同的CRT庫,可能發現意向不到的事情。

有一個大家可能遇到過相似的問題是,如果可執行程序在外面分配一塊內存(顯示的通過new或者malloc分配,或者通過調用strdupstrstreambuf::str等等函數隱式的分配),然後把分配得到的內存指針通過調用DLL暴露的函數傳入DLL裏面去釋放,如果這個DLL加載了一份不同的CRT庫,可能造成內存訪問違例(AV)或者堆破壞。

如果你正在調試程序,這個問題可能在調試輸出窗口打印一條如下的錯誤信息:

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


原因

每個CRT庫的副本都有一個單獨並且獨特的狀態,因此,一些類似文件句柄,語言環境和環境變量這樣的CRT對象,僅僅在分配它或者設置它的CRT庫裏面有效。當一個DLL和使用這個DLL的程序分別使用了兩個不同的CRT庫時,如果你把這些CRT對象在DLL和使用DLL的程序兩邊相互傳遞,並希望程序運行正常,那是不太可能的。

另外,因爲每個CRT庫的副本都有一個自己的堆管理器,在一個CRT庫裏面分配的內存傳遞(通過調用DLL暴露的函數)到另外一個CRT庫裏面去釋放可能導致堆破壞

如果你想設計一個可以傳遞CRT對象的DLL,或者明確的在DLL內部分配內存,而在DLL的外部釋放,那麼你必須限制這個DLL的使用者使用和DLL一樣的CRT庫。而要想DLL的使用者使用和DLL一樣的CRT庫除非二者鏈接到相同版本的CRT動態鏈接庫。如果應用程序和DLL的編譯環境不一樣,譬如應用程序使用Visual C++5.0編譯而DLL使用Visual C++ 4.1編譯或者更早版本,因爲Visual C++ 4.1使用的CRT庫是msvcrt40.dll,而Visual C++5.0使用的是msvcrt.dll,這可能會非常麻煩。你可能無法重新編譯你的應用程序使得和DLL使用的CRT庫一模一樣。

不過,有一個特殊情況。在一些特殊版本的Windows NT 4.0Windows 2000裏面(譬如,美國英語版本,德國,法國和捷克的本地化版本),使用了一個代理模式的msvcrt40.dll(具體版本是4.20)。在這些環境裏面,雖然DLL鏈接的是msvcrt40.dllDLL的使用者(應用程序)鏈接的是msvcrt.dll,但是因爲所有對msvcrt40.dll的調用都被轉調用到了msvcrt.dll,因此這種情況二者仍然使用的是同一個CRT庫。

然而,這個代理版本的msvcrt40.dllWindows 95,Windows 98,Windows Me和其它一些本地化的Windows NT 4.0Windows 2000(譬如日本,韓國和中國)版本里面是無效的。因此,如果你的應用程序的目標操作系統是這些環境,你需要獲得一個不依賴msvcrt40.dll的升級版本的DLL,或者更改你的應用程序。如果你已經把這個DLL開發出來了,那麼使用Visual C++ 4.2或者更高版本重新編譯一下DLL,如果這個DLL是第三方提供的組件,你可能需要相關開發商提供一個升級。


例子一

描述

這裏是一個跨越DLL邊界傳遞文件句柄的實例。

假如DLL文件和.exe文件都是使用/MD來編譯,那麼它們使用同一份CRT庫,這沒有問題。

如果你使用/MT來重新編譯,使得它們使用不同的CRT庫,然後運行test1Main.exe,馬上就會出現訪問違例(access violation)。

代碼

// 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" );

}

輸出

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();

}

如果DLL.EXE文件都使用/MD來編譯,那麼二者使用同一份CRT庫,此時輸出是New MYLIB variable is: c:\mylib;c:\yourlib,而如果二者都使用/MT編譯,那麼輸出將是MYLIB has not been set.

(注:以上兩個實例,譯者magictong並沒有進行過測試,如果有問題,請告訴magictong


參考文檔

C運行時庫 http://msdn.microsoft.com/en-us/library/abx4dbyh(v=vs.100).aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章