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.

Monday, November 7, 2011

Breaking The Build

Today was one of the hardest days at my new job (at Sears Israel) since I started working there...

It all started when I arrived very early at work (around 8:00am) because I wanted to get a lot done, but instead I arrived and immediately found out that one of the acceptance tests I wrote (automated UI testing) was failing and breaking the build on our integration server.
So instead of getting straight to my regular tasks, I started looking into the problem which unfortunately, took a big part of my day. I don't want to bore you with details about why the test was failing since there's nothing technically interesting about it, but I do want to share with you what I learned from it.

We write A LOT of tests! Unit tests, integration tests and acceptance tests. We spend a lot of time writing them and a lot of time thinking how we can improve the framework and make the tests even better in the future. I haven't been familiar to this kind of attitude in the past, but from what I see already at Sears, is that this attitude pays off big time in the long run!
Today, I spent a big part of the day "not getting any work done", and not learning anything new about good design, nor better programming techniques... I spent time digging into my code, until I finally found out that the problem had nothing to do with my code. Even though, I still think this is a good lesson to learn from. During the whole time spent debugging, all I could think about was putting an 'Ignore' attribute above my test, committing, and finally getting back to my regular work, but this would just be cheating and beating the point all together. Doing this would've been taking all the countless hours spent writing these tests in the company and saying they don't matter and just throwing them away. If we don't care enough to maintain these tests like they should be, then we are eventually throwing them away, along with all the time spent on them...

Today, if I start working on a project, even just for myself at home, just something for fun, I can't imagine doing that without writing the proper tests to go along with it. This is becoming an integral part of coding for me...

All in all, I went home today with a good feeling. I know that the tests work, the code is of good quality and I did my part to keep that true now, and in the future. This feeling is rewarding and pays forward to the "hard day" feeling I had while confronting the problem I just wrote about...