|
Динамический поиск подключаемых модулей
Автор: Рой Ошеров, N/A
Перевод: Шатохина Надежда(sna@uneta.org), Ukraine .Net
Alliance (http://www.uneta.org)
Декабрь 2003
Применяется к:
Microsoft® ASP.NET
Обзор: Расширяет инфраструктуру для
добавления поддержки подключаемых модулей в ваши .NET-приложения, чтобы вы также
могли осуществлять динамический поиск подключаемых модулей в собственном
каталоге приложения.
План игры
Использование System.Reflection — путь к спасению
Небольшая проблема
Важно!
Полезный инструмент отладки
Заключение
План игры
Прежде всего, эта статья — дополнение к моей предыдущей статье о подключаемых
модулях. Я рекомендую вам, прежде чем погрузиться в эту статью, ознакомиться с
первой. Основная цель этой статьи — избавить пользователя от файлов
конфигурации. Основная мысль — обеспечить, чтобы при загрузке ваше приложение
могло просматривать .DLL-файлы своего каталога, находить те, которые содержат
типы, поддерживающие интерфейс IPlugin, и создавать экземпляры
этих подключаемых модулей. Никакого вмешательства пользователя, за исключением
копирования .DLL-файла в каталог приложения, не должно быть.
Использование System.Reflection — путь к спасению
Одно из наиболее мощных пространств имен в Microsoft® .NET Framework —
System.Reflection. Как следует из имени, оно позволяет коду
«отбрасывать свою тень», раскрывая любые свойства, члены (как открытые, так и
закрытые), методы, интерфейсы, цепочки интерфейсов — практически все, что вы
хотели знать о Типе Х, но никогда не осмеливались спросить.
Используя это могущественное пространство имен, вы будете проходить по
каждому файлу, обнаруживая все находящиеся в нем типы, и для каждого типа будете
выяснять, поддерживает ли он интерфейс IPlugin. Класс, который
вам надо использовать, чтобы извлечь все типы, входящие в .NET-сборку,
называется System.Reflection.Assembly. Вот простой метод,
используемый этим классом именно для того, что мы только что обсуждали:
private void TryLoadingPlugin(string path)
{
Assembly asm= AppDomain.CurrentDomain.Load(path);
foreach(Type t in asm.GetTypes())
{
foreach(Type iface in t.GetInterfaces())
{
if(iface.Equals(typeof(IPlugin)))
{
AddToGoodTypesCollection(t);
break;
}
}
}
}
Как видите, с помощью пространства имен System.Reflection
очень просто извлечь большое количество информации о любом заданном файле
сборки. В приведенном выше методе вы вызываете метод для
GetInterfaces() для каждого Type, существующего в заданном
файле. Затем вы проверяете, является ли какой-нибудь интерфейс этого типа
интерфейсом IPlugin. Если да, это означает, что вы можете
загружать его в ваше приложение; поместите его в список массивов (Array List)
для хранения. Позже вы можете вернуться к этому списку массивов и использовать
Activator.CreateInstance(Type) этих типов и, таким образом,
создать экземпляр любого из обнаруженных вами подключаемых модулей.
Небольшая проблема
Этот код, безусловно, работоспособен и мог бы быть приемлемым, если бы не
существовало одной маленькой проблемы. Чтобы объяснить ее суть, вам понадобится
узнать о AppDomain. Я избавлю вас от собственных объяснений,
что такое AppDomain, и приведу цитаты из документации по этому
поводу:
Комментарий: Домены приложений, которые
представлены объектами AppDomain, обеспечивают изолированные,
выгружаемые и безопасные границы для выполнения управляемого кода.В одном
процессе могут выполняться несколько доменов приложений; однако не существует
взаимно-однозначного соответствия между доменами приложений и потоками. Одному
домену приложения могут принадлежать несколько потоков, и, пока данный поток не
ограничен отдельным доменом приложения, в любой момент времени поток выполняется
в одном домене приложения.Домены приложений создаются методом
CreateDomain. Экземпляры AppDomain
используются для загрузки и выполнения сборок. Если AppDomain
больше не используется, он может быть выгружен.
Я бы добавил следующее: любая сборка, загруженная в приложении, по умолчанию
загружается в AppDomain приложения. Само по себе это неплохо,
если бы не тот факт, что вы не можете напрямую выгрузить сборку, если загрузили
ее в AppDomain. Единственный способ выгрузить ее — выгрузить
сам AppDomain.
Отсюда несколько следствий:
- Любой .DLL-файл, проверяемый на наличие IPlugin, будет с
момента проверки загружен в ваше приложение на все оставшееся время
существования AppDomain.
- Проверка множества .DLL-файлов может привести к серьезным перерасходам
памяти для приложения.
Итак, теперь вы столкнулись с проблемой, как пройти по всем файлам каталога,
загрузить сборки, но при этом иметь возможность выгрузить их. Решение намного
проще, чем вы могли бы ожидать:
- Вы создадите новый AppDomain и загрузите все проверяемые
в данный момент сборки в этот AppDomain.
- Завершив проверку и обнаружив только те типы, экземпляры которых могут
быть созданы, вы выгрузите отдельный AppDomain.
- Затем вы загрузите «хорошие» типы в ваш AppDomain, таким
образом, вы избавите себя от мусора в памяти вашего приложения.
Создать новый AppDomain просто:
AppDomain domain = AppDomain.CreateDomain("PluginLoader");
PluginFinder finder = (PluginFinder)domain.CreateInstanceFromAndUnwrap(
Application.ExecutablePath,"Royo.PluggableApp.PluginFinder");
ArrayList FoundPluginTypes = finder.SearchPath(Environment.CurrentDirectory);
AppDomain.Unload(domain);
- Вы создаете новый экземпляр объекта AppDomain, используя
статический метод AppDomain. Вы передаете в него удобное для
пользователя имя этого нового AppDomain.
- Вы создаете экземпляр класса PluginFinder (в котором есть
метод SearchPath()) в AppDomain. Для этого
вы передаете в него (очень похоже на использование Activator)
имя сборки, в которой находится класс, и полное имя класса, экземпляр которого
надо создать.
- В результате последней операции вы получаете Proxy,
который выглядит и ведет себя так же, как ваш класс
PluginLoader, но на самом деле является посредником между
AppDomain вашего приложения и только что созданным вами новым
AppDomain. Из вышесказанного вы знаете, что с этого момента
любые загружаемые PluginLoader сборки будут на самом деле
загружаться в ваш новый AppDomain, а не в
AppDomain вашего приложения. Это означает, что после того,
как этот класс выполнит свою работу, вы сможете выгрузить новый
AppDomain, избавляясь, таким образом, от засорения памяти.
- Вы вызываете метод SearchPath() в Proxy
вашего реального класса PluginLoader находящегося в другом
AppDomain. Назад вы получаете список массивов, содержащий
только типы, использующие интерфейс IPlugin.
- Вы выгружаете другой AppDomain, поскольку он вам больше
не нужен.
- Теперь вы можете двигаться дальше и создавать экземпляры подключаемых
модулей, как описано в моей предыдущей статье («Создание подключаемого
модуля»), используя класс Activator.
Важно!
Т.к. вы используете Proxy при сообщении между
AppDomain, любой объект, экземпляр которого будет создан в этом
прокси (в данном случае, PluginLoader), должен быть
сериализуемым. Вы должны или унаследовать PluginLoader от
MarshalByRefObject, или применить атрибут
[Serializable] к этому классу. В противном случае, вы получите
исключение:
"Additional information: The type Royo.PluggableApp.PluginFinder in
Assembly PluggableApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null is
not marked as serializable."(«Дополнительная информация: тип
Royo.PluggableApp.PluginFinder в сборке PluggableApp, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null не отмечен как сериализуемый.»)
Полезный инструмент отладки
При работе с AppDomain и отладке исключений, которые могут
возникнуть при их загрузке и выгрузке, может возникнуть огромное количество
ошибок. Полезный инструмент, который практически не задокументирован —
fuslogvw.exe или Fusion Log Viewer. Fusion — это имя подсистемы загрузки. Вы
можете использовать этот инструмент для регистрации сбоев. Если вы получаете
ошибки во время загрузки сборок, обновите представление этого инструмента и
получите протокол исключительных ситуаций.
Заключение
Использование AppDomain — это не прогулка в парке, но они
четко работают, как только вы понимаете, почему все работает так, как работает.
Это важно, если вам надо выгружать сборку во время выполнения.
Более подробно и загрузке и выгрузке AppDomain смотрите в
статье AppDomains and Dynamic Loading
(http://msdn.microsoft.com/asp.net/default.aspx?pull=/library/en-us/dncscol/html/csharp05162002.asp)
Эрика Ганнерсона (Eric Gunnerson) (из которой я и взял основную часть материала
— спасибо, Эрик!).
Никакая часть настоящей статьи не может быть воспроизведена или
передана в какой бы то ни было форме и какими бы то ни было средствами, будь то
электронные или механические, если на то нет письменного разрешения владельцев
авторских прав.
Материал, изложенный в данной статье, многократно
проверен. Но, поскольку вероятность технических ошибок все равно существует,
сообщество не может гарантировать абсолютную точность и правильность приводимых
сведений. В связи с этим сообщество не несет ответственности за возможные
ошибки, связанные с использованием статьи.
|
|