1. 程式人生 > >如何控制C#引用DLL的位置

如何控制C#引用DLL的位置

原文地址: 

想必C#的開發者都遇到過這個問題,引用的dll都放在根目錄下,隨著專案的日益增大,根目錄下充滿了各種各樣的dll,非常的不美觀。

如果能夠把dll按照想要的目錄來存放,那麼系統就美觀多了,以下是我常用的程式各檔案的分佈:

  • 【3rdLibs】
    • NLog.dll
    • Newtonsoft.Json.dll
    • ……
  • 【MyLibs】
  • 【Resources】
  • 【Images】
  • Excecutable.exe
  • Excecuteble.exe.config

網上有很多的文章述說這個,比如使用Assembly.Load,但是沒有說明在程式中怎麼使用,也沒有給出具體的程式碼。這裡我結合自己多年的實踐經驗,再把整個流程和方法詳細敘述一遍,以便各位看官有個具體的體會。

系統搜尋dll的目錄以及順序

CLR解析一個程式集會在一個根目錄內進行搜尋,整個探索過程又稱Probing,這個根目錄很顯然就是當前包含當前程式集的目錄。

AppDomainSetup這個類儲存著探索目錄的資訊,其成員包括:ApplicationBasePrivateBinPath

程式搜尋dll的順序如下(區分強名稱簽名的和沒有強名稱簽名的程式集):

沒有做強名稱簽名的程式集:

  • 程式的根目錄
  • 根目錄下面,與被引用程式集同名的子目錄
  • 根目錄下面被明確定義為私有目錄的子目錄
  • 在目錄中查詢的時候,如果dll查詢不到,則會嘗試查詢同名的exe
  • 如果程式集帶有區域性,而不是語言中立的,則還會嘗試查詢以語言區域命名的子目錄

具有強名稱簽名的程式集:

  • 全域性程式集快取
  • 如果有定義codebase,則以codebase定義為準,如果codebase指定的路徑找不到,則直接報告錯誤
  • 程式的根目錄
  • 根目錄下面,與被引用程式集同名的子目錄
  • 根目錄下面被明確定義為私有目錄的子目錄
  • 在目錄中查詢的時候,如果dll查詢不到,則會嘗試查詢同名的exe
  • 如果程式集帶有區域性,而不是語言中立的,則還會嘗試查詢以語言區域命名的子目錄

如何讓程式識別不同目錄下的dll?

我們看到,上面的順序無論是否有強名稱簽名看,都提到了一個名詞“私有目錄”

方法一:配置App.config檔案的privatePath——【推薦】

這是最簡單的方法,當然也有一定的侷限性,就是沒法對dll做控制,另外,無法解決第三方DllImprt

中引入的程式集不在根目錄下的問題,不過無論怎麼說,這個都基本解決了問題。

配置如下,多個目錄用;分隔

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="3rdLib;MyLibs;SubFolder\Sub.dll"/>
     </assemblyBinding>
</runtime>

方法二:訂閱程式集解析事件AssemblyResolve在程式碼中解析

應用程式集域中支援在程式集解析時的處理:AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;。通過這個事件,我們可以在程式集解析時,根據不同的程式集做不用的處理,比如載入x86的程式集還是64位的程式集,當然也就可以指定程式集目錄了

這也正是Assembly.LoadAssembly.LoadFrom等方法的用武之地。

Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    AssemblyName assemblyName = new AssemblyName(args.Name);
    return Assembly.LoadFrom(Path.Combine(baseDirectory, "3rdLibs"));
}

方法三:在載入使用到dll的程式碼之前設定重置當前環境的目錄

這個方法就是通過Environment.CurrentDirectory=customPath,這樣,在呼叫dll方法時,因為目錄已經切換到了
這是一個取巧的方法,不是很實用,要來回切換程式集目錄,但是在某些情況下非常好用

如何處理[dllImport]中的程式集的載入

自己寫dllImport

如果是自己寫,那麼久好控制了,可以直接指定相對的目錄DllImport(3rdLibs\NLog.dll)。不過這種方法不一定可靠,在某些系統硬是載入不了,如果使用了dllImport還是,推薦下面的另外一種方法。

引用的C#的外掛又使用了dllImport

這是很多文章都沒有提及的:

因為無法更改路徑,那麼只能夠使用上述特殊的方法,更改當前程式的路徑

當然,還有更省事一點的做法,就是在系統環境中,增加一條記錄,指向要載入的dll的所在目錄。因為C++的程式碼中,Windows目錄和Windows\System32目錄以及環境變數設定的目錄都是搜尋路徑之一。

這裡提供怎麼從C#中修改系統環境變數的程式碼:

static void AddEnvironmentPaths(IEnumerable<string> paths)
{
    var path = new[] { Environment.GetEnvironmentVariable("PATH") ?? string.Empty };

    string newPath = string.Join(Path.PathSeparator.ToString(), path.Concat(paths));

    Environment.SetEnvironmentVariable("PATH", newPath);
}