Где обрабатывать событие AssemblyResolve в библиотеке классов? - PullRequest
3 голосов
/ 01 февраля 2012

Мне нужно динамически разрешать ссылки на сборки из одной библиотеки классов в другую.Библиотеки классов загружаются из сценария PowerShell, поэтому стандартное поведение .NET по поиску зависимых сборок в исполняемом файле напрямую не выполняется, поскольку исполняемым файлом является сам PowerShell.Как сделать так, чтобы эти зависимые ссылки на сборки разрешались / работали правильно?

Более подробно :

У меня есть две служебные библиотеки: основная и другая, которая выполняет некоторые задачи.очень специфические задачи разбора.Я хочу загружать их динамически в сценарии PowerShell , не устанавливая их в GAC .Вторая библиотека зависит от первой.В решении VS библиотека синтаксического анализа имеет ссылку на проект на базовую библиотеку: Copy Local = true.

Я могу загрузить и использовать обе библиотеки из выходного лотка библиотеки синтаксического анализа (/ Debug | /Release) после использования (PowerShell здесь):

[Reflection.Assembly]::LoadFile("C:\...thefile.dll")

Однако при вызове метода в библиотеке синтаксического анализа (зависимости), который вызывает что-либо из библиотеки ядра, он не может разрешить сборку ядра.Это ... расстраивает ... так как файлы находятся в одной папке.Не имеет значения, имеют ли один или оба ключа строгого имени.

Мой обходной путь теперь заключается в обработке события AssemblyResolve.Сложно выяснить, где поместить это в библиотеку классов, поскольку нет единой точки входа, которая будет всегда выполняться раньше, чем что-либо еще, как в исполняемом методе Main() (см. . Есть ли эквивалент Application_Start длябиблиотека классов в c # ).

На данный момент я создал статический класс Resolver со статическим конструктором, который присоединяет обработчик для AssemblyResolve, а затем имеет статический конструктор в каждом из классов синтаксического анализа, который ссылается на класс статического преобразователя., заставляя выполняться статический конструктор класса резолвера.В результате событие AssemblyResolve присоединяется ровно один раз и обрабатывается с помощью общего центрального кода.Так что это работает.Но я ненавижу добавлять фанк-статический конструктор ко всем моим классам синтаксического анализа.

Есть ли лучший способ справиться с этим?

Ответы [ 2 ]

2 голосов
/ 01 февраля 2012

Я нашел решение, которое следует шаблону «потребитель должен решить» и работает как для PowerShell, так и для обычных пользователей приложений .NET.

Идея:

  • Makeкласс с внутренним обработчиком события AssemblyResolve и статическим конструктором, который присоединяет обработчик к событию AppDomain.CurrentDomain.AssemblyResolve.(Пока это знакомо.)
  • Вместо того, чтобы вызывать класс Resolver из той же или другой библиотеки классов, вызывает его непосредственно потребителем .Когда PowerShell является потребителем, вызовите Resolver из PowerShell.
  • Это работает, потому что любой потребитель - включая PowerShell - имеет тот же CurrentDomain, что и загружаемые им сборки.Таким образом, даже если обработчик событий присоединен к какой-либо динамически загруженной сборке, он все равно будет вызываться при сбое разрешения сборки в основном потребляющем приложении.

Моя версия Resolver имеет:

  • Статическое свойство AssemblyDirectory, которое можно использовать для необязательного задания каталога для поиска.Если оставить это поле пустым, он будет использовать каталог, найденный в Assembly.GetCallingAssembly().Location
  • Пустой метод Register(), который действительно ничего не делает, кроме проверки вызова статического конструктора.

Код PowerShell:

# First, load the assembly with the Resolver class. I actually put it in the same 
# core library, but it really doesn't matter where it is. It could even be by itself
# in a dynamically compiled assembly built using the Add-Type commandlet
[Reflection.Assembly]::LoadFile("[an assembly that has the Resolver class].dll")

# Call the Register method to force the static constructor to run if it hasn't already
[mycorp.mylib.Resolver]::Register()

$sourcedir = "C:\foo\bar\..some random dir"
# Set the path to search for unresolved assemblies
[mycorp.mylib.Resolver]::AssemblyDirectory = $sourcedir

# Load the dependent assembly
[Reflection.Assembly]::LoadFile("$sourcedir\myparser.dll")

# Create and use an object from the dependent assembly. When the core assembly
# fails at first to resolve, the Resolver's handler is automatically called, 
# and everything is peachy
$encoder = New-Object mycorp.mylib.anamespace.aclass
$result = $encoder.DoStuff( $x, $y, $z ... etc )

Если вы хотите узнать, как на самом деле обрабатывать событие AssemblyResolve, ознакомьтесь с документацией на MSDN: Событие AppDomain.AssemblyResolve

Относительно Register-ObjectEvent:

Сначала я попытался построить распознаватель сборок непосредственно в PowerShell и зарегистрировать его с помощью командлета Register-ObjectEvent.Это будет работать, но для одной проблемы: PowerShell не поддерживает обработчики событий, которые возвращают значение.Обработчики AssemblyResolve возвращают объект Assembly.Это почти вся их цель.

0 голосов
/ 01 февраля 2012

Вы можете зарегистрировать событие в AppDomain (см. здесь ), но это должно быть зарегистрировано / обработано во многих, многих местах, потому что для каждого случая вы не можете гарантировать, что регистрация уже состоялась в любая заданная точка входа.

Вероятно, лучше обрабатывать это в приложении, которое использует библиотеку классов, а не в самой библиотеке.

...