Merging dlls into a single .exe with wpf

Viewed 50557

I'm currently working on a project where we have a lot of dependencies. I would like to compile all the referenced dll's into the .exe much like you would do with embedded resources. I have tried ILMerge but it can't handle .xaml resources.

So my question is: Is there a way to merge a WPF project with multiple dependencies into a single .exe?

10 Answers

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

This worked like a charm for me :) and its completely free.

Adding code in case the blog ever disappears.

1) Add this to your .csproj file:

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>

2) Make your Main Program.cs look like this:

[STAThreadAttribute]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    App.Main();
}

3) Add the OnResolveAssembly method:

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

.NET reactor has the feature of merging the assemblies and its not very expensive.

As of.NET Core 3.0 this functionality is now part of .NET:

https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#single-file-executables

You can publish as a single executable using dotnet:

dotnet publish -r win-x64 -p:PublishSingleFile=true

Or doing the equivalent operation in Visual Studio: in your Publish Profile Settings, choose a target runtime (you must have one selected to publish as a single file), then expand the "File Select Options" section and select "Produce Single File". Exact steps may vary with Visual Studio versions.

You can also specify these defaults in your .csproj file:

<PropertyGroup>
  <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
  <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

However using this approach I've had issues with running my unit tests, so I personally just select the option when publishing.

Since all the other solutions are in C#, and I needed this for VB.NET, this includes clarification about where to insert the configuration change, the necessary imports, and the way to add a handler, instead of C#'s += syntax.

For any WPF application, not each project, the following needs to be added to make the code compile to a single EXE. It will still include the DLL’s in the output folder, but the EXE will contain all of them.

  1. Unload the WPF project (usually the view)
  2. Right-click the project and edit it
  3. In the document, paste the following code after this line
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />

Code to paste

<Target Name="AfterResolveReferences">
   <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
         <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)
         </LogicalName>
      </EmbeddedResource>
   </ItemGroup>
</Target>
  1. Close it, save it, and then reload the project
  2. In the Application.xaml.vb file add the following code, or if something already exists in the file, add this to it:
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
Related