利用Costura.Fody制作绿色单文件程序(C#程序(含多个Dll)合并成一个Exe)

本文介绍如何使用 Fody.Costura 插件将项目依赖的 DLL 文件打包进 EXE 文件,使 EXE 文件能够独立运行。通过 NuGet 安装 Costura.Fody 后,构建项目即可实现 DLL 合并。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址;https://www.cnblogs.com/1024zy/p/6371590.html

开发程序的时候经常会引用一些第三方的DLL,然后编译生成的exe文件就不能脱离这些DLL独立运行了。这样交给用户很不方便,希望的效果是直接交付一个exe文件。

这时候就需要借助一款名为Fody.Costura的插件。Fody.Costura是一个Fody框架下的插件,可通过Nuget安装到VS工程中。安装之后,就可以将项目所依赖的DLL(甚至PDB)文件全部打包到EXE文件里。

使用 Costura.Fody ,这个强大之处是可以在build阶段直接将依赖的DLL合并在目标EXE中,支持非托管DLL进行mixed mode 打包。不足之处是只能将DLL合并到EXE中,不支持DLL和DLL的合并。

github地址:https://github.com/Fody/Costura

nuget地址:https://www.nuget.org/packages/Costura.Fody/

使用场景:最终发布只有一个EXE,将依赖的DLL合并到一个EXE中。

使用方法

1、在VS中,通过Nuget为目标EXE工程安装Costura.Fody。


2、重新构建项目。

构建完成后,到项目的输出目录下找到新生成的EXE文件,你同时会发现输出目录下仍然存在那些DLL。不过不用担心,这个EXE已经能够独立运行了。你可以把这些DLL全部删除后再运行EXE试试。

Fody.Costura还支持一些进阶的特性,例如:

  • 临时程序集文件:在运行EXE前自动,自动将DLL从EXE中解压到文件夹系统中,再通过常规的方式加载该DLL。
  • 合并非托管的DLL:Fody.Costura可以合并非托管的DLL,但是不会自动合。如果你的程序涉及非托管DLL,那么你需要通过修改Fody.Costura的配置文件来显示地告诉它你想合并哪些非托管的DLL。
  • 预加载DLL:Fody.Costura可以帮助你在程序启动时预先加载某些DLL,你甚至可以指定这些DLL的加载顺序。

以上这些进阶特性都需要你通过修改Fody.Costura的配置文件来实现,具体的操作步骤可以参考它的官方文档

实现原理介绍

当CLR试图加载一个程序集但加载失败时,它会引发AppDomain.AssemblyResolve事件。我们的程序可以监听这个事件,并且在这个事件的处理函数中返回这个CLR试图加载的程序集,从而使程序得以继续正常运行。

Fody.Costura在构建项目时会把EXE引用到的DLL全部嵌入到EXE文件中。当程序在运行的过程中用到其中某个DLL的时候(此时由于CLR无法找到该DLL文件,导致AppDomain.AssemblyResolve事件被触发)再从EXE文件的嵌入资源中提取所需的DLL。

下面这两个函数就是Fody.Costura实现这部分逻辑的代码。

复制代码
        public static void Attach()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.AssemblyResolve += (s, e) => ResolveAssembly(e.Name);
        }
public static Assembly ResolveAssembly(string assemblyName)
        {
            if (nullCache.ContainsKey(assemblyName))
            {
                return null;
            }

            var requestedAssemblyName = new AssemblyName(assemblyName);

            var assembly = Common.ReadExistingAssembly(requestedAssemblyName);
            if (assembly != null)
            {
                return assembly;
            }

            Common.Log("Loading assembly '{0}' into the AppDomain", requestedAssemblyName);

            assembly = Common.ReadFromEmbeddedResources(assemblyNames, symbolNames, requestedAssemblyName);
            if (assembly == null)
            {
                nullCache.Add(assemblyName, true);

                // Handles retargeted assemblies like PCL
                if (requestedAssemblyName.Flags == AssemblyNameFlags.Retargetable)
                {
                    assembly = Assembly.Load(requestedAssemblyName);
                }
            }
            return assembly;
       }
复制代码

可以看到,Attach方法监听了AppDomain.AssemblyResolve事件。当CLR无法成功加载某个程序集时, AssemblyResolve事件处理函数会被执行。AssemblyResolve会尝试通过Common.ReadFromEmbeddedResources方法从已加载的程序集的嵌入资源中获取目标程序集,并返回给CLR。

Attach方法是在什么时候执行的呢?

其实是这样的,对于C#语言来说,CLR隐藏了一个大招——CLR可以在每个模块(每个程序集都含有一个或多个模块)加载之前执行一些初始化的代码。但是很遗憾,C#语言无法控制这部分代码。Fody.Costura则是在内部将IL代码直接注入到EXE程序集内部模块的初始化函数中,而这部分IL代码其实就是执行了Attach方法。这样一来,EXE程序集被加载后,Attach方法就能够立即得到调用了。

与此相同原理的将DLL添加到EXE的资源(http://blogs.msdn.com/b/microsoft_press/archive/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition.aspx);


<think>好的,我现在需要回答用户关于如何在C#项目中使用Costura.Fody指定打包特定的DLL文件的问题。首先,我得回忆一下Costura.Fody的基本工作原理。根据引用[1],Costura.Fody会在构建时将所有被引用的DLL嵌入到EXE文件中,当程序运行时找不到某个DLL时会触发AssemblyResolve事件,然后从嵌入的资源中提取。不过用户的需求是只嵌入特定的DLL,而不是全部。 接下来,我需要查看用户提供的引用内容。引用[2]提到了在FodyWeavers.xml文件中配置Unmanaged32Assemblies和Unmanaged64Assemblies节点,指定SQLite.Interop。这可能是一个排除或包特定DLL的例子。但用户的问题是关于“仅嵌入指定的DLL”,也就是只打包他们选择的那些,而不是全部。 通常,Costura.Fody默认嵌入所有引用的DLL,所以如果要仅嵌入特定DLL,可能需要排除其他不需要的。根据官方文档或已知配置,Costura.Fody提供了IncludeAssemblies和ExcludeAssemblies的配置选项。例如,使用IncludeAssemblies来指定需要包DLL,或者用ExcludeAssemblies来排除不需要的。不过用户提供的引用中没有直接提到这些配置,可能需要结合已有的资料和用户提供的例子来推断。 引用[3]提到安装特定版本的Costura.Fody,但并没有直接说明如何配置包特定的DLL。因此,我需要确定正确的配置语法。根据引用[2]中的示例,FodyWeavers.xml的结构是包Costura节点,里面可以有不同的子节点。例如,要排除某些DLL,可以使用ExcludeAssemblies,并在其中用正则表达式匹配不需要的DLL名称。反之,如果要包特定的DLL,可能需要使用IncludeAssemblies节点,并列出需要包DLL名称。 假设用户想仅包指定的DLL,比如A.dll和B.dll,那么配置应该是这样的:在FodyWeavers.xml中,设置IncludeAssemblies节点,并使用正则表达式来匹配这些DLL的名称。同时,可能需要排除其他所有DLL,但这可能需要更复杂的正则表达式,或者结合使用Include和Exclude的组合。 例如,配置可能如下: <Costura> <IncludeAssemblies> A B </IncludeAssemblies> </Costura> 这样,只有A.dll和B.dll会被包,其他则不会被嵌入。但需要确认IncludeAssemblies是否支持多个条目,以及是否需要使用正则表达式。例如,可能需要写A|B来匹配这两个名称。 另外,用户提供的引用[2]中的例子是处理非托管的DLL,如SQLite.Interop,这可能属于特殊情况,需要单独配置。但这里用户的问题可能更一般,是关于托管DLL的嵌入。 总结步骤应该是: 1. 安装Costura.Fody包。 2. 创建或修改FodyWeavers.xml。 3.Costura节点下配置IncludeAssemblies,指定需要包DLL名称(不带扩展名)。 4. 确保其他DLL不被包,可能需要设置ExcludeAssemblies为.*,但需要测试或查阅文档。 需要注意可能的语法问题,比如是否需要正则表达式,以及是否区分大小写等。例如,IncludeAssemblies中的条目是否需要用竖线分隔,或者作为多个子元素。根据引用[2]的例子,Unmanaged32Assemblies中的内容是换行分隔的,所以可能每个DLL名称单独一行。 因此,正确的配置可能是在IncludeAssemblies节点下,每行一个DLL名称,使用正则表达式匹配。例如: <IncludeAssemblies> ^A$ ^B$ </IncludeAssemblies> 这样,只有精确匹配A和B的DLL会被包。而默认情况下,Costura可能排除其他所有未被包DLL。 需要验证这种配置是否有效,或者是否需要同时设置ExcludeAssemblies为.*,然后IncludeAssemblies覆盖。但根据Costura的文档,IncludeAssemblies会覆盖默认的包所有的行为,从而只包指定的DLL。因此,正确的配置应该是使用IncludeAssemblies列出所有需要包DLL,而其他未被列出的则不会被包。 因此,最终的步骤应该是用户创建FodyWeavers.xml文件,配置IncludeAssemblies节点,并列出所需的DLL名称,每个名称占一行,使用正则表达式确保精确匹配。</think>要在C#项目中使用Costura.Fody仅嵌入指定的DLL文件,可通过配置`FodyWeavers.xml`文件实现。以下是具体步骤: ### 1. 安装Costura.Fody 通过NuGet安装Costura.Fody包。对于.NET Framework项目,建议选择4.1.0版本[^3]: ```powershell Install-Package Costura.Fody -Version 4.1.0 ``` ### 2. 配置FodyWeavers.xml 在项目根目录下创建(或修改)`FodyWeavers.xml`文件,使用`IncludeAssemblies`或`ExcludeAssemblies`节点控制嵌入的DLL: ```xml <?xml version="1.0" encoding="utf-8"?> <Weavers> <Costura> <!-- 仅包指定DLL --> <IncludeAssemblies> ^YourDllName1$ <!-- 使用正则表达式匹配DLL名称(不扩展名) --> ^YourDllName2$ </IncludeAssemblies> <!-- 或排除其他DLL --> <ExcludeAssemblies> ^ExcludedDll1$ <!-- 排除不需要嵌入的DLL --> ^ExcludedDll2$ </ExcludeAssemblies> </Costura> </Weavers> ``` ### 3. 配置说明 - **包特定DLL**:在`IncludeAssemblies`中列出目标DLL名称(正则表达式格式),仅这些DLL会被嵌入。例如`^Newtonsoft.Json$`会嵌入`Newtonsoft.Json.dll`[^1]。 - **排除特定DLL**:若需排除部分DLL,使用`ExcludeAssemblies`节点,其余DLL默认嵌入。 - **混合配置**:可同时使用两个节点,优先执行`Include`再执行`Exclude`。 ### 4. 非托管DLL的特殊处理 对于非托管DLL(如`SQLite.Interop`),需单独配置[^2]: ```xml <Costura> <Unmanaged32Assemblies>SQLite.Interop</Unmanaged32Assemblies> <Unmanaged64Assemblies>SQLite.Interop</Unmanaged64Assemblies> </Costura> ``` ### 验证配置 构建项目后检查输出目录: - 若配置正确,目标DLL会被嵌入EXE,且输出目录中不再包这些DLL。 - 运行时若触发`AssemblyResolve`事件,会从EXE资源中提取DLL
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值