66

我目前正在从事一个我们有很多依赖项的项目。我想将所有引用的 dll 编译到 .exe 中,就像您对嵌入式资源所做的那样。我尝试过ILMerge,但它无法处理 .xaml 资源。

所以我的问题是:有没有办法将具有多个依赖项的 WPF 项目合并到一个 .exe 中?

4

10 回答 10

82

http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

这对我来说就像一个魅力:) 并且它完全免费。

添加代码以防博客消失。

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)让你的主要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);
    }
}
于 2011-02-14T17:14:31.450 回答
18

使用Costura.Fody - 它以 Nuget Pkg 的形式提供,用于在您的程序集中嵌入资源的最佳和最简单的方法。

Install-Package Costura.Fody

将其添加到项目后,它会自动将所有添加的引用嵌入到您的主程序集中。

于 2015-10-09T17:04:31.093 回答
13

{smartassembly} 就是这样一种产品。它可以混淆或嵌入您的 dll。

试试这个:http ://www.smartassembly.com/

您还可以对您的应用程序进行大量改进,使其运行得更快。

是的。您可以将它用于 WPF。

2015 年 8 月 6 日更新:ILRepack 2.0.0(它是 ILMerge 的开源替代品)现在支持大多数 WPF 案例合并:https ://twitter.com/Gluckies/status/607680149157462016

于 2009-06-24T04:48:44.080 回答
9

正如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[] 作为参数。

于 2011-08-11T12:17:59.313 回答
7

.NET reactor具有合并程序集的功能,而且成本不高。

于 2009-06-22T07:18:43.377 回答
3

这是来自 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 
            {
            }
        }
    }
}
于 2013-04-19T15:52:04.080 回答
2

试试 .Netz ( http://madebits.com/netz/ ) - 它是免费的(就像啤酒一样),如果你的目标是一个 exe,它会做一些好事。

于 2011-07-12T12:57:04.753 回答
2

从 .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>

然而,使用这种方法,我在运行单元测试时遇到了问题,所以我个人只是在发布时选择该选项。

于 2020-06-11T07:56:35.747 回答
1
  1. 将此添加到 .csprofj 文件:

>

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>
  1. 右键单击项目/属性/应用程序/启动对象/选择 Sinhro.Program

  2. 将此添加到您的 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);
        }
    }   
    

来源:http ://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

于 2015-07-22T08:41:52.257 回答
1

由于所有其他解决方案都在 C# 中,而我在 VB.NET 中需要它,这包括说明在何处插入配置更改、必要的导入以及添加处理程序的方式,而不是 C# 的 += 语法。

对于任何 WPF 应用程序,而不是每个项目,都需要添加以下内容以使代码编译为单个 EXE。它仍然会在输出文件夹中包含 DLL,但 EXE 将包含所有这些。

  1. 卸载 WPF 项目(通常是视图)
  2. 右键单击项目并编辑它
  3. 在文档中,将以下代码粘贴到此行之后
<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>
  1. 关闭它,保存它,然后重新加载项目
  2. 在 Application.xaml.vb 文件中添加以下代码,或者如果文件中已存在某些内容,请将其添加到其中:
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
于 2019-06-20T21:09:51.687 回答