У меня есть приложение, которое нужно загружать и выгружать при запуске некоторых плагинов в виде файлов 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 замечает изменения и:
создает новый AppDomain для новой библиотеки DLL
сохраняет состояние старого конкретизированного Человека объекты
assignes Людей объекты из старого класса Person к новому
разгружает AppDomain, содержащего старый Person класс
приложение начинает печатать новую строку, содержащую также адрес (то есть нуль, по крайней мере в начало)
Пункты 2 и 3 являются основой моей проблемы: Как я могу поддерживать эти объекты (или, по крайней мере, их поля) после разгрузки их класса, а затем создавать новый класс, который имеет такое же имя и аналогичные свойства, но это действительно другой класс?
Это то, что я, хотя до сих пор:
с помощью отражения можно скопировать в словаре каждое поле каждого объекта Person, удалить старые объекты, а затем создать экземпляр новых и скопировать обратно поля, где это возможно (если старое поле нет, то это пропускается если новый один присутствует он получает значение по умолчанию)
используя что-то вроде MessagePack автоматически сериализовать старые объекты Person, а затем десериализации их к новому Person объекты
Однако я не сделал никаких проверок, чтобы увидеть, могут ли эти 2 решения работать, главным образом потому, что я хотел бы знать, прежде чем начинать писать код, если эти 2 решения могут работать на самом деле или как раз в моем уме, и потому, что, возможно, у некоторых из вас есть более полированное/рабочее решение или даже лучшая инфраструктура/библиотека, которая уже делает это.
UPDATE:
Хорошо, я понял, что задаю этот вопрос, не контекстуализации это немного может привести к недоразумению, так: Вопрос я спрашиваю о моей диссертации, которая является модульным минимальным игровой движок (а не сервер). Идея состоит в том, чтобы сделать модуль модульным и, чтобы упростить тестирование различных функций, его модули могут быть изменены во время выполнения, немедленно применяя изменения без перезапуска или потери состояния «актеров» в игре. dll содержит от 1 до многих модулей. каждая dll загружается в 1 AppDomain, поэтому, если я выгружаю этот appdomain, я выгружаю каждый модуль в него. Модули внутри одного и того же appdomain могут иметь прямые ссылки на eachothers, так как при разгрузке они разгружаются вместе. Модули в разных сборках (так же, как и другие AppDomains) обмениваются сообщениями с использованием шины сообщений и никогда не имеют прямых ссылок. Если модуль, потребляющий сообщение, выгружается, система просто прекратит пересылать это сообщение этому модулю.
Важно то, что то, что представляет собой модуль, зависит от пользователя двигателя: модуль может представлять собой один игровой объект, такой как автомобиль, или весь физический модуль. Я не буду вдаваться в подробности, потому что сейчас они бесполезны, но я хочу, чтобы это можно было сделать:
У меня есть DLL, содержащая модуль под названием Car, который всегда движется вперед. Я меняю dll на новый, содержащий модуль Car, который теперь перемещается всегда назад. Результатом является то, что как только я заменю dll, автомобиль немедленно перевернет его направление.
Конечно, этот пример глуп, есть способы достичь этого гораздо более простым способом. Однако это может также применяться в ситуации, когда я нахожу ошибку в своем коде, я ее исправляю, отправляю, и ошибка просто исчезает или даже более сложные ситуации.
Вот почему мне нужно поддерживать живые объекты (я имею в виду, сохраняю их состояние в живых) и всю систему.
И о вашей точке 2: нет жестких ограничений в отношении того, почему я не позволил бы сосуществовать 2 модуля одного и того же типа, но с разными версиями, если они находятся в разных пространствах имен или в разных dll, поскольку в моей системе каждый модуль идентифицируется и ссылается на другой ID
Спасибо за разъяснение об AppDomain Randy. Итак, вы рекомендуете, чтобы каждый модуль отвечал за свою собственную загрузку/сохранение состояния вместо хост-приложения? Я думаю, это может сработать, но теперь ответственность за постоянство штата зависит от разработчика модуля (что может быть даже не так уж и плохо). Я сделал модуль наследованием от MarshalByRefObject, потому что мне нужно, чтобы некоторые из его методов (например, Print) вызывались хостом, поэтому модуль должен быть басирован по его границе. Я думаю, что я мог бы также передать объект «контейнер» с единственной целью для обмена данными между модулем и его хостом. – MastErAldo
Да, это правильно. Я бы использовал контейнер, который отвечает за управление плагином внутри порожденного AppDomain. Кроме того, вам не нужно оставлять упорство полностью до разработчика модуля. Вы можете ввести метод, который возвращает пары ключ/значение для представления состояния модуля и контейнера (ModuleManager в моем примере), может быть ответственным за фактическое сохранение, так что таким образом все согласовано во всех модулях. – Randy
Я обновил свой ответ, чтобы показать пример того, как может выглядеть состояние, сохраняемое внутри ModuleManager. – Randy