When you need to merge several managed assemblies into one file, you can use the ILMerge utility:
ILMerge.exe /t:winexe /out:test.exe test1.exe test2.dll
In this example of two assemblies, a combined executable file will be created. The / t: winexe attribute indicates that the result will be a windowed (WinForms) application.
However, the ILMerge utility cannot work with WPF applications. This is due to the features of compiling XAML files used in the WPF architecture for the declarative description of the structure, behavior and animation of the user interface:
- The XAML file is compiled into a BAML code (similar to IL), which is then placed in assembly resources.
- Using XML namespace declarations, the XAML file can reference other assemblies.
- XAML files can reference each other using, for example, elements such as combined resource dictionaries and frames.
When combining such assemblies, ILMerge should fix all the URIs of access to BAML resources, but this does not happen.
')
Fortunately, there is another way: to place the assembly files inside the combined as embedded resources.
In order for the placement to occur automatically, it is enough to change the project file by adding the following goal to it immediately after importing the Microsoft.CSharp.targets file:
<Target Name="AfterResolveReferences"> <ItemGroup> <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'"> <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName> </EmbeddedResource> </ItemGroup> </Target>
This goal is accomplished immediately after the
ResolveAssemblyReference task is
completed . As a result of the accomplishment of this goal, the associated assemblies are packaged as embedded resources. Each resource is named according to the relative path and file name of the assembly.
Using the path to the assembly in the naming of embedded resources is a more flexible approach and allows, for example, to embed an assembly along with localizations that usually have the same name.
After the assemblies are packaged in a single file, you must load all the assemblies into memory before the kernel and WPF software infrastructure are initialized.
The “magic” entry point into a WPF application is the App.xaml file. However, App.xaml is actually compiled into App.g.cs, which can be found inside the project in the obj directory. The App.g.cs file contains the standard entry point - the static Main method.
Thus, in order to load nested assemblies before initializing the WPF kernel, you must override the standard entry point.
For example, like this:
public class Program { [STAThread] public static void Main() { App.Main(); } }

The final step is your own AppDomain.AssemblyResolve event handler, which is triggered every time the standard loader cannot determine the physical location of the next assembly.
public class Program { static Dictionary<string, Assembly> assembliesDictionary = new Dictionary<string, Assembly>(); [STAThread] public static void Main() { AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; App.Main(); } private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args) { AssemblyName assemblyName = new AssemblyName(args.Name); Assembly executingAssembly = Assembly.GetExecutingAssembly(); string path = string.Format("{0}.dll", assemblyName.Name); if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) { path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path); } if (!assembliesDictionary.ContainsKey(path)) { using (Stream assemblyStream = executingAssembly.GetManifestResourceStream(path)) { if (assemblyStream != null) { var assemblyRawBytes = new byte[assemblyStream.Length]; assemblyStream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length); using (var pdbStream = executingAssembly.GetManifestResourceStream(Path.ChangeExtension(path, "pdb"))) { if (pdbStream != null) { var pdbData = new Byte[pdbStream.Length]; pdbStream.Read(pdbData, 0, pdbData.Length); var assembly = Assembly.Load(assemblyRawBytes, pdbData); assembliesDictionary.Add(path, assembly); return assembly; } } assembliesDictionary.Add(path, Assembly.Load(assemblyRawBytes)); } else { assembliesDictionary.Add(path, null); } } } return assembliesDictionary[path]; } }
Summary
The presented method is simple to implement and is designed to automatically assemble managed modules of any .NET application into one executable file. However, from the point of view of practical value, it is obvious that this solution is only suitable for building small projects. At the same time, the unmanaged code obviously remains “overboard” of the combined application file.
Bibliography:
1. Building a WPF Application |
msdn.microsoft.com/en-ru/library/aa970678.aspx2. MSBuild |
msdn.microsoft.com/ru-ru/library/ms171452.aspx3. Vlad Chistyakov: MSBuild |
www.rsdn.ru/article/devtools/msbuild-05.xml4. ResolveAssemblyReference |
msdn.microsoft.com/en-ru/library/9ad3f294.aspx5. Expanding the build process of Visual Studio |
msdn.microsoft.com/en-ru/library/ms366724.aspx6. Allowing assembly downloads |
msdn.microsoft.com/en-ru/library/ff527268.aspx27. C # + WPF + third-party builds -> one .exe-shnik |
habrahabr.ru/blogs/personal/67836Afterword
In June 2012,
Costura for Visual Studio 2010 opensource-plugin was
released , which allows to reduce the process described above to the management of project settings.