是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?
通常情况下,我不介意把dll放在外面,让安装程序处理所有事情,但工作中有几个人问过我这个问题,老实说,我不知道。
是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?
通常情况下,我不介意把dll放在外面,让安装程序处理所有事情,但工作中有几个人问过我这个问题,老实说,我不知道。
当前回答
我强烈推荐使用Costura。Fody -到目前为止在程序集中嵌入资源的最好和最简单的方法。它可以作为NuGet包使用。
Install-Package Costura.Fody
将其添加到项目后,它将自动将复制到输出目录的所有引用嵌入到主程序集中。你可能想要通过添加一个目标到你的项目来清除嵌入的文件:
Install-CleanReferencesTarget
您还可以指定是否包含pdb、排除某些程序集或动态提取程序集。据我所知,也支持非托管程序集。
更新
目前,一些人正在尝试添加对DNX的支持。
更新2
对于最新的Fody版本,您需要MSBuild 16(因此Visual Studio 2019)。Fody 4.2.1版本将支持MSBuild 15。(参考:Fody只支持MSBuild 16及以上版本。当前版本:15)
其他回答
如果程序集只有托管代码,ILMerge可以将程序集组合为一个程序集。您可以使用命令行应用程序,或添加对exe的引用,并以编程方式合并。对于GUI版本,有Eazfuscator和。net z,这两个都是免费的。付费应用包括BoxedApp和SmartAssembly。
如果你必须将程序集与非托管代码合并,我建议使用SmartAssembly。我在使用SmartAssembly时从未遇到过问题,但在使用其他所有产品时都遇到过问题。在这里,它可以将所需的依赖项作为资源嵌入到主exe中。
你可以手动完成所有这些,不需要担心程序集是否被管理,或者通过将dll嵌入到资源中,然后依赖AppDomain的程序集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.
对于托管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.
如果程序集是完全非托管的,您可以查看此链接或此链接以了解如何加载此类dll。
我使用从.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"
下面的.vbs脚本将提取ref、res和ico标记,以形成csc命令。
然后在Main中添加程序集解析器调用者:
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
.
...并在类中添加解析器本身:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { String resourceName = new AssemblyName(args.Name).Name + ".dll"; using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { Byte[] assemblyData = new Byte[stream.Length]; stream.Read(assemblyData, 0, assemblyData.Length); return Assembly.Load(assemblyData); } }
我将vbs脚本命名为匹配.cs文件名(例如ssh。VBS查找ssh.cs);这使得运行脚本更容易很多次,但如果你不是像我这样的白痴,那么一个通用脚本可以从拖放中获取目标.cs文件:
Dim name_,oShell,fso Set oShell = CreateObject("Shell.Application") Set fso = CreateObject("Scripting.fileSystemObject") 'TAKE THE VBS SCRIPT NAME AS THE TARGET FILE NAME '################################################ name_ = Split(wscript.ScriptName, ".")(0) 'GET THE EXTERNAL DLL's AND ICON NAMES FROM THE .CS FILE '####################################################### Const OPEN_FILE_FOR_READING = 1 Set objInputFile = fso.OpenTextFile(name_ & ".cs", 1) 'READ EVERYTHING INTO AN ARRAY '############################# inputData = Split(objInputFile.ReadAll, vbNewline) For each strData In inputData if left(strData,7)="//+ref>" then csc_references = csc_references & " /reference:" & trim(replace(strData,"//+ref>","")) & " " end if if left(strData,7)="//+res>" then csc_resources = csc_resources & " /resource:" & trim(replace(strData,"//+res>","")) & " " end if if left(strData,7)="//+ico>" then csc_icon = " /win32icon:" & trim(replace(strData,"//+ico>","")) & " " end if Next objInputFile.Close 'COMPILE THE FILE '################ 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)
在上面扩展@Bobby的回答。您可以编辑.csproj,以便在构建时使用IL-Repack自动将所有文件打包到单个程序集中。
使用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>
另一个可以很好地处理这个问题的产品是SmartAssembly,网址是SmartAssembly.com。该产品除了将所有依赖项合并到单个DLL中之外,还(可选地)混淆代码,删除额外的元数据以减少生成的文件大小,并且还可以实际优化IL以提高运行时性能。
它还向您的软件(如果需要的话)添加了某种全局异常处理/报告功能,这可能很有用。我相信它还有一个命令行API,因此您可以将其作为构建过程的一部分。
在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);
}
以下是我的博客原文: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/