Merging dlls into a single .exe with wpf
Asked Answered
S

10

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?

Surbased answered 22/6, 2009 at 7:14 Comment(2)
Are you looking only for free apps or you can pay few bucks for it?Vino
If the app is good enough and can handle WPF we would probably consider commercial apps as well.Surbased
V
7

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

Vino answered 22/6, 2009 at 7:18 Comment(4)
Can it handle WPF projects as well? It sure looks promising.Surbased
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.Agnail
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.Agnail
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.Spiers
S
87

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);
    }
}
Shalloon answered 14/2, 2011 at 17:14 Comment(10)
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.Croup
Is this solution MSBuild specific?Lazo
This is definitely the best answerInterstratify
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.Unmeaning
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.Holeproof
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.Iives
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?Milreis
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.Mud
I was using this for several years. Just today there were four computers where this failed. Any idea why?Cocaine
I can't get this to work. I'm trying to include my Resources.fr file with the .exe.Backwards
B
22

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.

Bartley answered 9/10, 2015 at 17:4 Comment(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 greatBallflower
by far best and easiest solutionOmnipotent
U
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

Unhallowed answered 24/6, 2009 at 4:48 Comment(2)
I tried this product and it worked correctly where ILMerge failed. It was very easy to use.Raper
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.Spiers
C
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.

Chandra answered 11/8, 2011 at 12:17 Comment(0)
V
7

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

Vino answered 22/6, 2009 at 7:18 Comment(4)
Can it handle WPF projects as well? It sure looks promising.Surbased
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.Agnail
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.Agnail
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.Spiers
B
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 
            {
            }
        }
    }
}
Barrett answered 19/4, 2013 at 15:52 Comment(0)
I
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.

Inhabiter answered 12/7, 2011 at 12:57 Comment(1)
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 caseHeartbreaker
C
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.

Causality answered 11/6, 2020 at 7:56 Comment(0)
F
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

Forster answered 22/7, 2015 at 8:41 Comment(1)
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?Hypothermal
T
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
Thi answered 20/6, 2019 at 21:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.