2010-04-23 41 views
6

我想有一个可以作为一个主机到许多其他的小应用程序的应用C#的主机应用程序。这些应用程序中的每一个都应该作为这个主应用程序的插件。我称它们为插件,并不是因为它们将某些东西添加到主应用程序中,而是因为它们只能使用此Host应用程序,因为它们依赖于它的某些服务。问题有关如何实现一个插件式的建筑风格

我的想法是让每一个不同的应用程序域中运行这些插件。问题似乎是,我的主机应用程序应该有一组服务,我的插件将要使用,并且我的理解是,从不同的应用程序域进出数据流并不是那么重要。一方面,我希望他们表现为独立应用程序(虽然,正如我所说的,他们需要使用主机应用程序服务的很多次),但另一方面,我希望如果他们中的任何一个崩溃,我的主要应用程序不会受到它的影响。

什么是最好的(.NET)的方法来这种情况?让它们都运行在相同的AppDomain上,但每个都在不同的线程中运行?使用不同的AppDomains?每个“插件”一个?我将如何让他们与主机应用程序通信?任何其他方式做到这一点?

虽然速度不是一个问题在这里,我不想对函数调用是远远低于他们,当我们只是一个普通的.NET应用程序的工作。

感谢

编辑:也许我真的需要使用不同的应用程序域。从我读到的内容来看,在不同AppDomain中加载程序集是以后能够从程序中卸载它们的唯一方法。

回答

5

我已经在System.Addin命名空间中使用Managed Addin Framework(MAF)在这些行上实现了一些东西。使用MAF,您可以将您的插件打包为单独的DLL,您的主机应用程序可以在其应用程序域中,在所有插件的单独域中或在其自己的域中的每个插件中发现并启动它们。有了影子副本和单独的域名,您甚至可以更新外挂程序而无需关闭您的hostapp。

您的主机应用程序和插件通过您从MAF界面派生的合同进行通信。您可以在主机和插件之间来回发送对象。 cotnracts提供了插件和主机之间的黑盒接口,允许你改变主机不知道的插件的实现。

如果主机告诉他们彼此之间的关系,Addins甚至可以在它们自己之间进行通信。在我的情况下,日志插件由其他人共享。这让我可以在不接触其他插件或主机的情况下插入不同的记录器。

对于我的应用程序,插件使用简单的超级用户类,在启动工人类中执行所有处理。工人们捕捉他们自己的例外情况,并通过回调方法返回给他们的主管。主管可以重新启动员工或采取其他措施。主机通过命令合同控制主管人员,指示他们启动和停止员工并返回数据。

我的主机应用程序是Windows服务。工作线程抛出所有常见原因(包括错误!)的异常,但主机应用程序从未在我们的任何安装中崩溃。由于调试服务不方便,插件允许我构建使用相同合同的测试应用程序,并保证我正在测试部署的内容。

Addins也可以公开UI元素。这对我非常有帮助,因为我需要在主机服务中部署控制器应用程序,因为服务没有UI。每个插件都包含自己的控制器接口。控制器应用程序本身非常简单 - 它加载插件并显示其UI元素。这使我可以通过更新的界面发送更新的插件,而不必运送新的控制器。

即使控制器和主机服务使用相同的插件,它们也不会互相踩在一起;事实上,他们甚至不知道另一个应用程序正在使用相同的插件。控制器和主机通过共享数据库相互通话,但您也可以使用另一个应用程序间机制,如MSMQ。在下一个版本中,主机将成为一个WCF服务,在后端使用插件和Web服务进行控制。

这有点冗长,但我想给你一个多功能MAF的概念。它并不像它看起来那么复杂,而且你可以用它来构建稳固的应用程序。

1

这是一个相关的问题。

我的第一个想法是简单地实现从主机应用程序接口,在你的插件应用程序,让他们通过反思沟通,但这将只允许通信,不会带来真正的“沙箱状”结构。

我的第二个想法是设计一个面向服务的平台。主机应用程序将是一种“插件广播器”,它将在不同线程的ServiceHost中发布插件。由于这方面的需求,真正做到反应迅速,“没有道理可配置”,主机应用程序可以通过命名管道通道插件(NetNamedPipesBinding为WCF),这意味着只能与本地主机的管道沟通,不需要任何网络配置或知识在所有通信。我认为这可能是解决您的问题的好方法。

问候。

2

这取决于您希望允许扩展的信任程度。我正在研究一个类似的应用程序,我选择了大部分信任扩展代码,因为这大大简化了事情。我叫成从一个共同点代码(在我的情况,这些分机没有真正在任何连续的循环“运行”,而是执行主应用想要做某些任务)在此线程和捕获异常,从而提供有用的警告,指出加载的扩展名行为不当。

目前没有什么保持这些扩展从推出自己的线程会抛出异常和崩溃的整个应用程序,但这正是我必须做出的安全性和复杂性之间的权衡。我的应用程序不是关键任务(不像Web服务器或数据库服务器),所以我认为可能会导致错误的扩展可能会导致我的应用程序无法接受。我提供了更安全的保护措施,以更礼貌地涵盖最常见的失败案例,并将其交给插件开发人员(现在主要是内部人员),以清理其错误。


在问候卸载,是的,你只能如果你把它放在一个AppDomain卸载代码和元数据的组件。这就是说,除非你想在程序的整个生命周期中经常加载和卸载,否则将代码保存在内存中的开销不一定是问题。当停止使用GC时,使用GC中的类型的任何实际实例或资源仍会被GC清除,因此它仍在内存中的事实并不意味着内存泄漏。

如果您的主要用例是一系列插件,您可以在启动时定位一次,然后提供一个选项以在应用程序运行时实例化,我建议您在启动时调查与加载全部插件有关的实际内存占用情况并保持它们加载。如果您使用AppDomain,那么还会有额外的开销(例如,代理对象的内存和加载/ JITed代码以支持AppDomain编组)。编组和伴随序列化也会产生CPU开销。

总之,我只会用的AppDomain如果以下情况之一是真实的:

  • 我想要得到的代码安全性的目的(即我需要在一个孤立的运行不受信任的代码完全隔离方式)

  • 我的应用程序是关键任务,我绝对需要确保如果插件失败,它不能打倒我的核心应用程序。

  • 我需要重复加载和卸载同一个插件,以支持对DLL的动态更改。这主要是如果我的应用程序无法停止运行,但我想在它仍在运行时热插拔插件。

我不希望AppDomain的唯一目的是通过允许Unload来减少可能的内存占用。

+0

这是一个爱好应用程序,没什么特别的。我完全信任运行代码。问题是我需要我的每一个插件随时都在运行(也就是说,我不能让我的主机应用程序调用我的插件,它们必须是独立的,并为自己运行)。 – 2010-04-23 01:44:06

+1

在这种情况下,我只是给每个他们自己的Thread运行并调用您定义的任何'Main'方法。如果您希望在插件引发异常时提供友好的回退,您可以选择在try/catch中将该线索调用为线程中的'Main'。 – 2010-04-23 02:14:46

+0

是的,但请参阅上面的修改。这样,我不能以后卸载程序集加载,而无需重新启动应用程序。我必须让它们在不同的应用程序域中,以便我可以随时卸载它们。 – 2010-04-23 12:35:28