Корректная загрузка зависимостей для плагинов с использованием Assembly.Load, Assembly.LoadFile

Я работаю над структурой на основе плагинов, которая позволяет плагинам обмениваться данными друг с другом на основе какого-то контракта (интерфейса).

В настоящее время служба Windows может загружать плагины двумя способами:

  1. В том же AppDomain, что и служба Windows, на которой размещены плагины
  2. В другом процессе служба Windows взаимодействует через именованные каналы (WCF).

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

Вот структура папок:

  • Каталог услуг Windows (AppDomainBase)
    • Плагины
      • Plugin1
        • Plugin1.dll
        • SharedDependency.dll (1.0.0.0)
      • Plugin2
        • Plugin2.dll
        • SharedDependency.dll (1.0.1.0)

Я уже провел много исследований и пробовал много разных вещей. Это говорит:

  1. Я не могу перенаправить привязки сборки через файл app.config. Хотя это работает, но это не практично, потому что я не знаю всех зависимостей заранее и не могу добавить все в app.config.
  2. Я не могу использовать GAC
  3. Я не хочу загружать несколько версий одной и той же сборки, только самую новую.

Я прочитал о 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.

Другие вопросы по тегам