将DLLembedded到已编译的可执行文件中
你知道,我没有看到这个地方有一个很好的答案。 是否有可能将预先存在的DLLembedded已编译的C#可执行文件(以便只有一个文件需要分发)? 如果可能的话,怎么去做呢?
通常情况下,我只是把DLL留在外面,让安装程序处理所有的事情,但是有很多人问我这个问题,而且我真的不知道。
我强烈build议使用Costura.Fody – 迄今为止将资源embedded到程序集中的最好和最简单的方法。 它以NuGet包的forms提供。
Install-Package Costura.Fody
将其添加到项目后,它会自动将所有复制到输出目录的引用embedded到主程序集中。 您可能希望通过向项目添加目标来清除embedded文件:
Install-CleanReferencesTarget
您还可以指定是否包含pdb,排除特定的程序集,或者即时提取程序集。 据我所知,还支持非托pipe程序集。
更新
目前,有些人正试图增加对DNX的支持 。
如果他们实际上是托pipe程序集,则可以使用ILMerge 。 对于本地DLL,您将有更多的工作要做。
另请参阅: 如何将C ++ Windows DLL合并到C#应用程序EXE?
只需在Visual Studio中右键单击您的项目,select“项目属性” – >“资源” – >“添加资源” – >“添加现有文件…”,并将下面的代码包含到您的App.xaml.cs或同等版本中。
public App() { AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve); } System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll",""); dllName = dllName.Replace(".", "_"); if (dllName.EndsWith("_resources")) return null; System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly()); byte[] bytes = (byte[])rm.GetObject(dllName); return System.Reflection.Assembly.Load(bytes); }
这是我原来的博客文章: http : //codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/
是的,可以将.NET可执行文件与库合并。 有多种工具可以完成工作:
- ILMerge是一个实用程序,可用于将多个.NET程序集合并到一个程序集中。
- Mono mkbundle将一个exe文件和所有包含libmono的程序集打包到一个二进制包中。
- IL-Repack是ILMerge的FLOSS替代品,具有一些额外的function。
另外,这可以与Mono Linker结合使用,它可以移除未使用的代码,从而使得组装得更小。
另一种可能性是使用.NETZ ,它不仅允许压缩程序集,而且还可以将DLL直接打包到exe中。 与上面提到的解决scheme不同的是,.NETZ不合并它们,它们保持独立的程序集,但是被打包到一个包中。
.NETZ是一个开源工具,用于压缩和打包Microsoft .NET Framework可执行文件(EXE,DLL)以使其更小。
如果程序集只有托pipe代码, ILMerge可以将程序集合到一个程序集中 。 您可以使用命令行应用程序,或添加对exe的引用和编程合并。 对于一个GUI版本有Eazfuscator和.Netz这两个都是免费的。 付费应用程序包括BoxedApp和SmartAssembly 。
如果您必须将程序集与非托pipe代码合并,我会build议使用SmartAssembly 。 我从来没有与SmartAssembly打嗝,但所有其他人。 在这里,它可以将所需的依赖项作为资源embedded到主exe文件中。
您可以手动执行所有这些操作,无需担心程序集是否被pipe理,或者通过将dllembedded到资源中,然后依靠AppDomain的Assembly ResolveHandler
以混合模式进行ResolveHandler
。 这是采取最糟糕的情况的一站式解决scheme,即具有非托pipe代码的程序集。
static void Main() { AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { string assemblyName = new AssemblyName(args.Name).Name; if (assemblyName.EndsWith(".resources")) return null; string dllName = assemblyName + ".dll"; string dllFullPath = Path.Combine(GetMyApplicationSpecificPath(), dllName); using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName)) { byte[] data = new byte[stream.Length]; s.Read(data, 0, data.Length); //or just byte[] data = new BinaryReader(s).ReadBytes((int)s.Length); File.WriteAllBytes(dllFullPath, data); } return Assembly.LoadFrom(dllFullPath); }; }
这里的关键是将字节写入文件并从其位置加载。 为了避免鸡和鸡蛋的问题,你必须确保你在访问程序集之前声明处理程序,并且不要在加载(程序集parsing)部分访问程序集成员(或实例化任何需要处理程序集的东西)。 还要注意确保GetMyApplicationSpecificPath()
不是临时目录,因为临时文件可能会被其他程序或自己删除(而不是在程序访问dll时会被删除,但至less是一个麻烦。 AppData是很好的位置)。 另外请注意,你必须每次写入字节,你不能从位置加载'因为DLL已经驻留在那里。
对于托pipedll,您不需要写入字节,而是直接从dll的位置加载,或者只是读取字节并从内存中加载程序集。 像这样左右:
using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName)) { byte[] data = new byte[stream.Length]; s.Read(data, 0, data.Length); return Assembly.Load(data); } //or just return Assembly.LoadFrom(dllFullPath); //if location is known.
如果程序集是完全不受pipe理的,你可以看到这个链接或这个如何加载这样的DLL。
Jeffrey Richter的摘录非常好。 简而言之,添加库作为embedded式资源并在其他之前添加callback。 下面是我在控制台应用程序的Main方法的开始部分(在他的页面的注释中find的)的一个版本(只要确保使用这个库的任何调用与Main有不同的方法)。
AppDomain.CurrentDomain.AssemblyResolve += (sender, bargs) => { String dllName = new AssemblyName(bargs.Name).Name + ".dll"; var assem = Assembly.GetExecutingAssembly(); String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName)); if (resourceName == null) return null; // Not found, maybe another handler will find it using (var stream = assem.GetManifestResourceStream(resourceName)) { Byte[] assemblyData = new Byte[stream.Length]; stream.Read(assemblyData, 0, assemblyData.Length); return Assembly.Load(assemblyData); } };
在上面展开@Bobby's asin 。 您可以编辑.csproj以使用IL-Repack在构build时自动将所有文件打包到单个程序集中。
- 使用
Install-Package ILRepack.MSBuild.Task
安装nuget ILRepack.MSBuild.Task包 - 编辑.csproj的AfterBuild部分
这是一个简单的示例,它将ExampleAssemblyToMerge.dll合并到您的项目输出中。
<!-- ILRepack --> <Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'"> <ItemGroup> <InputAssemblies Include="$(OutputPath)\$(AssemblyName).exe" /> <InputAssemblies Include="$(OutputPath)\ExampleAssemblyToMerge.dll" /> </ItemGroup> <ILRepack Parallel="true" Internalize="true" InputAssemblies="@(InputAssemblies)" TargetKind="Exe" OutputFile="$(OutputPath)\$(AssemblyName).exe" /> </Target>
我build议你检查一下.NETZ工具,它也用你select的scheme来压缩程序集:
您可以将DLL添加为embedded式资源,然后让程序在启动时将它们解压缩到应用程序目录(在检查它们是否已经存在之后)。
安装文件是如此容易,但我认为这不值得。
编辑:这个技术将很容易与.NET程序集。 对于非.NET DLL,这将是更多的工作(你必须找出解压文件和注册的位置等)。
检查boxedapp
它可以embedded到任何应用程序的DLL。 当然用C#写的:)
希望它有帮助。
ILMerge方法和Lars Holm Jensen处理AssemblyResolve事件都不适用于插件主机。 假设可执行文件Hdynamic加载程序集P ,并通过单独程序集中定义的接口IP访问它。 为了将IPembedded到H中 ,需要对Lars的代码进行一些修改:
Dictionary<string, Assembly> loaded = new Dictionary<string,Assembly>(); AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { Assembly resAssembly; string dllName = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll",""); dllName = dllName.Replace(".", "_"); if ( !loaded.ContainsKey( dllName ) ) { if (dllName.EndsWith("_resources")) return null; System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly()); byte[] bytes = (byte[])rm.GetObject(dllName); resAssembly = System.Reflection.Assembly.Load(bytes); loaded.Add(dllName, resAssembly); } else { resAssembly = loaded[dllName]; } return resAssembly; };
处理重复尝试解决相同程序集并返回现有程序而不是创build新实例的技巧。
编辑:为了避免它破坏.NET的序列化,请确保为所有未embedded到您的程序集返回null,从而默认为标准行为。 您可以通过以下方式获取这些库的列表:
static HashSet<string> IncludedAssemblies = new HashSet<string>(); string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames(); for(int i = 0; i < resources.Length; i++) { IncludedAssemblies.Add(resources[i]); }
如果传入的程序集不属于IncludedAssemblies
则返回null。
SmartAssembly.com上的另一个可以处理这个问题的产品是SmartAssembly 。 除了将所有依赖项合并到一个DLL之外,(可选)将代码混淆,删除额外的元数据以减less生成的文件大小,还可以实际优化IL以提高运行时性能。 它还增加了一些全局的exception处理/报告function(如果需要的话),我没有花时间理解,但可能会有帮助。 我相信它也有一个命令行API,所以你可以使它成为你的构build过程的一部分。
ILMerge正是你想要的。
除了ILMerge ,如果你不想打扰命令行开关,我真的推荐ILMerge-Gui 。 这是一个开源项目,非常好!
这可能听起来很简单,但WinRar提供了将一堆文件压缩到自解压可执行文件的选项。
它有很多可configuration的选项:最终图标,提取文件到给定的path,提取后执行的文件,在提取过程中显示popup窗口的自定义标志/文本,根本没有popup窗口,许可协议文本等。
在某些情况下可能会有用。
我使用从.vbs脚本调用的csc.exe编译器。
在您的xyz.cs脚本中,在指令之后添加以下行(我的示例是针对Renci SSH):
using System; using Renci;//FOR THE SSH using System.Net;//FOR THE ADDRESS TRANSLATION using System.Reflection;//FOR THE Assembly //+ref>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll" //+res>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll" //+ico>"C:\Program Files (x86)\Microsoft CAPICOM 2.1.0.2 SDK\Samples\c_sharp\xmldsig\resources\Traffic.ico"
ref,res和ico标签将被下面的.vbs脚本拾取以形成csc命令。
然后在Main中添加程序集parsing器调用者:
public static void Main(string[] args) { AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); .
…并在类中的某处添加parsing器:
静态组装CurrentDomain_AssemblyResolve(对象发件人,ResolveEventArgs参数) { String resourceName = new AssemblyName(args.Name).Name +“.dll”; 使用(var stream = Assembly.GetExecutingAssembly()。GetManifestResourceStream(resourceName)) { Byte [] assemblyData = new Byte [stream.Length]; stream.Read(assemblyData,0,assemblyData.Length); 返回Assembly.Load(assemblyData); } }
我将vbs脚本命名为与.cs文件名匹配(例如,ssh.vbs查找ssh.cs); 这使得脚本无数次运行变得容易很多,但是如果你不是像我这样的白痴,那么一个通用的脚本就可以通过拖放操作来获取目标.cs文件:
Dim name_,oShell,fso 设置oShell = CreateObject(“Shell.Application”) 设置fso = CreateObject(“Scripting.fileSystemObject”) '以VBS脚本名称作为目标文件名称 “################################################ name_ = Split(wscript.ScriptName,“。”)(0) '从.CS文件中获取外部DLL的名称和图标名称 “################################################# ###### Const OPEN_FILE_FOR_READING = 1 设置objInputFile = fso.OpenTextFile(name_&“.cs”,1) “把所有东西都写进一个arrays “############################# inputData = Split(objInputFile.ReadAll,vbNewline) 对于每个strData在inputData中 如果离开(strData,7)=“// + ref>”那么 csc_references = csc_references&“/ reference:”&trim(replace(strData,“// + ref>”,“”))&“” 万一 如果离开(strData,7)=“// + res>”那么 csc_resources = csc_resources&“/ resource:”&trim(replace(strData,“// + res>”,“”))&“” 万一 如果离开(strData,7)=“// + ico>”那么 csc_icon =“/ win32icon:”&trim(replace(strData,“// + ico>”,“”))&“” 万一 下一个 objInputFile.Close “编译文件 “################ oShell.ShellExecute“c:\ windows \ microsoft.net \ framework \ v3.5 \ csc.exe”,“/ warn:1 / target:exe”&csc_references&csc_resources&csc_icon&“”&name_&“.cs” ,“”,“runas”,2 WScript.Quit(0)
在C#中创build一个混合的本地/托pipe程序集是可能的,但并非那么容易。 如果您使用C ++,则会更容易,因为Visual C ++编译器可以像创build其他任何东西一样轻松地创build混合程序集。
除非你有严格的要求来制作一个混合程序集,否则我会同意MusiGenesis对于C#来说这并不值得。 如果你需要这样做,也许应该转向C ++ / CLI。
一般来说,您需要某种forms的后期构build工具来执行您所描述的程序集合并。 有一个名为Eazfuscator(eazfuscator.blogspot.com/)的免费工具,它是专门为字节码修改而devise的,它也处理程序集合并。 您可以将这添加到Visual Studio的后期构build命令行中以合并您的程序集,但是由于在任何非trival程序集合并场景中会出现问题,您的里程将有所不同。
您也可以检查构build是否使得NANT能够在构build之后合并程序集,但是我不太熟悉NANT自己说是否内置了该function。
还有许多Visual Studio插件将执行程序集合作为构build应用程序的一部分。
或者,如果您不需要自动完成此操作,则会有许多工具(如ILMerge)将.net程序集合并到一个文件中。
我合并程序集时遇到的最大问题是如果他们使用任何类似的命名空间。 或者更糟的是,引用不同版本的相同的DLL(我的问题通常与NUnit DLL文件)。
我在embeddedDLL的代码项目中尝试了这个解决scheme: http : //www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
它工作得很好。