日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

重温.NET下Assembly的加载过程

發(fā)布時間:2023/12/4 asp.net 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 重温.NET下Assembly的加载过程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

最近在工作中牽涉到了.NET下的一個古老的問題:Assembly的加載過程。雖然網(wǎng)上有很多文章介紹這部分內(nèi)容,很多文章也是很久以前就已經(jīng)出現(xiàn)了,但閱讀之后發(fā)現(xiàn),并沒能解決我的問題,有些點寫的不是特別詳細,讓人看完之后感覺還是云里霧里。最后,我決定重新復習一下這個經(jīng)典而古老的問題,并將所得總結(jié)于此,然后會有一個實例對這個問題進行演示,希望能夠幫助到大家。

.NET下Assembly的加載過程

.NET下Assembly的加載,最主要的一步就是確定Assembly的版本。在.NET下,托管的DLL和EXE都稱之為Assembly,Assembly由AssemblyName來唯一標識,AssemblyName也就是大家所熟悉的Assembly.FullName,它是由五部分:名稱、版本、語言、公鑰Token、處理器架構(gòu)組成的,這一點相信大家都知道。有關(guān)Assembly Name的詳細描述,請參考:https://docs.microsoft.com/en-us/dotnet/framework/app-domains/assembly-names。那么版本,就是AssemblyName中的一個重要組成部分。其它四部分相同,版本如果不同的話,就不能算作是同一個Assembly。設計這樣一個Assembly的版本策略,微軟本身就是為了解決最開始的DLL Hell的問題,在維基百科上著關(guān)于這段黑歷史的詳細描述,地址是:https://en.wikipedia.org/wiki/DLL_Hell,在此也就不多啰嗦了。

Assembly版本的重定向和最終確定

.NET下Assembly的加載過程,其實也是Assembly版本的確定和Assembly文件的定位過程,步驟如下:

  • 在一個Assembly被編譯的時候,它所引用的Assembly的全名(FullName)就會被編譯器強行寫入Assembly的Metadata,這個值是死的,從ILSpy可以看到,每個Reference都有它的全名信息:

    例如上圖,System.Data依賴System.Xml,它所需要的版本是4.0.0.0,那么當CLR加載System.Data的時候,就可以暫且認為接下來需要加載的System.Xml版本是4.0.0.0。這里強調(diào)“暫且認為”,是因為這只是確定Assembly版本的第一步,那么最終System.Xml到底是不是使用4.0.0.0的版本呢?就需要看接下來這步的處理結(jié)果,也就是Assembly版本的重定向

  • 首先,檢查應用程序的配置文件,看是否存在Assembly版本重定向的設定。我們暫時先討論應用程序配置文件就在AppDomain內(nèi)的情況(如果在AppDomain之外,則需要首先下載配置文件,再繼續(xù),這里先不深入討論)。應用程序配置文件常見的有.exe.config和web.config兩種。在配置文件中,可以在runtime節(jié)點下的assemblyBinding中進行配置。例如:

    在這個例子中,asm6 Assembly的版本號被重定向到2.0.0.0。那么假設這就是asm6的最終版本號,那么接下來當CLR開始加載asm6的時候,如果2.0.0.0的版本沒有找到,則直接拋出FileLoadException(即使3.0.0.0的版本是存在的),整個Assembly加載過程結(jié)束。FileLoadException的詳細信息類似于:Could not load file or assembly 'asm6, Version=3.0.0.0, Culture=neutral, PublicKeyToken=c0305c36380ba429' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference

  • 如果在配置文件中找到了對應的版本重定向設定,那么,再接著查看Publisher Policy文件。Publisher Policy文件是一個僅包含配置文件的.NET Assembly,被安裝到GAC里。它的Assembly版本重定向配置內(nèi)容跟上面的應用程序配置文件的配置內(nèi)容相同,不同的是,它的作用域是所有使用了該Assembly的應用程序。這種做法對于開發(fā)系統(tǒng)級通用框架的Assembly升級非常有用,比如.NET Framework。下面就是安裝在GAC里的Publisher Policy文件的樣本,需要注意:Publisher Policy會override應用程序配置信息中的版本重定向配置,而不是相反。換言之,假如asm6在上面這一步被確定為2.0.0.0,而所對應的Publisher Policy文件又將其確定為2.5.0.0,那么,暫且認為,CLR應該要加載2.5.0.0的版本。同理,“暫且認為”這個詞表示,版本確定的過程還未結(jié)束


  • 接下來,查找machine.config文件。同理,如果machine.config文件中存在版本重定向的設定,那么就會使用machine.config文件中的這個值,作為CLR應該去加載的Assembly的版本

  • 至此,Assembly的最終版本已被確定,接下來就是搜索Assembly文件并進行加載的過程了。

    Assembly文件的搜索和加載過程

    現(xiàn)在,CLR已經(jīng)開始加載確定版本的Assembly了,接下來就是搜索Assembly文件的過程。這個過程也叫作Assembly Probing。CLR會做以下事情:

  • 首先,查看所需的Assembly是否已經(jīng)加載過,如果已經(jīng)加載了,那就直接使用那個已經(jīng)加載的Assembly的版本與當前所需的版本進行比對,如果匹配,則使用那個已經(jīng)加載的Assembly,如果不匹配,則拋出FileLoadException,執(zhí)行結(jié)束

  • 然后,看Assembly是否已被強簽名(Strongly Named),如果是,則去GAC里查找Assembly。如果找到,則直接加載,整個Assembly加載過程結(jié)束。如果沒有找到,那么就進行下一步,繼續(xù)搜索Assembly文件。當然,如果Assembly沒有進行強簽名,那么就跳過這一步,直接繼續(xù)

  • 接著,CLR開始搜索(Probing)可能的Assembly位置,這又要分多種情況:

  • 首先,查看文件中是否有指定<codeBase>,codeBase配置允許應用程序針對Assembly的不同版本指定裝載地址,遵循如下規(guī)律:

  • 如果所指定的Assembly文件位于當前應用程序域的啟動目錄(或其子目錄)下,則使用相對路徑指定href的值

  • 如果所指定的Assembly文件位于其它目錄,或任何其它地方,則href必須給出全路徑,并且Assembly必須強簽名的

  • 然后,CLR對應用程序域的根目錄以及相關(guān)的子目錄進行探索:

  • 假設Assembly的名字是abc.dll,那么CLR會探索以下目錄:

  • [appdomain_base]\abc.dll

  • [appdomain_base]\abc\abc.dll

  • 假設abc.dll還有語言設置(culture不是neutral),那么CLR會探索以下目錄:

  • [appdomain_base]\[culture]\abc.dll

  • [appdomain_base]\[culture]\abc\abc.dll

  • 如果找到符合版本的Assembly,則加載,否則進入下一步

  • 最后,CLR會查看應用程序配置文件中是否有<probling>節(jié)點,如果有,則按probling節(jié)點所指定的privatePath值進行逐一探索。這個過程也會考慮culture的因素,類似于上面這步這樣,對相應的子目錄進行搜索。如果找到對應的Assembly,則加載,否則拋出FileLoadException,整個加載過程結(jié)束。注意,這里“逐一探索”的過程,不是遍歷并找最佳匹配的過程。CLR僅根據(jù)Assembly的名字(不帶版本號的名字)在privatePath下查找Assembly的文件,找到第一個名字匹配但是版本不匹配的話,就拋異常并終止加載了,它不會繼續(xù)搜索privatePath中余下的其它路徑

  • 在加載Assembly文件失敗的時候,AppDomain會觸發(fā)AssemblyResolve的事件,在這個事件的訂閱函數(shù)中,允許客戶程序自定義對加載失敗的Assembly的處理方式,比如,可以通過Assembly.LoadFrom或者Assembly.LoadFile調(diào)用“手動地”將Assembly加載到AppDomain。

    fuslogvw Assembly綁定日志查看器

    在.NET SDK中帶了一個fuslogvw.exe的應用程序,通過它可以查看詳細的Assembly加載過程。使用方法非常簡單,使用管理員身份啟動Visual Studio 2017 Developer Command Prompt,然后在命令行輸入fuslogvw.exe,即可啟動日志查看器。啟動之后,點擊Settings按鈕,以啟用日志記錄功能:

    日志啟動之后,點擊Refresh按鈕,然后啟動你的.NET應用程序,就可以看到當前應用程序所依賴的Assembly的加載過程日志了:

    接下來,我會做一個例子程序,然后使用這個工具來分析Assembly的加載過程。

    插件系統(tǒng)的實現(xiàn)與Assembly加載過程的分析

    理論結(jié)合實際,看看如何通過實際代碼來詮釋以上所述Assembly的加載過程。一個比較好的例子就是設計一個簡單的插件系統(tǒng),并通過觀察系統(tǒng)加載插件的過程,來了解Assembly加載的來龍去脈。為了簡單直觀,我把這個插件系統(tǒng)稱為PluginDemo。這個插件很簡單,主體程序是一個控制臺應用程序,然后我們實現(xiàn)兩個插件:Earth和Mars,在不同的插件的Initialize方法中,會輸出不同的字符串。

    整個應用程序的項目結(jié)構(gòu)如下:

    該插件系統(tǒng)包含4個C#的項目:

    • PluginDemo.Common:它定義了AddIn抽象類,所有的插件實現(xiàn)都需要繼承于這個抽象類。此外,AddInDefinition類是一個用來保存插件Metadata的類。為了演示,插件的Metadata僅僅包含插件類型的Assembly Qualified Name

    • PluginDemo.App:插件系統(tǒng)的應用程序。這個程序執(zhí)行的時候,會掃描程序目錄下Modules目錄中的DLL,并根據(jù)module.xml的Metadata信息,加載相應的插件對象,并執(zhí)行Initialize方法

    • PluginDemo.Plugins.Earth:其中的一個插件實現(xiàn)

    • PluginDemo.Plugins.Mars:另一個插件實現(xiàn)

    注意:除了PluginDemo.Common之外的其它三個項目,都對PluginDemo.Common有引用關(guān)系。而PluginDemo.App項目僅僅在項目本身依賴于PluginDemo.Plugins.Earth和PluginDemo.Plugins.Mars,它不會去引用這兩個項目。目的就是為了當PluginDemo.App被編譯時,其余兩個插件項目也會同時被編譯并輸出到指定位置。

    在Earth插件的CustomAddIn類中,我們實現(xiàn)了Initialize方法,并在此輸出一個字符串:


    public?class?CustomAddIn : AddIn{????public?override?string?Name => "Earth AddIn";????public?override?void?Initialize()????{????????Console.WriteLine("Earth Plugin initialized.");????}}

    在Mars插件的CustomAddIn類中,我們也實現(xiàn)了Initialize方法,并在此輸出一個字符串:


    public?class?CustomAddIn : AddIn{????public?override?string?Name => "Mars AddIn";????public?override?void?Initialize()????{????????Console.WriteLine("Mars AddIn initialized.");????}}

    那么,在插件系統(tǒng)主程序中,就會掃描Modules子目錄下的module.xml文件,然后解析每個module.xml文件獲得每個插件類的Assembly Qualified Name,然后通過Type.GetType方法獲得插件類,進而創(chuàng)建實例、調(diào)用Initialize方法。代碼如下:


    static?void?Main(){????var?directory = new?DirectoryInfo("Modules");????foreach(var?file in?directory.EnumerateFiles("module.xml", SearchOption.AllDirectories))????{????????var?addinDefinition = AddInDefinition.ReadFromFile(file.FullName);????????var?addInType = Type.GetType(addinDefinition.FullName);????????var?addIn = (AddIn)Activator.CreateInstance(addInType);????????Console.WriteLine($"{addIn.Id} - {addIn.Name}");????????addIn.Initialize();????}}

    接下來,修改App.config文件,修改為:


    <?xml?version="1.0" encoding="utf-8" ?><configuration>??<runtime>????<assemblyBinding?xmlns="urn:schemas-microsoft-com:asm.v1">??????<probing?privatePath="Modules\Earth;Modules\Mars;" />????</assemblyBinding>??</runtime></configuration>

    此時,運行程序,可以得到:

    目前沒有什么問題。接下來,對兩個AddIn分別做一些修改。讓這兩個AddIn依賴于不同版本的Newtonsoft.Json,比如,Earth依賴于7.0.0.0的版本,Mars依賴于6.0.0.0的版本,然后分別修改兩個CustomAddIn的Initialize方法,在方法中各自調(diào)用一次JsonConvert.SerializeObject方法,以觸發(fā)Newtonsoft.Json這個Assembly的加載。此時再次運行程序,你將看到下面的異常:

    現(xiàn)在,刷新fuslogvw.exe,找到Newtonsoft.Json的日志:

    雙擊打開日志,可以看到如下信息:

    從整個過程可以看出:

  • PluginDemo.App.exe正在試圖加載PluginDemo.Plugins.Mars Assembly

  • PluginDemo.Plugins.Mars開始調(diào)用Newtonsoft.Json

  • 掃描應用程序配置文件、Host配置文件以及machine.config文件,均無找到Newtonsoft.Json的重定向信息,此時,Newtonsoft.Json版本確定為6.0.0.0

  • GAC掃描失敗,繼續(xù)查找文件

  • 首先查找應用程序當前目錄下有沒有Newtonsoft.Json,以及Newtonsoft.Json子目錄下有沒有Newtonsoft.Json.dll,發(fā)現(xiàn)都沒有,繼續(xù)

  • 然后,通過App.config中的probing的privatePath設定,首先查找Modules\Earth目錄(因為這個目錄放在privatePath的第一個),找到了一個叫做Newtonsoft.Json.dll的Assembly,于是,判斷版本是否相同。結(jié)果,找到的是7.0.0.0,而它需要的卻是6.0.0.0,版本不匹配,于是就拋出異常,退出程序

  • 那么接下來,改一改App.config文件,將privatePath下的兩個值換個位置呢?

    再試試:

    此時,Earth AddIn又出錯了。那么,我們加上版本重定向的配置,指定當程序需要加載7.0.0.0版本的Newtonsoft.Json時,讓它重定向到6.0.0.0的版本:

    再次執(zhí)行,成功了:

    看看日志:

    版本已經(jīng)被重定向到6.0.0.0,并且在Mars目錄下找到了6.0.0.0的Newtonsoft.Json,加載成功了。

    這個案例的源代碼可以點擊此處下載

    總結(jié)

    本文詳細介紹了.NET下Assembly的版本確定和加載過程,最后給出了一個實例,對這個過程進行了演示。

    原文:https://www.cnblogs.com/daxnet/p/8525249.html


    .NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com

    總結(jié)

    以上是生活随笔為你收集整理的重温.NET下Assembly的加载过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。