Wednesday, November 23, 2011

Compiling multiple projects into a single exe file in WPF


A long time ago I wrote a simple application using WPF (I will write about this utility in my next post) that I recently dug up from the lost archives and I wanted to use in my every day life (at work & at home).

I realized that this small application was made up of 3 projects, thus creating one exe file and 2 extra dll files.
Since I want to pack it light, and I truly see no reason to carry around 3 files that are all under 1MB, I looked for a way to compile all of this under one file.

After looking on the internet a little bit, I found a simple solution, which probably isn't the best way to do it but it works good enough for me...

First, in my main project, I created a folder called Assemblies, and put the other compiled dll's into this folder manually. Then, I defined these dlls' Build Action to be 'Embedded Resource' (this is found in the properties window of every file in your project - image attached).
This will cause the dll's to be compiled as resources embedded into the output exe file.



And now that the dll's are in the exe file, we can delete the dll's in our output folder and just run our exe file, right?
Wrong! The CLR will still look for these files in the folder (and also in the GAC) and when it doesn't find them it won't know what to do.

So all we need to do actually, is just tell the compiler where to look when it doesn't find the files in the original location.
This is done by overriding the method 'AssemblyResolve' in our current AppDomain object like this :

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
    {
        String resourceName = "MyProject.Assemblies." + new AssemblyName(args.Name).Name + ".dll";
        using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            if (stream == null) return;

            byte[] assemblyData = new byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            return Assembly.Load(assemblyData);
        }
    };

What we did here is just retrieve all the embedded resources and find the specific assembly the CLR was looking for.

Now, after compiling your project, you can delete the other dll files in your output folder and the exe file will work on its own.

While thinking of this solution, I didn't like the idea of having to copy the assemblies after every change into the 'Assemblies' folder I created, and then compiling again... but after thinking about it for a while, I think it's not that bad in my case if I consider the circumstances - I'm talking about a very small solution, only 3 projects, and all i need this packing into one file for is to move it around. I also don't believe I will be working on it that much more, so this means I just need to do this once. If it was a current project at work or even for fun, I wouldn't go with this option, and i would've found a better way around this...


An extra note : While running the application for the first time, it immediately crashed. I opened it in debug mode (which actually meant I needed to put a Thread.Sleep() in the initialization and then attach to process through VS since I wanted to delete the dll files before debugging). I realized that the application was looking for an assembly called XmlSerializers that I never created. I don't know exactly why, but it turns out that this was automatically generated while compiling. No worries though, this can be taken care of. Just go to the project properties > Build > Output > Generate serialization assembly = Off.

5 comments:

  1. That's a cool approach, although I would look into automating the packing step into the build.

    Did you know about ILMerge? I never used it, but supposedly it does exactly what you're trying to do here.
    http://www.microsoft.com/download/en/details.aspx?id=17630

    ReplyDelete
  2. I am indeed familiar with ILMerge, but unfortunately it doesn't work with WPF because of the way WPF handles assembly resources.

    I didn't state this in the post, but that is why I intentionally put 'WPF' in the title - so some people with the same problem might run into this post... :)

    ReplyDelete
  3. You can always make the dlls copy to your project automatically, using a pre-build event script in the Build Events of the main project. After all, if your project has references to the other projects, the others will be built first anyway. Heck, you can even use that to automatically remove the excess DLLs afterwards.

    This is how I did it:
    copy /y "$(ProjectDir)..\EmbeddedProject\$(OutDir)EmbeddedProject.dll" "$(ProjectDir)Resources"
    (with EmbeddedProject being the dll to embed, and the resource dlls in the main project being in a "Resources" subfolder).

    And to delete the dll files:
    if exist "$(ProjectDir)$(OutDir)EmbeddedProject.*" del "$(ProjectDir)$(OutDir)EmbeddedProject.*"

    I saw a variant of this on StackOverflow where the file was specifically added to the project's Resources.resx file, which allowed it to be retrieved through the ResourceManager.

    ReplyDelete
  4. Hi, thanks for sharing. Everything is good, but I had an error with corrupted dll files. I don't know if it is only on my machine, but if you have the same try this website http://fix4dll.com/xinput1_3_dll. I replace the wrobg file with a good one and all worked well.

    ReplyDelete