Dll Within
Идея
Достаточно давно я сделал программу которая использовала 2 сторонних библиотеки. Получается, что на выходе у меня было как минимум 3 файла, но я хочу один файл! На тот момент я не особенно озаботился тем, как это сделать и так и осталось там 3 файла.
Сейчас я снова призадумался над этой проблемой, так как гораздо лучше давать ссылку/распространять один исполнительный файл, который запустит все что надо, чем распаковывать архив, соображать, где исполняемый файл. Вот не надо тут хихи, это у программистов и прочих технически граммотных людей показываются расширения файлов, а у простых пользователей только имена показываются с невнятными иконками, поди разберись.
Итак, будем делать так, чтобы можно было внедрить в один файл, любые библиотеки, как свои так и третьих сторон.
Подготовка
Для демонстрации технологии понадобится как минимум 2 проекта, пусть это будут уже привычные WinForms и ClassLibrary проекты. Добавим ссылку на проект ClassLibrary в WinForms.
Чтобы увидеть, что все заработает как надо добавим на форму текстовое поле (Label) и будем туда записывать значение из класса определенного в проекте динамической библиотеки.
namespace ClassLibrary {
public class Class1 {
public string Message = "Hello world! Yeah!";
}
}
namespace Gui {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
label1.Text = new Class1().Message;
}
}
}
Компилируем приложение и смотрим на выход файлов. Должно получится как на картинке.
Да, видно что у нас в итоге получится 2 файла. Если сейчас удалить ClassLibrary.dll, то ничего не заработает. Будем исправлять.
Нет не переносить класс в другой проект =)
Создаем в проекте Gui папку, где будем в некотором смысле «хранить» необходимые библиотеки. После того, как папка создана (в момем случае Magic), добавляем уже сущестующий элемент.

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

Но это далеко не все. Теперь переходим на экран «Свойства» (F4), и выбираем в строке Build Action значение Embedded Resource.
После этого можно перейти в раздел ссылок на сборки и указать, чтобы сборка ClassLibrary не копировалась при компилировании в итоговую директорию.
Выбираем ClassLibrary, жмем F4 и в окне свойств для свойства CopyLocal выставляем значение False.
Можно сказать что большая часть работы сделана, осталось только дописать небольшой кусок кода, и только в одном месте. Итак, открываем Program.cs, и подписываемся на событие AssemblyResolve для текущего домена, ДО того как пойдет весь основной код.
private static void Main() {
AppDomain.CurrentDomain.AssemblyResolve += LoadAssemblyFromMemory;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
AssemblyResolve вызывается после того, как приложение не смогло найти ссылку на требуемую сборку, что собственно нам и надо.
Теперь осталось реализовать метод LoadAssemblyFromMemory. В этом методе запросим ресурс как поток, переведем его в массив байт и создадим на его основе библиотеку в памяти. Которую и отдадим основному домену на регистрацию.
private static Assembly LoadAssemblyFromMemory(object sender, ResolveEventArgs args) {
var assembly = Assembly.GetExecutingAssembly();
var name = args.Name.Substring(0, args.Name.IndexOf(","));
var stream = assembly.GetManifestResourceStream(string.Format("Gui.Magic.{0}.dll", name));
var bytes = new BinaryReader(stream).ReadBytes((int) stream.Length);
return Assembly.Load(bytes);
}
Вот собственно и все. Теперь можно удалить из Bin папки библиотеку ClassLibrary, чтобы получилось как на картинке ниже.
Пересоберем приложение, видим, что теперь библиотека не появляется в исходной папке, но программа работает!
Чтобы убедится что все у нас хорошо и не требует дальнейшей настройки поменяйте строку в классе Class1, и запустите приложение еще раз. На интерфейсе видно, что строка изменилась!
Выводы
Итак, мы получили все достоинства разделения приложения по библиотекам, нормальный стиль программирования и только один файл на выходе. Настройка новой библиотеки проводится только один раз и до конца проекта.
В процессе исследования вопроса встречал методы,
- где библиотеки из памяти сохранялись на диск и тогда цеплялись автоматом,
- советовали использовать ILMerge, но эта программа не умеет работать с WPF сборками,
- особо извращенные методы были основаны на рефлексии и куче констант для методов. Понятное дело, что о IntelliSense можно забыть при таком подходе.
Вот собственно и все, о чем я хотел рассказать сегодня.
Hard’n’Heavy!
Нет обратных ссылок на эту запись.







Сентябрь 16th, 2010 - 11:45
Производительность не страдает?
Сентябрь 16th, 2010 - 15:02
А почему должна? Все точно также, как и если бы у тебя файл был на жестком диске. Там фреймворк тоже пытается его найти своими средствами и подгрузить в память.У него это не получается (очень быстро) и ты подсовываешь библиотеку в этот момент. При обращении к другим классам из этой библиотеки она уже будет в памяти и не будет происходить resolve библиотеки.
На мой взгляд все гладко )
Сентябрь 16th, 2010 - 12:41
Спасибо,
Март 13th, 2011 - 09:09
спасибо