Использование абстрактного класса в качестве контракта в платформе плагина
-
22-09-2019 - |
Вопрос
Можно ли использовать абстрактный класс в качестве объекта контракта между «Хостом» и «плагином»?Идея в том, что плагин наследует контракт (мы называем его адаптером).Мы также понимаем, что все участники структуры должны унаследовать MarshalByRefObject
(МБРО).Итак, вот о чем мы подумали -
Хозяин:
class Host : MarshalByRefObject
{
}
Договор:
public abstract class PluginAdapter : MarshalByRefObject
{
}
Плагин:
class myPlugin : PluginAdapter
{
}
Все три существуют в отдельных ассемблерах.Наш хост создаст новый AppDomain для каждого плагина, а PluginAdapter создается следующим образом:
{
ObjectHandle instHandle = Activator.CreateInstance(
newDomain, data.Assembly.FullName, data.EntryPoint.FullName);
PluginAdapter adapter = (PluginAdapter)instHandle.Unwrap();
}
РЕДАКТИРОВАТЬ:где data
это конкретный тип myPlugin
.
Нам было интересно, будет ли работать такая реализация фреймворка.Мы видели статьи, использующие интерфейс (IPlugin) для создания плагина и конкретный класс в качестве контракта.В этих статьях также говорится, что можно использовать абстрактный класс, но не приводятся примеры такой реализации.Требуется ли, чтобы контракт был конкретным классом?
РЕДАКТИРОВАТЬ:В этом примере Ричарда Блюетта - С# отражение - он использует гораздо более простую реализацию:
Договор:
public interface IPlugIn
{
// do stuff
}
Плагин:
public class PlugIn : MarshalByRefObject, IPlugIn
{
}
Теперь, если в качестве контракта используется абстрактный класс, плагин не может наследовать одновременно контракт и MBRO.Что в таком случае становится лучшей реализацией масштабируемой платформы плагинов.Должны ли мы пойти дальше и внедрить удаленное взаимодействие, даже если изначально мы разрабатываем систему для работы на одной машине?Ожидается, что этот проект будет распространяться по сети, возможно, также и через Интернет.Мы просто еще не реализовали TCP, потому что пытаемся полностью понять и использовать основы платформы плагинов.
Имеет ли смысл реализовывать удаленное взаимодействие TCP на одной машине с использованием обратной связи?
Решение
Имхо, абстрактные классы — лучший выбор для этого.Это прежде всего потому, что интерфейсы сложнее версионировать. Этот пост в блоге описывает проблему, с которой вы можете столкнуться в будущем, если не будете использовать базовые классы.Кстати, это правило применимо не только к плагинам.
О вашем дизайне...
Плагины не должны расширять MBRO.Вам следует использовать свой хост (который должен расширять MBRO) для перенаправления всех вызовов к вашим плагинам, включая обработку событий плагинов.Очень легко непреднамеренно загрузить DLL плагина в ваш основной домен приложения, если вы попытаетесь перетащить их и использовать их прокси.
Например, если плагин возвращает IEnumerable для одного из своих методов, он может вернуть реализацию IEnumerable, определенную в сборке плагина.Если это не расширит MBRO, основной домен приложения должен будет загрузить сборку плагина.
Я загрузил здесь три проекта, связанных с доменами приложений:
Один из них использует обратные вызовы между доменами приложений, второй — обработку событий между доменами приложений, а третий — пример плагина.
В примере с плагином приложение определяет интерфейс плагина (это демонстрация, а не лучшие практики!) и хост плагина.Приложение загружает необработанную сборку плагина с диска и передает ее в домен приложения плагина через прокси-сервер хоста плагина, где она загружается.Затем хост плагина создает экземпляр плагина и использует его.Но когда хост возвращает тип, определенный в сборке плагина, обратно в домен приложения приложения, сборка плагина загружается в основной домен приложения, делая весь плагин бессмысленным.
Лучше всего этого избежать — предоставить абстрактный базовый класс для плагинов, который не помечен как сериализуемый и не расширяет MBRO, и возвращать только примитивы или запечатанные типы, которые вы определяете, обратно через границу из домена плагина.
ПРИМЕЧАНИЕ: все проекты имеют версию 4.0 RC.Вам понадобится это или выше, чтобы запустить их.В противном случае вам придется редактировать файлы проекта вручную или пересобирать их, чтобы они работали в b2 или 2008.
Другие советы
Если «data.EntryPoint.FullName» является полным именем типа, приведенный выше код должен работать.
Однако, если вы пытаетесь изолировать этот тип в отдельном AppDomain, вам следует быть осторожным.При выполнении data.Assembly
, вы поместите сборку (и ее типы) в свой AppDomain, в результате чего типы будут загружены в исполняемый AppDomain...
Возможно, вы захотите взглянуть на MAF (инфраструктура управляемых надстроек) это платформа расширения, встроенная в .NET для создания надстроек.Он похож (и старше), чем MEF (инфраструктура управляемой расширяемости), но имеет больше возможностей, среди прочего, для хранения плагинов в собственном домене приложения.
Если data.EntryPoint.FullName
относится к конкретному типу myPlugin
Я не вижу причин, по которым это не сработало бы (если только не возникнут проблемы с загрузкой сборки в другом домене приложения, но это другая проблема).