我目前正在从事一个我们有很多依赖项的项目。我想将所有引用的 dll 编译到 .exe 中,就像您对嵌入式资源所做的那样。我尝试过ILMerge,但它无法处理 .xaml 资源。
所以我的问题是:有没有办法将具有多个依赖项的 WPF 项目合并到一个 .exe 中?
这对我来说就像一个魅力:) 并且它完全免费。
添加代码以防博客消失。
.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);
}
}
使用Costura.Fody - 它以 Nuget Pkg 的形式提供,用于在您的程序集中嵌入资源的最佳和最简单的方法。
Install-Package Costura.Fody
将其添加到项目后,它会自动将所有添加的引用嵌入到您的主程序集中。
{smartassembly} 就是这样一种产品。它可以混淆或嵌入您的 dll。
试试这个:http ://www.smartassembly.com/
您还可以对您的应用程序进行大量改进,使其运行得更快。
是的。您可以将它用于 WPF。
2015 年 8 月 6 日更新:ILRepack 2.0.0(它是 ILMerge 的开源替代品)现在支持大多数 WPF 案例合并:https ://twitter.com/Gluckies/status/607680149157462016
正如ILMerge 网站上发布的那样,将这些dll 视为资源,来自 Jeffrey Richter :
许多应用程序由一个依赖于许多 DLL 文件的 EXE 文件组成。部署此应用程序时,必须部署所有文件。但是,有一种技术可用于仅部署单个 EXE 文件。首先,确定您的 EXE 文件所依赖的所有 DLL 文件,这些文件不作为 Microsoft .NET Framework 本身的一部分提供。然后将这些 DLL 添加到您的 Visual Studio 项目中。对于您添加的每个 DLL 文件,显示其属性并将其“构建操作”更改为“嵌入式资源”。这会导致 C# 编译器将 DLL 文件嵌入到您的 EXE 文件中,您可以部署这个 EXE 文件。在运行时,CLR 将无法找到相关的 DLL 程序集,这是一个问题。要解决此问题,当您的应用程序初始化时,使用 AppDomain 的 ResolveAssembly 事件注册一个回调方法。
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
String resourceName = "AssemblyLoadingAndReflection." +
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);
}
};
现在,第一次线程调用引用依赖 DLL 文件中的类型的方法时,将引发 AssemblyResolve 事件,上面显示的回调代码将找到所需的嵌入式 DLL 资源并通过调用 Assembly 的 Load 方法的重载来加载它以 Byte[] 作为参数。
.NET reactor具有合并程序集的功能,而且成本不高。
这是来自 Matthieu 的引用代码的调整版本,不需要知道名称空间即可提取代码。对于 WPF,将其放在应用程序启动事件代码中。
AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
// Note: Requires a using statement for System.Reflection and System.Diagnostics.
Assembly assembly = Assembly.GetExecutingAssembly();
List<string> embeddedResources = new List<string>(assembly.GetManifestResourceNames());
string assemblyName = new AssemblyName(args.Name).Name;
string fileName = string.Format("{0}.dll", assemblyName);
string resourceName = embeddedResources.Where(ern => ern.EndsWith(fileName)).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(resourceName))
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
var test = Assembly.Load(assemblyData);
string namespace_ = test.GetTypes().Where(t => t.Name == assemblyName).Select(t => t.Namespace).FirstOrDefault();
#if DEBUG
Debug.WriteLine(string.Format("\tNamespace for '{0}' is '{1}'", fileName, namespace_));
#endif
return Assembly.Load(assemblyData);
}
}
return null;
};
为了使它们在编译时可用,我创建了一个名为 ExternalDLLs 的文件夹,并将 dll 复制到那里并将它们设置为 EmbeddedResource,如上所述。要在代码中使用它们,您仍然需要设置对它们的引用,但将 Copy local 设置为 False。要使代码干净无误地编译,您还需要将代码中的 using 语句设置为 dll 的命名空间。
这是一个小实用程序,它遍历嵌入的资源名称并在输出窗口中显示它们的命名空间。
private void getEmbeddedResourceNamespaces()
{
// Note: Requires a using statement for System.Reflection and System.Diagnostics.
Assembly assembly = Assembly.GetExecutingAssembly();
List<string> embeddedResourceNames = new List<string>(assembly.GetManifestResourceNames());
foreach (string resourceName in embeddedResourceNames)
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
try
{
var test = Assembly.Load(assemblyData);
foreach (Type type in test.GetTypes())
{
Debug.WriteLine(string.Format("\tNamespace for '{0}' is '{1}'", type.Name, type.Namespace));
}
}
catch
{
}
}
}
}
试试 .Netz ( http://madebits.com/netz/ ) - 它是免费的(就像啤酒一样),如果你的目标是一个 exe,它会做一些好事。
从 .NET Core 3.0 开始,此功能现在是 .NET 的一部分:
https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#single-file-executables
您可以使用以下命令作为单个可执行文件发布dotnet
:
dotnet publish -r win-x64 -p:PublishSingleFile=true
或者在 Visual Studio 中执行等效操作:在您的发布配置文件设置中,选择一个目标运行时(您必须选择一个作为单个文件发布),然后展开“文件选择选项”部分并选择“生成单个文件”。具体步骤可能因 Visual Studio 版本而异。
您还可以在.csproj
文件中指定这些默认值:
<PropertyGroup>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
然而,使用这种方法,我在运行单元测试时遇到了问题,所以我个人只是在发布时选择该选项。
>
<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
右键单击项目/属性/应用程序/启动对象/选择 Sinhro.Program
将此添加到您的 program.cs 文件中:
使用 System.Reflection;使用 System.IO;使用 System.Globalization;
[STAThreadAttribute]
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
...
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(args.Name);
string 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;
byte[] assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
由于所有其他解决方案都在 C# 中,而我在 VB.NET 中需要它,这包括说明在何处插入配置更改、必要的导入以及添加处理程序的方式,而不是 C# 的 += 语法。
对于任何 WPF 应用程序,而不是每个项目,都需要添加以下内容以使代码编译为单个 EXE。它仍然会在输出文件夹中包含 DLL,但 EXE 将包含所有这些。
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
要粘贴的代码
<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)
</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
Imports System.Reflection
Imports System.Globalization
Imports System.IO
Class Application
Public Sub New()
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf OnResolveAssembly
End Sub
Private Shared Function OnResolveAssembly(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly
Dim executingAssembly As Assembly = Assembly.GetExecutingAssembly()
Dim assemblyName As AssemblyName = New AssemblyName(args.Name)
Dim path = assemblyName.Name & ".dll"
If assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) = False Then path = String.Format("{0}\{1}", assemblyName.CultureInfo, path)
Using stream As Stream = executingAssembly.GetManifestResourceStream(path)
If stream Is Nothing Then Return Nothing
Dim assemblyRawBytes = New Byte(stream.Length - 1) {}
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length)
Return Assembly.Load(assemblyRawBytes)
End Using
End Function
End Class