Costura 是Fody的插件,将依赖项嵌入为资源实现程序发布成单个exe文件。本文主要介绍.NET(C#)中使用Costura.Fody将程序发布成单个exe文件的方法。

.NET Core项目推荐实现方式参考文档:

.NET Core 3.1 和 .NET 5 控制台程序发布成独立.exe可以执行程序的方法

1、安装引用Fody和Costura.Fody

1)使用Nuget界面管理器

直接分别搜索 "Fody" 和 “Costura.Fody”,找到对应的点安装即可。

相关文档VS(Visual Studio)中Nuget的使用

2)使用Package Manager命令安装

PM> Install-Package Fody
PM> Install-Package Costura.Fody

注意:如果安装最新版报编译器版本错误,可以降低 Fody 和 Costura.Fody版本,如Fody为4.2.1,Costura.Fody为3.3.3

2、FodyWeavers.xml配置文件

FodyWeavers.xml是配置文件,通过修改CosturaFodyWeavers.xml 中的节点来访问所有配置选项,默认配置如下,

<Weavers>
  <Costura/>
</Weavers>

1)CreateTemporaryAssemblies

将嵌入的文件复制到磁盘,然后再将它们加载到内存中。对于要从物理文件加载程序集的某些场景很有用。默认为false

<Costura CreateTemporaryAssemblies='true' />

2)IncludeDebugSymbols

配置是否还嵌入引用程序集的.pdb文件,默认为true。

<Costura IncludeDebugSymbols='false' />

3)IncludeRuntimeReferences

配置.NET Core所使用的内嵌依赖项的运行时文件夹是否被内嵌。默认为true。

<Costura IncludeRuntimeReferences='false' />

4)UseRuntimeReferencePaths

配置运行时程序集是嵌入其完整路径还是只嵌入其程序集名称。.NET Framework项目默认为false,.NET Core项目默认为true

<Costura UseRuntimeReferencePaths='true' />

5)DisableCompression

默认情况下,嵌入式程序集是压缩的,加载时则不压缩。可以使用此选项关闭压缩。

<Costura DisableCompression='true' />

6)DisableCleanup

作为Costura的一部分,嵌入式组件不再包含在构建中。这种Cleanup可以关闭。默认为false

<Costura DisableCleanup='true' />

7)LoadAtModuleInit

默认情况下,Costura将作为模块初始化的一部分加载。该选项禁用该行为。确保在代码的某个地方调用了CosturaUtility.Initialize()。默认为true

<Costura LoadAtModuleInit='false' />

8)IgnoreSatelliteAssemblies

默认情况下,Costura将使用名为'resources.dll'的程序集作为附属资源,并将输出路径放在前面。该配置项禁用该行为。

DLL项目程序集名称以'.resources'结尾,当设置为false时,*.resources.dll将导致错误。默认为false,配置如下,

<Costura IgnoreSatelliteAssemblies='true' />

9)ExcludeAssemblies / ExcludeRuntimeAssemblies

要从默认操作 "嵌入所有复制本地引用" 中排除的程序集名称列表。

不要在名称中包含.exe.dll

不能用IncludeAssemblies定义。

可以对部分程序集名称匹配使用通配符。例如,System.*将排除所有以System.开头的程序集。通配符只能在条目的末尾使用,因此,例如,System.*.Private.*将不起作用。

可以使用多行配置:

<Costura>
  <ExcludeAssemblies>
    Foo
    Bar
  </ExcludeAssemblies>
</Costura>

或者配置在一行使用|分隔,如下,

<Costura ExcludeAssemblies='Foo|Bar' />

10)IncludeAssemblies / IncludeRuntimeAssemblies

从默认操作“嵌入所有复制本地引用”中包含的程序集名称列表。

不要在名称中包含.exe.dll

不能用ExcludeAssemblies / incluuntimeassemblies定义。

可以在名称的末尾使用通配符进行部分匹配。

可以使用多行配置:

<Costura>
  <IncludeAssemblies>
    Foo
    Bar
  </IncludeAssemblies>
</Costura>

或者配置在一行使用|分隔,如下,

<Costura IncludeAssemblies='Foo|Bar' />

10)Unmanaged32Assemblies 和 Unmanaged64Assemblies

不能以与托管程序集相同的方式加载非托管程序集。

为了帮助Costura识别哪些程序集是Mixed-mode,以及在什么环境中加载它们,应该将它们的名称包含在一个或两个列表中。

不要在名称中包含.exe.dll

可以在名称的末尾使用通配符进行部分匹配。

可以使用多行配置:

<Costura>
  <Unmanaged32Assemblies>
    Foo32
    Bar32
  </Unmanaged32Assemblies>
  <Unmanaged64Assemblies>
    Foo64
    Bar64
  </Unmanaged64Assemblies>
</Costura>

或者配置在一行使用|分隔,如下,

<Costura
    Unmanaged32Assemblies='Foo32|Bar32' 
    Unmanaged64Assemblies='Foo64|Bar64' />

3、配置预加载程序集顺序

Costura可以自动加载本地库。要包含本机库,请将其作为嵌入式资源包含在项目中称为costura32costura64的文件夹中,这取决于库的bittyness。

还可以选择指定预加载库的加载顺序。当从磁盘混合模式使用临时程序集时,程序集也会被预加载。

要指定预加载程序集的顺序,请向配置中添加PreloadOrder元素。

可以使用多行配置:

<Costura>
  <PreloadOrder>
    Foo
    Bar
  </PreloadOrder>
</Costura>

或者配置在一行使用|分隔,如下,

<Costura PreloadOrder='Foo|Bar' />

4、CosturaUtility

CosturaUtility是一个类,它允许在自己的代码中手动初始化Costura系统。主要用于模块初始化器不起作用的场景,比如库和Mono。

要使用,请在代码中尽可能早地调用CosturaUtility.Initialize()。例如,

class Program
{
    static Program()
    {
        CosturaUtility.Initialize();
    }

    static void Main(string[] args) { ... }
}

5、单元测试框架

大多数单元测试框架需要.dll文件来发现和执行单元测试。可能需要将如下配置添加到测试组件中。

<Weavers>
    <Costura ExcludeAssemblies='TargetExe|TargetExeTest'
             CreateTemporaryAssemblies='true'
             DisableCleanup='true'/>
</Weavers>

6、读取Costura.Fody内嵌压缩的程序集

合成之后被压缩的程序集需要读取,可以参考如下代码,

using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;

namespace ConsoleApp1
{
  class Program
  {
    static void CopyTo(Stream source, Stream destination) {
      int count;
      var array = new byte[81920];
      while ((count = source.Read(array, 0, array.Length)) != 0) {
        destination.Write(array, 0, count);
      }
    }

    static Stream LoadStream(string fullname) {
      FileStream stream = default(FileStream);
      if (fullname.EndsWith(".zip")) {
        using (stream = new FileStream(fullname, FileMode.Open)) {
          using (var compressStream = new DeflateStream(stream, CompressionMode.Decompress)) {
            var memStream = new MemoryStream();
            CopyTo(compressStream, memStream);
            memStream.Position = 0;
            return memStream;
          }
        }
      }
      return stream;
    }

    static void Main(string[] args) {
      Stream stream; Stream file;
      string RefilePath = @"^.+[^\\]+\\"; string fullname; string newFile;
      for (int i = 0; i < args.Count(); i++) {
        fullname = args[i];
        newFile = Regex.Replace(fullname, "\\.zip$", string.Empty);
        Console.Write("{0} -> {1}\r\n",
          Regex.Replace(fullname, RefilePath, string.Empty),
          Regex.Replace(newFile, RefilePath, string.Empty));
        try
        {
          stream = LoadStream(fullname);
          using (file = File.Create(newFile)) {
            CopyTo(stream, file);
          }
        }
        catch (Exception ex) {
          Console.Write("{0}", ex.ToString());
        }
      }
    }
  }
}

注意:如果其它静态的资源文件,可以在VS中文件属性"生成操作" 选择 "Resource",然后在读取资源文件。

推荐文档