How to combine DLLs with .exe inside of a wpf / winforms application (with pictures)

11,194

Solution 1

Update 3

.Net has this built in https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file

Update 2

you don't even have to include the dlls as embedded resources at all, just use Fody.Costura and it will resolve the referenced assemblies and auto-include them

Also if you are using .net core 3+ you can use the Single file executable & Assembly linking features

Update

if you want an easy tool to merge the assemblies without worrying about doing ANY work at all then Fody.Costura is the best choice you have, as all you need to do is just include the dlls and change their Build Action to Embedded Resource and it will work right away.


1 - make a folder that contains all of the Dlls or place them separately as you like Adding Dlls

2 - Click on each DLL from the "Solution Explorer" and make sure they have these properties

  • Build Action = Embedded Resources

  • Copy To Output Directory = Do not copy

change their Build Action

3 - Go to : Project > Properties > References , And make sure every Dll you add has the same name as the assembly like this :

In References :-

Reference name = Dll name

In Solution Explorer :-

Reference name = Dll name

Note :-

It's Better to Make copy local = True in references as it will give you an updated DLL each time you publish the project

Now at this point you have your DLLs added to the EXE , all is remaining now is to tell the program how to read these DLLs from the EXE (that's why we made build action = Embedded resources)

4 - In Solution Explorer Open your (Application.xaml.vb) file ([App.xaml.cs] in c#)
OR
Go To : Project > Properties > Application > View Application Events
Application events

Now in this page we are going to handle the very first event of the application (Construction event) to tell the program how to handle the assemblies we add as DLLs before loading/reading them by using the AssemblyResolve Event

Check this MSDN page for more Info about the AssemblyResolve Event https://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve(v=vs.110).aspx

5 - Now to The code part :
first of all Import this namespace

vb.net

Imports System.Reflection  

c#

using System.Reflection;

In the Constructor ([Sub New] in vb) add this code

Vb.net

 Public Sub New()
        AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf OnResolveAssembly           
 End Sub

c#.net

public App()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;       
}

Then add the OnResolveAssembly Function
vb.net

''' <summary>
''' Tells the program that the Assembly it's Seeking is located in the Embedded resources By using the
''' <see cref="Assembly.GetManifestResourceNames"/> Function To get All the Resources
''' </summary>
''' <param name="sender"></param>
''' <param name="args"></param>
''' <returns></returns>
''' <remarks>Note that this event won't fire if the dll is in the same folder as the application (sometimes)</remarks>
Private Shared Function OnResolveAssembly(sender As Object, args As ResolveEventArgs) As Assembly
    Try
        'gets the main Assembly
        Dim parentAssembly = Assembly.GetExecutingAssembly()
        'args.Name will be something like this
        '[ MahApps.Metro, Version=1.1.3.81, Culture=en-US, PublicKeyToken=null ]
        'so we take the name of the Assembly (MahApps.Metro) then add (.dll) to it
        Dim finalname = args.Name.Substring(0, args.Name.IndexOf(","c)) & ".dll"
        'here we search the resources for our dll and get the first match
        Dim ResourcesList = parentAssembly.GetManifestResourceNames()
        Dim OurResourceName As String = Nothing
        '(you can replace this with a LINQ extension like [Find] or [First])
        For i As Integer = 0 To ResourcesList.Count - 1
            Dim name = ResourcesList(i)
            If name.EndsWith(finalname) Then
                'Get the name then close the loop to get the first occuring value
                OurResourceName = name
                Exit For
            End If
        Next

        If Not String.IsNullOrWhiteSpace(OurResourceName) Then
            'get a stream representing our resource then load it as bytes
            Using stream As Stream = parentAssembly.GetManifestResourceStream(OurResourceName)
                'in vb.net use [ New Byte(stream.Length - 1) ]
                'in c#.net use [ new byte[stream.Length]; ]
                Dim block As Byte() = New Byte(stream.Length - 1) {}
                stream.Read(block, 0, block.Length)
                Return Assembly.Load(block)
            End Using
        Else
            Return Nothing
        End If
    Catch ex As Exception
        Return Nothing
    End Try
End Function

c#.net

/// <summary>
/// Tells the program that the Assembly its Seeking is located in the Embedded resources By using the
/// <see cref="Assembly.GetManifestResourceNames"/> Function To get All the Resources
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <remarks>Note that this event won't fire if the dll is in the same folder as the application (sometimes)</remarks>
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
    try {
        //gets the main Assembly
        var parentAssembly = Assembly.GetExecutingAssembly();
        //args.Name will be something like this
        //[ MahApps.Metro, Version=1.1.3.81, Culture=en-US, PublicKeyToken=null ]
        //so we take the name of the Assembly (MahApps.Metro) then add (.dll) to it
        var finalname = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll";
        //here we search the resources for our dll and get the first match
        var ResourcesList = parentAssembly.GetManifestResourceNames();
        string OurResourceName = null;
        //(you can replace this with a LINQ extension like [Find] or [First])
        for (int i = 0; i <= ResourcesList.Count - 1; i++) {
            var name = ResourcesList(i);
            if (name.EndsWith(finalname)) {
                //Get the name then close the loop to get the first occuring value
                OurResourceName = name;
                break;
            }
        }

        if (!string.IsNullOrWhiteSpace(OurResourceName)) {
            //get a stream representing our resource then load it as bytes
            using (Stream stream = parentAssembly.GetManifestResourceStream(OurResourceName)) {
                //in vb.net use [ New Byte(stream.Length - 1) ]
                //in c#.net use [ new byte[stream.Length]; ]
                byte[] block = new byte[stream.Length];
                stream.Read(block, 0, block.Length);
                return Assembly.Load(block);
            }
        } else {
            return null;
        }
    } catch (Exception ex) {
        return null;
    }
}

6 - now publish the application or build it and all your dlls will be embedded in a single EXE file (with some extra milliseconds delay to load them)

To Update the DLLs

1 - Simply drag and drop your new dll to the Solution Explorer as the same folder as the old dll then accept the override (make sure to check that [Build Action = Embedded Resources] AND [Copy To Output Directory = Do not copy])

change their Build Action

To Add New DLLs

just repeat step 1 => 3

Credits :

http://richarddingwall.name/2009/05/14/wpf-how-to-combine-mutliple-assemblies-into-a-single-exe/

*Feel free to ask if you had any problem

Solution 2

Wasn't able to get bigworld12 answer to work for me, but I found this link and it worked perfectly for me.

Basically the gist of the page is to unload your project and add the following code 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>

Underneath this line of code.

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Once that is done, reload the project and add a new class to it, and add the following code to your new class.

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

App.Main(); // Run WPF startup code.
}

private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
{
var thisAssembly = Assembly.GetExecutingAssembly();

// Get the Name of the AssemblyFile
var assemblyName = new AssemblyName(e.Name);
var dllName = assemblyName.Name + ".dll";

// Load from Embedded Resources - This function is not called if the Assembly is already
// in the same folder as the app.
var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
if (resources.Any())
{

    // 99% of cases will only have one matching item, but if you don't,
    // you will have to change the logic to handle those cases.
    var resourceName = resources.First();
    using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
    {
        if (stream == null) return null;
        var block = new byte[stream.Length];

        // Safely try to load the assembly.
        try
        {
            stream.Read(block, 0, block.Length);
            return Assembly.Load(block);
        }
        catch (IOException)
        {
            return null;
        }
        catch(BadImageFormatException)
        {
            return null;
        }
    }
}

// in the case the resource doesn't exist, return null.
return null;
}

Save your class, and open the properties menu of your project and select the startup object comboboxand select the name of the class you just created from the list.

All that's left is to build your project.

Share:
11,194
Ahmed Fwela
Author by

Ahmed Fwela

flutter (dart) + wpf (c#) programmer

Updated on June 04, 2022

Comments

  • Ahmed Fwela
    Ahmed Fwela almost 2 years

    How to combine multiple dlls into the main .exe file? (without using third-party programs)

  • David Basarab
    David Basarab over 7 years
    I got this to work easily. The only step to remember is you want to include the .dlls you are using as an embedded resource and also add them to you project as a reference.
  • hughes
    hughes over 6 years
    In step 3, I have no References tab in Project > Preferences of my C# project in Visual Studio 2017
  • Ahmed Fwela
    Ahmed Fwela over 6 years
    @hughes it's in the solution explorer in a tab called References i.imgur.com/TrNq0ye.png , just click on the reference and in the property window edit the CopyLocal and make sure the name is the same as the dll reference
  • hughes
    hughes over 6 years
    Thanks for the reply. If I have the .dll file in my dlls folder, but it doesn't show up in the References section of the Solution Explorer, does that mean I'm not referencing it properly somewhere?
  • hughes
    hughes over 6 years
    Actually never mind, just realized that the Fody.Costura solution just works :)
  • Ahmed Fwela
    Ahmed Fwela over 6 years
    happy to help ^^