.NET 將多個程序集合併成單一程序集的 4+3 種方法

編寫 .NET 程序的時候,我們經常會在項目的輸出目錄下發現一大堆的文件。除了我們項目自己生成的程序集之外,還能找到這個項目所依賴的一大堆依賴程序集。有沒有什麼方法可以把這些依賴和我們的程序集合併到一起呢?

本文介紹四種將程序集和依賴打包合併到一起的方法,每一種方法都有其不同的原理和優缺點。我將介紹這些方法的原理並幫助你決定哪種方法最適合你想要使用的場景。


四種方法

目前我已知的將 .NET 程序集與依賴合併到一起的方法有下面四種:

  1. 使用 .NET Core 3.0 自帶的 PublishSingleFile 屬性合併依賴
  2. 使用 Fody
  3. 使用 SourceYard 源代碼包
  4. 使用 ILMerge(微軟所寫)或者 ILRepack(基於 Mono.Ceil)
  5. 其他方法

如果你還知道有其他的方法,歡迎評論指出,非常感謝!

上面的第五種方法我也會做一些介紹,要麼是因爲無法真正完成任務或者適用場景非常有限,要麼是其原理我還不理解,因此只進行簡單介紹。

使用 .NET Core 3.0 自帶的 PublishSingleFile 屬性合併依賴

.NET Core 3.0 自 Preview 5 開始,增加了發佈成單一 exe 文件的功能。

在你的項目文件中增加下面的兩行可以開啓此功能:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.0</TargetFramework>
++      <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
++      <PublishSingleFile>true</PublishSingleFile>
      </PropertyGroup>
    
    </Project>

第一行 RuntimeIdentifier 一定需要指定,因爲發佈的單一文件是特定於架構的。這裏,我們指定了 win10-x64,你也可以指定爲其他的值。可以使用的值你可以在這篇文章中查詢到:

第二行 PublishSingleFile 即開啓發布時單一文件的功能。這樣,你在發佈你的程序的時候可以得到一個單一的可執行程序。發佈一個 .NET Core 項目的方法是在命令行中輸入:

dotnet publish

當然,如果你沒有更改任何你的項目文件(沒有增加上面的那兩行),那麼你在使用發佈命令的時候就需要把這兩個屬性再增加上。因此完整的發佈命令是下面這樣的:

dotnet publish -r win10-x64 /p:PublishSingleFile=true

這裏的 -r 就等同於在項目中指定 RuntimeIdentifier 持續。這裏的 /p 是在項目中增加一個屬性,而增加的屬性名是 PublishSingleFile,增加的屬性值是 true

使用 .NET Core 3.0 這種自帶的發佈單一 exe 的方法會將你的程序的全部文件(包括所有依賴文件,包括非託管程序集,包括各種資源文件)全部打包到一個 exe 中。當運行這個 exe 的時候,會首先將所有這些文件生成到本地計算機中一個臨時目錄下。只有第一次運行這個 exe 的時候纔會生成這個目錄和其中的文件,之後的運行是不會再次生成的。

下面說一些 .NET Core 3.0 發佈程序集的一點擴展——.NET Core 3.0 中對於發佈程序集的三種處理方式可以放在一起使用:

  • 裁剪程序集(Assembly Trimmer)
  • 提前編譯(Ahead-of-Time compilation,通過 crossgen)後面馬上會說到 Microsoft.DotNet.ILCompiler
  • 單一文件打包(Single File Bundling)本小節

關於 .NET Core 3.0 中發佈僅一個 exe 的方法、原理和實踐,可以參見林德熙的博客:

.NET Core 在 GitHub 上開源:

使用 Fody

在你的項目中安裝一個 NuGet 包 Costura.Fody。一般來說,安裝完之後,你編譯的時候就會生成僅有一個 exe 的程序集了。

如果你繼續留意,可以發現項目中多了一個 Fody 的專屬配置文件 FodyWeavers.xml,內容如下:

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <Costura/>
</Weavers>

僅僅到此爲止你已經足夠利用 Fody 完成程序集的合併了。

但是,如果希望對 Fody 進行更精細化的配置,可以閱讀葉洪的博客:

Fody 在 GitHub 上開源:

使用 SourceYard 源代碼包

SourceYard 源代碼包在程序集合並上是另闢蹊徑的一種合併方式。它不能幫助你將所有的依賴全部合併,但足以讓你在發佈一些簡單應用的時候不至於引入大量的依賴。

例如,你可以考慮新建一個項目,然後安裝下面的 NuGet 包:

安裝完成之後,你就可以在你的項目中使用到此 NuGet 包爲你帶來的獲取 MAC 地址的工具類了。

using System;
using lindexi.src;

namespace Walterlv.Demo
{
    internal static class Program
    {
        static void Main()
        {
            var macList = MacAddress.GetActiveMacAddress();
            foreach (var mac in macList)
            {
                Console.WriteLine(mac);
            }
        }
    }
}

編譯完你的項目,你會發現你的項目沒有攜帶任何依賴。你安裝的 NuGet 包並沒有成爲你的依賴,反而成爲你正在編譯的程序集的一部分。

如果你要製作一個像上面那樣的源代碼包,只需要在你要製作 NuGet 包的項目安裝上 dotnetCampus.SourceYard,在你打包成 NuGet 包的時候,就會生成一個普通的 NuGet 包以及一個 *.Source.nupkg 的源代碼包。將源代碼包上傳到 nuget.org 上,其他人便可以安裝你製作的源代碼包了。

關於如何使用 SourceYard 製作一個源代碼包的方法可以閱讀林德熙的博客:

關於能夠做出源代碼包的原理,可以閱讀我的博客:

SourceYard 在 GitHub 上開源:

使用 ILMerge 或者 ILRepack 等工具

ILMerge 和 ILRepack 的合併就更加富有技術含量——當然坑也更多。

這兩個都是工具,因此,你需要將工具下載下來使用。你有很多種方法下載到工具使用,因此我會推薦不同的人羣使用不同的工具。

ILMerge

ILMerge 命令行工具是微軟官方出品,下載地址:

其使用方法請參見我的博客:

ILRepack

ILRepack 基於 Mono.Ceil 來進行 IL 合併,其使用方法可以參見我的博客:

ILMerge-GUI 工具(已過時,但適合新手隨便玩玩)

你可以在以下網址中找到 ILMerge-GUI 的下載鏈接:

ILMerge-GUI 工具在 Bitbucket 上開源:

其他方法

使用 Microsoft.DotNet.ILCompiler

可以將 .NET Core 編譯爲單個無依賴的 Native 程序。

你需要先安裝一個預覽版的 NuGet 包 Microsoft.DotNet.ILCompiler

關於 Microsoft.DotNet.ILCompiler 的使用,你可以閱讀林德熙的博客:

使用 dnSpy

dnSpy 支持添加一個模塊到程序集,也可以創建模塊,還可以將程序集轉換爲模塊。因此,一個程序集可以包含多個模塊的功能就可以被充分利用起來。

添加模塊到程序集

使用 Warp

Warp 在 GitHub 上開源:

其使用可以參見林德熙的博客:

各種方法的原理和使用場景比較

原理

使用 .NET Core 3.0 自帶的 PublishSingleFile 屬性合併依賴,其原理是生成一個啓動器容器程序。最終沒有對程序進行任何修改,只是單純的打包而已。

使用 Fody,是將程序集依賴放到了資源裏面。當要加載程序集的時候,會直接將資源中的程序集流加載到內存中。

使用 SourceYard 源代碼包,是直接將源代碼合併到了目標項目裏面。

使用 ILMerge / ILRepack,是在 IL 級別對程序集進行了合併。

我們可以通過下面一張圖來感受一下後三種原理上的不同。

這是一個分別通過 Fody、SourceYard 和 ILMerge / ILRepack 生成的程序集的反編譯圖。可以看到,對於 ILRepack / ILMerge 和 SourceYard,反編譯後看到的源代碼都在目標程序集中,而對於 Fody,依賴僅僅出現在資源中。

原理差別

適用範圍

由於其原理不同,所以其適用範圍和造成的副作用也不同。

如果你基於 .NET Core 3.0 開發,並且也不在意在目標計算機上生成的臨時文件夾,那麼可以考慮使用 PublishSingleFile 屬性合併依賴。

如果你不在乎啓動性能以及內存消耗,那麼可以考慮 Fody(這意味着小型程序比較適合採用)。

如果你的程序非常在乎啓動性能,那麼就需要考慮 SourceYard、ILMerge / ILRepack 了。

對於 ILMerge / ILRepack 和 SourceYard 的比較,可以看下面這張表格:

方案 ILRepack / ILMerge SourceYard
適用於 任意 .NET 程序集 通過 SourceYard 發佈的 NuGet 包
WPF ILRepack 支持,ILMerge 不支持 支持
調試(支持) 僅支持一般方法的調試 支持一般程序集支持的所有調試方法
調試(不支持) 不支持異步方法調試,不支持顯示局部變量 沒有不支持的
隱藏 API internal 的類型和成員可以隱藏 必須是 private 類型和成員纔可隱藏

可以發現,如果我們能夠充分將我們需要的包通過 SourceYard 發佈成 NuGet,那麼我們將可以獲得比 ILRepack / ILMerge 更好的編寫和調試體驗。

表格之外還有一些特別需要說明的:

  1. ILRepack 額外支持修改 WPF 編譯生成的 Baml 文件,將資源的引用路徑修改成新程序集的路徑。
  2. SourceYard 的類型需要寫成 private 纔可以隱藏,但是隻有內部類纔可以寫 private,因此如果特別需要隱藏,請首先寫一個內部類。(因此,你可能會發現有一個類型有很多個分部類,每一個分部類中都是一個私有的內部類)

開源社區

最後說一下,以上所說的所有方法全部是開源的,有問題歡迎在社區討論一起解決:


我的博客會首發於 https://blog.walterlv.com/,而 CSDN 會從其中精選發佈,但是一旦發佈了就很少更新。

如果在博客看到有任何不懂的內容,歡迎交流。我搭建了 dotnet 職業技術學院 歡迎大家加入。

知識共享許可協議

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:https://walterlv.blog.csdn.net/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章