68

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?

Farawin
  • 1,375
  • 2
  • 14
  • 19

10 Answers10

86

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);
    }
}
CubanX
  • 5,176
  • 2
  • 29
  • 44
Wegged
  • 2,383
  • 20
  • 26
  • Nice solution. Had some trouble when loading dlls using .LoadFrom() because if different context but otherwise a nice and clean way of automating the merge directly in VS. – jishi May 10 '11 at 12:50
  • Is this solution MSBuild specific? – TarunG Jul 06 '11 at 06:30
  • 5
    This is definitely the best answer – reggaeguitar Nov 03 '14 at 17:00
  • 5
    Surprisingly easy! In short only three steps: 1. edit your .csproject and paste the provided lines 2. create a .cs file, paste the provided lines and auto resolve the missing assemblies 3. change the startup object to the file you just created. – Boern Sep 07 '16 at 15:06
  • Such an elegant solution with no external dependencies required. Love it! Only thing I did differently was override App.OnStartup to hook AssemblyResolve so I didn't need a new startup object. – Dan Aug 11 '17 at 00:33
  • 2
    In my case, `App.OnStartup()` was too late to register `OnAssemblyResolve`, because by the time `OnStartup` was called, my application referenced some additional assemblies. Instead, I put the `OnAssemblyResolve` registration in the `App()` constructor and it worked like a charm. – Michael Plautz Mar 12 '18 at 15:52
  • What I'm missing here is how to "convert" the dependencies to EmbeddedResource. Having the dependencies added with NuGet I've added them one by one as links to a new folder in my solution and this way they are embedded into the output executable. Still, they are copied to the output folder. I simply ignore them during the deployment. Is there any (better) solution to this? – Mike Aug 01 '18 at 11:55
  • Thanks so much! Had to adapt it a little but it's a lot better than having to get or pay for some other solution in my case. – Josh Jun 26 '19 at 19:36
  • I was using this for several years. Just today there were four computers where this failed. Any idea why? – Neil B Aug 02 '19 at 16:23
  • I can't get this to work. I'm trying to include my Resources.fr file with the .exe. – Dynamiquel Sep 19 '20 at 01:12
20

Use Costura.Fody - It is available as Nuget Pkg for best and easiest way to embed resources in your assembly.

Install-Package Costura.Fody

After adding it to the project, it will automatically embed all added references to your main assembly.

Dark Knight
  • 819
  • 8
  • 12
  • 2
    Note that this package is in maintenance mode now and the developers recommend to use alternatives. However, from my experience this package still works great – Bennik2000 Feb 23 '21 at 15:19
13

{smartassembly} is one such product. It can obsfucate or embedd your dlls.

Try this: http://www.smartassembly.com/

You can also do a lot of improvements on your application so it will run faster.

And yes. You can use it for WPF.

Update 8/06/2015: ILRepack 2.0.0 (which is an open-source alternative to ILMerge) has now support for most of WPF cases merging: https://twitter.com/Gluckies/status/607680149157462016

Timotei
  • 1,909
  • 2
  • 22
  • 31
  • I tried this product and it worked correctly where ILMerge failed. It was very easy to use. – Duncan Edwards Jan 29 '10 at 11:12
  • Looks impressive, but it does not let you to point out what files you would like to assembly (it limits you to detected files). As the effect all resource dlls (with translated strings) are left alone, and you still have one executable with additional dlls. I thought that the point of merging would be one executable and 0 dlls. – greenoldman Aug 17 '10 at 09:00
9

As posted on ILMerge website, treat those dlls as ressources, from Jeffrey Richter here :

Many applications consist of an EXE file that depends on many DLL files. When deploying this application, all the files must be deployed. However, there is a technique that you can use to deploy just a single EXE file. First, identify all the DLL files that your EXE file depends on that do not ship as part of the Microsoft .NET Framework itself. Then add these DLLs to your Visual Studio project. For each DLL file you add, display its properties and change its “Build Action” to “Embedded Resource.” This causes the C# compiler to embed the DLL file(s) into your EXE file, and you can deploy this one EXE file. At runtime, the CLR won’t be able to find the dependent DLL assemblies, which is a problem. To fix this, when your application initializes, register a callback method with the AppDomain’s ResolveAssembly event. The code should look something like this:

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

   }

}; 

Now, the first time a thread calls a method that references a type in a dependent DLL file, the AssemblyResolve event will be raised and the callback code shown above will find the embedded DLL resource desired and load it by calling an overload of Assembly’s Load method that takes a Byte[] as an argument.

Matthieu
  • 4,605
  • 4
  • 40
  • 60
7

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

Hemant
  • 19,486
  • 24
  • 91
  • 127
  • Can it handle WPF projects as well? It sure looks promising. – Farawin Jun 22 '09 at 07:49
  • Has anyone succeeded in using Reactor to solve their WPF merging problems? At $180 it's actually affordable as a product for a poor shareware developer like myself, unlike SmartAssembly. – devios1 Jul 30 '10 at 22:46
  • Well I tried it out and it works! However I still want to see if I can get things working without having to shell out almost $200. – devios1 Jul 30 '10 at 23:21
  • 1
    I tried it on Net4.0 WPF assemblies and it does not work for me -- all my merged app does it displaying info the merging was done by unregistered version of .Net Reactor. – greenoldman Aug 17 '10 at 08:40
  • Thanks for this link, very nice bit of software! – Andy Aug 16 '13 at 15:48
3

Here is a tweaked version of the quoted code from Matthieu that doesn't require knowing the namespace to extract the code. For WPF, put this in the application startup event code.

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

To make them available at compile time, I create a folder named ExternalDLLs and copy the dlls there and set them to EmbeddedResource as noted above. To use them in your code, you still need to set a reference to them, but set Copy local to False. To get the code to compile cleanly without errors you also need to set using statments in your code to the namespaces of the dlls.

Here is a little utility that spins through the embedded resource names and displays their namespaces in the output window.

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 
            {
            }
        }
    }
}
j2associates
  • 1,115
  • 10
  • 19
2

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

https://learn.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.

Alice Heaton
  • 1,140
  • 11
  • 16
2

Try .Netz ( http://madebits.com/netz/ ) - it's free (as in beer) and does some nice things if you're target is an exe.

Nils
  • 9,682
  • 6
  • 46
  • 72
  • NetZ is a great option for batch command controlling (so you can build it into your visual studio easily on the post build event). However, on WPF it does not work for me: [My problem case](http://stackoverflow.com/questions/19029813/using-netz-and-wpf-causes-system-io-ioexception) – Martin Braun Sep 26 '13 at 15:43
1
  1. add this to .csprofj file:

>

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>
  1. right click project/ properties/application/starup object / select Sinhro.Program

  2. add this to your program.cs file:

    using System.Reflection; using System.IO; using 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);
        }
    }   
    

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

Tone Škoda
  • 1,463
  • 16
  • 20
  • Can this even be applied to a WPF application. I tried to unload, but none of .cs file is opening up. Should i put the c# code in the statupUri cs file and the XAML part in App.config? – NishantM Mar 04 '17 at 04:48
1

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
James Igoe
  • 443
  • 5
  • 14