Корректная загрузка зависимостей для плагинов с использованием Assembly.Load, Assembly.LoadFile
Я работаю над структурой на основе плагинов, которая позволяет плагинам обмениваться данными друг с другом на основе какого-то контракта (интерфейса).
В настоящее время служба Windows может загружать плагины двумя способами:
- В том же AppDomain, что и служба Windows, на которой размещены плагины
- В другом процессе служба Windows взаимодействует через именованные каналы (WCF).
Это хорошо работает большую часть времени. Однако существуют определенные сценарии, в которых один плагин может ссылаться на сборку, а другой плагин ссылается на более новую версию сборки. В этом сценарии я всегда хочу, чтобы загружалась более новая версия зависимости, независимо от того, какой плагин загружен первым.
Вот структура папок:
- Каталог услуг Windows (AppDomainBase)
- Плагины
- Plugin1
- Plugin1.dll
- SharedDependency.dll (1.0.0.0)
- Plugin2
- Plugin2.dll
- SharedDependency.dll (1.0.1.0)
- Plugin1
- Плагины
Я уже провел много исследований и пробовал много разных вещей. Это говорит:
- Я не могу перенаправить привязки сборки через файл app.config. Хотя это работает, но это не практично, потому что я не знаю всех зависимостей заранее и не могу добавить все в app.config.
- Я не могу использовать GAC
- Я не хочу загружать несколько версий одной и той же сборки, только самую новую.
Я прочитал о Assembly.Load, LoadFrom и LoadFile и попытался использовать все из них. Я до сих пор не на 100% уверен в разнице между Load и LoadFrom. Похоже, что они оба автоматически загружают зависимости каждого плагина через слияние и поиск из каталога, в который они загружены.
Мое текущее решение заключается в поиске во всех подкаталогах AppDomainBase, чтобы найти и кэшировать все библиотеки DLL в папке каждого плагина. Если я сталкиваюсь с одной и той же сборкой более одного раза, я всегда отслеживаю новейшую версию и ее местоположение.
Затем я загружаю каждый плагин, вызывая Assembly.LoadFile, чтобы никакие зависимости не загружались слиянием. Я подписываюсь на событие AppDomain.CurrentDomain.AssemblyResolve. Когда возникает это событие, я проверяю Имя сборки, чтобы определить, какая сборка должна быть загружена с предварительным кэшированием, а затем загружаю ее, вызывая Assembly.Load.
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
Log(string.Format("Resolving: {0}", args.Name));
// Determine if the assembly is already loaded in the AppDomain. Only the name of the assembly is compared here.
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().FullName.Split(',')[0] == args.Name.Split(',')[0]);
if (asm == null)
{
// The requsted assembly is not loaded in the current AppDomain
Log(string.Format("Assembly is not loaded in AppDomain: [{0}]", args.Name));
// Determine if the assembly is one that has already been found and cached
var asmName = _RefCandidates.Find(a => a.Name.FullName.Split(',')[0] == args.Name.Split(',')[0]);
if (asmName != null)
{
// The assembly exists in the cache, but has not been loaded. Load it.
Log(string.Format("Pre-loaded assembly found in cache. Loading: [{0}], [{1}]", asmName.Name, asmName.Name.CodeBase));
return Assembly.LoadFile(asmName.File.FullName);
}
}
else
Log(string.Format("Assembly is already loaded in AppDomain: [{0}], [{1}]", asm.GetName(), asm.GetName().CodeBase));
return asm;
}
catch (Exception ex)
{
Logger.Write(ex, LogEntryType.Error);
return null;
}
}
Прежде всего, как лучше всего выполнить то, что мне нужно, и что я делаю неправильно?
Во-вторых, что произойдет, если зависимость в цепочке ожидает ссылки на то, что находится в GAC? Я предполагаю, что он больше не будет найден, так как я использую LoadFile и пропускаю fusion все вместе. Кроме того, я прочитал некоторые вещи о сериализации, не работающей с LoadFile. Что конкретно? Как насчет ресурсов сборок?
Эта модель предполагает, что все новые версии зависимых сборок имеют обратную совместимость, так как я буду загружать только самую новую версию.
Любой вклад с благодарностью.
1 ответ
Невозможно загрузить сборку вручную, если разрешение этой сборки не дает сбой (только в этом случае AssemblyResolve повышается). Таким образом, ваша архитектура плагина не может быть реализована с помощью методов Assembly.Load*.
Сейчас есть два пути.
Первое (не рекомендуется): каким-то образом снять ограничение (например, использовать Assembly.Load при запуске, и в любой момент вам нужен объект, искать сборки CurrentDomain, выбирать правильную сборку и отражать объекты конструкции и использовать их).
Второй (рекомендуется): проанализируйте исходную проблему и найдите решение, которое может быть простым в реализации и обслуживании.
Посмотрите, что Managed Extensibility Framework предлагает для архитектуры плагинов.
Ребята из SharpDevelop написали книгу, рассказывающую нам о своем дереве плагинов.
Найти свой путь.
См. Также эту ссылку о контексте загрузки сборки, чтобы понять, почему не будет работать Assembly.Load без события AssemblyResolve.