ResolveEventHandler после Assembly.LoadFrom - PullRequest
4 голосов
/ 03 апреля 2012

Я загружаю сборку, используя Assembly.LoadFrom(), так как сборки расположены по другому пути, чем каталог Application Base.

Dim oAssembly As Assembly = _
Assembly.LoadFrom("C:\\MyFolder\\" + ddlXlate.SelectedItem.ToString() + ".dll")

И я без проблем потребляю Type из этой сборки:

oXML = CType(oAssembly.CreateInstance(sBaseType + ".XlateContainer"), _
XlateBase.XlateContainer)

Однако проблема возникает, когда я пытаюсь использовать Type из этой сборки из другого метода, подобного приведенному ниже:

oComboBox.DataSource = _
[Enum].GetValues(Type.GetType(sType + "+ItemEnum," + sAssemblyName))

sAssemblyName - это тот, который я загружал, используя LoadFrom() на самом деле. После того, как он сказал, что не может найти сборку, я использовал событие AssemblyResolve, которое решило мою проблему:

Подписка AssemblyResolve событие:

AddHandler AppDomain.CurrentDomain.AssemblyResolve, _
AddressOf MyResolveEventHandler

Метод обработчика событий:

Private Shared Function MyResolveEventHandler(ByVal sender As Object, _
    ByVal args As ResolveEventArgs) As Assembly
    Return Assembly.LoadFrom("C:\\PSIOBJ\\" + args.Name + ".dll")
End Function

И я подумал, что, возможно, ошибка возникает из-за того, что он не может найти зависимую сборку, определенную в файле манифеста сборки, который я уже загружал с использованием LoadFrom(), но когда я проверил args.Name, я увидел, что он пытается загрузить ту же сборку, и после этого это работало без проблем. Таким образом, в основном тип в загруженной сборке не может быть найден до события, добавляющего изменение.

В моем старом коде использовались методы AppDomain.CurrentDomain.Load() и Assembly.Load(), и они работали нормально без события AssemblyResolve. Я был в состоянии достигнуть типов в динамически загруженных Assembly отовсюду в пределах того же AppDomain.

LoadFrom() может автоматически находить зависимости в пределах одного и того же запрошенного пути сборки, и это не могло быть проблемой, поскольку все, что нужно dll, было там. Поэтому сначала это выглядело как проблема AppDomain, так как похоже, что он может достигать сборок из контекста Load вместо контекста LoadFrom, и теперь я использую контекст LoadFrom.

  1. Но теперь, похоже, я должен передать oAssembly экземпляр evertwhere, чтобы использовать любой тип из загруженной сборки?
  2. Разве он не загружает сборку, где я могу найти его везде (тот же AppDomain), используя простой метод Type.GetType(...)?

Может кто-нибудь заполнить пропущенные баллы и ответить на мои вопросы?

Вы можете использовать C #, на самом деле мне не нравится VB.NET, но я должен использовать его здесь, в Office.

Ответы [ 2 ]

9 голосов
/ 09 апреля 2012

Если я правильно понимаю ваш вопрос, вы пытаетесь сделать что-то вроде этого:

var asm = Assembly.LoadFrom(@"D:\Projects\_Libraries\FluentNH 1.1\Castle.Core.dll");
var obj = asm.CreateInstance("Castle.Core.GraphNode");
var type = Type.GetType(obj.GetType().AssemblyQualifiedName, true);  // fails

Проблема, с которой вы сталкиваетесь, заключается в том, что какую бы форму загрузки сборки вы не использовали, когда библиотека нев том же пути, что и ваш исполняемый файл, переменная type всегда будет null.

Причина

Вы сталкиваетесь с проблемой различных контекстов загрузки для сборок в.NET .Короче говоря, существует три, на самом деле четыре типа контекстов загрузки:

  • Контекст загрузки по умолчанию.Это используется для всех сборок в GAC, текущей выполняющейся сборки, сборок в текущем пути (см. BaseDirectory) и сборок в PrivatePath (см. RelativeSearchPath),Assembly.Load(string,..) использует этот контекст.
  • Контекст загрузки.Этот контекст используется для любой сборки на диске , а не в пути поиска, обычно загружается с Assembly.LoadFrom.
  • Контекст только для отражения.Типы в этом контексте не могут быть выполнены.
  • Контекст без контекста.При загрузке сборки с использованием любого из методов Assembly.Load(byte\[\],..) и Assembly.LoadFile или при загрузке динамической сборки, не сохраненной на диск, используется этот контекст.

Типы, загруженные в одном контексте, не совместимы с другим контекстом (вы даже не можете приводить одинаковые типы из одного контекста в другой!).Методы, конкретно работающие в одном контексте, не могут получить доступ к другому контексту.Type.GetType(string) может загружать типы только в контексте по умолчанию , если только вы не поможете методу.

Это именно то, с чем вы столкнулись.Когда сборка dll была в пути вашего приложения, все работало нормально.Как только вы переместили его, все начало разваливаться.

Более конкретно:
Когда вы звоните Type.GetType(string), он будет запрашивать все статически ссылка сборки в пути и динамически загруженные сборки в текущем пути (AppDomain.BaseDirectory), GAC и в AppDomain.RelativeSearchPath и.К сожалению, относительный путь поиска должен быть относительно базового каталога.

Результат:
Результатом этого поведения является то, что GetType не не просто проверить все загруженные сборки.Вместо этого он работает наоборот:

  1. GetType использует часть сборки первого параметра для определения местоположения сборки, используя Assembly.Load
  2. Если найдено, отразите типиз этой сборки.
  3. Если не найден, возвращает ноль, не пробуя ничего другого (или выдает FileNotFoundException, что может быть довольно запутанным).

Вы можете проверить это для себя:Assembly.Load не будет работать, если вы просто передадите ему имя сборки.

Решения

Существует несколько решений.Тот, который вы уже назвали сами, и это держит объект сборки вокруг.Есть и другие, у каждого из которых есть свои недостатки:

  1. Использование GetType() на самом экземпляре объекта вместо статического метода Type.GetType(string).Преимущество этого в том, что вам не нужно имя типа, уточненное сборкой, что может быть трудно получить (в вашем примере вы не говорите, как вы установили sAssemblyName, но разве это не то, что вам нужно?плавать вокруг?).

  2. Использовать универсальный распознаватель, который проверяет загруженные сборки и возвращает загруженную сборку.Вам не нужно снова звонить LoadFrom.Я протестировал следующее, и это работает великолепно и довольно быстро:

    // works for any loaded assembly, regardless of the path
    private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
    {
        // you may not want to use First() here, consider FirstOrDefault() as well
        var asm = (from a in AppDomain.CurrentDomain.GetAssemblies()
                  where a.GetName().FullName == args.Name
                  select a).First();
        return asm;
    }
    
    // set it as follows somewhere in the beginning of your program:
    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
    
  3. Используйте вместе события AppDomain.CurrentDomain.AssemblyLoad и .AssemblyResolve.Первый вы используете для запоминания каждой загруженной сборки в кэше словаря (по полному имени), второй вы используете для проверки этого словаря, получая значение из него по имени.Это относительно тривиально для реализации и может работать немного лучше, чем в предыдущем решении.

  4. Использовать обработчик событий AppDomain.CurrentDomain.TypeResolve. Я не пробовал это, поэтому я не уверен, что это будет работать в вашем сценарии. : это не работает. GetType first пытается загрузить сборку, когда это не удается, он не пытается определить тип, и это событие никогда не запускается.

  5. Добавьте библиотеки, которые вы хотите разрешить, в GAC или в любой (относительный) путь вашего приложения. Это, безусловно, самое простое решение.

  6. Добавьте пути к app.config. Это работает только для строго типизированных сборок, и в этом случае вы можете так же легко загрузить их в GAC. Сборки с не строго типизированными данными должны по-прежнему находиться в относительном пути к текущему приложению.

Заключение

Статическая группа методов Type.GetType(..) ведет себя довольно неинтуитивно, когда дело касается загруженных сборок с первого взгляда. Как только вы поймете идеи, лежащие в основе нескольких контекстов, попробуйте поместить сборку в контекст по умолчанию. Когда это невозможно, вы можете создать обработчик событий AssemblyResolve, который не так сложно сделать общеприменимым.

0 голосов
/ 06 апреля 2012

Попробуйте вернуть сборку только там, где имя args.name правильное ...

 Private Shared Function MyResolveEventHandler(ByVal sender As Object, _
 ByVal args As ResolveEventArgs) As Assembly
 //Ex:     if (args.name.contains("XLATEBASE")) {Return Assembly.LoadFrom("C:\\PSIOBJ\\" + args.Name + ".dll")}
 End Function
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...