gpt4 book ai didi

c# - 保持事件对象的插件系统

转载 作者:行者123 更新时间:2023-11-30 21:53:12 25 4
gpt4 key购买 nike

我有一个需要在运行时加载和卸载.dll文件形式的插件的应用程序。这些dll文件包含一个或多个从我的抽象类Module派生的类,如下所示:

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

public abstract void Print();

}


据我了解:在C#中,卸载程序集及其类的唯一方法是将程序集放入专用的Appdomain中,然后在不再需要时将其卸载,并且AppDomain之间进行通信的唯一方法是从MarshalByRefObject或MarshalByValueObject派生。然后,当我将模块的引用传递给另一个AppDomain时,我实际上并没有获取对象,而是获得了透明代理。任何人都可以确认我上面写的内容吗?

好的,现在是真正的问题:假设在我的文件PersonPlugin.dll中,我有一个扩展了Module的“ Person”类。此类包含字段名称,姓氏和电话号码。

定期调用Print方法(定期调用print没有意义,但仅作为示例)将打印这3个字段。

后来,我使用新版本的Person类构建了一个新的dll,该类具有一个名为address的新字段,并且Print方法现在还会打印该地址。
注意:插件是由第三方制作的,我不知道新版本是与旧版本相似还是完全不同,但是就我而言,可以肯定的是,类Person在各个版本之间不会有太大差异。

然后,我用这个新文件替换旧的dll文件(该dll在专用的appdomain中被影影复制,因此该文件可写/可删除)

FileSystemWatcher注意到更改,并且:


为新的dll创建一个新的appdomain
保存旧的实例化Person对象的状态
将旧的Person类中的Person对象分配给新的Person类
卸载包含旧Person类的appdomain
应用程序开始打印还包含地址的新字符串(至少在开头为null)


第2点和第3点是我的问题的核心:如何在卸载它们的类后如何使这些对象(或至少它们的字段)保持活动,然后实例化一个具有相同名称和相似属性的新类,但实际上这是另一个类?

这是我到目前为止的内容:


使用反射,我可以在字典中复制每个Person对象的每个字段,删除旧的对象,然后实例化新的对象,并在可能的情况下复制回这些字段(如果不存在旧的字段,则跳过该字段,如果存在一个新的字段,则得到该字段)其默认值)
使用MessagePack之类的东西自动序列化旧的Person对象,然后反序列化为新的Person对象


但是,我没有进行任何测试以查看这两个解决方案是否可行,主要是因为我想在开始编写代码之前先知道这两个解决方案是否可能在现实中或只是在我脑海中工作,并且因为也许其中一些人更完善/可行的解决方案,甚至更好的已经可以做到的框架/库。

更新:

好的,我意识到在不考虑上下文的​​情况下提出这个问题可能会引起误解,因此:
我要问的问题是关于我的论文的,这是一个模块化的最小游戏引擎(不是服务器)。这个想法是使引擎模块化,并使其易于测试各种功能,可以在运行时立即更改其模块,并立即应用更改,而无需重新启动或失去游戏中“ actor”的状态。
一个dll包含1到许多模块。每个dll都加载到1个AppDomain中,因此,如果我卸载该appdomain,则会卸载其中的每个模块。同一appdomain中的模块可以相互直接引用,因为在卸载时,它们会一起卸载。不同程序集中的模块(也就是不同的AppDomain)中的模块使用消息总线进行通信,并且永远不会直接引用。如果卸载了使用该消息的模块,则系统将仅停止将该消息转发到该模块。

重要的是模块代表的内容取决于引擎的用户:模块可以代表单个游戏对象(例如汽车)或整个物理模块。我将不进一步探讨细节,因为它们现在没有用,但是我想要实现的目标可以通过以下示例来说明:

我有一个包含名为Car的模块的dll,该模块始终向前移动。我用一个包含模块Car的新DLL更改了dll,该模块现在总是向后移动。
结果是,一旦我替换了dll,汽车将立即反转方向。

当然,这个例子很愚蠢,有很多方法可以更轻松地实现这一目标。但是,这也适用于以下情况:我在代码中发现了一个错误,我对其进行了纠正,提交并导致该错误消失了,甚至是更复杂的情况。

这就是为什么我需要保持活动对象(我的意思是,保持它们的状态保持活动)以及整个系统。

关于您的观点2:对于为什么我不允许共存2个相同类型但不同版本的模块没有严格的限制,只要它们在不同的名称空间或不同的dll中就可以了,因为在我的系统中,每个模块都被标识和引用具有不同的ID

最佳答案

首先,让我评论您的第一点。


  据我了解:C#中卸载程序集和
  它的类是将程序集放在专用的Appdomain中,然后
  不再需要它时将其卸载,这是唯一的通信方式
  AppDomain之间的关系是从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内部,并在您的 ModuleManager上公开一些方法知道如何做实际的坚持。

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)
{
...
}
}


Person应该负责保存模块的状态,因此每个模块开发人员都不必实现此功能。它要做的就是在键/值对的字典中读写状态。

您会发现此方法易于管理,而不是尝试在宿主应用程序中执行所有持久性。

这里的想法是在主机应用程序和插件之间封送尽可能少的数据。每个插件都知道 中有哪些字段,因此让他们负责指示需要保留的内容。

关于c# - 保持事件对象的插件系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34097131/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com