Pull to refresh

C# + WPF + сторонние сборки -> один .exe-шник

Reading time3 min
Views25K
Бывает приходится разработать маленькое приложение на C# и WPF, однако в следствие использования сторонних сборок(например SharpZipLib, Unity) — получается так что на выходе у нас кроме нашего маленького .exe-шника получается ещё и куча .dll-ок, а нам нужно чтобы был именно один .exe-шник.

Немногие знающие люди тут вспомнят про утилиту ILMerge от Microsoft, но к несчастью — есть проблемы с WPF-приложениями из-за особенностей компиляции XAML-файлов.
Так же бывают проблемы с хитрыми сборками, по которым прошлись приличными обфускаторами.

Я предлагаю другой выход который и использую в некоторых своих продуктах — включить все сторонние сборки как встраиваемые ресурсы(embedded resource), а в App.xaml.cs сделать так:

  1.   public partial class App : Application
  2.   {
  3.     //Обработчик события запуска приложения
  4.     private void OnStartup(object sender, StartupEventArgs e)
  5.     {
  6.       //Для текущего домена приложения вешаем свой обработчик в котором и будем вручную подсовывать нужные сборки
  7.       AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
  8.     }
  9.  
  10.     //Наш обработчик для резолва сборок
  11.     static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
  12.     {
  13.       //Получаем текущую сборку которая выполняется(чтобы из нее брать ресурсы)
  14.       Assembly thisAssembly = Assembly.GetExecutingAssembly();
  15.       //Формируем имя ресурса
  16.       var name = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll";
  17.       //Находим ресурс по имени
  18.       var resourceName = thisAssembly.GetManifestResourceNames().First(s => s.EndsWith(name));
  19.  
  20.       using (Stream stream = thisAssembly.GetManifestResourceStream(resourceName))
  21.       {
  22.         //Считываем ресурс в массив байтов
  23.         byte[] block = new byte[stream.Length];
  24.         stream.Read(block, 0, block.Length);
  25.         //Загружаем сборку из массива байтов в текущий домен приложения и возвращаем её
  26.         return Assembly.Load(block);
  27.       }
  28.     }
  29.   }
* This source code was highlighted with Source Code Highlighter.


Соответственно с таких подходом мы подразумеваем что мы нормально называем файлы сборок.

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

Что ещё можно улучшить — можно уменьшить размер выходного .exe-шника храня сборки сжатыми через DeflateStream и считывая их соответственно распаковывая через него же. Можно периодически проверять не обновилась ли сборка и если обновилась — грузить её из сети (особенно если это чтото мелкое).

И да, важный момент, что скорее всего не будет работать — при такой сборке скорее всего не будут работать сборки с нативными методами, т.е. написанные на C++/CLI. Поправьте меня если я ошибаюсь, но у меня не работали.

[update]
Насчёт C++/CLI — согласно MSDN — выкидывается исключение BadImageFormatException в случае если компилятор C++ удалил .reloc секцию из .exe-шника. Если скомпилировать сборку на C++/CLI с /fixed:no то возможно она будет успешно загружаться
Tags:
Hubs:
Total votes 18: ↑14 and ↓4+10
Comments12

Articles