2015-12-04 4 views
2

У меня есть приложение, которое нужно загружать и выгружать при запуске некоторых плагинов в виде файлов DLL. Этот DLL-файлы содержат один или несколько классов, которые вытекают из моего абстрактного модуля класса, который выглядит следующим образом:Система плагинов, которая хранит живые объекты

public abstract class Module : MarshalByRefObject 
{ 
    //various fields here, not reported as they are not useful to understand 

    public abstract void Print(); 

} 

Из того, что я понял: единственный способ в C#, чтобы выгрузить сборку и его классы, чтобы поместить сборку в выделенный Appdomain, а затем выгружать его, когда он больше не нужен, и единственным способом связи между AppDomains является вывод из MarshalByRefObject или MarshalByValueObject. Затем, когда я передаю ссылку модуля на другой AppDomain, я действительно не получаю объект, кроме прозрачного прокси. Может ли кто-нибудь подтвердить, что я написал выше, пожалуйста?

Хорошо, теперь настоящая проблема: скажем в моем файле PersonPlugin.dll У меня есть класс «Личность», который расширяет модуль. этот класс содержит имена полей, фамилию и номер телефона.

Метод Print, который вызывается периодически (периодическое вызов печати не имеет смысла, но это пример) печатает эти 3 поля.

Позже я создаю новую dll с новой версией класса Person, у которой есть новое поле, называемое адресом, и метод Print теперь печатает также адрес. NB: Плагины сделаны третьими сторонами, и я не знаю, похожа ли новая версия на более старую или совершенно другую, но в моем случае можно с уверенностью предположить, что класс Person не будет сильно отличаться от версий.

я затем заменить старый DLL файл с новым файлом (библиотека DLL является shadowcopied в выделенном AppDomain, так что файл доступен для записи/файла удаляемого)

FileSystemWatcher замечает изменения и:

  1. создает новый AppDomain для новой библиотеки DLL

  2. сохраняет состояние старого конкретизированного Человека объекты

  3. assignes Людей объекты из старого класса Person к новому

  4. разгружает AppDomain, содержащего старый Person класс

  5. приложение начинает печатать новую строку, содержащую также адрес (то есть нуль, по крайней мере в начало)

Пункты 2 и 3 являются основой моей проблемы: Как я могу поддерживать эти объекты (или, по крайней мере, их поля) после разгрузки их класса, а затем создавать новый класс, который имеет такое же имя и аналогичные свойства, но это действительно другой класс?

Это то, что я, хотя до сих пор:

  1. с помощью отражения можно скопировать в словаре каждое поле каждого объекта Person, удалить старые объекты, а затем создать экземпляр новых и скопировать обратно поля, где это возможно (если старое поле нет, то это пропускается если новый один присутствует он получает значение по умолчанию)

  2. используя что-то вроде MessagePack автоматически сериализовать старые объекты Person, а затем десериализации их к новому Person объекты

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

UPDATE:

Хорошо, я понял, что задаю этот вопрос, не контекстуализации это немного может привести к недоразумению, так: Вопрос я спрашиваю о моей диссертации, которая является модульным минимальным игровой движок (а не сервер). Идея состоит в том, чтобы сделать модуль модульным и, чтобы упростить тестирование различных функций, его модули могут быть изменены во время выполнения, немедленно применяя изменения без перезапуска или потери состояния «актеров» в игре. dll содержит от 1 до многих модулей. каждая dll загружается в 1 AppDomain, поэтому, если я выгружаю этот appdomain, я выгружаю каждый модуль в него. Модули внутри одного и того же appdomain могут иметь прямые ссылки на eachothers, так как при разгрузке они разгружаются вместе. Модули в разных сборках (так же, как и другие AppDomains) обмениваются сообщениями с использованием шины сообщений и никогда не имеют прямых ссылок. Если модуль, потребляющий сообщение, выгружается, система просто прекратит пересылать это сообщение этому модулю.

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

У меня есть DLL, содержащая модуль под названием Car, который всегда движется вперед. Я меняю dll на новый, содержащий модуль Car, который теперь перемещается всегда назад. Результатом является то, что как только я заменю dll, автомобиль немедленно перевернет его направление.

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

Вот почему мне нужно поддерживать живые объекты (я имею в виду, сохраняю их состояние в живых) и всю систему.

И о вашей точке 2: нет жестких ограничений в отношении того, почему я не позволил бы сосуществовать 2 модуля одного и того же типа, но с разными версиями, если они находятся в разных пространствах имен или в разных dll, поскольку в моей системе каждый модуль идентифицируется и ссылается на другой ID

ответ

1

Во-первых, позвольте мне прокомментировать ваш первый пункт.

Из того, что я понял: единственный способ в C#, чтобы выгрузить сборку и своих классов, чтобы поместить сборку в специальном AppDomain, а затем выгрузить его, когда он больше не нужен, и единственным способом общения между AppDomains следует вывести из MarshalByRefObject или MarshalByValueObject. Затем Когда я передаю ссылку модуля на еще один AppDomain, я действительно не получаю объект, кроме прозрачного прокси-сервера . Может ли кто-нибудь подтвердить, что я написал выше, пожалуйста?

Подтверждено.

Это совершенно точно. Сборка .NET не может быть выгружена из AppDomain после их загрузки. Весь AppDomain должен быть выгружен.

В случае модульной системы вы должны загрузить плагины в отдельный AppDomain и данные маршала между хост-приложением и плагином.

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

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

public interface IModule 
{ 
    Person GetPerson(); 
    void Print(); 
} 

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

public class Person : MarshalByRefObject 
{ 
    ... 
} 

Теперь о сохраняющемся состоянии Person даже если новые атрибуты были добавлены.

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

public interface IModule 
{ 
    Person GetPerson(); 
    void Print(); 

    IDictionary<string, object> GetState(); 
    void SetState(IDictionary<string, object> state); 
} 

public class ModuleManager : MarshalByRefObject 
{ 
    public IModule Module { get; set; } 

    public void Initialize(string path) 
    { 
     // Load the module 
     // Setup file watcher 
    } 

    public void SaveState(string file) 
    { 
     var state = this.Module.GetState(); 
     // Write this state to a file 
    } 

    public void LoadState(string file) 
    { 
     var state = this.ReadState(file); 
     this.Module.SetState(state); 
    } 

    private IDictionary<string, object> ReadState(string file) 
    { 
     ... 
    } 
} 

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

Вы найдете этот подход управляемым, а не пытаетесь выполнить все упорство в своем хост-приложении.

Идея здесь состоит в том, чтобы как можно меньше данных между хост-приложением и плагином. Каждый плагин знает, какие поля доступны в Person, поэтому пусть они несут ответственность за указание того, что необходимо сохранить.

+0

Спасибо за разъяснение об AppDomain Randy. Итак, вы рекомендуете, чтобы каждый модуль отвечал за свою собственную загрузку/сохранение состояния вместо хост-приложения? Я думаю, это может сработать, но теперь ответственность за постоянство штата зависит от разработчика модуля (что может быть даже не так уж и плохо). Я сделал модуль наследованием от MarshalByRefObject, потому что мне нужно, чтобы некоторые из его методов (например, Print) вызывались хостом, поэтому модуль должен быть басирован по его границе. Я думаю, что я мог бы также передать объект «контейнер» с единственной целью для обмена данными между модулем и его хостом. – MastErAldo

+0

Да, это правильно. Я бы использовал контейнер, который отвечает за управление плагином внутри порожденного AppDomain. Кроме того, вам не нужно оставлять упорство полностью до разработчика модуля. Вы можете ввести метод, который возвращает пары ключ/значение для представления состояния модуля и контейнера (ModuleManager в моем примере), может быть ответственным за фактическое сохранение, так что таким образом все согласовано во всех модулях. – Randy

+0

Я обновил свой ответ, чтобы показать пример того, как может выглядеть состояние, сохраняемое внутри ModuleManager. – Randy

2

Я думаю, вы действительно хотите поиграть с гнездом шершней.

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

1) Почему вы должны быть «онлайн» все время. Почему вы не можете быть в автономном режиме, например, через 1 час после перезагрузки. Независимо от того, какое решение вы реализуете (reflection/serializaiton), вы получите стоимость исполнения, не говоря уже о огромных расходах на обслуживание.

2) Возможно ли, что вы хотите 2 версии одного и того же плагина. В java-мире существует инфраструктура OSGI. и то, что вы там делаете, заключается в том, что вы зависите от определенного модуля, но в некоторых случаях возможно одновременное использование двух версий одного и того же модуля. Особенно, когда класс человека добавил поле адреса, тогда все люди должны быть обновлены, как с этим справиться. Также все модули, в зависимости от модуля пользователя, должны быть обновлены одновременно.

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

Это, я думаю, вы должны посмотреть на архитектуру микросервисов. Тогда каждый модуль, например, был бы собственным webapi. Каждый модуль отвечает за сохранение себя в живых (у вас могут быть небольшие обертки вокруг всех модулей, чтобы избежать дублирования кода здесь). Затем вы можете просто вызвать модуль с помощью webapi, и стандартная сериализация newtonsoft json позаботится об остальном (и/или шине сообщений, аналогичной идее там, немного другой реализации). Преимущество заключается в том, что если вы вызываете/api/print, модуль будет печатать человека. Если у вас когда-либо появился новый модуль, который по какой-то причине не может реализовать печать, но реализует/api/writeline, тогда модуль будет отвечать за реализацию логики, которая также конвертирует вызовы из старого/api/print в новый/api/writeline.

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

+0

Привет, Батавия, спасибо за ответ. Я обновил свой первоначальный вопрос, чтобы лучше его контекстуализировать и лучше объяснить мою ситуацию. – MastErAldo

 Смежные вопросы

  • Нет связанных вопросов^_^