說道“動態庫版本相容”,很多人頭腦中首先蹦出的就是“Dll Hell”。啊,這曾經讓人頭疼的難題。時至今日,這個難題已經很好地解決了。
在進一步討論之前來思考一個問題:Linux下為什麼沒有讓人頭痛的“DllHell”。
回答這個問題,非常easy,因為——Linux下根本沒有dll。
哈哈,當然這隻是個玩笑,接下來展開一下這個話題,很多有動態庫的系統都會面臨這個難題,但各自解決的思路卻各不相同。
Dll hell是指windows 上動態庫新版本覆蓋舊版本,但是卻不相容老版本。常常發生在程式升級之後,動態庫更新,原有程式運行不起來;或者裝新軟體,但是已有的軟體運行不起來。
一、linux下的解決方案——命名規範
Linux 上的Dll ,叫sharedlibrary。Linux 系統面臨和Window一樣的問題,如何控制動態庫的多個版本問題。為解決這個問題,Linux 為解決這個問題,引入了一套命名機制,如果遵守這個機制來做,就可以避免這個問題。但是這隻事一個約定,不是強制的。但是建議遵守這個約定,否則同樣也會出現 Linux 版的Dll hell 問題。
Real Name
首先是共用庫本身的檔案名稱:共用庫的命名必須如 libname.so.x.y.z
最前面使用首碼”lib”,中間是庫的名字和尾碼”.so”,最後三個數字是版本號碼。x是主要版本號(Major Version Number),y是次版本號碼(Minor Version Number),z是發布版本號碼(Release Version Number)。
主要版本號(不相容):重大升級,不同主要版本的庫之間的庫是不相容的。所以如果要保證向後相容就不能刪除舊的動態庫的版本。
次版本號碼(向下相容): 增量升級,增加一些新的介面但保留原有介面。高次版本號碼的庫向後相容低次版本號碼的庫。
發布版本號碼(相互相容):庫的一些諸如錯誤修改、效能改進等,不添加新介面,也不更改介面。主要版本號和此版本號碼相同的前提下,不同發布版本之間完全相容。
SO-NAME
嚴格遵守上述規定,確實能避免動態庫因為版本衝突的問題,但是讀者可能有疑問:在程式載入或啟動並執行時候,動態連結器是如何知道程式依賴哪些庫,如何選擇庫的不同版本。
Solaris和Linux等採用SO-NAME( Shortfor shared object name )的命名機制來記錄共用庫的依賴關係。每個共用庫都有一個對應的“SO-NAME”(共用庫檔案名稱去掉次版本號碼和發布版本號碼)。比如一個共用庫名為libtest.so.3.8.2,那麼它的SO-NAME就是libtest.so.3。
在Linux系統中,系統會為每個共用庫所在的目錄建立一個跟SO-NAME相同的並且指向它的軟串連(Symbol Link)。這個軟串連會指向目錄中主要版本號相同、次版本號碼和發布版本號碼最新的共用庫。也就是說,比如目錄中有兩個共用庫版本分別為:/lib/libtest.so.3.8.2和/lib/libtest.so.3.7.5,麼軟串連/lib/libtest.so.3指向/lib/libtest.so.3.8.2。
建立以SO-NAME為名字的軟串連的目的是,使得所有依賴某個共用庫的模組,在編譯、連結和運行時,都使用共用庫的SO-NAME,而不需要使用詳細版本號碼。在編譯生產ELF檔案時候,如果檔案A依賴於檔案B,那麼A的連結檔案中的”.dynamic”段中會有DT_NEED類型的欄位,欄位的值就是B的SO-NAME。這樣當動態連結器進行共用庫依賴檔案尋找時,就會依據系統中各種共用庫目錄中的SO-NAME軟串連自動定向到最新相容版本的共用庫。
★ readelf -d sharelibrary 可以查看so-name
★ Linux提供了一個工具——ldconfig,當系統中安裝或更新一個共用庫時,需要運行這個工具,它會遍曆預設所有共用庫目錄,比如/lib, /usr/lib等,然後更新所有的軟連結,使她們指向最新共用庫。
Link Name
當我們在編譯器裡使用共用庫的時候,如用GCC的“-l”參數連結共用庫libtXXX.so.3.8.1,只需要在編譯器命令列指定 -l XXX 即可,省略了首碼和版本資訊。編譯器會根據當前環境,在系統中的相關路徑(往往由-L參數指定)尋找最新版本的XXX庫。這個XXX就是共用庫的“連結名”。不同類型的庫可能有相同的連結名,比如C語言運行庫有靜態版本(libc.a)也動態版本(libc.so.x.y.z)的區別,如果在連結時使用參數”-lc”,那麼連接器就會根據輸出檔案的情況(動態/靜態)來選擇合適版本的庫。eg. ld使用“-static”參數時嗎,”-lc”會尋找libc.a;如果使用“-Bdynamic”(預設),會尋找最新版本的libc.so.x.y.z。
更詳細可以參見
http://www.linuxidc.com/Linux/2012-04/59071.htm
.Net下的解決方案——Manifest檔案
.Net架構中,一個程式集(Assembly)有兩種類型:應用程式程式集(.exe)與庫程式集(DLL動態連結程式庫)。一個程式集包括一個或個檔案,所以需要一個資訊清單檔(Manifest檔案)來描述程式集。Manifest檔案描述了程式集的名字,版本號碼以及程式集的各種資源,同時也描述了該程式集的運行所依賴的資源,包括DLL以及其他資源檔等。Manifest是一個XML檔案。每個DLL有自己的Manifest。對於應用程式而言,manifest檔案可以和可執行檔在同一目錄,也可以作為一個資源嵌入到可執行檔內部(Embed Manifest)。
XP以前的windows版本,在執行可執行檔是不會考慮manifest檔案的。它會直接到system32目錄下尋找可執行檔鎖依賴的DLL。在這種情況下,manifest是多餘的。XP之後的作業系統,在執行可執行檔時則會首先讀取程式集的manifest檔案,獲得該可執行檔需要調用的DLL列表,作業系統再根據DLL的manifest檔案去尋找對應的DLL調用。
Windows的解決方案——COM組件
採用標準COM組件,有很多好處:面向介面和對象程式設計語言無關性,採用二進位標準,可以實現跨語言調用版本升級方便,增加新介面,組件升級後老客戶程式不用重新編譯位置透明,客戶程式不用關心組件的位置重用方便,通過包容和彙總可以快速重用已有組件我們可以看到標準COM組件非常強大,但是很多時候我們並不需要標準COM組件的所有特性,比如我們不希望引入註冊表,也不希望引入COM運行庫,我們希望我們的程式是完全“綠色”的。這時我們就會採用“COM思想架構“開發非標準的COM組件。
轉自 http://blog.sina.com.cn/s/blog_5cf54f0e0101cpct.html