【问题标题】:Using an abstract class as the contract in plugin framework在插件框架中使用抽象类作为契约
【发布时间】:2023-03-04 07:37:07
【问题描述】:

可以将抽象类用作“主机”和“插件”之间的契约对象吗?这个想法是插件继承了合同(我们称之为适配器)。我们也理解框架中的所有参与者都必须继承MarshalByRefObject (MBRO)。所以,这就是我们的想法 -

主持人

class Host : MarshalByRefObject
{
}

合同

public abstract class PluginAdapter : MarshalByRefObject
{
}

插件

class myPlugin : PluginAdapter
{
}

这三个都存在于单独的 asm 中。我们的Host会为每个插件创建一个新的AppDomain,PluginAdapter的创建如下:

{
    ObjectHandle instHandle = Activator.CreateInstance(
    newDomain, data.Assembly.FullName, data.EntryPoint.FullName);

    PluginAdapter adapter = (PluginAdapter)instHandle.Unwrap();
}

EDIT:其中datamyPlugin 的具体类型。

我们想知道这个框架的实现是否可行。我们已经看到使用接口(IPlugin)进行插件派生的文章,以及使用具体类作为契约的文章。那些文章也会说可以使用抽象类,但没有给出该实现的示例。是否要求契约是一个具体的类?

编辑: 在 Richard Blewett(C# Reflection)的这个示例中,他使用了一个更简单的实现:

合同

public interface IPlugIn  
{  
    // do stuff  
}

插件

public class PlugIn : MarshalByRefObject, IPlugIn  
{  
}

现在,如果使用抽象类作为合约,插件不能同时继承合约和 MBRO。那么,什么成为可扩展插件框架的最佳实现。即使最初我们正在为单机操作进行开发,我们是否应该继续实施远程处理?该项目预计将分布在网络上,也可能分布在 Internet 上。我们只是还没有实现 Tcp,因为我们正试图让插件框架的基础知识得到充分理解和操作。

在单机上使用环回实现 Tcp 远程处理有意义吗?

【问题讨论】:

    标签: c# .net-3.5 plugins marshalbyrefobject


    【解决方案1】:

    抽象类是更好的选择,恕我直言。这主要是因为接口更难版本化。 This blog post 描述了如果您不使用基类,您可能会发现自己遇到的问题。顺便说一句,这条规则不仅适用于插件。

    关于你的设计...

    插件不应扩展 MBRO。您应该使用您的主机(应该扩展 MBRO)来编组对您的插件的所有调用,包括处理插件事件。如果您尝试将插件 DLL 拉过并使用它们的代理,很容易无意中将插件 DLL 加载到您的主应用程序域中。

    例如,如果插件为其方法之一返回 IEnumerable,它可能会返回插件程序集中定义的 IEnumerable 实现。如果这不扩展 MBRO,则主 appdomain 将不得不加载插件程序集。


    我在这里上传了三个处理 appdomains 的项目:

    http://cid-f8be9de57b85cc35.skydrive.live.com/self.aspx/Public/NET%20AppDomain%20Tests/appdomaintests.zip

    一是跨appdomains使用回调,二是跨appdomain事件处理,三是插件示例。

    在插件示例中,应用程序定义了一个插件接口(它是一个演示,而不是最佳实践!)和一个插件主机。该应用程序从磁盘加载原始插件程序集,并通过插件主机代理将其传递给插件应用程序域,并在此加载。插件宿主然后实例化插件并使用它。但是当主机将插件程序集中定义的类型返回到应用程序应用程序域时,插件程序集被加载到主应用程序域中,从而使整个插件变得毫无意义。

    避免这种情况的最佳方法是为未标记可序列化且不扩展 MBRO 的插件提供一个抽象基类,并且仅将您定义的原语或密封类型从插件域带回边界。

    注意:这些项目都是 4.0 RC。你需要这个或更高版本来运行它们。否则,您将不得不手动编辑项目文件或重建它们以使它们在 b2 或 2008 中运行。

    【讨论】:

    • 根据我们的经验,如果myPlugin 没有扩展MBRO,那么我们会得到一个异常,即它不可序列化。我们知道[Serializable] 将有效地将类实例化到主机的应用域而不是插件的应用域。
    • @dboarman 你得到这个是因为你将插件从插件域拉回应用程序域。正如我所说,当这种情况发生时,你就是在危及自己。如果您试图将插件保留在自己的应用程序域中,则必须控制应用程序域之间通信的每种类型。您的插件主机必须存在于插件域中。它发回的类型只能是您提供的那些类型。否则可能会在您的域中加载插件程序集。那些“不可序列化”的异常是好的。他们告诉你你做错了什么——把他们的代码拿过来。
    • @dboarman 管理域是一个棘手的过程,需要一些时间和精力才能做到正确。最简单的方法是使用您编写的单一类型,然后在插件域中解包。这种类型负责加载和管理插件。从插件域传输的任何内容都必须是您提供的类型,并且应该密封它们以防止插件返回自己的版本。创建系统的小规模原型对改进这些技术非常有帮助;我做到了,学到了很多。
    • 在我提供的第二次编辑中,Richard Blewett 示例的链接确实显示了扩展 MBRO 的插件。他的“主人”和合同不延长 MBRO。我真的很想了解其中的区别以及为什么会选择一种方法而不是另一种方法。
    • @dboarman 好吧,示例并不总是最好的设计或最好的代码。我有一个不错的测试应用程序,用于测试一些跨域场景。回复一下,我会记得把它上传到某个地方。
    【解决方案2】:

    如果“data.EntryPoint.FullName”是完整的类型名称,上面的代码应该可以工作。

    但是,如果您尝试将此类型隔离在其自己的 AppDomain 中,则在此处应小心。通过执行data.Assembly,您会将程序集(及其类型)拉入您的 AppDomain,从而将类型加载到正在执行的 AppDomain...

    【讨论】:

    • 在我们添加抽象合约中定义的事件之前,这似乎“大部分”有效。当主机订阅事件时,我们会收到一个 TargetInvocationException,其中无法解析程序集。所以这可能需要成为另一个问题。我们只是想从“这行得通”这个基本问题开始......
    【解决方案3】:

    您可能想查看MAF (the Managed Addin Framework),这是一个内置于 .NET 中的可扩展框架,用于执行插件。它与MEF (Managed Extensibility Framework) 类似(并且更早),但在将插件保留在自己的应用程序域等方面有更多选择。

    【讨论】:

    • 我看过 MAF 和 Mono.Addins。我们的目标是比 Windows 机器更广泛的基础。
    【解决方案4】:

    如果data.EntryPoint.FullName 指的是具体类型myPlugin,我看不出有任何原因导致这不起作用(除非在另一个应用程序域中存在程序集加载问题,但这是一个不同的问题)。

    【讨论】:

    • 是...添加了编辑以澄清data.EntryPoint.FullName 指的是具体类型myPlugin
    猜你喜欢
    • 2016-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-01
    相关资源
    最近更新 更多