欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊


    .NET 中部署的基本單元,也是所有類(lèi)型的容器。程序集包含已編譯的類(lèi)型及其中間語(yǔ)言 (IL) 代碼、運(yùn)行時(shí)資源和信息,以幫助進(jìn)行版本控制和引用其他程序集。程序集還定義類(lèi)型解析的邊界。在 .NET 中,程序集由擴(kuò)展名為的單個(gè)文件組成。

    注意

    在 .NET 中生成可執(zhí)行應(yīng)用程序時(shí),最終會(huì)得到兩個(gè)文件:程序集 () 和適用于目標(biāo)平臺(tái)的可執(zhí)行啟動(dòng)器 ()。

    這與 .NET Framework 中發(fā)生的情況不同,后者生成可 (PE) 程序集。PE 具有擴(kuò)展,并充當(dāng)程序集和應(yīng)用程序啟動(dòng)器。PE 可以同時(shí)面向 32 位和 64 位版本的 Windows。

    本章中的大多數(shù)類(lèi)型都來(lái)自以下命名空間:

    System.Reflection
    System.Resources
    System.Globalization

    程序集中的內(nèi)容

    程序集包含四種內(nèi)容:

    程序集清單

    向 CLR 提供信息,例如程序集的名稱(chēng)、版本及其引用的其他程序集

    應(yīng)用程序清單

    向操作系統(tǒng)提供信息,例如應(yīng)如何部署程序集以及是否需要管理提升

    已編譯的類(lèi)型

    程序集中定義的類(lèi)型的已編譯 IL 代碼和元數(shù)據(jù)

    資源

    嵌入在程序集中的其他數(shù)據(jù),如圖像和可本地化的文本

    其中,只有程序集清單是必需的,盡管程序集幾乎總是包含已編譯的類(lèi)型(除非它是資源)。參見(jiàn))。

    程序集清單

    程序集清單有兩個(gè)用途:

    • 它向托管宿主環(huán)境描述程序集。
    • 它充當(dāng)程序集中模塊、類(lèi)型和資源的目錄。

    因此,程序集是。使用者可以發(fā)現(xiàn)程序集的所有數(shù)據(jù)、類(lèi)型和函數(shù),而無(wú)需其他文件。

    注意

    程序集清單不是顯式添加到程序集的內(nèi)容,而是作為編譯的一部分自動(dòng)嵌入到程序集中。

    以下是清單中存儲(chǔ)的功能重要數(shù)據(jù)的摘要:

    • 程序集的簡(jiǎn)單名稱(chēng)
    • 版本號(hào)(匯編版本)
    • 程序集的公鑰和簽名哈希(如果具有強(qiáng)名稱(chēng))
    • 引用的程序集的列表,包括其版本和公鑰
    • 程序集中定義的類(lèi)型的列表
    • 它所針對(duì)的區(qū)域性,如果是附屬程序集 ( AssemblyCulture )

    清單還可以存儲(chǔ)以下信息數(shù)據(jù):

    • 完整的標(biāo)題和描述(程序集標(biāo)題和程序集說(shuō)明)
    • 公司和版權(quán)信息(組裝公司和組裝版權(quán))
    • 顯示版本(匯編信息版本)
    • 自定義數(shù)據(jù)的其他屬性

    其中一些數(shù)據(jù)派生自提供給編譯器的參數(shù),例如引用的程序集列表或用于對(duì)程序集進(jìn)行簽名的公鑰。其余部分來(lái)自括號(hào)中指示的程序集屬性。

    注意

    可以使用 .NET 工具 查看程序集清單的內(nèi)容。在第中,我們描述了如何使用反射以編程方式執(zhí)行相同的操作。

    指定程序集屬性

    可以在 Visual Studio 中項(xiàng)目的“屬性”頁(yè)上的“包”選項(xiàng)卡上指定常用程序集屬性。該選項(xiàng)卡上的設(shè)置將添加到項(xiàng)目文件 () 中。

    若要指定“包”選項(xiàng)卡不支持的屬性,或者如果不使用 文件,可以在源代碼中指定程序集屬性(這通常在名為 的文件中完成)。

    專(zhuān)用屬性文件僅包含 using 語(yǔ)句和程序集屬性聲明。例如,若要向單元測(cè)試項(xiàng)目公開(kāi)內(nèi)部作用域的類(lèi)型,應(yīng)執(zhí)行以下操作:

    using System.Runtime.CompilerServices;
    
    [assembly:InternalsVisibleTo("MyUnitTestProject")]

    應(yīng)用程序清單 (Windows)

    應(yīng)用程序清單是一個(gè) XML 文件,用于將有關(guān)程序集的信息傳達(dá)給操作系統(tǒng)。在生成過(guò)程中,應(yīng)用程序清單作為 Win32 資源嵌入到啟動(dòng)可執(zhí)行文件中。如果存在,則在 CLR 加載程序集之前讀取和處理清單,并可能影響 Windows 啟動(dòng)應(yīng)用程序進(jìn)程的方式。

    .NET 應(yīng)用程序清單在 XML 命名空間 urn:schemas-microsoft-com:asm.v1 中具有一個(gè)名為程序集的根元素:

    <?xml version="1.0" encoding="utf-8"?>
    <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
      <!-- contents of manifest -->
    </assembly>

    以下清單指示操作系統(tǒng)請(qǐng)求管理提升:

    <?xml version="1.0" encoding="utf-8"?>
    <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
      <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
          <requestedPrivileges>
            <requestedExecutionLevel level="requireAdministrator" />
          </requestedPrivileges>
        </security>
      </trustInfo>
    </assembly>

    (UWP 應(yīng)用程序具有更詳細(xì)的清單,如 文件中所述。這包括程序功能的聲明,這些功能決定了操作系統(tǒng)授予的權(quán)限。編輯此文件的最簡(jiǎn)單方法是使用 Visual Studio,當(dāng)您雙擊清單文件時(shí),它會(huì)顯示一個(gè)對(duì)話(huà)框。

    部署應(yīng)用程序清單

    可以通過(guò)在“解決方案資源管理器”中右鍵單擊項(xiàng)目,依次選擇“添加”、“新建項(xiàng)”,然后選擇“應(yīng)用程序清單文件”,將應(yīng)用程序清單添加到 Visual Studio 中的 .NET 項(xiàng)目中。生成后,清單將嵌入到輸出程序集中。

    注意

    .NET 工具 對(duì)嵌入式應(yīng)用程序清單的存在視而不見(jiàn)。但是,Visual Studio 指示如果在解決方案資源管理器中雙擊程序集,則嵌入的應(yīng)用程序清單是否存在。

    模塊

    程序集的內(nèi)容實(shí)際上打包在稱(chēng)為的中間容器中。模塊對(duì)應(yīng)于包含程序集內(nèi)容的文件。這個(gè)額外的容器層的原因是允許程序集跨越多個(gè)文件,這是.NET Framework中存在但在.NET 5和.NET Core中不存在的功能。 說(shuō)明了這種關(guān)系。


    單文件程序集

    盡管 .NET 不支持多文件程序集,但有時(shí)需要注意模塊施加的額外容器交付級(jí)別。主要場(chǎng)景是反射(請(qǐng)參閱中的“”和)。

    程序集類(lèi)

    System.Reflection 中的程序集類(lèi)是在運(yùn)行時(shí)訪問(wèn)程序集元數(shù)據(jù)的網(wǎng)關(guān)。有多種方法可以獲取程序集對(duì)象:最簡(jiǎn)單的方法是通過(guò) Type 的程序集屬性:

    Assembly a = typeof (Program).Assembly;

    您還可以通過(guò)調(diào)用 Assembly 的靜態(tài)方法之一來(lái)獲取程序集對(duì)象:

    GetExecutingAssembly

    返回定義當(dāng)前正在執(zhí)行的函數(shù)的類(lèi)型的程序集

    獲取呼叫程序集

    與 GetExecutingAssembly 執(zhí)行相同的操作,但針對(duì)調(diào)用當(dāng)前執(zhí)行函數(shù)的函數(shù)

    獲取條目程序集

    返回定義應(yīng)用程序的原始輸入方法的程序集

    擁有程序集對(duì)象后,可以使用其屬性和方法來(lái)查詢(xún)程序集的元數(shù)據(jù)并反映其類(lèi)型。 顯示了這些功能的摘要。

    大會(huì)成員

    功能

    目的

    請(qǐng)參閱該部分...

    全名 , 獲取名稱(chēng)

    返回完全限定名或程序集名稱(chēng)對(duì)象

    “程序集名稱(chēng)”

    代碼庫(kù) , 位置

    程序集文件的位置

    “加載、解析和隔離程序集”

    加載 , 加載自 ,

    手動(dòng)將程序集加載到內(nèi)存中

    “加載、解析和隔離程序集”

    獲取衛(wèi)星組裝

    定位給定區(qū)域性的附屬程序集

    “資源和衛(wèi)星組件”

    GetType , GetTypes

    返回程序集中定義的一個(gè)或多個(gè)類(lèi)型。


    入口點(diǎn)

    返回應(yīng)用程序的輸入方法,作為 MethodInfo


    GetModule , GetModules , ManifestModule

    返回程序集的所有模塊或主模塊

    中的

    GetCustomAttribute, GetCustomAttributes

    返回程序集的屬性

    中的

    強(qiáng)名稱(chēng)和程序集簽名

    注意

    在 .NET Framework 中,強(qiáng)命名程序集非常重要,原因有兩個(gè):

    • 它允許將程序集加載到“全局程序集緩存”中。
    • 它允許程序集通過(guò)其他強(qiáng)名稱(chēng)程序集引用。

    強(qiáng)命名在 .NET 5 和 .NET Core 中的重要性要低得多,因?yàn)檫@些運(yùn)行時(shí)沒(méi)有全局程序集緩存,也不會(huì)施加第二個(gè)限制。

    程序集具有唯一的標(biāo)識(shí)。它的工作原理是向清單添加兩個(gè)元數(shù)據(jù)位:

    • 屬于程序集作者的唯一
    • 程序集的,證明唯一編號(hào)持有者生成了程序集

    這需要公鑰/私鑰對(duì)。提供唯一的標(biāo)識(shí)號(hào),便于簽名。

    注意

    簽名與簽名不同。我們將在本章后面介紹 Authenticode。

    公鑰在保證程序集引用的唯一性方面很有價(jià)值:強(qiáng)名稱(chēng)程序集將公鑰合并到其標(biāo)識(shí)中。

    在 .NET Framework 中,私鑰可防止程序集被篡改,因?yàn)槿绻麤](méi)有私鑰,任何人都無(wú)法在不破壞簽名的情況下發(fā)布程序集的修改版本。實(shí)際上,當(dāng)將程序集加載到 .NET Framework 的全局程序集緩存中時(shí),這會(huì)很有用。在 .NET 5 和 .NET Core 中,簽名幾乎沒(méi)有用處,因?yàn)樗鼜奈幢贿x中。

    向以前“弱”命名的程序集添加強(qiáng)名稱(chēng)會(huì)更改其標(biāo)識(shí)。出于這個(gè)原因,如果您認(rèn)為程序集將來(lái)可能需要強(qiáng)名稱(chēng),從一開(kāi)始就對(duì)程序集進(jìn)行強(qiáng)名稱(chēng)是值得的。

    如何對(duì)程序集進(jìn)行強(qiáng)命名

    要為程序集指定一個(gè)強(qiáng)名稱(chēng),請(qǐng)首先使用 實(shí)用程序生成公鑰/私鑰對(duì):

    sn.exe -k MyKeyPair.snk

    注意

    Visual Studio 安裝一個(gè)名為 的快捷方式,該快捷方式啟動(dòng)一個(gè)命令提示符,其 PATH 包含 等開(kāi)發(fā)工具。

    這將生成一個(gè)新的密鑰對(duì),并將其存儲(chǔ)到名為的文件中。如果隨后丟失此文件,將永久失去使用相同的標(biāo)識(shí)重新編譯程序集的能力。

    可以通過(guò)更新項(xiàng)目文件來(lái)使用此文件對(duì)程序集進(jìn)行簽名。從 Visual Studio 轉(zhuǎn)到“項(xiàng)目屬性”窗口,然后在“簽名”選項(xiàng)卡上,選中“對(duì)程序集進(jìn)行”復(fù)選框并選擇 文件。

    同一密鑰對(duì)可以對(duì)多個(gè)程序集進(jìn)行簽名 - 如果它們的簡(jiǎn)單名稱(chēng)不同,它們?nèi)詫⒕哂胁煌臉?biāo)識(shí)。

    程序集名稱(chēng)

    程序集的“標(biāo)識(shí)”包含其清單中的四個(gè)元數(shù)據(jù):

    • 它的簡(jiǎn)單名稱(chēng)
    • 其版本(如果不存在,則為“0.0.0.0”)
    • 它的文化(如果不是衛(wèi)星,則為“中立”)
    • 其公鑰標(biāo)記(如果未強(qiáng)名稱(chēng),則為“null”)

    簡(jiǎn)單名稱(chēng)不是來(lái)自任何屬性,而是來(lái)自最初編譯到的文件的名稱(chēng)(減去任何擴(kuò)展名)。因此,程序集的簡(jiǎn)單名稱(chēng)是“System.Xml”。重命名文件不會(huì)更改程序集的簡(jiǎn)單名稱(chēng)。

    版本號(hào)來(lái)自 AssemblyVersion 屬性。它是一個(gè)字符串,分為四個(gè)部分,如下所示:

    major.minor.build.revision

    您可以指定版本號(hào),如下所示:

    [assembly: AssemblyVersion ("2.5.6.7")]

    區(qū)域性來(lái)自 AssemblyCulture 屬性,適用于附屬程序集,稍后將在一節(jié)中介紹。

    公鑰標(biāo)記來(lái)自編譯時(shí)提供的強(qiáng)名稱(chēng),如上一節(jié)所述。

    完全限定名稱(chēng)

    完全限定的程序集名稱(chēng)是包含所有四個(gè)標(biāo)識(shí)組件的字符串,格式如下:

    simple-name, Version=version, Culture=culture, PublicKeyToken=public-key

    例如,System.Private.CoreLib.dll 的完全限定名稱(chēng)是 。

    如果程序集沒(méi)有 AssemblyVersion 屬性,則版本顯示為 0.0.0.0 。如果未簽名,則其公鑰標(biāo)記顯示為 null 。

    程序集對(duì)象的 FullName 屬性返回其完全限定名稱(chēng)。編譯器在清單中記錄程序集引用時(shí)始終使用完全限定的名稱(chēng)。

    注意

    完全限定的程序集名稱(chēng)不包括目錄路徑,以幫助在磁盤(pán)上查找它。查找駐留在另一個(gè)目錄中的程序集是我們?cè)谥刑幚淼耐耆?dú)立的問(wèn)題。

    程序集名稱(chēng)類(lèi)

    AssemblyName 是一個(gè)類(lèi),對(duì)于完全限定程序集名稱(chēng)的四個(gè)組件中的每一個(gè)組件都有一個(gè)類(lèi)型化屬性。程序集名稱(chēng)有兩個(gè)用途:

    • 它分析或生成完全限定的程序集名稱(chēng)。
    • 它存儲(chǔ)一些額外的數(shù)據(jù),以幫助解決(查找)程序集。

    可以通過(guò)以下任一方式獲取程序集名稱(chēng)對(duì)象:

    • 實(shí)例化程序集名稱(chēng) ,提供完全限定的名稱(chēng)。
    • 在現(xiàn)有程序集 上調(diào)用 GetName。
    • 調(diào)用 AssemblyName.GetAssemblyName ,提供磁盤(pán)上程序集文件的路徑。

    還可以實(shí)例化不帶任何參數(shù)的 AssemblyName 對(duì)象,然后設(shè)置其每個(gè)屬性以生成完全限定名。以這種方式構(gòu)造時(shí),程序集名稱(chēng)是可變的。

    以下是它的基本屬性和方法:

    string      FullName    { get; }            // Fully qualified name
    string      Name        { get; set; }       // Simple name
    Version     Version     { get; set; }       // Assembly version
    CultureInfo CultureInfo { get; set; }       // For satellite assemblies
    string      CodeBase    { get; set; }       // Location
    
    byte[]      GetPublicKey();                 // 160 bytes
    void        SetPublicKey (byte[] key);
    byte[]      GetPublicKeyToken();            // 8-byte version
    void        SetPublicKeyToken (byte[] publicKeyToken);

    版本本身是一種強(qiáng)類(lèi)型表示形式,具有“主要”、“次要”、“內(nèi)部版本”和“修訂號(hào)”的屬性。GetPublicKey 返回完整的加密公鑰;GetPublicKeyToken 返回用于建立標(biāo)識(shí)的最后八個(gè)字節(jié)。

    使用程序集名稱(chēng)獲取程序集的簡(jiǎn)單名稱(chēng):

    Console.WriteLine (typeof (string).Assembly.GetName().Name);
    // System.Private.CoreLib

    獲取程序集版本:

    string v = myAssembly.GetName().Version.ToString();

    程序集信息和文件版本

    另外兩個(gè)程序集屬性可用于表示與版本相關(guān)的信息。與AssemblyVersion不同,以下兩個(gè)屬性不會(huì)影響程序集的標(biāo)識(shí),因此對(duì)編譯時(shí)或運(yùn)行時(shí)發(fā)生的情況沒(méi)有影響:

    匯編信息版本

    向最終用戶(hù)顯示的版本。這在“Windows 文件屬性”對(duì)話(huà)框中顯示為“產(chǎn)品版本”。任何字符串都可以轉(zhuǎn)到此處,例如“5.1 Beta 2”。通常,將為應(yīng)用程序中的所有程序集分配相同的信息版本號(hào)。

    匯編文件版本

    這旨在引用該程序集的內(nèi)部版本號(hào)。這在“Windows 文件屬性”對(duì)話(huà)框中顯示為“文件版本”。與AssemblyVersion一樣,它必須包含一個(gè)由最多四個(gè)數(shù)字組成的字符串,這些數(shù)字由句點(diǎn)分隔。

    驗(yàn)證碼簽名

    是一種代碼簽名系統(tǒng),其目的是證明發(fā)布者的身份。驗(yàn)證碼和簽名是獨(dú)立的:您可以使用其中一個(gè)或兩個(gè)系統(tǒng)對(duì)程序集進(jìn)行簽名。

    盡管強(qiáng)名稱(chēng)簽名可以證明程序集 A、B 和 C 來(lái)自同一參與方(假設(shè)私鑰未泄露),但它無(wú)法告訴您該參與方是誰(shuí)。要知道派對(duì)是喬·阿爾巴哈里(Joe Albahari)或Microsoft公司(Joe Albahari),你需要Authenticode。

    從 Internet 下載程序時(shí),驗(yàn)證碼很有用,因?yàn)樗梢员WC程序來(lái)自證書(shū)頒發(fā)機(jī)構(gòu)指定的任何人,并且在傳輸過(guò)程中未被修改。它還可以防止首次運(yùn)行下載的應(yīng)用程序時(shí)出現(xiàn)“未知發(fā)布者”警告。將應(yīng)用提交到 Windows 應(yīng)用商店時(shí),驗(yàn)證碼簽名也是一項(xiàng)要求。

    驗(yàn)證碼不僅適用于 .NET 程序集,還適用于非托管可執(zhí)行文件和二進(jìn)制文件(如部署文件)。當(dāng)然,Authenticode 并不能保證程序沒(méi)有惡意軟件,盡管它確實(shí)降低了它的可能性。個(gè)人或?qū)嶓w愿意將其名稱(chēng)(由護(hù)照或公司文件支持)放在可執(zhí)行文件或庫(kù)后面。

    注意

    CLR 不會(huì)將驗(yàn)證碼簽名視為程序集標(biāo)識(shí)的一部分。但是,它可以按需讀取和驗(yàn)證驗(yàn)證碼簽名,您很快就會(huì)看到。

    使用 Authenticode 簽名要求您聯(lián)系 (CA),并提供您的個(gè)人身份或公司身份(公司章程等)的證據(jù)。CA 檢查您的文檔后,它將頒發(fā)一個(gè) X.509 代碼簽名證書(shū),該證書(shū)的有效期通常為一到五年。這使您能夠使用 實(shí)用程序?qū)Τ绦蚣M(jìn)行簽名。您也可以使用 實(shí)用程序自己制作證書(shū);但是,只有在顯式安裝了證書(shū)的計(jì)算機(jī)上才能識(shí)別它。

    (非自簽名)證書(shū)可以在任何計(jì)算機(jī)上工作的事實(shí)依賴(lài)于基礎(chǔ)結(jié)構(gòu)。實(shí)質(zhì)上,您的證書(shū)是使用屬于 CA 的另一個(gè)證書(shū)簽名的。CA 是受信任的,因?yàn)樗?CA 都加載到操作系統(tǒng)中。 (轉(zhuǎn)到 Windows 控制面板,然后在搜索框中鍵入。在“管理工具”部分中,單擊“管理計(jì)算機(jī)證書(shū)”。這將啟動(dòng)證書(shū)管理器。打開(kāi)“受信任的根證書(shū)頒發(fā)機(jī)構(gòu)”節(jié)點(diǎn),然后單擊“證書(shū)”。如果泄露,CA 可以吊銷(xiāo)發(fā)布者的證書(shū),因此驗(yàn)證驗(yàn)證碼簽名需要定期向 CA 詢(xún)問(wèn)證書(shū)吊銷(xiāo)的最新列表。

    由于驗(yàn)證碼使用加密簽名,因此如果隨后有人篡改文件,驗(yàn)證碼簽名無(wú)效。我們將在第 中討論加密、散列和簽名。

    如何使用驗(yàn)證碼簽名

    獲取和安裝證書(shū)

    第一步是從 CA 獲取代碼簽名證書(shū)(請(qǐng)參閱下面的側(cè)欄)。然后,可以將證書(shū)作為受密碼保護(hù)的文件使用,也可以將證書(shū)加載到計(jì)算機(jī)的證書(shū)存儲(chǔ)中。執(zhí)行后者的好處是,您無(wú)需指定密碼即可簽名。這是有利的,因?yàn)樗梢苑乐姑艽a在自動(dòng)生成腳本或批處理文件中可見(jiàn)。

    從何處獲取代碼簽名證書(shū)

    只有少數(shù)代碼簽名 CA 作為根證書(shū)頒發(fā)機(jī)構(gòu)預(yù)加載到 Windows 中。其中包括Comodo,Go Daddy,GlobalSign,DigiCert,Thawte和Symantec。

    還有一些經(jīng)銷(xiāo)商,如K Software,提供上述當(dāng)局的折扣代碼簽名證書(shū)。

    由K Software,Comodo,Go Daddy和GlobalSign頒發(fā)的Authenticode證書(shū)被宣傳為限制較少,因?yàn)樗鼈円矊⒑炇鸱荕icrosoft程序。除此之外,所有供應(yīng)商的產(chǎn)品在功能上都是等效的。

    請(qǐng)注意,SSL 證書(shū)通常不能用于驗(yàn)證碼簽名(盡管使用相同的 X.509 基礎(chǔ)結(jié)構(gòu))。這部分是因?yàn)镾SL證書(shū)是關(guān)于證明域所有權(quán)的;Authenticode是關(guān)于證明你是誰(shuí)。

    若要將證書(shū)加載到計(jì)算機(jī)的證書(shū)存儲(chǔ)中,請(qǐng)按照前面所述打開(kāi)證書(shū)管理器。打開(kāi)“個(gè)人”文件夾,右鍵單擊其“證書(shū)”文件夾,然后選擇“所有任務(wù)/導(dǎo)入”。導(dǎo)入向?qū)⒅笇?dǎo)您完成此過(guò)程。導(dǎo)入完成后,單擊證書(shū)上的“查看”按鈕,轉(zhuǎn)到“詳細(xì)信息”選項(xiàng)卡,然后復(fù)制證書(shū)的。這是隨后在簽名時(shí)需要標(biāo)識(shí)證書(shū)的 SHA-256 哈希。

    注意

    如果還希望對(duì)程序集進(jìn)行強(qiáng)名稱(chēng)簽名,則必須在驗(yàn)證碼簽名執(zhí)行此操作。這是因?yàn)?CLR 知道驗(yàn)證碼簽名,但反之則不然。因此,如果在對(duì)程序集進(jìn)行驗(yàn)證碼簽名程序集進(jìn)行強(qiáng)名稱(chēng)簽名,則后者會(huì)將添加 CLR 的強(qiáng)名稱(chēng)視為未經(jīng)授權(quán)的修改,并認(rèn)為程序集。

    使用簽名工具簽名.exe

    您可以使用 Visual Studio 附帶的 實(shí)用工具對(duì)程序進(jìn)行驗(yàn)證簽名(查看”下的 文件夾)。下面使用安全的 SHA256 哈希算法對(duì)名為 的文件進(jìn)行簽名.exe該文件的證書(shū)位于計(jì)算機(jī)的“中,名為“Joseph Albahari”:

    signtool sign /n "Joseph Albahari" /fd sha256 LINQPad.exe

    您還可以使用 /d 和 /du 指定描述和產(chǎn)品 URL:

     ... /d LINQPad /du http://www.linqpad.net

    在大多數(shù)情況下,您還需要指定。

    時(shí)間戳

    證書(shū)過(guò)期后,您將無(wú)法再對(duì)程序進(jìn)行簽名。但是,如果在簽名時(shí)使用 /tr 開(kāi)關(guān)指定了,則在過(guò)期簽名的程序仍將有效。CA將為此目的為您提供一個(gè)URI:以下內(nèi)容適用于Comodo(或K軟件):

     ... /tr http://timestamp.comodoca.com/authenticode /td SHA256

    驗(yàn)證程序是否已簽名

    查看文件驗(yàn)證碼簽名的最簡(jiǎn)單方法是在 Windows 資源管理器中查看文件的屬性(在“數(shù)字簽名”選項(xiàng)卡中查看)。實(shí)用程序也為此提供了一個(gè)選項(xiàng)。

    資源和附屬組件

    應(yīng)用程序通常不僅包含可執(zhí)行代碼,還包含文本、圖像或 XML 文件等內(nèi)容。此類(lèi)內(nèi)容可以通過(guò)在程序集中表示。資源有兩個(gè)重疊的用例:

    • 合并無(wú)法進(jìn)入源代碼的數(shù)據(jù),例如圖像
    • 在多語(yǔ)言應(yīng)用程序中存儲(chǔ)可能需要翻譯的數(shù)據(jù)

    程序集資源最終是具有名稱(chēng)的字節(jié)流。您可以將程序集視為包含按字符串鍵控的字節(jié)數(shù)組字典。如果在 ildasm 中反匯編包含名為 的資源的程序集.jpg 和名為 的資源,則可以在 中看到這一點(diǎn):

    .mresource public banner.jpg
    {
      // Offset: 0x00000F58 Length: 0x000004F6
    }
    .mresource public data.xml
    {
      // Offset: 0x00001458 Length: 0x0000027E
    }

    在本例中,和數(shù)據(jù)直接包含在程序集中 — 每個(gè)都作為其自己的嵌入資源。這是最簡(jiǎn)單的工作方式。

    .NET 還允許您通過(guò)中間 容器添加內(nèi)容。這些旨在保存可能需要翻譯成不同語(yǔ)言的內(nèi)容。本地化的 可以打包為單獨(dú)的附屬程序集,這些附屬程序集在運(yùn)行時(shí)根據(jù)用戶(hù)的操作系統(tǒng)語(yǔ)言自動(dòng)選取。

    演示了一個(gè)程序集,其中包含兩個(gè)直接嵌入的資源,以及一個(gè)名為 容器,我們?yōu)槠鋭?chuàng)建了兩個(gè)本地化的附屬服務(wù)器。


    資源

    直接嵌入資源

    注意

    在 Window 應(yīng)用商店應(yīng)用中不支持將資源嵌入到程序集中。相反,請(qǐng)將任何額外的文件添加到部署包中,并通過(guò)從應(yīng)用程序 StorageFolder ( Package.Current.InstalledLocation ) 讀取來(lái)訪問(wèn)它們。

    要使用 Visual Studio 直接嵌入資源,請(qǐng)執(zhí)行以下操作:

    • 將文件添加到項(xiàng)目中。
    • 將其生成操作設(shè)置為“嵌入的資源”。

    Visual Studio 始終在資源名稱(chēng)前面加上項(xiàng)目的默認(rèn)命名空間,以及包含該文件的任何子文件夾的名稱(chēng)。因此,如果項(xiàng)目的默認(rèn)命名空間是 Westwind.Reports 并且您的文件稱(chēng)為 .jpg則在文件夾中,資源名稱(chēng)將為 。

    注意

    資源名稱(chēng)區(qū)分大小寫(xiě)。這使得 Visual Studio 中包含資源的項(xiàng)目子文件夾名稱(chēng)有效地區(qū)分大小寫(xiě)。

    若要檢索資源,請(qǐng)?jiān)诎撡Y源的程序集上調(diào)用 GetManifestResourceStream。這將返回一個(gè)流,然后您可以像任何其他流一樣讀取該流:

    Assembly a = Assembly.GetEntryAssembly();
    
    using (Stream s = a.GetManifestResourceStream ("TestProject.data.xml"))
    using (XmlReader r = XmlReader.Create (s))
      ...
    
    System.Drawing.Image image;
    using (Stream s = a.GetManifestResourceStream ("TestProject.banner.jpg"))
      image = System.Drawing.Image.FromStream (s);

    返回的流是可搜索的,因此您也可以執(zhí)行以下操作:

    byte[] data;
    using (Stream s = a.GetManifestResourceStream ("TestProject.banner.jpg"))
      data = new BinaryReader (s).ReadBytes ((int) s.Length);

    如果使用 Visual Studio 嵌入資源,則必須記住包含基于命名空間的前綴。為了幫助避免錯(cuò)誤,可以使用在單獨(dú)的參數(shù)中指定前綴。類(lèi)型的命名空間用作前綴:

    using (Stream s = a.GetManifestResourceStream (typeof (X), "data.xml"))

    X 可以是具有所需資源命名空間的任何類(lèi)型(通常是同一項(xiàng)目文件夾中的類(lèi)型)。

    注意

    在 Visual Studio 中將項(xiàng)目項(xiàng)的生成操作設(shè)置為 Windows Presentation Foundation (WPF) 應(yīng)用程序中的資源與將其生成操作設(shè)置為“嵌入的資源”不同。前者實(shí)際上將該項(xiàng)添加到名為 的 文件中,該文件的內(nèi)容通過(guò) WPF 的應(yīng)用程序類(lèi)訪問(wèn),使用 URI 作為鍵。

    為了增加混淆,WPF 進(jìn)一步重載了術(shù)語(yǔ)“資源”。資源和動(dòng)態(tài)資源都與程序集無(wú)關(guān)!

    GetManifestResourceNames 返回程序集中所有資源的名稱(chēng)。

    .資源文件

    文件是潛在可本地化內(nèi)容的容器。 文件最終會(huì)成為程序集中的嵌入資源,就像任何其他類(lèi)型的文件一樣。不同之處在于您必須執(zhí)行以下操作:

    • 首先將內(nèi)容打包到 文件中
    • 通過(guò) ResourceManager 或 而不是 GetManifestResourceStream 訪問(wèn)其內(nèi)容

    文件以二進(jìn)制形式構(gòu)建,因此不可人工編輯;因此,您必須依靠 .NET 和 Visual Studio 提供的工具來(lái)處理它們。字符串或簡(jiǎn)單數(shù)據(jù)類(lèi)型的標(biāo)準(zhǔn)方法是使用 .resx 格式,可以通過(guò) Visual Studio 或 工具將其轉(zhuǎn)換為 文件。 格式也適用于用于 Windows 窗體或 ASP.NET 應(yīng)用程序的圖像。

    在 WPF 應(yīng)用程序中,必須對(duì)需要由 URI 引用的圖像或類(lèi)似內(nèi)容使用 Visual Studio 的“資源”生成操作。無(wú)論是否需要本地化,這都適用。

    我們將在以下各節(jié)中介紹如何執(zhí)行其中的每一個(gè)操作。

    .resx 文件

    文件是用于生成 文件的設(shè)計(jì)時(shí)格式。 文件使用 XML,其結(jié)構(gòu)為名稱(chēng)/值對(duì),如下所示:

    <root>
      <data name="Greeting">
        <value>hello</value>
      </data>
      <data name="DefaultFontSize" type="System.Int32, mscorlib">
        <value>10</value>
      </data>
    </root>

    若要在 Visual Studio 中創(chuàng)建 文件,請(qǐng)?zhí)砑宇?lèi)型為“資源文件”的項(xiàng)目項(xiàng)。其余工作將自動(dòng)完成:

    • 將創(chuàng)建正確的標(biāo)頭。
    • 提供了一個(gè)設(shè)計(jì)器,用于添加字符串、圖像、文件和其他類(lèi)型的數(shù)據(jù)。
    • 文件會(huì)自動(dòng)轉(zhuǎn)換為 格式,并在編譯時(shí)嵌入到程序集中。
    • 編寫(xiě)一個(gè)類(lèi)來(lái)幫助您稍后訪問(wèn)數(shù)據(jù)。

    注意

    資源設(shè)計(jì)器將圖像添加為類(lèi)型化圖像對(duì)象 (),而不是字節(jié)數(shù)組,這使得它們不適合 WPF 應(yīng)用程序。

    讀取 .resources 文件

    注意

    如果在 Visual Studio 中創(chuàng)建 文件,則會(huì)自動(dòng)生成一個(gè)同名的類(lèi),其中包含用于檢索其每個(gè)項(xiàng)的屬性。

    類(lèi)讀取程序集中嵌入的 文件:

    ResourceManager r = new ResourceManager ("welcome",
                                             Assembly.GetExecutingAssembly());

    (如果資源是在 Visual Studio 中編譯的,則第一個(gè)參數(shù)必須以命名空間為前綴。

    然后,您可以通過(guò)使用強(qiáng)制轉(zhuǎn)換調(diào)用 GetString 或 GetObject 來(lái)訪問(wèn)內(nèi)部?jī)?nèi)容:

    string greeting = r.GetString ("Greeting");
    int fontSize = (int) r.GetObject ("DefaultFontSize");
    Image image = (Image) r.GetObject ("flag.png");      

    枚舉 文件的內(nèi)容:

    ResourceManager r = new ResourceManager (...);
    ResourceSet set = r.GetResourceSet (CultureInfo.CurrentUICulture,
                                        true, true);
    foreach (System.Collections.DictionaryEntry entry in set)
      Console.WriteLine (entry.Key);

    在 Visual Studio 中創(chuàng)建包 URI 資源

    在 WPF 應(yīng)用程序中,XAML 文件需要能夠通過(guò) URI 訪問(wèn)資源。例如:

    <Button>
      <Image Height="50" Source="flag.png"/>
    </Button>

    或者,如果資源位于另一個(gè)程序集中:

    <Button>
      <Image Height="50" Source="UtilsAssembly;Component/flag.png"/>
    </Button>

    (組件是一個(gè)文字關(guān)鍵字。

    若要?jiǎng)?chuàng)建可以這種方式加載的資源,不能使用 文件。相反,必須將文件添加到項(xiàng)目中,并將其生成操作設(shè)置為“資源”(而不是“嵌入的資源”)。然后,Visual Studio 將它們編譯為名為 的 . 文件,該文件也是編譯的 XAML () 文件的主頁(yè)。

    若要以編程方式加載 URI 鍵資源,請(qǐng)調(diào)用 Application.GetResourceStream:

    Uri u = new Uri ("flag.png", UriKind.Relative);
    using (Stream s = Application.GetResourceStream (u).Stream)

    請(qǐng)注意,我們使用了相對(duì) URI。您還可以使用完全以下格式的絕對(duì) URI(三個(gè)逗號(hào)不是拼寫(xiě)錯(cuò)誤):

    Uri u = new Uri ("pack://application:,,,/flag.png");

    如果您希望指定程序集對(duì)象,則可以使用資源管理器來(lái)檢索內(nèi)容:

    Assembly a = Assembly.GetExecutingAssembly();
    ResourceManager r = new ResourceManager (a.GetName().Name + ".g", a);
    using (Stream s = r.GetStream ("flag.png"))
      ...

    資源管理器還允許您枚舉給定程序集中 . 容器的內(nèi)容。

    附屬組件

    中的數(shù)據(jù)是可本地化的。

    當(dāng)應(yīng)用程序在構(gòu)建為以不同語(yǔ)言顯示所有內(nèi)容的 Windows 版本上運(yùn)行時(shí),資源本地化是相關(guān)的。為了保持一致性,應(yīng)用程序也應(yīng)使用相同的語(yǔ)言。

    典型的設(shè)置如下:

    • 主程序集包含默認(rèn)語(yǔ)言或語(yǔ)言的 。
    • 單獨(dú)的包含翻譯成不同語(yǔ)言的本地化 。

    當(dāng)應(yīng)用程序運(yùn)行時(shí),.NET 會(huì)檢查當(dāng)前操作系統(tǒng)的語(yǔ)言(來(lái)自 CultureInfo.CurrentUICulture )。每當(dāng)使用 資源管理器 請(qǐng)求資源時(shí),運(yùn)行時(shí)都會(huì)查找本地化的附屬程序集。如果可用(并且它包含您請(qǐng)求的資源密鑰),則使用該密鑰代替主程序集的版本。

    這意味著您只需添加新的附屬組件即可增強(qiáng)語(yǔ)言支持,而無(wú)需更改主程序集。

    注意

    附屬程序集不能包含可執(zhí)行代碼,只能包含資源。

    附屬程序集部署在程序集文件夾的子目錄中

    programBaseFolder\MyProgram.exe
                     \MyLibrary.exe
                     \XX\MyProgram.resources.dll
                     \XX\MyLibrary.resources.dll

    XX指兩個(gè)字母的語(yǔ)言代碼(例如“de”表示德語(yǔ))或語(yǔ)言和地區(qū)代碼(例如“en-GB”表示英國(guó)的英語(yǔ))。此命名系統(tǒng)允許 CLR 自動(dòng)查找并加載正確的附屬程序集。

    構(gòu)建附屬程序集

    回想一下我們之前的 示例,其中包括以下內(nèi)容:

    <root>
      ...
      <data name="Greeting"
        <value>hello</value>
      </data>
    </root>

    然后,我們?cè)谶\(yùn)行時(shí)檢索問(wèn)候語(yǔ),如下所示:

    ResourceManager r = new ResourceManager ("welcome",
                                             Assembly.GetExecutingAssembly());
    Console.Write (r.GetString ("Greeting"));

    假設(shè)我們希望它改為在德語(yǔ)版本的Windows上運(yùn)行時(shí)寫(xiě)“hallo”。第一步是添加另一個(gè)名為文件,該文件用代替:

    <root>
      <data name="Greeting">
        <value>hallo<value>
      </data>
    </root>

    在 Visual Studio 中,只需執(zhí)行以下操作 — 重新生成時(shí),將在名為 的子目錄中自動(dòng)創(chuàng)建名為 的附屬程序集。

    測(cè)試附屬程序集

    若要模擬在具有不同語(yǔ)言的操作系統(tǒng)上運(yùn)行,必須使用 Thread 類(lèi)更改 CurrentUICulture:

    System.Threading.Thread.CurrentThread.CurrentUICulture
      = new System.Globalization.CultureInfo ("de");

    CultureInfo.CurrentUICulture 是同一屬性的只讀版本。

    注意

    一個(gè)有用的測(cè)試策略是將 l?¢αl??? 轉(zhuǎn)換為仍然可以讀作英語(yǔ)的單詞,但不要使用標(biāo)準(zhǔn)的羅馬 Unicode 字符。

    Visual Studio 設(shè)計(jì)器支持

    Visual Studio 中的設(shè)計(jì)器為本地化組件和可視元素提供了擴(kuò)展支持。WPF 設(shè)計(jì)器具有自己的本地化工作流;其他基于組件的設(shè)計(jì)器使用僅設(shè)計(jì)時(shí)屬性來(lái)使組件或 Windows 窗體控件看起來(lái)具有語(yǔ)言屬性。若要針對(duì)其他語(yǔ)言進(jìn)行自定義,只需更改 Language 屬性,然后開(kāi)始修改組件。屬性為可本地化的控件的所有屬性都將保存到該語(yǔ)言的 文件中。只需更改 Language 屬性,即可隨時(shí)在語(yǔ)言之間切換。

    文化和亞文化

    文化分為文化和亞文化。文化代表一種特定的語(yǔ)言;亞文化代表該語(yǔ)言的區(qū)域變體。.NET 運(yùn)行時(shí)遵循 RFC1766 標(biāo)準(zhǔn),該標(biāo)準(zhǔn)使用兩個(gè)字母的代碼表示區(qū)域性和子區(qū)域性。以下是英語(yǔ)和德語(yǔ)區(qū)域性的代碼:

    En
    de

    以下是澳大利亞英語(yǔ)和奧地利德語(yǔ)亞文化的代碼:

    en-AU
    de-AT

    區(qū)域性在 .NET 中使用 System.Globalization.CultureInfo 類(lèi)表示。可以檢查應(yīng)用程序的當(dāng)前區(qū)域性,如下所示:

    Console.WriteLine (System.Threading.Thread.CurrentThread.CurrentCulture);
    Console.WriteLine (System.Threading.Thread.CurrentThread.CurrentUICulture);

    在針對(duì)澳大利亞本地化的計(jì)算機(jī)上運(yùn)行此函數(shù)說(shuō)明了兩者之間的區(qū)別:

    en-AU
    en-US

    CurrentCulture 反映 Windows 控制面板的區(qū)域設(shè)置,而 CurrentUICulture 反映操作系統(tǒng)的語(yǔ)言。

    區(qū)域設(shè)置包括時(shí)區(qū)以及貨幣和日期的格式等。CurrentCulture 確定諸如 DateTime.Parse 之類(lèi)的函數(shù)的默認(rèn)行為。可以自定義區(qū)域設(shè)置,使其不再類(lèi)似于任何特定區(qū)域性。

    CurrentUICulture 確定計(jì)算機(jī)與用戶(hù)通信的語(yǔ)言。澳大利亞不需要單獨(dú)的英語(yǔ)版本,所以它只使用美國(guó)的英語(yǔ)版本。如果我在奧地利工作了幾個(gè)月,我會(huì)轉(zhuǎn)到控制面板并將我的 CurrentCulture 更改為奧地利德語(yǔ)。但是,鑒于我不會(huì)說(shuō)德語(yǔ),我的 CurrentUICulture 將仍然是美國(guó)英語(yǔ)。

    默認(rèn)情況下,資源管理器使用當(dāng)前線程的 CurrentUICulture 屬性來(lái)確定要加載的正確附屬程序集。資源管理器在加載資源時(shí)使用回退機(jī)制。如果定義了亞文化程序集,則使用該組合體;否則,它將回退到通用區(qū)域性。如果泛型區(qū)域性不存在,它將回退到主程序集中的默認(rèn)區(qū)域性。

    加載、解析和隔離程序集

    從已知位置加載程序集是一個(gè)相對(duì)簡(jiǎn)單的過(guò)程。我們將其稱(chēng)為。

    但是,更常見(jiàn)的是,您(或 CLR)需要加載僅知道其完整(或簡(jiǎn)單)名稱(chēng)的程序集。這稱(chēng)為。程序集分辨率與加載的不同之處在于,必須首先找到程序集。

    在兩種情況下觸發(fā)程序集解析:

    • 通過(guò) CLR,當(dāng)它需要解析依賴(lài)項(xiàng)時(shí)
    • 顯式地,當(dāng)您調(diào)用諸如 Assembly.Load(AssemblyName) 之類(lèi)的方法時(shí)

    為了說(shuō)明第一種方案,請(qǐng)考慮一個(gè)包含主程序集和一組靜態(tài)引用的庫(kù)程序集(依賴(lài)項(xiàng))的應(yīng)用程序,如以下示例所示:

    AdventureGame.dll    // Main assembly
    Terrain.dll          // Referenced assembly
    UIEngine.dll         // Referenced assembly

    通過(guò)“靜態(tài)引用”,我們的意思是.dll是引用和編譯的。編譯器本身不需要執(zhí)行程序集解析,因?yàn)樗桓嬷@式或由 MSBuild)在哪里查找 .dll 和 。在編譯過(guò)程中,它將 Terrain 和 UIEngine 程序集的寫(xiě)入 的元數(shù)據(jù)中,但沒(méi)有關(guān)于在哪里可以找到它們的信息。因此,在運(yùn)行時(shí),必須地形和 UIEngine 程序集。

    程序集加載和解析由 (ALC) 處理;具體來(lái)說(shuō),System.Runtime.Loader 中的 AssemblyLoadContext 類(lèi)的一個(gè)實(shí)例。由于 是應(yīng)用程序的主程序集,因此 CLR 使用 ( AssemblyLoadContext.Default ) 來(lái)解析其依賴(lài)關(guān)系。默認(rèn) ALC 首先通過(guò)查找并檢查名為 的文件(該文件描述了在何處查找依賴(lài)項(xiàng))來(lái)解決依賴(lài)項(xiàng),或者如果不存在,它會(huì)在應(yīng)用程序基文件夾中查找,在該文件夾中可以找到 和 。(默認(rèn) ALC 還會(huì)解析 .NET 運(yùn)行時(shí)程序集。

    作為開(kāi)發(fā)人員,您可以在程序執(zhí)行期間動(dòng)態(tài)加載其他程序集。例如,您可能希望將可選功能打包到僅在購(gòu)買(mǎi)這些功能后部署的程序集中。在這種情況下,您可以通過(guò)調(diào)用 Assembly 來(lái)加載額外的程序集(如果存在)。加載(程序集名稱(chēng)) .

    一個(gè)更復(fù)雜的示例是實(shí)現(xiàn)插件系統(tǒng),用戶(hù)可以在其中提供應(yīng)用程序在運(yùn)行時(shí)檢測(cè)和加載的第三方程序集,以擴(kuò)展應(yīng)用程序的功能。之所以出現(xiàn)復(fù)雜性,是因?yàn)槊總€(gè)插件程序集可能都有自己的依賴(lài)項(xiàng),這些依賴(lài)項(xiàng)也必須解決。

    通過(guò)子類(lèi)化 AssemblyLoadContext 并重寫(xiě)其程序集解析方法 ( Load ),可以控制插件查找其依賴(lài)項(xiàng)的方式。例如,您可能決定每個(gè)插件都應(yīng)駐留在其自己的文件夾中,并且其依賴(lài)項(xiàng)也應(yīng)駐留在該文件夾中。

    ALC 還有另一個(gè)用途:通過(guò)為每個(gè) ALC 實(shí)例化一個(gè)單獨(dú)的 AssemblyLoadContext(插件 + 依賴(lài)項(xiàng)),您可以保持每個(gè) ALC 的隔離,確保它們的依賴(lài)項(xiàng)并行加載并且不會(huì)相互干擾(或主機(jī)應(yīng)用程序)。例如,每個(gè)都可以有自己的 JSON.NET 版本。因此,除了和之外,ALC還提供了機(jī)制。在某些條件下,甚至可以ALC,從而釋放其內(nèi)存。

    在本節(jié)中,我們將詳細(xì)闡述這些原則中的每一個(gè),并描述以下內(nèi)容:

    • ALC 如何處理負(fù)載和分辨率
    • 默認(rèn) ALC 的角色
    • Assembly.Load and for context ALC
    • 如何使用程序集依賴(lài)項(xiàng)解析程序
    • 如何加載和解析非托管庫(kù)
    • 卸載鋁型鋁
    • 舊程序集加載方法

    然后,我們將理論付諸實(shí)踐,并演示如何編寫(xiě)具有ALC隔離的插件系統(tǒng)。

    注意

    AssemblyLoadContext 類(lèi)是 .NET 5 和 .NET Core 的新增功能。在 .NET Framework 中,ALC 存在,但受到限制和隱藏:創(chuàng)建和與它們交互的唯一方法是間接通過(guò)程序集類(lèi)上的 LoadFile(string)、LoadFrom(string) 和 Load(byte[]) 靜態(tài)方法。與 ALC API 相比,這些方法不靈活,它們的使用可能會(huì)導(dǎo)致意外(尤其是在處理依賴(lài)項(xiàng)時(shí))。因此,最好支持在 .NET 5 和 .NET Core 中顯式使用 AssemblyLoadContext API。

    程序集加載上下文

    正如我們剛才所討論的,AssemblyLoadContext 類(lèi)負(fù)責(zé)加載和解析程序集,并提供隔離機(jī)制。

    每個(gè) .NET 程序集對(duì)象只屬于一個(gè) AssemblyLoadContext 。您可以獲取程序集的 ALC,如下所示:

    Assembly assem = Assembly.GetExecutingAssembly();
    AssemblyLoadContext context = AssemblyLoadContext.GetLoadContext (assem);
    Console.WriteLine (context.Name);

    相反,您可以將 ALC 視為“包含”或“擁有”程序集,可以通過(guò)其 Assemblies 屬性獲取這些程序集。繼上一個(gè)之后:

    foreach (Assembly a in context.Assemblies)
      Console.WriteLine (a.FullName);

    類(lèi)還具有枚舉所有 ALC 的靜態(tài) All 屬性。

    你可以通過(guò)實(shí)例化AssemblyLoadContext并提供一個(gè)名稱(chēng)來(lái)創(chuàng)建新的ALC(該名稱(chēng)在調(diào)試時(shí)很有幫助),盡管更常見(jiàn)的是,你首先要子類(lèi)AssemblyLoadContext,以便你可以實(shí)現(xiàn)邏輯來(lái)依賴(lài)關(guān)系;換句話(huà)說(shuō),從程序集加載程序集。

    加載程序集

    AssemblyLoadContext 提供了以下方法來(lái)將程序集顯式加載到其上下文中:

    public Assembly LoadFromAssemblyPath (string assemblyPath);
    public Assembly LoadFromStream (Stream assembly, Stream assemblySymbols);

    第一種方法從文件路徑加載程序集,而第二種方法從 Stream(可以直接來(lái)自?xún)?nèi)存)加載程序集。第二個(gè)參數(shù)是可選的,對(duì)應(yīng)于項(xiàng)目 debug () 文件的內(nèi)容,該文件允許堆棧跟蹤在代碼執(zhí)行時(shí)包含源代碼信息(在異常報(bào)告中很有用)。

    使用這兩種方法時(shí),不會(huì)進(jìn)行。

    下面將程序集 加載到其自己的 ALC 中:

    var alc = new AssemblyLoadContext ("Test");
    Assembly assem = alc.LoadFromAssemblyPath (@"c:\temp\foo.dll");

    如果程序集有效,則加載將始終成功,但要遵守一條重要規(guī)則:程序集的在其 ALC 中必須是唯一的。這意味著不能將同名程序集的多個(gè)版本加載到單個(gè) ALC 中;為此,您必須創(chuàng)建其他 ALC。我們可以加載另一個(gè) 的副本

    var alc2 = new AssemblyLoadContext ("Test 2");
    Assembly assem2 = alc2.LoadFromAssemblyPath (@"c:\temp\foo.dll");

    請(qǐng)注意,源自不同程序集對(duì)象的類(lèi)型是不兼容的,即使這些程序集在其他方面是相同的。在我們的示例中,assem 中的類(lèi)型與 assem2 中的類(lèi)型不兼容。

    加載組件后,除非卸載其 ALC,否則無(wú)法卸載該組件(請(qǐng)參見(jiàn))。CLR 在加載文件的持續(xù)時(shí)間內(nèi)保持文件的鎖定。

    注意

    您可以通過(guò)字節(jié)數(shù)組加載程序集來(lái)避免鎖定文件:

    bytes[] bytes = File.ReadAllBytes (@"c:\temp\foo.dll");
    var ms = new MemoryStream (bytes);
    var assem = alc.LoadFromStream (ms);

    這有兩個(gè)缺點(diǎn):

    • 程序集的“位置”屬性最終將為空。有時(shí),了解程序集的加載位置很有用(某些 API 依賴(lài)于填充程序集)。
    • 專(zhuān)用內(nèi)存消耗必須立即增加,以適應(yīng)程序集的完整大小。如果改為從文件名加載,CLR 將使用內(nèi)存映射文件,這將啟用延遲加載和進(jìn)程共享。此外,如果內(nèi)存不足,操作系統(tǒng)可以釋放其內(nèi)存并根據(jù)需要重新加載,而無(wú)需寫(xiě)入頁(yè)面文件。

    LoadFromAssemblyName

    AssemblyLoadContext 還提供了以下方法,該方法按加載程序集:

    public Assembly LoadFromAssemblyName (AssemblyName assemblyName);

    與剛才討論的兩種方法不同,您不會(huì)傳入任何信息來(lái)指示程序集所在的位置;相反,您正在指示 ALC 程序集。

    解析程序集

    上述方法觸發(fā)。CLR 還會(huì)在加載依賴(lài)項(xiàng)時(shí)觸發(fā)程序集解析。例如,假設(shè)程序集 A 靜態(tài)引用程序集 B。為了解析引用 B,CLR 會(huì)在加載 程序集上觸發(fā)程序集解析。

    注意

    CLR 通過(guò)觸發(fā)程序集解析(無(wú)論觸發(fā)程序集是默認(rèn)程序集還是自定義 ALC)來(lái)解析依賴(lài)項(xiàng)。不同之處在于,使用默認(rèn) ALC,解析規(guī)則是硬編碼的,而使用自定義 ALC,您可以自己編寫(xiě)規(guī)則。

    然后會(huì)發(fā)生什么:

    1. CLR 首先檢查該 ALC 中是否已發(fā)生相同的解析(具有匹配的完整程序集名稱(chēng));如果是這樣,它將返回之前返回的程序集。
    2. 否則,它將調(diào)用 ALC 的(虛擬受保護(hù))Load 方法,該方法執(zhí)行定位和加載程序集的工作。默認(rèn) ALC 的加載方法應(yīng)用我們?cè)谥忻枋龅囊?guī)則。使用自定義 ALC,完全取決于您如何定位程序集。例如,您可以在某個(gè)文件夾中查找,然后在找到程序集時(shí)調(diào)用 LoadFromAssemblyPath。從相同或另一個(gè) ALC 返回已加載的程序集也是完全合法的(我們?cè)谥醒菔玖诉@一點(diǎn))。
    3. 如果步驟 2 返回 null,則 CLR 將在默認(rèn) ALC 上調(diào)用 Load 方法(這用作解析 .NET 運(yùn)行時(shí)和常見(jiàn)應(yīng)用程序程序集的有用“回退”)。
    4. 如果步驟 3 返回 null,則 CLR 將在兩個(gè) ALC 上觸發(fā)解析事件 — 首先在默認(rèn) ALC 上觸發(fā),然后在原始 ALC 上觸發(fā)。
    5. (為了與 .NET Framework 兼容):如果程序集仍未解析,則會(huì)觸發(fā) AppDomain.CurrentDomain.AssemblyResolve 事件。
    6. 注意
    7. 此過(guò)程完成后,CLR 將執(zhí)行“健全性檢查”,以確保加載的任何程序集的名稱(chēng)都與請(qǐng)求的名稱(chēng)兼容。簡(jiǎn)單名稱(chēng)必須匹配;公鑰標(biāo)記必須匹配()。版本不需要匹配 - 它可以高于或低于請(qǐng)求的版本。

    由此,我們可以看到有兩種方法可以在自定義 ALC 中實(shí)現(xiàn)程序集解析:

    • 覆蓋 ALC 的加載方法。這使您的ALC“首先決定”發(fā)生的事情,這通常是可取的(并且在您需要隔離時(shí)是必不可少的)。
    • 處理 ALC 的解析事件。僅當(dāng)默認(rèn) ALC 解析程序集失敗,才會(huì)觸發(fā)此操作。

    注意

    如果將多個(gè)事件處理程序附加到 Resolve 事件,則第一個(gè)返回非 null 值的事件處理程序優(yōu)先。

    為了說(shuō)明這一點(diǎn),假設(shè)我們要加載一個(gè)主應(yīng)用程序在編譯時(shí)一無(wú)所知的程序集,稱(chēng)為 ,位于 (與我們的應(yīng)用程序文件夾不同)。我們還假設(shè) 對(duì) 有私有依賴(lài)。我們希望確保當(dāng)我們加載 并執(zhí)行其代碼時(shí), 可以正確解析。我們還希望確保foo及其私有依賴(lài)項(xiàng)bar不會(huì)干擾主應(yīng)用程序。

    讓我們首先編寫(xiě)一個(gè)覆蓋 Load 的自定義 ALC:

    using System.IO;
    using System.Runtime.Loader;
    
    class FolderBasedALC : AssemblyLoadContext
    {
      readonly string _folder;
      public FolderBasedALC (string folder) => _folder = folder;
    
      protected override Assembly Load (AssemblyName assemblyName)
      {
        // Attempt to find the assembly:
        string targetPath = Path.Combine (_folder, assemblyName.Name + ".dll");
    
        if (File.Exists (targetPath))
          return LoadFromAssemblyPath (targetPath);   // Load the assembly
    
        return null;    // We can’t find it: it could be a .NET runtime assembly
      }
    }

    請(qǐng)注意,在 Load 方法中,如果程序集文件不存在,則返回 null。此檢查很重要,因?yàn)?也將依賴(lài)于 .NET BCL 程序集;因此,Load 方法將在諸如 System.Runtime 之類(lèi)的程序集上調(diào)用。通過(guò)返回 null,我們?cè)试S CLR 回退到默認(rèn) ALC,這將正確解析這些程序集。

    注意

    請(qǐng)注意,我們沒(méi)有嘗試將 .NET 運(yùn)行時(shí) BCL 程序集加載到我們自己的 ALC 中。這些系統(tǒng)程序集不是設(shè)計(jì)為在默認(rèn) ALC 之外運(yùn)行,嘗試將它們加載到您自己的 ALC 中可能會(huì)導(dǎo)致不正確下降和意外的類(lèi)型不兼容。

    以下是我們?nèi)绾问褂米远x ALC 在 中加載 程序集:

    var alc = new FolderBasedALC (@"c:\temp");
    Assembly foo = alc.LoadFromAssemblyPath (@"c:\temp\foo.dll");
    ...

    當(dāng)我們隨后開(kāi)始在 foo 程序集中調(diào)用代碼時(shí),CLR 在某些時(shí)候?qū)⑿枰鉀Q對(duì) 的依賴(lài)關(guān)系。此時(shí),自定義 ALC 的 Load 方法將觸發(fā)并在 中成功找到 程序集。

    在這種情況下,我們的 Load 方法也能夠解析 ,因此我們可以將代碼簡(jiǎn)化為:

    var alc = new FolderBasedALC (@"c:\temp");
    Assembly foo = alc.LoadFromAssemblyName (new AssemblyName ("foo"));
    ...

    現(xiàn)在,讓我們考慮一個(gè)替代解決方案:我們可以實(shí)例化一個(gè)普通的 AssemblyLoadContext 并處理其解析事件,而不是子類(lèi)化 AssemblyLoadContext 并覆蓋 Load:

    var alc = new AssemblyLoadContext ("test");
    alc.Resolving += (loadContext, assemblyName) =>
    {
      string targetPath = Path.Combine (@"c:\temp", assemblyName.Name + ".dll");
      return alc.LoadFromAssemblyPath (targetPath);   // Load the assembly
    };
    Assembly foo = alc.LoadFromAssemblyName (new AssemblyName ("foo"));

    現(xiàn)在請(qǐng)注意,我們不需要檢查程序集是否存在。由于 Resolve 事件在默認(rèn) ALC 有機(jī)會(huì)解析程序集觸發(fā)(并且僅在它失敗時(shí)觸發(fā)),因此我們的處理程序不會(huì)為 .NET BCL 程序集觸發(fā)。這使得此解決方案更簡(jiǎn)單,盡管存在缺點(diǎn)。請(qǐng)記住,在我們的場(chǎng)景中,主應(yīng)用程序在編譯時(shí)對(duì) 或 一無(wú)所知。這意味著主應(yīng)用程序本身可能依賴(lài)于稱(chēng)為 或 的程序集。如果發(fā)生這種情況,解析事件將永遠(yuǎn)不會(huì)觸發(fā),而是加載應(yīng)用程序的 foo 和 bar 程序集。換言之,我們將無(wú)法實(shí)現(xiàn)。

    注意

    我們的 FolderBasedALC 類(lèi)非常適合說(shuō)明程序集解析的概念,但它在現(xiàn)實(shí)生活中的用處較少,因?yàn)樗鼰o(wú)法處理特定于平臺(tái)和(對(duì)于庫(kù)項(xiàng)目)開(kāi)發(fā)時(shí) NuGet 依賴(lài)項(xiàng)。在”中,我們描述了這個(gè)問(wèn)題的解決方案,在中,我們給出了一個(gè)詳細(xì)的例子。

    默認(rèn) ALC

    當(dāng)應(yīng)用程序啟動(dòng)時(shí),CLR 會(huì)為靜態(tài) AssemblyLoadContext 分配一個(gè)特殊的 ALC。默認(rèn)屬性。默認(rèn) ALC 是加載啟動(dòng)程序集及其靜態(tài)引用依賴(lài)項(xiàng)和 .NET 運(yùn)行時(shí) BCL 程序集的位置。

    默認(rèn) ALC 首先在默認(rèn)路徑中查找以自動(dòng)解析程序集(請(qǐng)參閱);這通常等同于應(yīng)用程序的 . 和 . 文件中指示的位置。

    如果 ALC 在其默認(rèn)探測(cè)路徑中找不到程序集,則會(huì)觸發(fā)其解析事件。通過(guò)處理此事件,您可以從其他位置加載程序集,這意味著您可以將應(yīng)用程序的依賴(lài)項(xiàng)部署到其他位置,例如子文件夾、共享文件夾,甚至作為宿主程序集內(nèi)的二進(jìn)制資源:

    AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) =>
    {
      // Try to locate assemblyName, returning an Assembly object or null.
      // Typically you’d call LoadFromAssemblyPath after finding the file.
      // ...
    };

    當(dāng)自定義 ALC 無(wú)法解析時(shí)(換句話(huà)說(shuō),當(dāng)其 Load 方法返回 null 時(shí)),默認(rèn) ALC 中的 Resolve 事件也會(huì)觸發(fā),并且默認(rèn) ALC 無(wú)法解析程序集。

    還可以從解析事件外部將程序集加載到默認(rèn) ALC 中。但是,在繼續(xù)之前,您應(yīng)該首先確定是否可以通過(guò)使用單獨(dú)的 ALC 或使用我們?cè)谙乱还?jié)中描述的方法(使用和 ALC)來(lái)更好地解決問(wèn)題。硬編碼為默認(rèn) ALC 會(huì)使代碼變得脆弱,因?yàn)樗荒茏鳛橐粋€(gè)整體進(jìn)行隔離(例如,通過(guò)單元測(cè)試框架或 LINQPad)。

    如果仍要繼續(xù),最好調(diào)用(即 LoadFromAssemblyName)而不是(例如 LoadFromAssemblyPath),尤其是在程序集被靜態(tài)引用的情況下。這是因?yàn)槌绦蚣赡芤呀?jīng)加載,在這種情況下,LoadFromAssemblyName 將返回已加載的程序集,而 LoadFromAssemblyPath 將引發(fā)異常。

    (使用 LoadFromAssemblyPath ,您還可以冒著從與 ALC 的默認(rèn)解析機(jī)制不一致的位置加載程序集的風(fēng)險(xiǎn)。

    如果程序集位于 ALC 不會(huì)自動(dòng)找到它的位置,您仍然可以按照此過(guò)程進(jìn)行操作,并另外處理 ALC 的解析事件。

    請(qǐng)注意,調(diào)用 LoadFromAssemblyName 時(shí),不需要提供全名;簡(jiǎn)單名稱(chēng)就可以了(即使程序集是強(qiáng)名稱(chēng)的,也是有效的):

    AssemblyLoadContext.Default.LoadFromAssemblyName ("System.Xml");

    但是,如果在名稱(chēng)中包含公鑰標(biāo)記,則它必須與加載的內(nèi)容匹配。

    默認(rèn)探測(cè)

    默認(rèn)探測(cè)路徑通常包括以下內(nèi)容:

    • 中指定的路徑(其中 是應(yīng)用程序主程序集的名稱(chēng))。如果此文件不存在,則改用應(yīng)用程序基文件夾。
    • 包含 .NET 運(yùn)行時(shí)系統(tǒng)程序集的文件夾(如果應(yīng)用程序依賴(lài)于框架)。

    MSBuild 自動(dòng)生成一個(gè)名為 的文件,該文件描述了在何處查找其所有依賴(lài)項(xiàng)。其中包括放置在應(yīng)用程序基文件夾中的與平臺(tái)無(wú)關(guān)的程序集,以及放置在運(yùn)行時(shí)子目錄下(如 或 )下的特定于平臺(tái)的程序集。

    生成的 . 文件中指定的路徑相對(duì)于應(yīng)用程序基文件夾,或您在 AppName.runtimeconfig.json 和/或 配置文件的 additionalProbingPath 部分中指定的任何其他文件夾(后者僅適用于開(kāi)發(fā)環(huán)境)。

    “當(dāng)前”的 ALC

    在上一節(jié)中,我們警告不要將程序集顯式加載到默認(rèn) ALC 中。相反,您通常想要的是加載/解析到“當(dāng)前”ALC 中。

    在大多數(shù)情況下,“當(dāng)前”ALC 是包含當(dāng)前正在執(zhí)行的程序集的 ALC:

    var executingAssem = Assembly.GetExecutingAssembly();
    var alc = AssemblyLoadContext.GetLoadContext (executingAssem);
    
    Assembly assem = alc.LoadFromAssemblyName (...);  // to resolve by name
            // OR: = alc.LoadFromAssemblyPath (...);  // to load by path

    以下是獲取 ALC 的更靈活、更明確的方法:

    var myAssem = typeof (SomeTypeInMyAssembly).Assembly;
    var alc = AssemblyLoadContext.GetLoadContext (myAssem);
    ...

    有時(shí),無(wú)法推斷“當(dāng)前”ALC。例如,假設(shè)您負(fù)責(zé)編寫(xiě) .NET 二進(jìn)制序列化程序(我們將 的聯(lián)機(jī)補(bǔ)充中介紹序列化)。像這樣的序列化程序?qū)懭胨蛄谢念?lèi)型的全名(包括其程序集名稱(chēng)),必須在反序列化期間這些名稱(chēng)。問(wèn)題是,您應(yīng)該使用哪種 ALC?依賴(lài)正在執(zhí)行的程序集的問(wèn)題在于,它將返回包含反序列化程序的任何程序集,而不是反序列化程序的程序集。

    最好的解決方案不是猜測(cè),而是問(wèn):

    public object Deserialize (Stream stream, AssemblyLoadContext alc)
    {
      ...
    }

    明確可以最大限度地提高靈活性并最大限度地減少犯錯(cuò)的機(jī)會(huì)。調(diào)用方現(xiàn)在可以決定什么應(yīng)該算作“當(dāng)前”ALC:

    var assem = typeof (SomeTypeThatIWillBeDeserializing).Assembly;
    var alc = AssemblyLoadContext.GetLoadContext (assem);
    var object = Deserialize (someStream, alc);

    Assembly.Load and Concontext ALC

    幫助處理將程序集加載到當(dāng)前執(zhí)行的 ALC 中的常見(jiàn)情況;那是

    var executingAssem = Assembly.GetExecutingAssembly();
    var alc = AssemblyLoadContext.GetLoadContext (executingAssem);
    Assembly assem = alc.LoadFromAssemblyName (...);

    Microsoft已在程序集類(lèi)中定義了以下方法

    public static Assembly Load (string assemblyString);

    以及接受 AssemblyName 對(duì)象的功能相同的版本:

    public static Assembly Load (AssemblyName assemblyRef);

    (不要將這些方法與傳統(tǒng)的 Load(byte[]) 方法混淆,后者的行為方式完全不同 — 請(qǐng)參閱

    與 LoadFromAssemblyName 一樣,您可以選擇指定程序集的簡(jiǎn)單名稱(chēng)、部分名稱(chēng)或全名:

    Assembly a = Assembly.Load ("System.Private.Xml");

    這會(huì)將 System.Private.Xml 程序集加載到的任何 ALC 中。

    在本例中,我們指定了一個(gè)簡(jiǎn)單的名稱(chēng)。以下字符串也是有效的,并且在 .NET 中所有字符串的結(jié)果都相同:

    "System.Private.Xml, PublicKeyToken=cc7b13ffcd2ddd51"
    "System.Private.Xml, Version=4.0.1.0"
    "System.Private.Xml, Version=4.0.1.0, PublicKeyToken=cc7b13ffcd2ddd51"

    如果選擇指定公鑰標(biāo)記,則它必須與加載的內(nèi)容匹配。

    注意

    Microsoft開(kāi)發(fā)人員網(wǎng)絡(luò) (MSDN) 警告不要從部分名稱(chēng)加載程序集,建議您指定確切的版本和公鑰標(biāo)記。它們的基本原理基于與 .NET Framework 相關(guān)的因素,例如全局程序集緩存和代碼訪問(wèn)安全性的影響。在 .NET 5 和 .NET Core 中,不存在這些因素,從簡(jiǎn)單名稱(chēng)或部分名稱(chēng)加載通常是安全的。

    這兩種方法都嚴(yán)格用于,因此無(wú)法指定文件路徑。(如果在 AssemblyName 對(duì)象中填充 CodeBase 屬性,則將忽略該屬性。

    警告

    不要落入使用 Assembly.Load 加載靜態(tài)引用程序集的陷阱。在這種情況下,您需要做的就是引用程序集中的一個(gè)類(lèi)型,并從中獲取程序集:

    Assembly a = typeof (System.Xml.Formatting).Assembly;

    或者,您甚至可以這樣做:

    Assembly a = System.Xml.Formatting.Indented.GetType().Assembly;

    這可以防止對(duì)程序集名稱(chēng)進(jìn)行硬編碼(將來(lái)可能會(huì)更改),同時(shí)在 ALC 上觸發(fā)程序集解析(就像 一樣)。

    如果你要寫(xiě)大會(huì).自己加載方法,它(幾乎)看起來(lái)像這樣:

    [MethodImpl(MethodImplOptions.NoInlining)]
    Assembly Load (string name)
    {
      Assembly callingAssembly = Assembly.GetCallingAssembly();
      var callingAlc = AssemblyLoadContext.GetLoadContext (callingAssembly);
      return callingAlc.LoadFromAssemblyName (new AssemblyName (name));
    }

    進(jìn)入情境反思

    集會(huì)。加載 使用調(diào)用程序集的 ALC 上下文的策略在程序集 失敗時(shí)失敗。負(fù)載通過(guò)中介(如反序列化程序或單元測(cè)試運(yùn)行程序)調(diào)用。如果中介在不同的程序集中定義,則使用中介的加載上下文,而不是調(diào)用方的加載上下文。

    注意

    我們之前在討論如何編寫(xiě)反序列化程序時(shí)描述了這種情況。在這種情況下,理想的解決方案是強(qiáng)制調(diào)用方指定 ALC,而不是使用 Assembly.Load(string) 推斷它。

    但是,由于 .NET 5 和 .NET Core 是從 .NET Framework 演變而來(lái)的(在 .NET Framework 中,隔離是通過(guò)應(yīng)用程序域而不是 ALC 完成的),因此理想的解決方案并不普遍,并且在無(wú)法可靠地推斷 ALC 的情況下,有時(shí)會(huì)不恰當(dāng)?shù)厥褂?Assembly.Load(string)。一個(gè)例子是 .NET 二進(jìn)制序列化程序。

    要允許程序集 。加載在這種情況下仍然有效,Microsoft向AssemblyLoadContext添加了一個(gè)名為EnterContextualReflection的方法。這會(huì)將 ALC 分配給 AssemblyLoadContext。當(dāng)前上下文反射上下文 .盡管這是一個(gè)靜態(tài)屬性,但其值存儲(chǔ)在 AsyncLocal 變量中,因此它可以在不同的線程上保存單獨(dú)的值(但仍會(huì)在整個(gè)異步操作中保留)。

    如果此屬性為非空,則程序集 .Load 自動(dòng)使用它,而不是調(diào)用 ALC:

    Method1();
    
    var myALC = new AssemblyLoadContext ("test");
    using (myALC.EnterContextualReflection())
    {
       Console.WriteLine (
         AssemblyLoadContext.CurrentContextualReflectionContext.Name);  // test
    
       Method2();
    }
    
    // Once disposed, EnterContextualReflection() no longer has an effect.
    Method3();
    
    void Method1() => Assembly.Load ("...");    // Will use calling ALC
    void Method2() => Assembly.Load ("...");    // Will use myALC
    void Method3() => Assembly.Load ("...");    // Will use calling ALC

    我們之前演示了如何編寫(xiě)功能類(lèi)似于 匯編 的方法。負(fù)荷。下面是一個(gè)更準(zhǔn)確的版本,它考慮了上下文反射上下文:

    [MethodImpl(MethodImplOptions.NoInlining)]
    Assembly Load (string name)
    {
      var alc = AssemblyLoadContext.CurrentContextualReflectionContext
         ?? AssemblyLoadContext.GetLoadContext (Assembly.GetCallingAssembly());
    
      return alc.LoadFromAssemblyName (new AssemblyName (name));
    }

    盡管上下文反射上下文在允許舊代碼運(yùn)行方面很有用,但更可靠的解決方案(如前所述)是修改調(diào)用 Assembly.Load 的代碼,使其改為在調(diào)用方傳入的 ALC 上調(diào)用 LoadFromAssemblyName。

    注意

    .NET Framework 沒(méi)有等效的 EnterContextualReflection,也不需要它,盡管具有相同的程序集。加載方法。這是因?yàn)槭褂?.NET Framework,隔離主要通過(guò)而不是 ALC 實(shí)現(xiàn)。應(yīng)用程序域提供了更強(qiáng)的隔離模型,其中每個(gè)應(yīng)用程序域都有自己的默認(rèn)加載上下文,因此即使僅使用默認(rèn)加載上下文,隔離仍然可以工作。

    加載和解析非托管庫(kù)

    ALC 還可以加載和解析本機(jī)庫(kù)。調(diào)用標(biāo)有 [DllImport] 屬性的外部方法時(shí),將觸發(fā)本機(jī)解析:

    [DllImport ("SomeNativeLibrary.dll")]
    static extern int SomeNativeMethod (string text);

    由于我們沒(méi)有在 [DllImport] 屬性中指定完整路徑,因此調(diào)用 SomeNativeMethod 會(huì)在包含定義 SomeNativeMethod 的程序集的任何 ALC 中觸發(fā)解析。

    ALC 中的虛擬方法稱(chēng)為 LoadUnmanagedDll ,方法稱(chēng)為 LoadUnmanagedDllFromPath:

    protected override IntPtr LoadUnmanagedDll (string unmanagedDllName)
    {
      // Locate the full path of unmanagedDllName...
      string fullPath = ...
      return LoadUnmanagedDllFromPath (fullPath);    // Load the DLL
    }

    如果找不到該文件,可以返回 IntPtr.Zero 。然后,CLR 將觸發(fā) ALC 的 ResolvevingUnmanagedDll 事件。

    有趣的是,LoadUnmanagedDllFromPath 方法受到保護(hù),因此通常無(wú)法從 ResolvevingUnmanagedDll 事件處理程序調(diào)用它。但是,您可以通過(guò)調(diào)用靜態(tài) NativeLibrary.Load 來(lái)獲得相同的結(jié)果:

    someALC.ResolvingUnmanagedDll += (requestingAssembly, unmanagedDllName) =>
    {
      return NativeLibrary.Load ("(full path to unmanaged DLL)");
    };

    盡管本機(jī)庫(kù)通常由 ALC 解析和加載,但它們并不“屬于”ALC。加載后,本機(jī)庫(kù)將獨(dú)立存在,并負(fù)責(zé)解析它可能具有的任何傳遞依賴(lài)項(xiàng)。此外,本機(jī)庫(kù)是進(jìn)程的全局庫(kù),因此如果它們具有相同的文件名,則無(wú)法加載兩個(gè)不同版本的本機(jī)庫(kù)。

    程序集依賴(lài)解析程序

    在中,我們說(shuō)過(guò)默認(rèn) ALC 會(huì)讀取 .deps.json 和 . 文件(如果存在),以確定在何處查找以解析特定于平臺(tái)和開(kāi)發(fā)時(shí) NuGet 依賴(lài)項(xiàng)。

    如果要將程序集加載到具有特定于平臺(tái)或 NuGet 依賴(lài)項(xiàng)的自定義 ALC 中,則需要以某種方式重現(xiàn)此邏輯。可以通過(guò)分析配置文件并仔細(xì)遵循特定于平臺(tái)的名字對(duì)象上的規(guī)則來(lái)實(shí)現(xiàn)此目的,但這樣做不僅困難,而且如果規(guī)則在更高版本的 .NET 中發(fā)生更改,則編寫(xiě)的代碼將中斷。

    程序集依賴(lài)解析器類(lèi)解決了這個(gè)問(wèn)題。若要使用它,請(qǐng)使用要探測(cè)其依賴(lài)項(xiàng)的程序集的路徑實(shí)例化它:

    var resolver = new AssemblyDependencyResolver (@"c:\temp\foo.dll");

    然后,若要查找依賴(lài)項(xiàng)的路徑,請(qǐng)調(diào)用 ResolveAssemblyToPath 方法:

    string path = resolver.ResolveAssemblyToPath (new AssemblyName ("bar"));

    在沒(méi)有 . 文件的情況下(或者如果 . 不包含任何與 bar.dll) 相關(guān)的內(nèi)容,這將計(jì)算為 。

    同樣,可以通過(guò)調(diào)用 ResolveUnmanagedDllToPath 來(lái)解析非托管依賴(lài)項(xiàng)。

    說(shuō)明更復(fù)雜的方案的一個(gè)好方法是創(chuàng)建一個(gè)名為 ClientApp 的新控制臺(tái)項(xiàng)目,然后添加對(duì) 的 NuGet 引用。添加以下類(lèi):

    using Microsoft.Data.SqlClient;
    
    namespace ClientApp
    {
      public class Program
      {
        public static SqlConnection GetConnection() => new SqlConnection();
        static void Main() => GetConnection();   // Test that it resolves
      }
    }

    現(xiàn)在構(gòu)建應(yīng)用程序并查看輸出文件夾:您將看到一個(gè)名為 的文件。但是,此文件在運(yùn)行時(shí)加載,嘗試顯式加載它會(huì)引發(fā)異常。實(shí)際加載的程序集位于(或)子文件夾中;默認(rèn) ALC 知道加載它,因?yàn)樗馕?文件。

    如果要嘗試從另一個(gè)應(yīng)用程序加載 .dll 程序集,則需要編寫(xiě)一個(gè)可以解析其依賴(lài)項(xiàng)的 ALC,。這樣做時(shí),僅查看 所在的文件夾是不夠的(就像中所做的那樣)。相反,您需要使用 AssemblyDependencyResolver 來(lái)確定該文件對(duì)于正在使用的平臺(tái)的位置:

    string path = @"C:\source\ClientApp\bin\Debug\netcoreapp3.0\ClientApp.dll";
    var resolver = new AssemblyDependencyResolver (path);
    var sqlClient = new AssemblyName ("Microsoft.Data.SqlClient");
    Console.WriteLine (resolver.ResolveAssemblyToPath (sqlClient));

    在 Windows 計(jì)算機(jī)上,這將輸出以下內(nèi)容:

    C:\source\ClientApp\bin\Debug\netcoreapp3.0\runtimes\win\lib\netcoreapp2.1
    \Microsoft.Data.SqlClient.dll

    我們?cè)谥薪o出了一個(gè)完整的示例。

    卸載鋁型鋁

    在簡(jiǎn)單的情況下,可以卸載非默認(rèn)的 AssemblyLoadContext ,釋放內(nèi)存并釋放它加載的程序集上的文件鎖。為此,ALC 必須已使用 isCollectible 參數(shù) true 進(jìn)行實(shí)例化:

    var alc = new AssemblyLoadContext ("test", isCollectible:true);

    然后,可以在 ALC 上調(diào)用 Unload 方法來(lái)啟動(dòng)卸載過(guò)程。

    卸載模型是合作的,而不是搶占的。如果 ALC 的任何程序集中的任何方法正在執(zhí)行,則卸載將延遲到這些方法完成。

    實(shí)際卸載發(fā)生在垃圾收集期間;如果來(lái)自 ALC 外部的任何內(nèi)容對(duì) ALC 內(nèi)部的任何內(nèi)容(包括對(duì)象、類(lèi)型和程序集)有任何(非弱)引用,則不會(huì)發(fā)生這種情況。API(包括 .NET BCL 中的 API)在靜態(tài)字段或字典中緩存對(duì)象或訂閱事件的情況并不少見(jiàn),這使得創(chuàng)建防止卸載的引用變得容易,尤其是在 ALC 中的代碼以非平凡的方式使用其 ALC 外部的 API 時(shí)。確定卸載失敗的原因很困難,需要使用 WinDbg 等工具。

    舊版加載方法

    如果您仍在使用 .NET Framework(或編寫(xiě)面向 .NET Standard 的庫(kù),并希望支持 .NET Framework),您將無(wú)法使用 AssemblyLoadContext 類(lèi)。加載是通過(guò)使用以下方法完成的:

    public static Assembly LoadFrom (string assemblyFile);
    public static Assembly LoadFile (string path);
    public static Assembly Load (byte[] rawAssembly);

    LoadFile 和 Load(byte[]) 提供隔離,而 LoadFrom 不提供。

    解析是通過(guò)處理應(yīng)用程序域的 AssemblyResolve 事件來(lái)實(shí)現(xiàn)的,該事件的工作方式類(lèi)似于默認(rèn) ALC 的解析事件。

    Assembly.Load(string) 方法也可用于觸發(fā)解析,并以類(lèi)似的方式工作。

    加載自

    LoadFrom 將程序集從給定路徑加載到默認(rèn) ALC 中。這有點(diǎn)像調(diào)用AssemblyLoadContext.Default.LoadFromAssemblyPath,除了:

    • 如果默認(rèn) ALC 中已存在具有相同簡(jiǎn)單名稱(chēng)的程序集,則 LoadFrom 將返回該程序集,而不是引發(fā)異常。
    • 如果默認(rèn) ALC 中具有相同簡(jiǎn)單名稱(chēng)的程序集,并且發(fā)生了加載,則會(huì)為該程序集提供特殊的“LoadFrom”狀態(tài)。此狀態(tài)會(huì)影響默認(rèn) ALC 的解析邏輯,因?yàn)槿绻摮绦蚣谕挥腥魏我蕾?lài)項(xiàng),則這些依賴(lài)項(xiàng)將自動(dòng)解析。

    注意

    .NET Framework 有一個(gè) (GAC)。如果程序集存在于 GAC 中,則 CLR 將始終從那里加載。這適用于所有三種加載方法。

    LoadFrom 自動(dòng)解析傳遞相同文件夾依賴(lài)項(xiàng)的能力可能很方便 - 直到它加載不應(yīng)該加載的程序集。由于此類(lèi)方案可能難以調(diào)試,因此最好使用 Load(string) 或 LoadFile 并通過(guò)處理應(yīng)用程序域的 AssemblyResolve 事件來(lái)解析傳遞依賴(lài)項(xiàng)。這使您能夠決定如何解析每個(gè)程序集,并允許調(diào)試(通過(guò)在事件處理程序中創(chuàng)建斷點(diǎn))。

    加載文件和加載(字節(jié)[])

    LoadFile 和 Load(byte[]) 將程序集從給定的文件路徑或字節(jié)數(shù)組加載到新的 ALC 中。與 LoadFrom 不同,這些方法提供隔離,并允許您加載同一程序集的多個(gè)版本。但是,有兩個(gè)注意事項(xiàng):

    • 使用相同的路徑再次調(diào)用 LoadFile 將返回以前加載的程序集。
    • 在 .NET Framework 中,這兩種方法都首先檢查 GAC 并從那里加載(如果程序集存在)。

    使用 LoadFile 和 Load(byte[]) ,您最終會(huì)得到每個(gè)程序集的單獨(dú) ALC(請(qǐng)注意)。這樣可以實(shí)現(xiàn)隔離,盡管它可能會(huì)使管理更加尷尬。

    要解析依賴(lài)關(guān)系,請(qǐng)?zhí)幚?AppDomain 的解析事件,該事件在所有 ALC 上觸發(fā):

    AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
    {
      string fullAssemblyName = args.Name;
      // return an Assembly object or null
      ...
    };

    args 變量還包括一個(gè)名為 請(qǐng)求程序集 ,它告訴您哪個(gè)程序集觸發(fā)了解析。

    找到程序集后,可以調(diào)用程序集 。加載文件以加載它。

    注意

    可以使用 AppDomain.CurrentDomain.GetAssemblies() 枚舉已加載到當(dāng)前應(yīng)用程序域中的所有程序集。這也適用于 .NET 5,它等效于以下內(nèi)容:

    AssemblyLoadContext.All.SelectMany (a => a.Assemblies)

    編寫(xiě)插件系統(tǒng)

    為了充分演示本節(jié)中介紹的概念,讓我們編寫(xiě)一個(gè)插件系統(tǒng),該系統(tǒng)使用可卸載的 ALC 來(lái)隔離每個(gè)插件。

    我們的演示系統(tǒng)最初將包含三個(gè) .NET 項(xiàng)目:

    插件.通用 (庫(kù))

    定義插件將實(shí)現(xiàn)的接口

    資本化器(圖書(shū)館)

    將文本大寫(xiě)的插件

    Plugin.Host (控制臺(tái)應(yīng)用程序)

    查找和調(diào)用插件

    假設(shè)項(xiàng)目駐留在以下目錄中:

    c:\source\PluginDemo\Plugin.Common
    c:\source\PluginDemo\Capitalizer
    c:\source\PluginDemo\Plugin.Host

    所有項(xiàng)目都將引用 Plugin.Common 庫(kù),并且不會(huì)有其他項(xiàng)目間引用。

    注意

    如果 Plugin.Host 引用 Capitalizer,我們就不會(huì)編寫(xiě)插件系統(tǒng);中心思想是插件是在 Plugin.Host 和 Plugin.Common 發(fā)布后由第三方編寫(xiě)的。

    如果您使用的是 Visual Studio,為了本演示,將所有三個(gè)項(xiàng)目放入一個(gè)解決方案中會(huì)很方便。如果這樣做,請(qǐng)右鍵單擊 Plugin.Host 項(xiàng)目,選擇“構(gòu)建依賴(lài)項(xiàng)”>“項(xiàng)目依賴(lài)項(xiàng)”,然后勾選 Capitalizer 項(xiàng)目。這會(huì)強(qiáng)制 Capitalizer 在運(yùn)行 Plugin.Host 項(xiàng)目時(shí)構(gòu)建,而不添加引用。

    插件.常見(jiàn)

    讓我們從Plugin.Common開(kāi)始。我們的插件將執(zhí)行一個(gè)非常簡(jiǎn)單的任務(wù),即轉(zhuǎn)換字符串。以下是我們?nèi)绾味x接口:

    namespace Plugin.Common
    {
      public interface ITextPlugin
      {
        string TransformText (string input);
      }
    }

    這就是Plugin.Common的全部?jī)?nèi)容。

    資本化器(插件)

    我們的 Capitalizer 插件將引用 Plugin.Common 并包含一個(gè)類(lèi)。現(xiàn)在,我們將保持邏輯簡(jiǎn)單,以便插件沒(méi)有額外的依賴(lài)項(xiàng):

    public class CapitalizerPlugin : Plugin.Common.ITextPlugin
    {
      public string TransformText (string input) => input.ToUpper();
    }

    如果同時(shí)生成這兩個(gè)項(xiàng)目并查看 Capitalizer 的輸出文件夾,您將看到以下兩個(gè)程序集:

    Capitalizer.dll      // Our plug-in assembly
    Plugin.Common.dll    // Referenced assembly

    插件主機(jī)

    Plugin.Host 是一個(gè)具有兩個(gè)類(lèi)的控制臺(tái)應(yīng)用程序。第一個(gè)類(lèi)是用于加載插件的自定義 ALC:

    class PluginLoadContext : AssemblyLoadContext
    {
      AssemblyDependencyResolver _resolver;
    
      public PluginLoadContext (string pluginPath, bool collectible)
        // Give it a friendly name to help with debugging:
        : base (name: Path.GetFileName (pluginPath), collectible)
      {
        // Create a resolver to help us find dependencies.
        _resolver = new AssemblyDependencyResolver (pluginPath);
      }
    
      protected override Assembly Load (AssemblyName assemblyName)
      {
        // See below
        if (assemblyName.Name == typeof (ITextPlugin).Assembly.GetName().Name)
          return null;
    
        string target = _resolver.ResolveAssemblyToPath (assemblyName);
    
        if (target != null)
          return LoadFromAssemblyPath (target);
    
        // Could be a BCL assembly. Allow the default context to resolve.
        return null;   
      }
    
      protected override IntPtr LoadUnmanagedDll (string unmanagedDllName)
      {
        string path = _resolver.ResolveUnmanagedDllToPath (unmanagedDllName);
    
        return path == null
          ? IntPtr.Zero
          : LoadUnmanagedDllFromPath (path);
      }
    }

    在構(gòu)造函數(shù)中,我們傳入主插件程序集的路徑以及一個(gè)標(biāo)志,以指示我們是否希望 ALC 是可收集的(以便可以卸載它)。

    Load 方法是我們處理依賴(lài)項(xiàng)解析的地方。所有插件都必須引用 Plugin.Common,以便它們可以實(shí)現(xiàn) ITextPlugin 。這意味著 Load 方法將在某個(gè)時(shí)候觸發(fā)以解析 Plugin.Common。我們需要小心,因?yàn)椴寮妮敵鑫募A可能不僅包含 .dll,還包含它自己的 副本。如果我們要把 的副本加載到 PluginLoadContext 中,我們最終會(huì)得到程序集的兩個(gè)副本:一個(gè)在主機(jī)的默認(rèn)上下文中,另一個(gè)在插件的 PluginLoadContext 中。程序集將不兼容,主機(jī)會(huì)抱怨插件沒(méi)有實(shí)現(xiàn) ITextPlugin!

    為了解決這個(gè)問(wèn)題,我們顯式檢查此條件:

        if (assemblyName.Name == typeof (ITextPlugin).Assembly.GetName().Name)
          return null;

    返回 null 允許主機(jī)的默認(rèn) ALC 改為解析程序集。

    注意

    我們可以返回typeof(ITextPlugin),而不是返回null。組裝 ,它也可以正常工作。我們?nèi)绾未_定 ITextPlugin 將在主機(jī)的 ALC 上解析,而不是在我們的 PluginLoadContext 上解析?請(qǐng)記住,我們的 PluginLoadContext 類(lèi)是在 Plugin.Host 程序集中定義的。因此,從此類(lèi)靜態(tài)引用的任何類(lèi)型都將在 Plugin.Host 的 ALC 上觸發(fā)程序集解析。

    檢查公共程序集后,我們使用 AssemblyDependencyResolver 來(lái)查找插件可能具有的任何私有依賴(lài)項(xiàng)。(現(xiàn)在,不會(huì)有。

    請(qǐng)注意,我們還重寫(xiě)了 LoadUnamangedDll 方法。這可確保如果插件具有任何非托管依賴(lài)項(xiàng),這些依賴(lài)項(xiàng)也將正確加載。

    在 Plugin.Host 中編寫(xiě)的第二個(gè)類(lèi)是主程序本身。為簡(jiǎn)單起見(jiàn),讓我們對(duì) Capitalizer 插件的路徑進(jìn)行硬編碼(在現(xiàn)實(shí)生活中,您可能會(huì)通過(guò)在已知位置查找 DLL 或從配置文件中讀取來(lái)發(fā)現(xiàn)插件的路徑):

    class Program
    {
      const bool UseCollectibleContexts = true;
    
      static void Main()
      {
        const string captializer = @"C:\source\PluginDemo\"
          + @"Capitalizer\bin\Debug\netcoreapp3.0\Capitalizer.dll";
    
        Console.WriteLine (TransformText ("big apple", captializer));
      }
    
      static string TransformText (string text, string pluginPath)
      {
        var alc = new PluginLoadContext (pluginPath, UseCollectibleContexts);
        try
        {
          Assembly assem = alc.LoadFromAssemblyPath (pluginPath);
    
          // Locate the type in the assembly that implements ITextPlugin:
          Type pluginType = assem.ExportedTypes.Single (t => 
                            typeof (ITextPlugin).IsAssignableFrom (t));
    
          // Instantiate the ITextPlugin implementation:
          var plugin = (ITextPlugin)Activator.CreateInstance (pluginType);
    
          // Call the TransformText method
          return plugin.TransformText (text);
        }
        finally
        {
          if (UseCollectibleContexts) alc.Unload();    // unload the ALC
        }  
      }
    }

    讓我們看一下 TransformText 方法。我們首先為插件實(shí)例化一個(gè)新的 ALC,然后要求它加載主插件程序集。接下來(lái),我們使用 Reflection 來(lái)定位實(shí)現(xiàn) ITextPlugin 的類(lèi)型(我們將在第 中詳細(xì)介紹)。然后,我們實(shí)例化插件,調(diào)用 TransformText 方法,并卸載 ALC。

    注意

    如果需要重復(fù)調(diào)用 TransformText 方法,更好的方法是緩存 ALC,而不是在每次調(diào)用后卸載它。

    下面是輸出:

    BIG APPLE

    添加依賴(lài)項(xiàng)

    我們的代碼完全能夠解析和隔離依賴(lài)項(xiàng)。為了說(shuō)明這一點(diǎn),讓我們首先添加對(duì) 的 NuGet 引用。您可以通過(guò)Visual Studio UI或?qū)⒁韵略靥砑拥轿募?lái)執(zhí)行此操作:

      <ItemGroup>
        <PackageReference Include="Humanizer.Core" Version="2.6.2" />
      </ItemGroup>

    現(xiàn)在,修改資本插件,如下所示:

    using Humanizer;
    namespace Capitalizer
    {
      public class CapitalizerPlugin : Plugin.Common.ITextPlugin
      {
        public string TransformText (string input) => input.Pascalize();
      }
    }

    如果重新運(yùn)行該程序,輸出現(xiàn)在將是這樣的:

    BigApple

    接下來(lái),我們創(chuàng)建另一個(gè)名為 Pluralizer 的插件。創(chuàng)建一個(gè)新的 .NET 庫(kù)項(xiàng)目,并添加對(duì) 的 NuGet 引用:

      <ItemGroup>
        <PackageReference Include="Humanizer.Core" Version="2.7.9" />
      </ItemGroup>

    現(xiàn)在,添加一個(gè)名為 復(fù)數(shù)器插件 。這將類(lèi)似于 資本化插件 ,但我們調(diào)用 Pluralize 方法,改為:

    using Humanizer;
    namespace Pluralizer
    {
      public class PluralizerPlugin : Plugin.Common.ITextPlugin
      {
        public string TransformText (string input) => input.Pluralize();
      }
    }

    最后,我們需要在 Plugin.Host 的 Main 方法中添加代碼來(lái)加載和運(yùn)行 Pluralizer 插件:

      static void Main()
      {
        const string captializer = @"C:\source\PluginDemo\"
          + @"Capitalizer\bin\Debug\netcoreapp3.0\Capitalizer.dll";
    
        Console.WriteLine (TransformText ("big apple", captializer));
    
        const string pluralizer = @"C:\source\PluginDemo\"
          + @"Pluralizer\bin\Debug\netcoreapp3.0\Pluralizer.dll";
    
        Console.WriteLine (TransformText ("big apple", pluralizer));
      }

    輸出現(xiàn)在將如下所示:

    BigApple
    big apples

    若要完全了解發(fā)生了什么,請(qǐng)將 UseCollectibleContexts 常量更改為 false,并將以下代碼添加到 Main 方法以枚舉 ALC 及其程序集:

    foreach (var context in AssemblyLoadContext.All)
    {
      Console.WriteLine ($"Context: {context.GetType().Name} {context.Name}");
    
      foreach (var assembly in context.Assemblies)
          Console.WriteLine ($"  Assembly: {assembly.FullName}");
    }

    在輸出中,您可以看到兩個(gè)不同版本的 Humanizer,每個(gè)版本都加載到自己的 ALC 中:

    Context: PluginLoadContext Capitalizer.dll
      Assembly: Capitalizer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...
      Assembly: Humanizer, Version=2.6.0.0, Culture=neutral, PublicKeyToken=...
    Context: PluginLoadContext Pluralizer.dll
      Assembly: Pluralizer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...
      Assembly: Humanizer, Version=2.7.0.0, Culture=neutral, PublicKeyToken=...
    Context: DefaultAssemblyLoadContext Default
      Assembly: System.Private.CoreLib, Version=4.0.0.0, Culture=neutral,...
      Assembly: Host, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
      ...

    注意

    即使兩個(gè)插件都使用相同的 Humanizer 版本,隔離單獨(dú)的程序集仍然是有益的,因?yàn)槊總€(gè)程序集都有自己的靜態(tài)變量。

    本文分享內(nèi)容來(lái)自圖書(shū)《學(xué)習(xí)OpenCV 4:基于Python的算法實(shí)戰(zhàn)》,該書(shū)內(nèi)容如下:

    第1章 OpenCV快速入門(mén);
    第2章 圖像讀寫(xiě)模塊imgcodecs;
    第3章 核心庫(kù)模塊core;
    第4章 圖像處理模塊imgproc(一);
    第5章 圖像處理模塊imgproc(二);
    第6章 可視化模塊highgui;
    第7章 視頻處理模塊videoio;
    第8章 視頻分析模塊video;
    第9章 照片處理模塊photo;
    第10章 2D特征模塊features2d;
    第11章 相機(jī)標(biāo)定與三維重建模塊calib3d;
    第12章 傳統(tǒng)目標(biāo)檢測(cè)模塊objdetect;
    第13章 機(jī)器學(xué)習(xí)模塊ml;
    第14章 深度神經(jīng)網(wǎng)絡(luò)模塊dnn

    歡迎關(guān)注圖書(shū)《深度學(xué)習(xí)計(jì)算機(jī)視覺(jué)實(shí)戰(zhàn)》與《學(xué)習(xí)OpenCV4:基于Python的算法實(shí)戰(zhàn)》.


    圖像格式即圖像文件存放在存儲(chǔ)卡等介質(zhì)上的格式,常用的圖像格式有BMP、JPEG、TIFF、RAW等,受到存儲(chǔ)容量的限制,圖像文件通常都會(huì)經(jīng)過(guò)壓縮再存儲(chǔ),表2.1列舉了OpenCV中圖像讀寫(xiě)支持的格式,本節(jié)對(duì)一些常用格式做簡(jiǎn)單介紹。

    (1)BMP格式

    Windows Bitmaps格式圖像文件又稱(chēng)位圖,后綴為.bmp或.dib,是Windows系統(tǒng)中最常見(jiàn)的圖像格式,也是Windows環(huán)境中圖像數(shù)據(jù)處理的一種標(biāo)準(zhǔn)格式,因此Windows環(huán)境中的圖像處理軟件都支持BMP格式。

    BMP格式采用位映射存儲(chǔ)格式,除了圖像深度可以設(shè)置之外(圖像深度可設(shè)置為1bit、4bit、8bit及24bit),不采用其他的壓縮方式,因此存儲(chǔ)BMP文件所需占用的空間會(huì)很大。BMP文件進(jìn)行數(shù)據(jù)存儲(chǔ)時(shí),采用從左到右、從下到上的圖像掃描方式。

    (2)JPEG格式

    JPEG(Joint Photographic Experts Group,聯(lián)合圖像專(zhuān)家小組)是面向連續(xù)色調(diào)靜止圖像的一種壓縮標(biāo)準(zhǔn),該標(biāo)準(zhǔn)由國(guó)際標(biāo)準(zhǔn)化組織(ISO)制訂。JPEG格式圖像文件采用JPEG標(biāo)準(zhǔn),是目前最常用的圖像文件格式,后綴名為.jpg或.jpeg。

    JPEG格式是一種先進(jìn)的壓縮格式,可以去除圖像中的冗余數(shù)據(jù),該格式壓縮比通常在10:1到40:1,用JPEG格式存儲(chǔ)的文件大小是其他類(lèi)型文件的1/10~1/20,它能夠?qū)D像壓縮在很小的存儲(chǔ)空間內(nèi)。JPEG格式屬于有損壓縮格式,壓縮比越大,圖像的品質(zhì)就越低,所以如果要求高品質(zhì)圖像,壓縮比則不宜設(shè)置過(guò)高。

    JPEG格式可分為標(biāo)準(zhǔn)JPEG、漸進(jìn)式JPEG及JPEG2000三種格式。

    標(biāo)準(zhǔn)JPEG格式在網(wǎng)頁(yè)中加載時(shí),需要圖像文件全部加載完畢才能展示圖像。

    漸進(jìn)式JPEG在網(wǎng)頁(yè)中加載時(shí)會(huì)先呈現(xiàn)圖像的粗略外觀,然后逐漸呈現(xiàn)圖像細(xì)節(jié),因而稱(chēng)為漸進(jìn)式JPEG。漸進(jìn)式JPEG格式圖像文件比標(biāo)準(zhǔn)JPEG格式文件小,所以網(wǎng)頁(yè)端圖像的展示建議使用這種格式。

    JPEG2000是新一代圖像壓縮方法,壓縮品質(zhì)更高,JPEG2000格式文件后綴為.jp2。在無(wú)線傳輸圖像時(shí),經(jīng)常會(huì)遇到信號(hào)不穩(wěn)造成的馬賽克現(xiàn)象,或者圖像位置錯(cuò)亂的問(wèn)題,JPEG2000可以改善這種情況下的圖像傳輸品質(zhì)。JPEG2000的壓縮率比標(biāo)準(zhǔn)JPEG高約30%,支持有損壓縮和無(wú)損壓縮兩種方式,支持漸進(jìn)式傳輸,支持設(shè)定感興趣區(qū)域(即指定圖片上感興趣區(qū)域的壓縮質(zhì)量),而且還可以選擇圖像中的某一部分先行解壓。

    (3)PNG格式

    PNG(Portable Network Graphics,便攜式網(wǎng)絡(luò)圖形)格式是一種采用無(wú)損壓縮數(shù)據(jù)算法的位圖格式,后綴為.png,該格式當(dāng)初的設(shè)計(jì)意圖是替代有專(zhuān)利的GIF和TIFF文件格式,是目前比較常用的一種圖像格式。

    (4)WebP格式

    WebP由Google發(fā)布,文件后綴為.webp,提供了有損壓縮與無(wú)損壓縮兩種方式,該格式派生于VP8,支持的最大像素?cái)?shù)量是16383x16383。相較于JPEG格式,WebP格式采用有損壓縮在保持與JPEG格式相同的圖片質(zhì)量的情況下,文件大小會(huì)比JPEG格式文件小,WebP無(wú)損壓縮的圖像比PNG圖像少了45%的存儲(chǔ)大小,因此能夠有效的減少圖片在網(wǎng)絡(luò)上的傳輸時(shí)間。

    (5)TIFF 格式

    TIFF(Tag Image File Format,標(biāo)簽圖像文件格式)是一種靈活的位圖格式,文件后綴為.tiff或.tif。TIFF格式采用3級(jí)體系結(jié)構(gòu),內(nèi)部結(jié)構(gòu)分為三個(gè)部分:文件頭信息區(qū)、標(biāo)識(shí)信息區(qū)和圖像數(shù)據(jù)區(qū)。文件頭信息區(qū)存儲(chǔ)TIFF文件解析所必需的信息,標(biāo)識(shí)信息區(qū)包含了有關(guān)于圖像的所有信息,圖像數(shù)據(jù)區(qū)存儲(chǔ)圖像信息。

    TIFF格式應(yīng)用廣泛,擁有多種壓縮方案,可以描述多種類(lèi)型的圖像,不依賴(lài)于硬件,具有可移植性。

    OpenCV進(jìn)行圖像編解碼時(shí),在Windows和MacOSX操作系統(tǒng)上,默認(rèn)情況下使用OpenCV自帶的編解碼器(libjpeg、libpng、libtiff和libjasper),因此在這兩種系統(tǒng)上OpenCV總是可以讀取JPEG、PNG和TIFF格式的圖像文件。在MacOSX上,還可以選擇使用MacOSX本機(jī)的圖像讀取器。

    在Linux、BSD或其他類(lèi)Unix的操作系統(tǒng)上,OpenCV尋找與操作系統(tǒng)鏡像一起提供的編解碼器,安裝相關(guān)軟件包時(shí)不要忘記安裝開(kāi)發(fā)文件,例如Debian和Ubuntu中的“l(fā)ibjpeg dev”。

網(wǎng)站首頁(yè)   |    關(guān)于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶(hù)案例   |    售后服務(wù)   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話(huà):010-     郵箱:@126.com

備案號(hào):冀ICP備2024067069號(hào)-3 北京科技有限公司版權(quán)所有