是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?
通常情况下,我不介意把dll放在外面,让安装程序处理所有事情,但工作中有几个人问过我这个问题,老实说,我不知道。
是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?
通常情况下,我不介意把dll放在外面,让安装程序处理所有事情,但工作中有几个人问过我这个问题,老实说,我不知道。
当前回答
杰弗里·里希特的节选非常好。简而言之,将库作为嵌入式资源添加,并在其他任何事情之前添加回调。下面是我放在控制台应用程序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);
}
};
其他回答
ILMerge方法和Lars Holm Jensen处理AssemblyResolve事件都不适用于插件主机。例如,可执行文件H动态加载程序集P,并通过在单独的程序集中定义的接口IP访问它。要将IP嵌入到H one中,需要对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;
};
处理重复尝试解析相同程序集并返回现有程序集而不是创建新实例的技巧。
编辑: 为了避免破坏. 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]); }
如果传递的程序集不属于IncludedAssemblies,则返回null。
在上面扩展@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,因此您可以将其作为构建过程的一部分。
您可以将dll作为嵌入式资源添加,然后让程序在启动时将它们解压缩到应用程序目录中(在检查它们是否已经存在之后)。
虽然制作安装文件非常容易,但我认为这样做并不值得。
编辑:这种技术对于. net程序集来说很容易。与非。NET dll,这将是更多的工作(你必须弄清楚在哪里解包文件和注册它们等等)。
是的,可以将. net可执行文件与库合并。有多种可用的工具来完成这项工作:
ILMerge是一个实用程序,可用于将多个. net程序集合并为单个程序集。 Mono mkbundle,将libmono的exe和所有程序集打包到一个二进制包中。 IL-Repack是ILMerge的FLOSS替代品,具有一些附加特性。
此外,这可以与Mono链接器结合使用,它可以删除未使用的代码,从而使生成的程序集更小。
另一种可能是使用。net z,它不仅允许压缩程序集,还可以将dll直接打包到exe中。与上面提到的解决方案的不同之处在于,.NETZ不会合并它们,它们是单独的程序集,但被打包到一个包中。
. netz是一个开源工具,它压缩和打包Microsoft . net Framework可执行文件(EXE, DLL),以使它们更小。