Program from one exe

    As a rule, when writing .NET programs, not only classes from .NET BCL are used, but also third-party libraries. At runtime, all libraries used must be found. To do this, the dependent dlls are put in the same folder as the exe file.

    However, there are programs that use third-party libraries, but at the same time consisting of one single file. All utilities from SysInternals , as well as my favorite LINQPad, are one file that contains everything that is required to work. Using such utilities is a pleasure - 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 sew a library with compressionAutoMapper into the program and how to get and use it later.


    Code for the article


    The source code for the article - download.

    The program code uses a third-party AutoMapper library . To make sure that the library is operational after it has been sewn into resources, the program calls the code from the samples to the library. This code is not shown here, because this article is not about AutoMapper. But the library itself is interesting and useful - I recommend to see what it does in the code.


    An approach


    In order for .NET code to work, in the AppDomain using it, you need to load the assembly containing the types that are used in the code. If the type is in an assembly that has not yet been loaded into AppDomain, the CLR searches for this assembly by its full name. The search takes place in several places, where exactly - it depends on the settings of AppDomain. For desktop applications, this is usually the GAC and the current folder.

    If the CLR could not find the assembly, the AppDomain.AssemblyResolve event is raised. The event makes it possible to load the required assembly manually. Therefore, to implement a stand-alone program consisting of one exe file, it is enough to sew all dependent assemblies into resources and load them in the AssemblyResolve handler.

    Details about the assembly location mechanism 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 Assemblies


    Conveniently put in the assembly resources in 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 faster on the network.

    The .NET library has an archiving stream - DeflateStream. In fact, it performs zip archiving, but is not associated with patents. 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. I would like the project exe file to be autonomous and not require dependent dlls in its directory.

    We add the archived assembly obtained earlier to the project resources through Project Properties-Resources-Files. When adding a resource, the studio generates code that allows you to use the added resource through the Resources class.

    Register the AssemblyResolve handler (before using the 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 rare build takes more than a megabyte, so reading should happen at a time. For good, you need to read into a buffer with a size equal to the size of the contents of the stream, but DeflateStream does not support the Length property.

    For several dependent libraries, it is worth introducing an agreement according to which the name of the resource and the desired assembly coincide. Then, using reflection, you can automatically search for the required library in the resources.

    By default, dependent libraries added through References are copied to the project's output directory. For AssemblyResolve to work, you must either copy the output exe file to another directory, or prohibit copying dependent libraries to the final directory through References-AutoMapper-Properties-Copy Local = false.


    Conclusion


    The inclusion of dependent assemblies in the resources of the program itself allows it to work autonomously. To run it requires only one exe file. This is important for utility 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 USB flash drive. Archiving assemblies allows you to reduce the size of the program and place more of these programs on a USB flash drive / download faster from the network.

    Also popular now: