架构师_程序员

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3116|回复: 15

[WinForm] .net/c# 使用Costura.Fody将DLL打包到EXE中

[复制链接]
发表于 2018-4-14 14:41:49 | 显示全部楼层 |阅读模式
做winform/wpf开发不可避免的是会引用很多第三方dll,可是每次打包的时候需要将Debug目录下的文件都拷贝给客户或者分享出去,这样用起来不是很方便。之前试过几种合并dll文件的软件,比如ILMerge、Enigma Virtual Box等,但直到我了解到“Costura.Fody”后,感觉世界都美好多了~

下面是winform写的程序,引用了“HttpHelper.dll”和“Newtonsoft.Json.dll”两个dll,我们生成程序的时候,会有一个exe文件和这两个dll文件,让人感觉到很不舒服,而且少一个dll,程序都可能会报异常,如下图:

QQ截图20180414141236.jpg

下面的链接是以前的用法(不建议使用)

C#将dll打包到exe的程序中
http://www.itsvse.com/thread-2841-1-1.html
(出处: 架构师_程序员)



使用Costura.Fody工具可以将源DLL合并到目标EXE

项目主页:https://github.com/Fody/Costura

使用方法,直接nuget包安装:

  1. Install-Package Costura.Fody -Version 1.6.2
复制代码


成功添加Costura.Fody后,解决方案会自动添加FodyWeavers.xml文件。

重新生成解决方案,就可以在程序生成目录看到你的成功啦,所有dll完美的合并入exe里啦,你可以直接拷贝exe文件到客户机独立运行了。如下图:

1>------ 已启动全部重新生成: 项目: itsvse, 配置: Debug Any CPU ------
1>    Fody: Fody (version 2.0.0.0) Executing
1>      Fody/Costura:           No reference to 'Costura.dll' found. References not modified.
1>      Fody/Costura:           Embedding 'E:\project\itsvse\itsvse\HttpHelper.dll'
1>      Fody/Costura:           Embedding 'E:\project\itsvse\itsvse\Newtonsoft.Json.dll'
1>    Fody:   Finished Fody 609ms.
1>    Fody:   Skipped Verifying assembly since it is disabled in configuration
1>    Fody:   Finished verification in 3ms.
1>  itsvse -> E:\project\itsvse\itsvse\bin\Debug\itsvse.exe
========== 全部重新生成: 成功 1 个,失败 0 个,跳过 0 个 ==========



QQ截图20180414141850.jpg

从以上图片可以看出,生成的文件没有包含Newtonsoft.Json.dll、HttpHelper.dll与Costura.dll没有被生成,只有两个文件,itsvse.exe可以直接运行,并没有报错!(pdb文件可以删除)。

我们用ILSpy工具,来反编译一下我们的程序,看一下生成的源代码,如下图:

QQ截图20180414143505.jpg

实现原理介绍

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

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


  1. public static void Attach()
  2. {
  3.    var currentDomain = AppDomain.CurrentDomain;
  4.    currentDomain.AssemblyResolve += (s, e) => ResolveAssembly(e.Name);
  5. }

  6. public static Assembly ResolveAssembly(string assemblyName)
  7. {
  8.    if (nullCache.ContainsKey(assemblyName))
  9.    {
  10.       return null;
  11.    }   

  12.    var requestedAssemblyName = new AssemblyName(assemblyName);   

  13.    var assembly = Common.ReadExistingAssembly(requestedAssemblyName);
  14.    if (assembly != null)
  15.    {
  16.       return assembly;
  17.    }   

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

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

  23.       // Handles retargeted assemblies like PCL
  24.       if (requestedAssemblyName.Flags == AssemblyNameFlags.Retargetable)
  25.       {
  26.          assembly = Assembly.Load(requestedAssemblyName);
  27.       }
  28.    }
  29.    return assembly;
  30. }
复制代码
可以看到,Attach方法监听了AppDomain.AssemblyResolve事件。当CLR无法成功加载某个程序集时, AssemblyResolve事件处理函数会被执行。AssemblyResolve会尝试通过Common.ReadFromEmbeddedResources方法从已加载的程序集的嵌入资源中获取目标程序集,并返回给CLR。

看到这里,你可能会问,Attach方法是在什么时候执行的呢?

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

以上就是Fody.Costura实现原理的简单介绍。


高级配置

这些配置均在CosturaFodyWeavers.xml文件里添加或修改。

CreateTemporaryAssemblies
默认值:false
这会在将内嵌文件加载到内存之前将其嵌入到磁盘中。这对于希望从物理文件加载程序集的某些场景很有用。

IncludeDebugSymbols
默认值:true
控制是否也嵌入了参考装配的.pdbs。

DisableCompression
默认值:false
嵌入式程序集默认是压缩的,加载时是未压缩的。您可以使用此选项关闭压缩。
注意:在引用一些非标准dll,或者经过加密处理的dll时候,一定要关闭这个属性。不然会导致打不开exe的情况。这个时在我使用DSkin.dll的时候发现的。

DisableCleanup
默认值:false
作为Costura的一部分,嵌入式组件不再包含在构建中。这个清理可以关闭。

LoadAtModuleInit
默认值:true
Costura默认会作为模块初始化的一部分加载。该标志禁用该行为。确保你可以在某个地方使用CosturaUtility.Initialize()。

ExcludeAssemblies
使用方法:ExcludeAssemblies="DLL1|DLL2"
要从“嵌入所有复制本地引用”的默认操作中排除的程序集名称列表。

IncludeAssemblies
使用方法:IncludeAssemblies="DLL1|DLL2"
从“嵌入所有复制本地引用”的默认操作中包含的程序集名称列表。

Unmanaged32Assemblies&Unmanaged64Assemblies
使用方法:Unmanaged32Assemblies="DLL1|DLL2" Unmanaged64Assemblies="DLL1|DLL2"
无法以与托管程序集相同的方式加载混合模式程序集。因此,为帮助Costura识别哪些组件是混合模式的,以及在哪些环境中加载它们,应在其中一个或两个列表中包含它们的名称。不要包含.exe或.dll在名称中。

PreloadOrder
使用方法:PreloadOrder="DLL1|DLL2"
本地库可以由Costura自动加载。要包含本地库,请将其作为嵌入式资源包含在您的项目中,称为文件夹costura32或costura64取决于库的不稳定性。或者,您也可以指定预加载库的加载顺序。从磁盘混合使用临时组件时,也会预装入组件。

CosturaUtility
使用方法:

  1. class Program {
  2.     static Program() {
  3.         CosturaUtility.Initialize();
  4.     }

  5.     static void Main(string[] args) { ... }
  6. }
复制代码



CosturaUtility是一个让您可以在自己的代码中手动初始化Costura系统的类。这主要针对模块初始值设定程序不起作用的场景,例如库和Mono。


最后,文中源码下载:

游客,如果您要查看本帖隐藏内容请回复





上一篇:StreamReader读取文件时出现乱码的解决方案
下一篇:【Hyper-V】与【VirtualBox】【VMware】冲突的解决方法
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-6-7 09:35:19 | 显示全部楼层
关于把依赖组件Dll集成到C#编译的EXE中·这个方法你试试!
  1. 添加资源-添加现有文件-添加引用Dll

  2. System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
  3.         {
  4.             string dllName = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll", "");
  5.             dllName = dllName.Replace(".", "_");
  6.             if (dllName.EndsWith("_resources")) return null;
  7.             System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
  8.             byte[] bytes = (byte[])rm.GetObject(dllName);
  9.             return System.Reflection.Assembly.Load(bytes);
  10.         }

  11. AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
复制代码

好用的话·记得好评哦!哈哈·这个·只需要把Dll·添加到资源文件中就行了·简单实用!(没搞明白·我可以出个教程)@小渣渣
a3.png
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2018-6-6 10:30:42 | 显示全部楼层
如果为程序签名,会报错如下:

1>MSBUILD : error : Fody: An unhandled exception occurred:
1>MSBUILD : error : Exception:
1>MSBUILD : error : 无法为 StrongNameKeyPair 获得公钥。
1>MSBUILD : error : StackTrace:
1>MSBUILD : error :    在 System.Reflection.StrongNameKeyPair.ComputePublicKey()
1>MSBUILD : error :    在 System.Reflection.StrongNameKeyPair.get_PublicKey()
1>MSBUILD : error :    在 Mono.Cecil.ModuleWriter.WriteModuleTo(ModuleDefinition module, Disposable`1 stream, WriterParameters parameters)
1>MSBUILD : error :    在 Mono.Cecil.ModuleDefinition.Write(String fileName, WriterParameters parameters)
1>MSBUILD : error :    在 InnerWeaver.WriteModule() 位置 C:\projects\fody\FodyIsolated\ModuleWriter.cs:行号 18
1>MSBUILD : error :    在 InnerWeaver.Execute() 位置 C:\projects\fody\FodyIsolated\InnerWeaver.cs:行号 86
1>MSBUILD : error : Source:
1>MSBUILD : error : mscorlib
1>MSBUILD : error : TargetSite:
1>MSBUILD : error : Byte[] ComputePublicKey()
1>MSBUILD : error :
1>    Fody:   Finished Fody 551ms.


好像没有解决办法,参考文章:

https://github.com/Fody/ExtraConstraints/issues/5

https://github.com/Fody/Costura/issues/194


不要依赖强名来保证安全。

因此,为了保护不是安全功能的东西而进行额外的努力似乎毫无意义

码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2018-4-17 13:02:12 | 显示全部楼层
xxhh 发表于 2018-4-17 11:35
我直接在vs的NuGet 安装完Costura.Fody后 拖了一个Windows Media Playe 播放器产生俩自带dll  删掉还是报 ...

Windows Media Player控件是com组件,不知道“Costura.Fody”是否支持。

1:你应该看下“Costura.Fody”是否支持“com组件”
2:忽略打包那两个dll,至于咋忽略xml应该可以设置,具体咋设置,请参考官方文档说明
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-4-17 10:29:52 | 显示全部楼层
下载看看xml配置
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-4-17 10:33:34 | 显示全部楼层
楼主 在么  想问您一下  我能把视频和dll一起弄到一个exe  这个插件能做到么
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2018-4-17 11:16:15 | 显示全部楼层
xxhh 发表于 2018-4-17 10:33
楼主 在么  想问您一下  我能把视频和dll一起弄到一个exe  这个插件能做到么
...

可以做到,你把视频属性设置成“嵌入的资源”,然后,读取资源文件即可。
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-4-17 11:35:51 | 显示全部楼层
小渣渣 发表于 2018-4-17 11:16
可以做到,你把视频属性设置成“嵌入的资源”,然后,读取资源文件即可。 ...

我直接在vs的NuGet 安装完Costura.Fody后 拖了一个Windows Media Playe 播放器产生俩自带dll  删掉还是报错  
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-4-17 11:57:09 | 显示全部楼层
xml还需要设置么     
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-4-17 17:42:31 | 显示全部楼层
小渣渣 发表于 2018-4-17 11:16
可以做到,你把视频属性设置成“嵌入的资源”,然后,读取资源文件即可。 ...

楼主 还是我  我想问一下 我把视频放入嵌入式资源 读出来的是字节数组  有啥办法放到播放器直接播放  (不想写到本地在读取路径播放,视频比较大会卡)
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
 楼主| 发表于 2018-4-17 20:52:16 | 显示全部楼层
xxhh 发表于 2018-4-17 17:42
楼主 还是我  我想问一下 我把视频放入嵌入式资源 读出来的是字节数组  有啥办法放到播放器直接播放  ( ...

额  大的视频 不建议搞成资源文件,你看下播放器是否有其他方法可以直接传入byte[]进去
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2018-4-18 08:08:48 | 显示全部楼层
小渣渣 发表于 2018-4-17 20:52
额  大的视频 不建议搞成资源文件,你看下播放器是否有其他方法可以直接传入byte[]进去 ...

没有 都是读视频地址的  想问下您有什么好的想法
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

免责声明:
码农网所发布的一切软件、编程资料或者文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。

Mail To:help@itsvse.com

QQ|Archiver|手机版|小黑屋|架构师 ( 鲁ICP备14021824号-2 )|网站地图

GMT+8, 2018-7-22 18:53

Powered by Discuz! X3.4

© 2001-2014 Comsenz Inc.

快速回复 返回顶部 返回列表