Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 
Динамический поиск подключаемых модулей
Автор: Рой Ошеров, 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.

    Отсюда несколько следствий:

    1. Любой .DLL-файл, проверяемый на наличие IPlugin, будет с момента проверки загружен в ваше приложение на все оставшееся время существования AppDomain.
    2. Проверка множества .DLL-файлов может привести к серьезным перерасходам памяти для приложения.

    Итак, теперь вы столкнулись с проблемой, как пройти по всем файлам каталога, загрузить сборки, но при этом иметь возможность выгрузить их. Решение намного проще, чем вы могли бы ожидать:

    1. Вы создадите новый AppDomain и загрузите все проверяемые в данный момент сборки в этот AppDomain.
    2. Завершив проверку и обнаружив только те типы, экземпляры которых могут быть созданы, вы выгрузите отдельный AppDomain.
    3. Затем вы загрузите «хорошие» типы в ваш 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);
      
    4. Вы создаете новый экземпляр объекта AppDomain, используя статический метод AppDomain. Вы передаете в него удобное для пользователя имя этого нового AppDomain.
    5. Вы создаете экземпляр класса PluginFinder (в котором есть метод SearchPath()) в AppDomain. Для этого вы передаете в него (очень похоже на использование Activator) имя сборки, в которой находится класс, и полное имя класса, экземпляр которого надо создать.
    6. В результате последней операции вы получаете Proxy, который выглядит и ведет себя так же, как ваш класс PluginLoader, но на самом деле является посредником между AppDomain вашего приложения и только что созданным вами новым AppDomain. Из вышесказанного вы знаете, что с этого момента любые загружаемые PluginLoader сборки будут на самом деле загружаться в ваш новый AppDomain, а не в AppDomain вашего приложения. Это означает, что после того, как этот класс выполнит свою работу, вы сможете выгрузить новый AppDomain, избавляясь, таким образом, от засорения памяти.
    7. Вы вызываете метод SearchPath() в Proxy вашего реального класса PluginLoader находящегося в другом AppDomain. Назад вы получаете список массивов, содержащий только типы, использующие интерфейс IPlugin.
    8. Вы выгружаете другой AppDomain, поскольку он вам больше не нужен.
    9. Теперь вы можете двигаться дальше и создавать экземпляры подключаемых модулей, как описано в моей предыдущей статье («Создание подключаемого модуля»), используя класс 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) (из которой я и взял основную часть материала — спасибо, Эрик!).

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

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

       
       
         
      VBNet рекомендует