是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?
通常情况下,我不介意把dll放在外面,让安装程序处理所有事情,但工作中有几个人问过我这个问题,老实说,我不知道。
是否可以将一个预先存在的DLL嵌入到一个编译好的c#可执行文件中(这样你就只有一个文件可以分发)?如果这是可能的,人们会怎么去做呢?
通常情况下,我不介意把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>
其他回答
是的,可以将. net可执行文件与库合并。有多种可用的工具来完成这项工作:
ILMerge是一个实用程序,可用于将多个. net程序集合并为单个程序集。 Mono mkbundle,将libmono的exe和所有程序集打包到一个二进制包中。 IL-Repack是ILMerge的FLOSS替代品,具有一些附加特性。
此外,这可以与Mono链接器结合使用,它可以删除未使用的代码,从而使生成的程序集更小。
另一种可能是使用。net z,它不仅允许压缩程序集,还可以将dll直接打包到exe中。与上面提到的解决方案的不同之处在于,.NETZ不会合并它们,它们是单独的程序集,但被打包到一个包中。
. netz是一个开源工具,它压缩和打包Microsoft . net Framework可执行文件(EXE, DLL),以使它们更小。
通常,您需要某种形式的后构建工具来执行您所描述的程序集合并。有一个免费的工具叫做Eazfuscator (eazfuscator.blogspot.com/),它是为字节码分解设计的,同时也处理程序集合并。您可以将此添加到Visual Studio的构建后命令行中以合并程序集,但由于在任何重要的程序集合并场景中会出现的问题,您的里程将有所不同。
您还可以检查构建make untitil NANT是否具有在构建后合并程序集的功能,但我自己对NANT不够熟悉,无法确定是否内置了该功能。
还有很多很多Visual Studio插件会在构建应用程序时执行程序集合并。
或者,如果你不需要自动完成,有很多工具,比如ILMerge,可以将。net程序集合并到一个文件中。
我在合并程序集时遇到的最大问题是它们是否使用类似的名称空间。或者更糟的是,引用同一个dll的不同版本(我的问题通常是NUnit dll文件)。
以下方法不使用外部工具,自动包含所有需要的DLL(不需要手动操作,一切都在编译时完成)
我在这里读了很多回答说使用ILMerge, ILRepack或Jeffrey Ritcher方法,但这些方法都不适合WPF应用程序,也不容易使用。
当你有很多DLL时,很难手动将你需要的DLL包含在你的exe中。我发现的最好的方法是由wegger在StackOverflow上解释的
为了清晰起见,Copy将他的答案粘贴在这里(全部归功于weged)
1)添加到你的。csproj文件:
<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
2)让你的Main Program.cs看起来像这样:
[STAThreadAttribute]
public static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
App.Main();
}
3)添加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);
}
}
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。
在c#中创建本地/托管程序集是可能的,但并不那么容易。如果你使用c++的话,事情就简单多了,因为Visual c++编译器可以像创建其他程序集一样轻松地创建混合程序集。
除非你对生成混合程序集有严格的要求,否则我同意MusiGenesis的观点,这真的不值得用c#来麻烦。如果您需要这样做,也许可以考虑转移到c++ /CLI。