
As a rule, when writing .NET programs, not only classes from .NET BCL are used, but also third-party libraries. At runtime, all used libraries must be found. For this dependent dll put in the same folder with the exe file.
However, there are programs that use third-party libraries, but at the same time consisting of a single file. All utilities from
SysInternals , as well as my favorite
LINQPad, are one file that contains everything that is required for work. It is a pleasure to use such utilities - they are immediately ready for use, it is convenient to transfer and store them.
The article describes how to create such stand-alone programs from a single file. An example of how to compress the
AutoMapper library
sewn into the program and how to get it and use it is
analyzed .
')
Article Code
Article source code -
downloadThe program code uses a third-party
AutoMapper library. To make sure that the library is working after it is sewn into resources, the program calls the code from the samples to the library. This code is not given here, because this article is not about AutoMapper. But the library itself is interesting and useful - I recommend to look at what it does in the code.
An approach
In order for .NET code to work, the assembly that contains the types used in the code must be loaded into the AppDomain using it. If a type is in an assembly that has not yet been loaded into the AppDomain, the CLR searches for that assembly by its full name. The search takes place in several places, exactly - it depends on the AppDomain settings. For desktop applications, this is usually the GAC and the current folder.
If the CLR cannot find the assembly, the AppDomain.AssemblyResolve event is raised. The event allows you to load the desired assembly manually. Therefore, to implement a standalone program consisting of a single exe file, it is sufficient to stitch all dependent assemblies into resources and load them in the AssemblyResolve handler.
Details on the mechanism for finding assemblies can be found in the book Essential .NET, Volume 1: The Common Language Runtime (Don Box, Chris Sells) - Chapter 8, AppDomains and the Assembly Resolver section.Archiving builds
It is convenient to put in the assembly resources in the archived form. Archiving reduces the size of the final program by about 2 times. The launch speed increases, but few people will notice these fractions of a second. But reducing the file size will allow it to download over the network faster.
In the .NET library there is an archiving stream - DeflateStream.
In fact, it produces zip archiving, it is not connected with patents only. Compression of the assembly of the dependent library is as follows:
var fileInputName = @"OneExeProgram\Libraries\AutoMapper 1.0 RTW\AutoMapper.dll" ; var assembly = File .ReadAllBytes( fileInputName );
var fileOutputName = @"OneExeProgram\Libraries\AutoMapper 1.0 RTW\AutoMapper.dll.deflated" ; using ( var file = File .Open( fileOutputName, FileMode .Create ) ) using ( var stream = new DeflateStream ( file, CompressionMode .Compress ) ) using ( var writer = new BinaryWriter ( stream ) ) { writer.Write( assembly ); } |
Using assembly from resources
So, we have a working project using third-party libraries. It would be desirable, that exe a file of the project was autonomous and did not demand presence dependent dll in the directory.
We add the previously archived assembly to the project resources via Project Properties-Resources-Files. Studio when adding a resource generates code that allows you to use the added resource through the Resources class.
Register the AssemblyResolve handler (before using dependent library classes):
AppDomain .CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve; |
Handler Code:
private static Assembly AppDomain_AssemblyResolve( object sender, ResolveEventArgs args ) { if ( args.Name.Contains( "AutoMapper" ) ) { Console .WriteLine( "Resolving assembly: {0}" , args.Name );
// , using ( var resource = new MemoryStream ( Resources .AutoMapper_dll ) ) using ( var deflated = new DeflateStream ( resource, CompressionMode .Decompress ) ) using ( var reader = new BinaryReader ( deflated ) ) { var one_megabyte = 1024 * 1024; var buffer = reader.ReadBytes( one_megabyte ); return Assembly .Load( buffer ); } }
return null ; } |
A sparse build takes up more than a megabyte, so reading should happen at once. For good, you need to read a buffer with a size equal to the size of the stream content, but the DeflateStream does not support the Length property.
For several dependent libraries, it is worthwhile to enter into an agreement according to which the name of the resource and the assembly sought are the same. Then, using reflection, you can automatically search for the required library in the resources.
By default, dependent libraries added via References are copied to the project output directory. In order for AssemblyResolve to work, you must either copy the output exe file to another directory, or forbid copying dependent libraries to the final directory via References-AutoMapper-Properties-Copy Local = false.
Conclusion
The inclusion of dependent assemblies in the resources of the program itself allows it to work autonomously. Only one exe file is required to run. This is important for service utilities that are immediately ready for use.
In fact, such stand-alone programs do not require installation and are conveniently transferred over the network or stored on a flash drive. Archiving assemblies allows you to reduce the size of the program and place more such programs on a flash drive / pump it out faster from the network.