這是一個老問題,以前也有朋友寫過一些文章介紹,但可能還不是很全面。我也多次被人問到,這里結合案例再次談談,希望對大家有所幫助。
本文范例代碼可以通過這里下載 http://files.cnblogs.com/chenxizhang/AssemblyMatchDemoSolution.zip
根據程序集的特征,討論這個問題,我們大致上有兩個分類
沒有做強名稱簽名的程序集
對於這種情況,CLR查找和加載程序集的方式如下
- 程序的根目錄
- 根目錄下面,與被引用程序集同名的子目錄
- 根目錄下面被明確定義為私有目錄的子目錄
同時,這種情況下,如果有定義codebase,則codebase的優先級最高,而且如果codebase指定的路徑找不到,則直接報告錯誤,不再查找其他目錄
有做強名稱簽名的程序集
對於這種情況,CLR查找和加載程序集的方式如下
- 全局程序集緩存
- 如果有定義codebase,則以codebase定義為准,如果codebase指定的路徑找不到,則直接報告錯誤
- 程序的根目錄
- 根目錄下面,與被引用程序集同名的子目錄
- 根目錄下面被明確定義為私有目錄的子目錄
我們幫助大家更好地理解以上的說明,我准備用范例來做講解。
1.准備基本范例
下面的范例演示了一個應用程序(MyApplication),和一個類庫(MyLibrary) ,MyApplication是引用了MyLibrary的。
MyLibrary中有一個TestClass類型,提供了一個簡單的方法(SayHello)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyLibrary { public class TestClass { public void SayHello() { //這里為了演示方便,顯示出來當前加載的程序集完整路徑 Console.WriteLine(this.GetType().Assembly.Location); Console.WriteLine("Hello,world"); } } }
在MyApplication中,我們就是簡單地創建了這個類型的實例,然后調用方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyApplication { class Program { static void Main(string[] args) { var c = new MyLibrary.TestClass(); c.SayHello(); Console.Read(); } } }
默認情況下,如果我們編譯整個項目,那么MyLibrary.dll會被自動地復制到MyApplication的根目錄,如下圖所示
運行MyApplication.exe,我們能看到下面這樣的輸出
我們可以很清楚地看到,當前加載的MyLibrary.dll是來自於MyApplication的根目錄的。
2. 假如我們不想將MyLibrary.dll放在應用程序的根目錄
有時候,我們會希望單獨存放MyLibrary.dll,那么第一種做法就是,直接在應用程序根目錄下面建立一個與程序集同名的子目錄,然后將程序集放進去。
我們注意到,根目錄下面的MyLibrary.dll 被移動到了MyLibrary目錄
然后,我們再次運行MyApplication.exe,能看到下面這樣的輸出:
3.假如我們有很多程序集,希望統一放在一個目錄
第二步的方法雖然不錯,但有一個問題,就是如果我們引用的程序集很多的話,就需要在根目錄下面建立很多子目錄。那么,有沒有辦法統一地將這些程序集放在一個目錄中呢?
我們可以通過如下的方式,定義一個特殊的私有路徑(PrivatePath)
<?xml version="1.0" encoding="utf-8" ?> <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="libs"></probing> </assemblyBinding> </runtime> </configuration>
同時,我們將MyLibrary.dll 移動到libs這個子目錄下面去
然后,我們再次運行MyApplication.exe,能看到下面這樣的輸出:
這也就是說,對於沒有簽名的程序集,CLR一般會按照如下的規則查找和加載程序集
- 程序的根目錄
- 根目錄下面,與被引用程序集同名的子目錄
- 根目錄下面被明確定義為私有目錄的子目錄
但是,有一個例外
4. codebase的設置是優先的,而且是排他的
codebase是一個特殊的設置,我們可以在配置文件中明確地指定某個程序集的查找路徑,這個規則具有最高的優先級,而且如果你做了設置,CLR就一定會按照你的設置去查找,如果找不到,它就報告失敗,而不會繼續查找其他路徑。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="libs"/> <dependentAssembly> <assemblyIdentity name="MyLibrary" culture="neutral" /> <codeBase version="1.0.0.0" href="CodeBase\MyLibrary.dll" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
請注意,我們保留了libs目錄和Mylibrary目錄,而且根目錄下面也保留了一個MyLibrary.dll。 實際上,當前我們一共有4個dll. 那么到底會加載哪一個呢?
這種情況下,如果codebase下面找不到MyLibrary.dll 會怎么樣呢?
我們發現他是會報告錯誤的,而不會查找其他目錄的程序集。
5.如果有強名稱簽名會怎么樣呢?
對程序集進行強名稱簽名的好處是,可以將其添加到全局全局程序集緩存中。這樣既可以實現程序集的共享,又可以從一定程度上提高性能。
簽名后,我們將其添加到全局程序集緩存中去
那么這種情況下,不管我們在應用程序根目錄(或者下面的子目錄)有沒有MyLibrary.dll ,CLR都是嘗試先從全局程序集緩存中查找和加載的。
需要注意的是,如果程序集是經過了強名稱簽名,則在定義codebase的時候,應該注明publicKeyToken
<?xml version="1.0" encoding="utf-8" ?> <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="libs"/> <dependentAssembly> <assemblyIdentity name="MyLibrary" publicKeyToken="4a77fca346941a6c" culture="neutral" /> <codeBase version="1.0.0.0" href="CodeBase\MyLibrary.dll" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
總結
本文通過實例講解了CLR在查找和加載程序集的時候所遵循的一些規則,針對有強名稱和沒有強名稱的程序集,這些規則略有不同。本文范例代碼可以通過這里下載 http://files.cnblogs.com/chenxizhang/AssemblyMatchDemoSolution.zip
相關問題
本文還沒有涵蓋到的另外兩個特殊情況,在日常工作中不多見,大家有興趣可以再找些資料研讀。
1.在目錄中查找的時候,如果dll查找不到,則會嘗試查找同名的exe
2.如果程序集帶有區域性,而不是語言中立的,則還會嘗試查找以語言區域命名的子目錄。
通常情況下,我們都就是程序集設置為語言中立的,所以不存在這個問題