C# 在已编译的可执行文件中嵌入 DLL
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/189549/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Embedding DLLs in a compiled executable
提问by Merus
Is it possible to embed a pre-existing DLL into a compiled C# executable (so that you only have one file to distribute)? If it is possible, how would one go about doing it?
是否可以将预先存在的 DLL 嵌入到已编译的 C# 可执行文件中(这样您只需分发一个文件)?如果可能的话,人们会如何去做呢?
Normally, I'm cool with just leaving the DLLs outside and having the setup program handle everything, but there have been a couple of people at work who have asked me this and I honestly don't know.
通常,我只是将 DLL 留在外面并让安装程序处理所有事情,这很酷,但是有几个工作人员问过我这个问题,老实说我不知道。
采纳答案by Matthias
I highly recommend to use Costura.Fody- by far the best and easiest way to embed resources in your assembly. It's available as NuGet package.
我强烈建议使用Costura.Fody- 迄今为止在程序集中嵌入资源的最佳和最简单的方法。它以 NuGet 包的形式提供。
Install-Package Costura.Fody
After adding it to the project, it will automatically embed all references that are copied to the output directory into your mainassembly. You might want to clean the embedded files by adding a target to your project:
将它添加到项目后,它会自动将复制到输出目录的所有引用嵌入到主程序集中。您可能希望通过向项目添加目标来清理嵌入的文件:
Install-CleanReferencesTarget
You'll also be able to specify whether to include the pdb's, exclude certain assemblies, or extracting the assemblies on the fly. As far as I know, also unmanaged assemblies are supported.
您还可以指定是包含 pdb、排除某些程序集还是动态提取程序集。据我所知,还支持非托管程序集。
Update
更新
Currently, some people are trying to add support for DNX.
目前,有些人正在尝试添加对 DNX 的支持。
Update 2
更新 2
For the lastest Fody version, you will need to have MSBuild 16 (so Visual Studio 2019). Fody version 4.2.1 will do MSBuild 15. (reference: Fody is only supported on MSBuild 16 and above. Current version: 15)
对于最新的 Fody 版本,您将需要拥有 MSBuild 16(因此是 Visual Studio 2019)。Fody 4.2.1 版将执行 MSBuild 15。(参考:Fody 仅在 MSBuild 16 及更高版本上受支持。当前版本:15)
回答by MusiGenesis
You could add the DLLs as embedded resources, and then have your program unpack them into the application directory on startup (after checking to see if they're there already).
您可以将 DLL 添加为嵌入式资源,然后让您的程序在启动时将它们解压到应用程序目录中(在检查它们是否已经存在之后)。
Setup files are so easy to make, though, that I don't think this would be worth it.
不过,安装文件很容易制作,我认为这不值得。
EDIT: This technique would be easy with .NET assemblies. With non-.NET DLLs it would be a lot more work (you'd have to figure out where to unpack the files and register them and so on).
编辑:这种技术对于 .NET 程序集很容易。使用非 .NET DLL 将需要更多的工作(您必须弄清楚在哪里解压缩文件并注册它们等等)。
回答by Shog9
If they're actually managed assemblies, you can use ILMerge. For native DLLs, you'll have a bit more work to do.
如果它们实际上是托管程序集,则可以使用ILMerge。对于本机 DLL,您需要做更多的工作。
See also:How can a C++ windows dll be merged into a C# application exe?
回答by Chris Charabaruk
It's possible but not all that easy, to create a hybrid native/managed assembly in C#. Were you using C++ instead it'd be a lot easier, as the Visual C++ compiler can create hybrid assemblies as easily as anything else.
在 C# 中创建混合本机/托管程序集是可能的,但并非那么容易。如果您改用 C++,它会容易得多,因为 Visual C++ 编译器可以像创建其他任何东西一样轻松地创建混合程序集。
Unless you have a strict requirement to produce a hybrid assembly, I'd agree with MusiGenesis that this isn't really worth the trouble to do with C#. If you need to do it, perhaps look at moving to C++/CLI instead.
除非您对生产混合程序集有严格的要求,否则我同意 MusiGenesis 的观点,即这真的不值得用 C# 来做这些麻烦。如果您需要这样做,也许可以考虑转向 C++/CLI。
回答by Nathan
Another product that can handle this elegantly is SmartAssembly, at SmartAssembly.com. This product will, in addition to merging all dependencies into a single DLL, (optionally) obfuscate your code, remove extra meta-data to reduce the resulting file size, and can also actually optimize the IL to increase runtime performance.
另一个可以优雅地处理这个问题的产品是 SmartAssembly,位于SmartAssembly.com。除了将所有依赖项合并到单个 DLL 中之外,该产品还会(可选)混淆您的代码、删除额外的元数据以减小生成的文件大小,并且还可以实际优化 IL 以提高运行时性能。
There is also some kind of global exception handling/reporting feature it adds to your software (if desired) that could be useful. I believe it also has a command-line API so you can make it part of your build process.
还有一些可能有用的全局异常处理/报告功能(如果需要)添加到您的软件中。我相信它还有一个命令行 API,因此您可以将其作为构建过程的一部分。
回答by wllmsaccnt
Generally you would need some form of post build tool to perform an assembly merge like you are describing. There is a free tool called Eazfuscator (eazfuscator.blogspot.com/) which is designed for bytecode mangling that also handles assembly merging. You can add this into a post build command line with Visual Studio to merge your assemblies, but your mileage will vary due to issues that will arise in any non trival assembly merging scenarios.
通常,您需要某种形式的后期构建工具来执行您所描述的程序集合并。有一个名为 Eazfuscator (eazfuscator.blogspot.com/) 的免费工具,它专为字节码处理而设计,也处理程序集合并。您可以使用 Visual Studio 将其添加到构建后的命令行中以合并您的程序集,但您的里程会因任何非普通程序集合并方案中出现的问题而有所不同。
You could also check to see if the build make untility NANT has the ability to merge assemblies after building, but I am not familiar enough with NANT myself to say whether the functionality is built in or not.
您还可以检查构建直到 NANT 是否能够在构建后合并程序集,但我自己对 NANT 不够熟悉,无法说明该功能是否内置。
There are also many many Visual Studio plugins that will perform assembly merging as part of building the application.
还有许多 Visual Studio 插件将执行程序集合并作为构建应用程序的一部分。
Alternatively if you don't need this to be done automatically, there are a number of tools like ILMerge that will merge .net assemblies into a single file.
或者,如果您不需要自动完成此操作,则有许多工具(例如 ILMerge)可以将 .net 程序集合并到一个文件中。
The biggest issue I've had with merging assemblies is if they use any similar namespaces. Or worse, reference different versions of the same dll (my problems were generally with the NUnit dll files).
我在合并程序集时遇到的最大问题是它们是否使用任何类似的命名空间。或者更糟的是,引用同一个 dll 的不同版本(我的问题通常与 NUnit dll 文件有关)。
回答by Bobby
Yes, it is possible to merge .NET executables with libraries. There are multiple tools available to get the job done:
是的,可以将 .NET 可执行文件与库合并。有多种工具可用于完成工作:
- ILMergeis a utility that can be used to merge multiple .NET assemblies into a single assembly.
- Mono mkbundle, packages an exe and all assemblies with libmono into a single binary package.
- IL-Repackis a FLOSS alterantive to ILMerge, with some additional features.
- ILMerge是一个实用程序,可用于将多个 .NET 程序集合并为一个程序集。
- Mono mkbundle将 exe 和所有带有 libmono 的程序集打包到一个二进制包中。
- IL-Repack是 ILMerge 的 FLOSS 替代品,具有一些附加功能。
In addition this can be combined with the Mono Linker, which does remove unused code and therefor makes the resulting assembly smaller.
此外,这可以与Mono Linker结合使用,它确实删除了未使用的代码,因此使生成的程序集更小。
Another possibility is to use .NETZ, which does not only allow compressing of an assembly, but also can pack the dlls straight into the exe. The difference to the above mentioned solutions is that .NETZ does not merge them, they stay separate assemblies but are packed into one package.
另一种可能性是使用.NETZ,它不仅允许压缩程序集,还可以将 dll 直接打包到 exe 中。与上述解决方案的不同之处在于 .NETZ 不会合并它们,它们保持独立的程序集但打包成一个包。
.NETZ is a open source tool that compresses and packs the Microsoft .NET Framework executable (EXE, DLL) files in order to make them smaller.
.NETZ 是一种开源工具,可压缩和打包 Microsoft .NET Framework 可执行文件(EXE、DLL)以使它们更小。
回答by Lars Holm Jensen
Just right-click your project in Visual Studio, choose Project Properties -> Resources -> Add Resource -> Add Existing File… And include the code below to your App.xaml.cs or equivalent.
只需在 Visual Studio 中右键单击您的项目,选择项目属性 -> 资源 -> 添加资源 -> 添加现有文件...并将以下代码包含到您的 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);
}
Here's my original blog post: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/
这是我的原始博客文章:http: //codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/
回答by nawfal
ILMergecan combine assemblies to one single assembly provided the assembly has only managed code. You can use the commandline app, or add reference to the exe and programmatically merge. For a GUI version there is Eazfuscator, and also .Netzboth of which are free. Paid apps include BoxedAppand SmartAssembly.
ILMerge可以将程序集合并为一个程序集,前提是该程序集只有托管代码。您可以使用命令行应用程序,或添加对 exe 的引用并以编程方式合并。对于 GUI 版本,有Eazfuscator和.Netz,两者都是免费的。付费应用包括BoxedApp和SmartAssembly。
If you have to merge assemblies with unmanaged code, I would suggest SmartAssembly. I never had hiccups with SmartAssemblybut with all others. Here, it can embed the required dependencies as resources to your main exe.
如果您必须将程序集与非托管代码合并,我建议使用SmartAssembly。我从来没有在SmartAssembly 上打过嗝,但在所有其他方面。在这里,它可以将所需的依赖项作为资源嵌入到您的主 exe 中。
You can do all this manually not needing to worry if assembly is managed or in mixed mode by embedding dll to your resources and then relying on AppDomain's Assembly ResolveHandler
. This is a one stop solution by adopting the worst case, ie assemblies with unmanaged code.
您可以通过将 dll 嵌入到您的资源然后依赖 AppDomain 的 Assembly 来手动完成所有这些操作,而无需担心程序集是被管理的还是处于混合模式ResolveHandler
。这是采用最坏情况的一站式解决方案,即具有非托管代码的程序集。
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);
};
}
The key here is to write the bytes to a file and load from its location. To avoid chicken and egg problem, you have to ensure you declare the handler before accessing assembly and that you do not access the assembly members (or instantiate anything that has to deal with the assembly) inside the loading (assembly resolving) part. Also take care to ensure GetMyApplicationSpecificPath()
is not any temp directory since temp files could be attempted to get erased by other programs or by yourself (not that it will get deleted while your program is accessing the dll, but at least its a nuisance. AppData is good location). Also note that you have to write the bytes each time, you cant load from location just 'cos the dll already resides there.
这里的关键是将字节写入文件并从其位置加载。为避免先有鸡还是先有蛋的问题,您必须确保在访问程序集之前声明处理程序,并且您不访问加载(程序集解析)部件内的程序集成员(或实例化任何必须处理程序集的内容)。还要注意确保GetMyApplicationSpecificPath()
不是任何临时目录,因为临时文件可能会被其他程序或您自己尝试删除(不是在您的程序访问 dll 时它会被删除,但至少它是一个麻烦。AppData 是好的地点)。另请注意,您每次都必须写入字节,您无法从位置加载,因为 dll 已经驻留在那里。
For managed dlls, you need not write bytes, but directly load from the location of the dll, or just read the bytes and load the assembly from memory. Like this or so:
对于托管 dll,您不需要写入字节,而是直接从 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.
If the assembly is fully unmanaged, you can see this linkor thisas to how to load such dlls.
回答by Ant_222
Neither the ILMerge approach nor Lars Holm Jensen's handling the AssemblyResolve event will work for a plugin host. Say executable Hloads assembly Pdynamically and accesses it via interface IPdefined in an separate assembly. To embed IPinto Hone shall need a little modification to Lars's code:
ILMerge 方法和 Lars Holm Jensen 处理 AssemblyResolve 事件都不适用于插件主机。假设可执行文件H动态加载程序集P并通过在单独程序集中定义的接口IP访问它。要将IP嵌入到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;
};
The trick to handle repeated attempts to resolve the same assembly and return the existing one instead of creating a new instance.
处理重复尝试解析相同程序集并返回现有程序集而不是创建新实例的技巧。
EDIT:Lest it spoil .NET's serialization, make sure to return null for all assemblies not embedded in yours, thereby defaulting to the standard behaviour. You can get a list of these libraries by:
编辑:以免破坏 .NET 的序列化,确保为所有未嵌入您的程序集返回 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]); }
and just return null if the passed assembly does not belong to IncludedAssemblies
.
如果传递的程序集不属于IncludedAssemblies
.
回答by Steve
The excerpt by Jeffrey Richteris very good. In short, add the library's as embedded resources and add a callback before anything else. Here is a version of the code (found in the comments of his page) that I put at the start of Main method for a console app (just make sure that any calls that use the library's are in a different method to Main).
Jeffrey Richter的摘录非常好。简而言之,将库添加为嵌入式资源并在其他任何事情之前添加回调。这是我在控制台应用程序的 Main 方法的开头放置的代码版本(在他的页面的评论中找到)(只需确保使用库的任何调用与 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);
}
};