WPF Applications Deployment: объединение .NET сборок в одном исполняемом файле из песочницы

.NET*
Когда требуется объединить несколько управляемых сборок в одном файле, можно воспользоваться утилитой ILMerge:

ILMerge.exe /t:winexe /out:test.exe test1.exe test2.dll

В данном примере из двух сборок будет создан объединённый исполняемый файл. Атрибут /t:winexe указывает на то, что результатом будет оконное (WinForms) приложение.

Однако, с приложениями WPF утилита ILMerge работать не умеет. Это связано с особенностями компиляции XAML-файлов, использующихся в архитектуре WPF для декларативного описания структуры, поведения и анимации пользовательского интерфейса:
  • XAML-файл компилируется в BAML-код (аналог IL), который затем размещается в ресурсах сборки.
  • С помощью объявлений пространства имён XML XAML-файл может ссылаться на другие сборки.
  • XAML-файлы могут ссылаться друг на друга используя, к примеру, такие элементы, как объединённые словари ресурсов и фреймы.

При объединении подобных сборок ILMerge следовало бы исправить все URI доступа к BAML-ресурсам, однако этого не происходит.

К счастью есть другой способ: разместить файлы сборок внутри объединённой в качестве встроенных ресурсов.

Для того, чтобы размещение происходило автоматически, достаточно изменить файл проекта, добавив к нему следующую цель сразу после импорта файла Microsoft.CSharp.targets:
<Target Name="AfterResolveReferences">
  <ItemGroup>
   <EmbeddedResource Include="@(ReferenceCopyLocalPaths)"
     Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
    <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
   </EmbeddedResource>
  </ItemGroup>
</Target>


* This source code was highlighted with Source Code Highlighter.

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

После того, как сборки упакованы в едином файле, необходимо загрузить все сборки в память до того, как будут инициализированы ядро и программная инфраструктура WPF.
«Волшебной» точкой входа в приложение WPF является файл App.xaml. Однако, на самом деле App.xaml компонуется в файл App.g.cs, который можно найти внутри проекта в каталоге obj. В файле App.g.cs находится стандартная точка входа — статитческий метод Main.

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

Например вот так:
public class Program
{
  [STAThreadAttribute]
  public static void Main()
  {
    App.Main();
  }
}

* This source code was highlighted with Source Code Highlighter.

image

Последний шаг — собственный обработчик события AppDomain.AssemblyResolve, которое срабатывает каждый раз когда стандартный загрузчик не может определить физическое расположение очередной сборки.
public class Program
{
  [STAThreadAttribute]
  public static void Main()
  {
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    App.Main();
  }

  private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
  {
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName assemblyName = new AssemblyName(args.Name);

    string path = assemblyName.Name + ".dll";
    if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
    {
      path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
    }

    using (Stream stream = executingAssembly.GetManifestResourceStream(path))
    {
      if (stream == null)
        return null;

      byte[] assemblyRawBytes = new byte[stream.Length];
      stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
      return Assembly.Load(assemblyRawBytes);
    }
  }
}


* This source code was highlighted with Source Code Highlighter.

Резюме


Представленный способ прост в реализации и предназначен для автоматической сборки управляемых модулей любого .NET приложения в одном исполняемом файле. Однако с точки зрения практической ценности, очевидно, что данное решение подходходит лишь для сборки небольших проектов. При этом неуправляемый код, очевидно, остаётся «за бортом» объединённого файла приложения.

Список литературы:


1. Построение приложения WPF | msdn.microsoft.com/ru-ru/library/aa970678.aspx
2. MSBuild | msdn.microsoft.com/ru-ru/library/ms171452.aspx
3. Влад Чистяков: MSBuild | www.rsdn.ru/article/devtools/msbuild-05.xml
4. Задача ResolveAssemblyReference | msdn.microsoft.com/ru-ru/library/9ad3f294.aspx
5. Расширение процесса построения Visual Studio | msdn.microsoft.com/ru-ru/library/ms366724.aspx
6. Разрешение загрузки сборок | msdn.microsoft.com/ru-ru/library/ff527268.aspx2
7. C# + WPF + сторонние сборки -> один .exe-шник | habrahabr.ru/blogs/personal/67836/
+2
22 августа 2011, 14:41
9
Lumilest 3,0

комментарии (2)

0
raptor #
Добрый день Америка habrahabr.ru/blogs/personal/67836/
+1
Lumilest #
Уважаемый, raptor, а Вы не заметили, что Ваша статья включена в список литературы? Более того, в данной статье приведена полная схема реализации общей идеи, предлагается автоматизированное решение на основе MSBuild, адаптированное к подгрузке DLL различных культур.

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