STL 中Map容器的跨動態庫調用

在VC自帶STL版本中,兩個動態庫間引用傳遞一個MAP容器。
在實現的動態庫中MAP下
-_Tr{...}
+_Nil0x00000000
導致針對MAP的實現死機。
_Nil0x00000000應該是有值的。
編譯設置應該都是一樣,不知爲何。
使用SGI版本未發現問題


----------------------------------------------------------------------

不要在dll裏導出class。
STL容器的內存管理問題是很複雜的。
SGI版本未發現問題並不表明真的沒問題。


--------------------------------------------------------


問題是STL在不同的庫實現中有不同的代碼
這種狀態下就會有接口不兼容,C++中對STL的容器的內存佈局、如何實現
都沒有做任何假定,不同的實現可以有自己的策略

除非你能夠確定兩個動態庫使用的都是同樣的STL實現
比如都使用VC同一版本的STL,編譯選項也一樣

強烈建議,不要在動態庫接口中傳遞STL容器!!



--------------------------------------------------------

版本都是一樣,VC的STL版本,編譯選項也是一樣的。針對Vector引用的傳遞沒有問題,但是Map就出現問題了。
不在動態庫接口傳遞STL是一個什麼樣的概念。如果我用一個類包裝了STL容器。在另一個動態庫中調用,這個也是很常用的吧。

--------------------------------------------------------

一個可以考慮的方案
比如有兩個動態庫L1和L2,L2需要修改L1中的一個map,那麼我在L1中設置如下接口
int modify_map(int key, int new_value);
如果需要指定“某一個map”,則可以考慮實現一種類似於句柄的方式,比如可以傳遞一個DWORD
不過這個DWORD放的是一個地址

那麼modify_map就可以這樣實現:
int modify_map(DWORD map_handle, int key, int new_value)
{
    std::map<int, int>& themap = *(std::map<int, int>*)map_handle;
    themap[key] = new_value;
}

map_handle的值也首先由L1“告訴”L2:
DWORD get_map_handle();



L2可以這樣調用:
DWORD h = get_map_handle();
modify_map(h, 1, 2);








--------------------------------------------------------

不在動態庫接口傳遞STL是一個什麼樣的概念。

==================================
可以簡單理解爲別在動態庫導出的函數參數中涉及STL

--------------------------------------------------------

包裝STL,然後導出這個包裝類倒是一個可以嘗試的辦法
try it

--------------------------------------------------------

我的意思是,如果有一個類
Class A
{
public:
  VECTOR<INT> A;
}

這個類在其他動態庫中應用不也是屬於動態庫接口傳遞STL。
除非把容器的相關函數進行包裝??

--------------------------------------------------------

弄個接口,不要傳遞類,很難保證不出問題的。

--------------------------------------------------------

mark..

--------------------------------------------------------

把包裝類在接口裏到處是不一樣的效果,這樣一來使用這個包裝類的調用方實際上
也是使用動態庫內部使用的的STL代碼,你想想這是爲什麼?

包裝的時候注意,一定不要再將裏頭的容器暴露出來
比如傳遞這樣的接口:
class A
{
public:
std::map<int,int> innner_;
};

如果你再在調用方這樣調用:
A &a;
a.inner_.find(...)
那就功虧一簣了

--------------------------------------------------------

這種使用方法應該是沒有問題的。
但是,STL難道就一定不能在庫間進行調用?
另外,我覺得這種調用方式應該也比較常見...


--------------------------------------------------------

在VC自帶STL版本中,兩個動態庫間引用傳遞一個MAP容器。
——————————————————————————————————————————
總是說,加入一個額外的層,就可以解決問題。
所以,你需要將你的Map包裝在dll內部,而不是讓它出現在你的接口當中。
你的接口需要簡單、易於使用的!!!

--------------------------------------------------------

動態庫的接口越簡單越好 不好去傳太過複雜的東東是至理名言:)

--------------------------------------------------------

STL不一定不能在DLL間傳遞,但你必須徹底搞懂它的內部實現,並懂得爲何會出問題。


這是我以前回復一位網友的帖子,有關於這方面的內容。

問題現象:
HEAP[ABCD.exe]: Invalid Address specified to RtlValidateHeap( 04060000, 050A9F60 )
Windows has triggered a breakpoint in ABCD.exe.

This may be due to a corruption of the heap, and indicates a bug in ABCD.exe or any of the DLLs it has loaded.




第11樓 回覆於2007-2-1 15:30:05 修改內容  在回覆中引用本文內容
查了下,這似乎是vc6下、通過“一個exe/dll中的指針”指向或者引用“另一個exe/dll中的、包含stl的類”時、這個包含stl的類在析構時就可能出現這樣的問題。


這個問題似乎是ms的實現中所特有的。但沒有看到明確的說法。

沒有找到中文文檔,看這些吧:

微軟的解釋:
http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b172396
微軟給的解決辦法:
http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b168958



這個郵件列表談論了這個問題,不過其中一位認爲這是個普遍問題,並非ms獨有(我還沒有看完。。。):
http://www.thescripts.com/forum/thread60000.html





//當時工作忙,沒來的及看。然後好像是星期天……這是後來又發的:

第14樓 回覆於2007-2-5 9:59:09 修改內容  在回覆中引用本文內容
大致看了一下,也許還有很多地方沒有真正領會。以後有時間再補充或請其他朋友指正吧。



1、微軟的解釋:
大部分C++標準庫裏提供的類直接或間接地使用了靜態變量。由於這些類是通過模板擴展而來的,因此每個可執行映像(通常是.dll或.exe文件)就會存在一份只屬於自己的、給定類的靜態數據成員。當一個需要訪問這些靜態成員的類方法執行時,它使用的是“這個方法的代碼當前所在的那份可執行映像”裏的靜態成員變量。由於兩份可執行映像各自的靜態數據成員並未同步,這個行爲就可能導致訪問違例,或者數據看起來似乎丟失或被破壞了。

可能不太好懂,我舉個例子:假如類A<T>有個靜態變量m_s,那麼當1.exe使用了2.dll中提供的某個A<int>對象時,由於模板擴展機制,1.exe和2.dll中會分別存在自己的一份類靜態變量A<int>.m_s。
這樣,假如1.exe中從2.dll中取得了一個的類A<int>的實例對象a,那麼當在1.exe中直接訪問a.m_s時,其實訪問的是 1.exe中的對應拷貝(正確情況應該是訪問了2.dll中的a.m_s)。這樣就可能導致非法訪問、應當改變的數據沒有改變、不應改變的數據被錯誤地更改等異常情形。

原文:
Most classes in the Standard C++ Libraries use static data members directly or indirectly. Since these classes are generated through template instantiation, each executable image (usually with DLL or EXE file name extensions) will contain its own copy of the static data member for a given class. When a method of the class that requires the static data member is executed, it uses the static data member in the executable image in which the method code resides. Since the static data members in the executable images are not in sync, this action could result in an access violation or data may appear to be lost or corrupted.

2、郵件列表裏的觀點:

郵件列表裏的Conrad Weyns這樣認爲(意譯,就不一句句對應了):
每個.dll或.exe文檔可以看作一個執行單元。而由於stl的特性,每個執行單元中可能會有一個自己的內存分配器(通俗點說,就是堆內存管理器,或者內存池)。當跨越執行單元調用構造/析構函數時,如果這兩個調用所在的執行單元不同,就可能出現通過A的堆管理器去釋放B的堆管理器所分配的對象的問題。這就導致了RtlValidateHeap拋出異常。
他所認爲的正確解決辦法是:使用各種措施,保證程序中只用了一個堆管理器;或者使用智能型的堆管理器(作者建議使用SmartHeap)。




相比之下,我認爲微軟的說法更具普遍意義;而郵件列表中的說法覆蓋的面則要窄一些。



有了上面的基礎知識,就會明白:無論哪種情形,解決辦法都是一致的。
(是我自己的總結。不能代替微軟的說明。可以看作是基於微軟文檔基礎上的再次強調吧。)

1、保證資源的分配/刪除操作對等並處於同一個執行單元;
   比如,可以把這些操作(包括構造/析構函數、某些容器自動擴容{這個需要特別注意}時的內存再分配等)隱藏到接口函數裏面。換句話說:儘量不要直接從dll中輸出stl對象;如果一定要輸出,給它加上一層包裝,然後輸出這個包裝接口而不是原始接口。

2、保證所有的執行單元使用同樣版本的STL運行庫。
   比如,全部使用release庫或debug庫,否則兩個執行單元擴展出來的STL類的內存佈局就可能會不一樣。

--------------------------------------------------------

當初回覆是比較匆忙,沒有仔細覈對,有些意思沒有表達清楚。

只要記住關鍵就是:如果任何STL類使用了靜態變量(無論是直接還是間接使用),那麼就不要再寫出跨執行單元訪問它的代碼。

--------------------------------------------------------

看了樓上的回答,有收穫


--------------------------------------------------------

C++標準沒有說哪些類可以使用靜態變量,所以還是把STL容器類特化之後導出比較好

--------------------------------------------------------

這個問題也遇到過,是MS的STL纔有,SGI不存在這個問題。個人認爲MS的STL做得太差,不要使用

--------------------------------------------------------

shan_ghost 解釋的不錯,甚好。

--------------------------------------------------------

如果任何STL類使用了靜態變量(無論是直接還是間接使用),那麼就不要再寫出跨執行單元訪問它的代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章