是否可以将一个预先存在的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)
其他回答
您可以将dll作为嵌入式资源添加,然后让程序在启动时将它们解压缩到应用程序目录中(在检查它们是否已经存在之后)。
虽然制作安装文件非常容易,但我认为这样做并不值得。
编辑:这种技术对于. net程序集来说很容易。与非。NET dll,这将是更多的工作(你必须弄清楚在哪里解包文件和注册它们等等)。
在上面扩展@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>
试试这个:
https://github.com/ytk2128/dll-merger
在这里你可以合并所有32位dll /exe -即使它不是“。net”dll -所以对我来说更好,然后ilmerge为例…
如果程序集只有托管代码,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。
这听起来可能很简单,但WinRar提供了将一堆文件压缩为自提取可执行文件的选项。 它有很多可配置的选项:最终图标,提取文件到给定的路径,提取后执行的文件,自定义logo/文本在提取期间显示的弹出窗口,根本没有弹出窗口,许可协议文本,等等。 在某些情况下可能有用。