Теневое копирование библиотек

сложность 200

Меня периодически занимают вопросы о разных мелких трюках со сборками, как скрыть одну в другой, или как делается система плагинов. Так же очень давно меня занимает вопрос как загружать и использовать библиотеки так, чтобы код работал, а сам файл библиотеки можно было бы удалить. Честно сказать к вопросу этому я подходил раза 4 в течении года и все как-то безрезультатно, наверно плохо хотел получить результат, так как другого объяснения я не вижу, но наконец все получилось хорошо и быстро, о чем я и хочу рассказать.

Теневое копирование (Shadow Copy) – позволяет работать с заблокированными файлами. Приложение может блокировать и обычно блокирует все библиотеки которые были использованы/используются в процессе работы приложения, файлы с которыми ведется работа. Под работой может подразумеваться изменение, перезапись, удаление, переименование и так далее.

Зачем оно надо?

Теневое копирование позволяет приложению не зависеть от библиотеки расположенной в директории загрузки все время работы, так как она копируется в приватную директорию в профайле пользователя. Таким образом, например, работает IIS с ASP.NET приложениями.

На мой взгляд, данная технология может быть полезна в следующих случаях:

  • Система обновления приложения, когда надо обновлять библиотеки приложения и при полной загрузке их нельзя переписать.
  • Система плагинов для приложения.
  • Фокусы =)

После того, как определились что такое «теневое копирование» и зачем оно нужно, можно собственно перейти к обсуждению реализации.

Немного теории

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

Дальше наверно многие слышали, но на практике не сталкивались с тем, что домены мыслятся как полностью изолированные и передача объектов между ними пользовательских классов невозможна, если они исполнены в стандартном виде. Для того, чтобы объект мог «путешествовать» между доменами он должен быть наследован от класса MarshalByRefObject и объект должен быть сериализуемым.

Если мы пытаемся использовать каким-либо образом получить класс из сборки загруженный в другой домен, то та сборка попадет в основной домен, для того, чтобы работа с классом стала возможна.

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


Получается примерно следующая схема взаимодействия модулей в приложении. И вот как это выглядит в студии

Основные идеологические моменты кажется освещены и настало время практики.

Практическая часть

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

Интерфейс

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

С интерфейсом никаких заковык нет.

Дополнения

Переходим к реализации класса. Как вы должны помнить из теоретической части, класс должен быть:

  • Сериализуемым
  • Наследоваться от MarshalByRefObject

Конечно же еще реализовывать свой интерфейс общения. Итак:

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

  • класс сериализуемый, так как мы применили атрибут Serializable
  • класс может передаваться между доменами, так как наследован от класса MarshalByRefObject
  • класс наследуется от интерфейса IMyClass, через который будет осуществляться взаимодействие с объектом.

Осталось самое интересное видимо, как все это будет связано и работать в итоге.

Основное приложение

Теперь в основном приложении можно будет создать домен с опцией теневого копирования файлов. Это можно настроить с помощью специально класса AppDomainSetup. Нужно будет

  • указать директорию с основными файлами,
  • указать что будет использоваться теневое копирование
  • указать директорию к которой будет применятся теневое копирование (важно не забыть, так как пропуск этого пункта обычно вызывает проблемы и вопросы)

 

Для простоты я забил путь статично. В реальном приложении никогда так не делайте.

Далее нужно создать домен и получить ссылку на класс (реально это будет прокси) из другого домена.

В первой строке создается новый домен. Первым параметром идет имя, и его можно задавать абсолютно любым. Далее идет параметр типа Evidence, его можно пропустить в целом или же задать таким же как и в текущем домене. Последним параметром идет настройка домена, которую мы описали только что.

Далее идет создание класса в новом домене и распаковка его для использования. Для работы
надо указать имя сборки и полный путь класса. Имя сборки можно посмотреть в свойствах:

Обратите так же внимание, что полученный объект приводим к нужному интерфейсу, далее будем работать через эту переменную.

А это уже вызов необходимого функционала из другого домена. Результат работы лучше всего посмотреть на видео в конце статьи. Если очень лень смотреть, то можно провести следующий эксперимент: поставить точку остановки на второй строке и когда программа остановится в этом месте, то удалите библиотеку SomePlugin.dll, после чего продолжите выполнение программы. Во-первых, вам удастся удалить библиотеку, что уже хорошо, а во-вторых, программа продолжит свою работу в штатном режиме и текстовый файл будет создан, как и планировалось.

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

Итого

Надеюсь, что вы узнали что-то новое для себя, и возможно даже найдете применение этой технике в своем проекте. Только наверно не стоит переизобретать MEF, в виде системы плагинов, хотя кто знает, может у вас получится что-то более простое и гениальное =)

Самое главное в процессе, чтобы MainApp не имел ссылок на SomePlugins.

Source Code в SVN

 

Hard’n’Heavy!

 

 

Оставить комментарий