Dll Within

Идея

Достаточно давно я сделал программу которая использовала 2 сторонних библиотеки. Получается, что на выходе у меня было как минимум 3 файла, но я хочу один файл! На тот момент я не особенно озаботился тем, как это сделать и так и осталось там 3 файла.

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

Итак, будем делать так, чтобы можно было внедрить в один файл, любые библиотеки, как свои так и третьих сторон.

Подготовка

Для демонстрации технологии понадобится как минимум 2 проекта, пусть это будут уже привычные WinForms и ClassLibrary проекты. Добавим ссылку на проект ClassLibrary в WinForms.

Чтобы увидеть, что все заработает как надо добавим на форму текстовое поле (Label) и будем туда записывать значение из класса определенного в проекте динамической библиотеки.

Компилируем приложение и смотрим на выход файлов. Должно получится как на картинке.

Да, видно что у нас в итоге получится 2 файла. Если сейчас удалить ClassLibrary.dll, то ничего не заработает. Будем исправлять.

Нет не переносить класс в другой проект =)

Создаем в проекте Gui папку, где будем в некотором смысле «хранить» необходимые библиотеки. После того, как папка создана (в момем случае Magic), добавляем уже сущестующий элемент.

Но не совсем обычным способом.

Магия

Находим скомпилированную библиотеку в папке для ClassLibrary, и добавляем ее как ссылку. См на скриншот.

В итоге должно получится как на изображении ниже. На иконке файла должно быть видно что это ссылка.

Но это далеко не все. Теперь переходим на экран «Свойства» (F4), и выбираем в строке Build Action значение Embedded Resource.

После этого можно перейти в раздел ссылок на сборки и указать, чтобы сборка ClassLibrary не копировалась при компилировании в итоговую директорию.

Выбираем ClassLibrary, жмем F4 и в окне свойств для свойства CopyLocal выставляем значение False.

Можно сказать что большая часть работы сделана, осталось только дописать небольшой кусок кода, и только в одном месте. Итак, открываем Program.cs, и подписываемся на событие AssemblyResolve для текущего домена, ДО того как пойдет весь основной код.

AssemblyResolve вызывается после того, как приложение не смогло найти ссылку на требуемую сборку, что собственно нам и надо.

Теперь осталось реализовать метод LoadAssemblyFromMemory. В этом методе запросим ресурс как поток, переведем его в массив байт и создадим на его основе библиотеку в памяти. Которую и отдадим основному домену на регистрацию.

Вот собственно и все. Теперь можно удалить из Bin папки библиотеку ClassLibrary, чтобы получилось как на картинке ниже.

Пересоберем приложение, видим, что теперь библиотека не появляется в исходной папке, но программа работает!

Чтобы убедится что все у нас хорошо и не требует дальнейшей настройки поменяйте строку в классе Class1, и запустите приложение еще раз. На интерфейсе видно, что строка изменилась!

Выводы

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

В процессе исследования вопроса встречал методы,

  • где библиотеки из памяти сохранялись на диск и тогда цеплялись автоматом,
  • советовали использовать ILMerge, но эта программа не умеет работать с WPF сборками,
  • особо извращенные методы были основаны на рефлексии и куче констант для методов. Понятное дело, что о IntelliSense можно забыть при таком подходе.

Вот собственно и все, о чем я хотел рассказать сегодня.

Исходный код.

Hard’n’Heavy!

4 комментарий на “Dll Within

    • А почему должна? Все точно также, как и если бы у тебя файл был на жестком диске. Там фреймворк тоже пытается его найти своими средствами и подгрузить в память.У него это не получается (очень быстро) и ты подсовываешь библиотеку в этот момент. При обращении к другим классам из этой библиотеки она уже будет в памяти и не будет происходить resolve библиотеки.

      На мой взгляд все гладко )