Программа из одного exe

    Как правило, при написании .NET программ используются не только классы из .NET BCL, но и сторонние библиотеки. Во время выполнения программы все используемые библиотеки должны быть найдены. Для этого зависимые dll кладут в одну папку с exe файлом.

    Однако существуют программы, использующие сторонние библиотеки, но при этом состоящие из одного единственного файла. Все утилиты от SysInternals, а также любимый мной LINQPad представляют из себя один файл в котором содержится все, что требуется для работы. Пользоваться такими утилитами одно удовольствие — они сразу готовы к использованию, их удобно передавать и хранить.

    В статье рассказывается, как создавать такие автономные программы из одного файла. Разобран пример как со сжатием зашить библиотеку AutoMapper в программу и как ее потом достать и использовать.


    Код к статье


    Исходный код к статье — скачать

    Код программы использует стороннюю библиотеку AutoMapper. Чтобы убедиться в работоспособности библиотеки после ее зашития в ресурсы, в программе вызывается код из семплов к библиотеке. Этот код здесь не приведен, ибо это статья не об AutoMapper. Но сама библиотека интересная и полезная — рекомендую посмотреть, что же она делает в коде.


    Подход


    Чтобы .NET код мог работать, в использующий его AppDomain нужно загрузить сборку, содержащую типы, которые используются в коде. Если тип находится в сборке, которая еще не была загружена в AppDomain, CLR производит поиск этой сборки по ее полному имени. Поиск происходит в нескольких местах, где именно — зависит от настроек AppDomain. Для настольных приложений это обычно GAC и текущая папка.

    Если CLR не удалось найти сборку, вызывается событие AppDomain.AssemblyResolve. Событие дает возможность загрузить требуемую сборку вручную. Поэтому для реализации автономной программы, состоящей из одного exe файла, достаточно зашить все зависимые сборки в ресурсы и в обработчике AssemblyResolve подгружать их.

    Подробности про механизм нахождения сборок можно найти в книге Essential .NET, Volume 1: The Common Language Runtime (Don Box, Chris Sells) — 8 глава, AppDomains and the Assembly Resolver секция.


    Архивация сборок


    Удобно класть в ресурсы сборки в архивированном виде. Архивация уменьшает размер итоговой программы приблизительно в 2 раза. Скорость запуска увеличивается, но эти доли секунды мало кто заметит. А вот уменьшение размера файла позволит его быстрее качать по сети.

    В .NET библиотеке есть архивирующий поток — DeflateStream. По сути он производит zip архивацию, только с патентами не связан. Сжатие сборки зависимой библиотеки происходит следующим образом:
    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 );
    }

    Использование сборки из ресурсов


    Итак, у нас есть работающий проект, использующий сторонние библиотеки. Хочется, чтобы exe файл проекта был автономен и не требовал наличия зависимых dll в своем каталоге.

    Полученную ранее архивированную сборку добавляем в ресурсы проекта через Project Properties-Resources-Files. Студия при добавлении ресурса генерирует код, который позволяет использовать добавленный ресурс через Resources класс.

    Регистрируем обработчик AssemblyResolve (до использования классов зависимой библиотеки):
    AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve;
    Код обработчика:
    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;
    }
    Редкая сборка занимает более мегабайта, поэтому чтение должно произойти за один раз. По хорошему надо читать в буфер с размером равным размеру содержимого потока, но DeflateStream не поддерживает Length свойство.

    Для нескольких зависимых библиотек стоит ввести соглашение, согласно которому имя ресурса и искомой сборки совпадают. Тогда можно с помощью отражения автоматически искать требуемую библиотеку в ресурсах.

    По умолчанию зависимые библиотеки, добавляемые через References, копируются в выходную директорию проекта. Чтобы AssemblyResolve сработал, нужно либо скопировать выходной exe файл в другую директорию, либо запретить копировать зависимые библиотеки в конечную директорию через References-AutoMapper-Properties-Copy Local=false.


    Заключение


    Включение зависимых сборок в ресурсы самой программы позволяет ей работать автономно. Для запуска требуется один лишь exe файл. Это важно для служебных утилит, сразу готовых к использованию.

    Фактически такие автономные программы не требуют установки и их удобно передавать по сети или хранить на флешке. Архивирование сборок позволяет уменьшить размер программы и больше таких программ разместить на флешке / быстрее выкачивать из сети.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 75

      +9
      Думаю, стоит уточнить, что данный способ не позволит запихать среду исполнения .NET в EXE файл, чтобы его можно было запускать на компах без установленного .NET Framework.
        +2
        Да, к сожалению, такой магии не будет =)
        +10
        Более года пользуюсь для этих целей консольной утилитой ILMerge (Microsoft) как для корпоративных, так и для персональных утилит (.NET 2.0-3.5SP1). Механизм работы несколько отличается от описанного — разработка проекта ведётся обычным способом, а потом (например, в релизной конфигурации) все сборки и испольняемый файл «склеиваются» с сохранением пространтсв имен в единый исполняемый файл. Сжатие, соответственно, в этом случае не применяется.
          0
          У этого метода есть недостаток. Если кто то добавит в References вашу сборку и одну из сборок с которой ваша смерджена, то отхватит исключение. А приведенный автором способ позволяет это обойти.
            0
            Аналогично, используем ILMerge если нужно просто встроить библиотеки в exe'шник.
              0
              Раньше у ILMerge было ограничение на коммерческое использование. Сейчас разве нет?
              +1
              Я бы добавил, что подход, описанный в статье, реализован в утилитах, таких как NETZ (консольная), а также NBox (консольная, но собирает не из строки, а из нормального XML конфига). Последняя утилита — собственного написания, постоянно ей пользуюсь, недавно даже собрался и оформил краткий русский мануал.
              –44
              OMG. неужели так сложно слизать еще одну «фишку» макоси — .app?
                –38
                религия не позволяет? я так и думал… странно, а иконку в топик от макоси позволяет вставить…
                p.s.
                вот зачем было вынуждать меня писать этот провокационный комментарий?
                  +3
                  фишка о которой вы говорите несколько иное
                    –32
                    знаю, но в итоге мы получаем: один аккуратненький фаил(не будем рушить фантазии яблочников, и будем все же считать этот каталог файлом), он спокойно переносится и работает на любом другом маке.

                    p.s.
                    меня минусуют из-за того, что я упомянул мак в блоге .Net? эхх, а я уже начал считать .net'еров адекватными людьми…
                      +20
                      Не-не, вот если бы вы написали: «Вот неплохо было бы позаимствовать идею у Mac OS, где есть <описание фишки>, и сделать аналогичную под <ось, программный продукт или еще чего>, было б классно!» — К вам никаких претензий точно б небыло :)

                      А так как вы толсто троллить пришли в дотнет со своим ябблом — получайте полноразмерные, свежие, сочно-красные минусы. :) Заслужили.
                        –29
                        Ну я просто в не совсем вежливой форме описал, что я хотел бы видеть. И троллить я не собирался, я просто описал как все прекрасно у меня в макоси и не плохо было бы уж и эту фичу скопировать из макоси.
                          +1
                          Ну у нас на хабре, за три года, как бы сложилась взаимовежливая и толерантная к чужим предпочтениям среда. Не стоит все ж… :)
                            –24
                            судя потому, что меня все еще минусуют — адекватность под сомнением. и тут не никакой «взаимовежливая и толерантная к чужим предпочтениям среды» — посмотрите топики про iPad, H.264… и все же — почему так трудно позаимствовать идею у Mac OS, где есть удобный эпликейшен контейнер .app, и сделать аналогичную под Win7, было б классно!
                              +1
                              Вы когда-нибудь видели, как выглядит .app в винде?
                                –15
                                обычный каталог. сути не меняет. удобно же.
                                0
                                Ну только фанат мака может прийти в тему про сборку exe с библиотеками и начать рассказывать про свою ОС.
                                Чему вы удивляетесь?
                                  0
                                  тому, что только фанат может отрицать явную прелесть такого подхода. Я описать прелесть такого подхода, и даже ни разу не написал ".net говно если так не умеет!!111" конкретно описал, что было бы не плохим добавлением в разработку под Win7. Да, собрать ехе с библиотеками для маленькой софтины наверное удобно, а теперь попробуйте это сделать для фотошопа или любой попсовой игры?

                                  Или вам религия не позволяет смореть на чужую точку зрения? Быстрее надо заминусовать и скрыть, чтобы молодые и не опытные еще не просекли плюшки маков!!1111

                                  p.s.
                                  по поводу организации приложений в *nix like os ничего против не имею, вполне вменяемо, несколько приложей используют одну и туже библиотеку, вполне удобно, лучше чем плодить бесконечные копии этих библиотек, но тогда требуется система контроля зависимостей, которая не вписывается в мои понимания домашней системы. Ну вот глупо когда я хочу поставить гимп (зайти на сайт гимпа, скачать dmg, drag'n'drop, use it luke) запускать мэнеджер пакетов, искать там гимп, тыкать инсталл, ждать пока проверяться зависимости и тянуть все то, что ему надо — не дружелюбно все это.
                                    0
                                    Ммм… Я так и не понял кому вы ответили :)
                                    Если честно, я не понял, зачем собирать фотошоп со строенными библиотеками?
                                    Религия мне много чего позволяет. Я пользуюсь Windows 7 и Arch с Gentoo, меня, как видите, не разрывает на куски.

                                    к p.s.
                                    По вашему, написать aptitude (допустим, у вас убунту), emerge (допустим генту), pacman (допустим арч) и название «gimp» — это сложнее, чем зайти на офф сайт, скачать, поставить?
                                    Впрочем, больше не спорю, потому что я не все понимаю, что вы пишете.
                                      0
                                      эээ ну да мне проще зайти на сайт гимпа и слить пакет, чем тянуть все зависимости.

                                      >Ммм… Я так и не понял кому вы ответили :)
                                      Если честно, я не понял, зачем собирать фотошоп со строенными библиотеками?
                                      Религия мне много чего позволяет. Я пользуюсь Windows 7 и Arch с Gentoo, меня, как видите, не разрывает на куски.

                                      Я говорю о том, что в маке приложения это один фаил (да на самом деле это директория, но юзер видит только один фаил). Когда я хочу запустить это приложения на другом маке, я просто перетаскиваю этот .app, один «фаил» и все. инет никаких архаизмов как .reg.
                                        0
                                        Тогда, я не пойму, почему вы говорите, что это фишка мака описана в статье?
                                          0
                                          я этого не говорил… я говорил почему ребятам из MS так сложно скопировать и эту фишку и распирать ее как сверх новое и иновационое.
                                            0
                                            А зачем?
                                            Может, они к этому когда-нибудь и придут.
                                            К идее репозиториев же в win server пришли. Глядишь и до этого дойдут.
                    +2
                    Апп — папочка, а не исполняемый бинарный юниксовый файл из этой папочки, и переносятся аппликации, как правило сжатые или в файл зип или в яйца или в образы дмж.
                      –28
                      >один аккуратненький фаил(не будем рушить фантазии яблочников, и будем все же считать этот каталог файлом

                      в этом каталоге хранится вся информация которая требуется для запуска приложения. и переносить ее можно как угодно. Но для дистрибьюции используется чаще всего dmg со сжатием или банальный .tgz/.zip.

                      p.s.
                      человек вроде бы грамотный, а каталоги(директории) называет «папочкой». ну нет таких вещей как «папочка» и «мамочка», есть только каталоги директории.
                        0
                        Низзя, низзя тут никого неграмотными абзывать! Автор за это карму сбивает! Цензура знаешь ли!
                          –2
                          человек вроде бы грамотный, а каталоги(директории) называет «папочкой». ну нет таких вещей как «папочка» и «мамочка», есть только каталоги директории.

                          То есть, «Folder» никак нельзя перевести на русский язык?
                            0
                            Folder/Папка — это элемент интерфейса. Обычно в GUI каталоги/директории отображаются в папки. В случае .app как раз всё не так :-)
                        0
                        Чем вас не устраивает инсталлятор вида setup.exe? Один файл и вы можете спать спокойно.
                        Описанный автором способ выполняет немного другую задачу.
                          –2
                          То о чем говорится в статье — статическая линковка. Макоси не было, когда ее придумали.
                            0
                            почему забыли про jar файлы?

                            а вообще портабельный софт рулит до невозможности
                            0
                            примерно таким подходом я пользовался. Мне требовалось распаковать ресурсы, мучатся и сохранять все по одному файлу (а ресурс-файлов больше одного десятка) ну это тяжеловато.
                            Упаковал, добавил zip в ресурсы исполняемого файла, а затем просто сохранение архива на диск и распаковка в нужный момент.
                              0
                              Я так понимаю, про ILMerge автор не слышал.
                                +4
                                Он работает несколько иначе, в комментах выше уже обсуждали :)
                                  0
                                  Кроме того, могут быть проблемы с некоторыми аттрибутами, которые требуют тип, указанный текстом… там название сборки надо будет править
                                    0
                                    Не совсем понимаю сути проблемы. Если встречается тип, который хранится в сборке, которой нет в памяти, сборка погружается через AssemblyResolve.
                                      0
                                      Это я про ILMerge, который просто сливает все сборки в одну.
                                        0
                                        А можете привести пример? просто интересно
                                          0
                                          Если имеется ввиду пример проблемы с ILMerge, то пожалуйста:

                                          var myType = Type.GetType("MyNamespace.MyType, MySecondAssembly");

                                          после мержа работать не будет, т.к. MySecondAssembly уже физически не существует. Т.е. Из-за той части типа, которая содержит название сборки — весь сыр-бор.

                                          Как правило, текстовые имена типов используются такими атрибутами как Editor, Designer, TypeConverter…
                                            0
                                            Небольшая магия с AssemblyResolve легко решит эту проблему.
                                              0
                                              Это понятно. Я к тому, что без лишних телодвижений в коде, ни ILMerge ни AutoMapper попросту не работают…

                                              Хотелось бы чего-то типо xap, как в Silverlight :)
                                                0
                                                А вариант с архивацией сборок в ресурсы, кроме всего прочего, еще и не NGEN-абельный :)
                                                  0
                                                  Только для кода основного exe.
                                                  То, что хранится в ресурсах, будет JIT компилится всегда, похоже.
                                                    0
                                                    Кстати, NGEN используемых сборок реально ускоряет запуск софта.

                                                    Я в своих setup-ах на Wix, всегда все сборки ngen-ю во время исталляции…

                                                    Пусть инсталляция на 30 секунд медленнее будет, зато потом на запуске софта каждый день по нескольку раз эти 30 секунд экономятся. Рекомендую.
                                              0
                                              Да, вы полностью правы. Один из минусов использования ILMerge заключается в том, что указанный вами пример отработает некорректно. Однако и это, впринципе, несложно решается. Например, для того чтобы подгрузить из сборки, слепленной утилитой, embedded resource нужно тоже указать правильное имя ресурса, а оно зависит от имени сборки. В обоих случаях имя сборки легко получить, указав вместо статического «Assembly.GetEntryAssembly().GetName().Name», а сам namespace при «склеивании» у типа не меняется.
                                    0
                                    ILMerge не умеет сжимать, так что данная вещь типа более продвинутая. хотя я как пользовался ILMerge, так и буду :)
                                      0
                                      Кстати, я в порядке эксперимента сделал аналогичную утилиту, только она еще умеет удалять неиспользуемые типы. За счет чего сокращает размер ;)
                                    +7
                                    А Вы ничего не путаете насчет SysInternals?
                                    Помоему, там все утилиты написаны на Visual C++ и используеться только API Windows.
                                      –1
                                      Да, я знаю. Неаккуратно написал. Там используется такая же функциональность, я показал как реализовать под .NET.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                        0
                                        почему
                                          0
                                          надо в tar.gz
                                          это самый трушный вариант.
                                          (сарказм)
                                        0
                                        Для этого зависимые dll кладут в одну папку с exe файлом.
                                        О GAC слышали?
                                          +9
                                          * Прошу рассматривать прошлое сообщение как ненаписанное.
                                            0
                                            Далеко не всегда хорошо класть в GAC свои/зависимые сборки.
                                              0
                                              Никогда не любил пачкать реестр/gac и прочие системные «кучи» :)
                                              +3
                                              Что мне всегда нравилось в .NET — что никогда не задумываешься «как». Написание кода по времени сокращается в разы
                                                +1
                                                Есть еще пакер который укладывает все файлы в один EXE, как нативные так и .net — Molebox.
                                                Не только исполнительные, но и файлы ресурсов. Распаковывает все в память.
                                                  +1
                                                  Одно замечание по DeflateStream — степень сжатия крайне невысокая, плюс «архив» может занимать больше места, чем исходник (как наблюдал — до 1,5 раз)
                                                  https://connect.microsoft.com/VisualStudio/feedback/details/93636/gzipstream-deflatestream-increase-file-size-on-compression
                                                    –4
                                                    «Чтобы .NET код мог работать, в использующий его AppDomain нужно загрузить сборку, содержащую типы, которые используются в коде.»
                                                    -код работает?
                                                    -типы содержатся?
                                                    -сборка содержит типы?
                                                    -типы используются в коде?
                                                    Можно ведь как-то котролировать свое мысле-изложение? Извините, накипело. Такое ощущение, что читаю очередной перевод технической статьи девочкой филологом.
                                                      +1
                                                      Может быть я просто давно привык к косо звучащей русской терминологии, но я понял это предложение с первого раза. Прочёл, не спотыкаясь.
                                                        +2
                                                        Я ж не против. Это был всего лишь призыв к тому, что если садишься писать статью, то нужно помнить, что это не коммент в аське или на форуме. Эту статью будут читать много людей с совершенно разным уровнем компетенции, а не очкастые, бородатые быдлокодеры, которые уже кроме как через notepad общаться не умеют! ИМХО, замечание актуальное и из-за обиды карму сбивать — плохой тон!
                                                          0
                                                          Это вы кого назвали «очкастыми бородатыми быдлокодерами»?)

                                                          Хорошо. Как нужно было написать? Вся терминология верна вобщем-то. Type и Class не совсем одно и то же в дотнете, assembly оно и есть сборка, а что ещё?

                                                          Ваше замечание совсем не в кассу, статья читается нормально.
                                                      0
                                                      Еще такой вопрос — а вы на ResolveType тоже подписались, и вручную их ищете? Потому что Load(byte[]) — он же грузит в Load-neither контекст; как итог, некоторые типы могут не резолвиться.
                                                        0
                                                        Не подписывался. После отработки AssemblyResolve CLR сам находит типы из уже загруженных в память сборок.
                                                        0
                                                        Хорошая статья. Но для этих же целей можно использовать MoleBox, а если важен и объем тоже — то можно пройтись ASPACK'ом.
                                                          +1
                                                          Для решения проблем, описанных в заключении (один файл, независимость, передача по сети/на флеху) проще всего использовать архивацию (имеется ввиду всей папки каким-нибудь rar или zip). Это снимает все описанные проблемы, но сопровождать и обновлять такое приложение удобнее (при необходимости можно заменить необходимую библиотеку).
                                                          Это также касается вопроса передачи по сети: 1 библиотечку передать явно проще, чем заново полный продукт.
                                                          Также в любом маломальски крупном приложении будут какие-то файлы настроек, которые паковать описанным автором способом нельзя (они для того и хранятся отдельно, чтобы их корректировать, не меняя бинарники). Так если их хранить отдельно, то проще уже все паковать архиватором и распаковівать на принимающей стороне.
                                                            –1
                                                            Да, верно. Но Вы забыли об одной маленькой мелочи. При распаковывании такого файла — Вам понадобится еще и доп. место, которое ровно весу файла при обычных операциях.
                                                            Когда это 5-10 мб — это ничего. А если у меня файл 1 гиг? 4? 8?
                                                            Я покажусь извращенцем, но это я делаю. Пример — игра. любая. Тот же кс или варкрафт.
                                                            Первая весит от 1- до 6, в зависимости от карт, моделей и прочего. В ней ооочень много файлов. Распаковка займет время и ресурсы. Вторая — от 1 до 2-х гиг. Сравнительно мало файлов, но сами mpq весят много.
                                                            И теперь Вам нужно место — около 1 гига свободного места на диске ц под рар (если учесть что Вы не сменили в свойствах системы переменные temp path).
                                                            Зачем мне знать сколько места и все прочее — я просто работаю в своей среде, т.е. на флешке и ни байта не забрал с ситемы. Да. можно поспорить, что некоторые такие проекты требуют доступа именно к переменным папкам системы, то ли Мои документы, то ли Documents and Settings, но и это можно сэмулировать. Благо с реестром это проще сделать.
                                                              0
                                                              Экзешник размеров в 1 гиг? 4? 8
                                                              Мсье знает толк в извращениях.
                                                                –1
                                                                Следую принципу черепахи: Все свое ношу с собой ^-^.
                                                                Зато на работе можно пошпилить с флешки. или слить с инета 1 файл вместо стотысячьмиллионов.
                                                                0
                                                                Я думаю автор статьи при своим постом преследовал несколько другие цели, чем запаковывание игр размером 1-2 гига в один exe-шник. :) Я, честно говоря, не видел, чтобы так делали и производители игр. А как раз в таких ситуация, как Ваша, они именно архивируют, а потом распаковывают/инсталлируют у пользователя.
                                                                «Один exe-шник» хорош лишь в некоторых вырожденых случаях. И то дает больше неудобств, чем преимуществ.
                                                                  0
                                                                  Игры — это пример.
                                                                  Замените словом -софт — аналогичная ситуация.
                                                              +1
                                                              Сам в свое время смотрел как это в LinqPad сделано — спасибо что расписали все по шагам. Действительно, это наверное удобно, хотя остается вопрос — можно ли это же делать если у .Net сборки зависимости на C++ сборки? Тут ведь Assembly.Load работать не будет.
                                                                0
                                                                Смотря как реализована зависимость. Если это C++/CLI сборка, то Assembly.Load должен сработать.

                                                                Если это native dll, то тут нужно гуглить такую же статью на С++. В SysInternals инструментах это реализовано, так что должен быть соответствующий WinAPI вызов. Может у Рихтера в книге даже реализация найдется. В любом случае нужно будет перехватывать PInvoke вызовы к unmanaged DLL. Либо PInvoke обертки выделять в отдельные сборки и анализировать в AssemblyResolve, либо пробовать приспособить TypeResolve, либо принять соглашение об именовании таких оберток и использовать аспектное программирование.

                                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                              Самое читаемое