可以将现有的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 is only supported on MSBuild 16 and above. Current version: 15)
Install-CleanReferencesTarget
已不再有效,将会失败。在当前版本中已进行自动化处理。对于使用MSBuild 15的Visual Studio 2017,安装Nugets Fody 4.2.1
和Costura.Fody 3.3.3
才能成功编译。 - Arvo Bowen在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/
bytes
是否为null,如果是,则在那里返回null。毕竟,dll可能 不在 资源中。第二:只有当该类本身没有使用来自该程序集的任何内容时,这才起作用。对于命令行工具,我不得不将我的实际程序代码移动到一个新文件中,并创建一个小的新主程序,只需执行此操作,然后调用旧类中的原始主程序。 - NyergudsInitializeComponent()
之前加入AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
这一行代码。然后将整个System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
方法放在任何地方。编译并运行即可。这比最高得分的答案还要简单,因为不需要下载第三方工具。 - Dan W.dll
文件名中包含连字符(比如twenty-two.dll
),那么它们也会被替换成下划线(比如twenty_two.dll
)。你可以将这行代码改为:dllName = dllName.Replace(".", "_").Replace("-", "_");
。 - Micah VertalC++
。ILMerge 对于 VB NET 也非常容易使用。在这里查看https://github.com/dotnet/ILMerge。感谢 @Shog9。 - Ivan Ferrer Villa是的,可以将.NET可执行文件与库合并。有多个工具可用于完成此任务:
此外,这可以与 Mono Linker 结合使用,该工具可以删除未使用的代码,从而使生成的程序集更小。
另一个可能性是使用 .NETZ,它不仅允许压缩程序集,还可以直接将 dll 打包到 exe 中。与上述解决方案的区别在于 .NETZ 不会将它们合并,它们仍然是单独的程序集,但是被打包到一个包中。
.NETZ 是一种开源工具,用于压缩和打包 Microsoft .NET Framework 可执行文件(EXE、DLL),以使它们更小。
ILMerge可以将只包含托管代码的程序集合并为一个单独的程序集。您可以使用命令行应用程序,或者添加对exe的引用并以编程方式进行合并。对于GUI版本,有Eazfuscator和.Netz,两者都是免费的。付费应用程序包括BoxedApp和SmartAssembly。
如果你需要合并带有非托管代码的程序集,我建议使用SmartAssembly。我从未在SmartAssembly上遇到过问题,但在其他工具上都有。这里,它可以将所需的依赖项作为资源嵌入到主exe中。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);
};
}
GetMyApplicationSpecificPath()
不是任何临时目录,因为其他程序或您自己可能尝试删除临时文件(虽然它不会在您的程序访问dll时被删除,但至少会很烦人。AppData是一个好地方)。还要注意,您每次都必须写入字节,不能仅仅因为dll已驻留在那里而从位置加载。
对于托管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.
通过在项目文件(.csproj)中使用以下属性,可以启用此功能:
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
这是在没有任何外部工具的情况下完成的。
有关更多详细信息,请参见我在这个问题的回答。
Jeffrey Richter的摘录很好。简而言之,将库作为嵌入资源添加,并在任何其他操作之前添加回调。以下是我在控制台应用程序的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的回答进行扩展。您可以编辑.csproj文件,使用IL-Repack在构建时自动将所有文件打包为单个程序集。
Install-Package ILRepack.MSBuild.Task
安装nuget包ILRepack.MSBuild.Task以下是一个简单的示例,将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>
.csproj
文件中:<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
Program.cs
看起来像这样:[STAThreadAttribute]
public static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
App.Main();
}
OnResolveAssembly
方法:private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(args.Name);
var path = assemblyName.Name + ".dll";
if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
using (Stream stream = executingAssembly.GetManifestResourceStream(path))
{
if (stream == null) return null;
var assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
CultureInfo
的测试吗?是否有 en-us
或 fr-fr
子文件夹?这是指 DestinationSubDirectory
吗? - Orace