使用wpf将dll合并为单个.exe

时间:2009-06-22 07:14:56

标签: c# wpf dll ilmerge

我目前正在开发一个我们有很多依赖项目的项目。我想将所有引用的dll编译成.exe,就像使用嵌入式资源一样。我尝试了ILMerge但它无法处理.xaml资源。

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

11 个答案:

答案 0 :(得分:56)

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);
    }
}

答案 1 :(得分:40)

Costura Fody是一个开源工具,旨在处理合并wpf程序集。

https://github.com/Fody/Costura#how-it-works

答案 2 :(得分:12)

{smartassembly}就是这样一个产品。它可以阻止或嵌入你的d ..

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

您还可以对应用程序进行大量改进,以便更快地运行。

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

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

答案 3 :(得分:9)

发布于ILMerge website,将这些dll视为资源,来自Jeffrey Richter here

  

许多应用程序都包含一个依赖于许多DLL的EXE文件   文件。部署此应用程序时,所有文件必须是   部署。但是,您可以使用一种技术进行部署   只是一个EXE文件。首先,确定您的所有DLL文件   EXE文件取决于不作为Microsoft .NET的一部分提供的文件   框架本身。然后将这些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 []作为参数。

答案 4 :(得分:7)

.NET reactor具有合并程序集的功能,而且不是很昂贵。

答案 5 :(得分:6)

使用 Costura.Fody - 它可以作为Nuget Pkg使用,以便在装配中嵌入资源的最佳和最简单的方法。

Install-Package Costura.Fody

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

答案 6 :(得分:2)

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

答案 7 :(得分:1)

以下是来自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。要使代码干净地编译而没有错误,您还需要使用代码中的语句设置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 
            {
            }
        }
    }
}

答案 8 :(得分:0)

  1. 将此添加到.csprofj文件:
  2. &GT;

    <Target Name="AfterResolveReferences">
      <ItemGroup>
        <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
          <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
        </EmbeddedResource>
      </ItemGroup>
    </Target>
    
    1. 右键单击项目/属性/应用程序/ starup对象/选择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);
          }
      }   
      
    3. 来源:http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

答案 9 :(得分:0)

由于所有其他解决方案都在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

答案 10 :(得分:0)

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

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