关于如何使用类似插件的架构实现 ac# 主机应用程序的问题
我想要一个可以作为许多其他小型应用程序的主机的应用程序。这些应用程序中的每一个都应该作为该主应用程序的插件。我称它们为插件并不是因为它们向主应用程序添加了某些内容,而是因为它们只能与该主机应用程序一起使用,因为它们依赖于其某些服务。
我的想法是让每个插件在不同的应用程序域中运行。问题似乎是我的主机应用程序应该有一组我的插件想要使用的服务,并且根据我的理解,使数据从不同的应用程序域流入和流出并不是一件好事。
一方面,我希望它们表现得像独立应用程序(尽管,正如我所说,它们需要多次使用主机应用程序服务),但另一方面,如果它们中的任何一个崩溃,我希望它们,我的主要应用程序不会受到影响。
对于这种情况,最好的 (.NET) 方法是什么?让它们都在同一个 AppDomain 上运行,但每个都在不同的线程中运行?使用不同的应用程序域?每个“插件”一个?我如何让它们与主机应用程序通信?还有其他方法可以做到这一点吗?
尽管速度在这里不是问题,但我不希望函数调用比我们仅使用常规 .NET 应用程序时慢得多。
谢谢
编辑:也许我真的需要使用不同的AppDomains。根据我的阅读,在不同的 AppDomain 中加载程序集是以后能够从进程中卸载它们的唯一方法。
I want to have an application that works as a Host to many other small applications. Each one of those applications should work as kind of plugin to this main application. I call them plugins not in the sense they add something to the main application, but because they can only work with this Host application as they depend on some of its services.
My idea was to have each of those plugins run in a different app domain. The problem seems to be that my host application should have a set of services that my plugins will want to use and from what is my understanding making data flow in and out from different app domains is not that great of a thing.
On one hand I'd like them to behave as stand-alone applications(although, as I said, they need to use lots of times the host application services), but on the other hand I'd like that if any of them crashes, my main application wouldn't suffer from it.
What is the best (.NET) approach to this kind of situation? Make them all run on the same AppDomain but each one in a different Thread? Use different AppDomains? One for each "plugin"? How would I make them communicate with the Host Application? Any other way of doing this?
Although speed is not an issue here, I wouldn't like for function calls to be that much slower than they are when we're working with just a regular .NET application.
Thanks
EDIT: Maybe I really need to use different AppDomains. From what I've been reading, loading assemblies in different AppDomains is the only way to later be able to unload them from the process.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我已经使用 System.Addin 命名空间中的托管插件框架 (MAF) 实现了一些类似的功能。使用 MAF,您可以将外接程序打包为单独的 DLL,您的主机应用程序可以在其应用程序域、所有外接程序的单独域或每个外接程序在其自己的域中发现并启动它们。通过卷影副本和单独的域,您甚至可以在不关闭主机应用程序的情况下更新插件。
您的主机应用程序和插件通过从 MAF 接口派生的合同进行通信。您可以在主机和插件之间来回发送对象。 cotnracts 在插件和主机之间提供了一个黑盒接口,允许您在主机不知道的情况下更改插件的实现。
如果主机告诉它们彼此的信息,插件甚至可以在它们之间进行通信。就我而言,日志记录插件由其他人共享。这让我可以放入不同的记录器,而无需接触其他插件或主机。
对于我的应用程序,插件使用简单的管理程序类,这些管理程序类在自己的线程上启动工作程序类来执行所有处理。工作人员捕获自己的异常,并通过回调方法将其返回给主管。主管可以重新启动工人或采取其他行动。主机通过命令合约控制supervisor,指示它们启动和停止worker并返回数据。
我的主机应用程序是 Windows 服务。工作线程因所有常见原因(包括错误!)而引发异常,但主机应用程序从未在我们的任何安装中崩溃。由于调试服务很不方便,插件允许我构建使用相同合约的测试应用程序,并进一步确保我正在测试我部署的内容。
插件也可以公开 UI 元素。这对我非常有帮助,因为我需要使用主机服务部署控制器应用程序,因为服务没有 UI。每个插件都包含自己的控制器接口。控制器应用程序本身非常简单 - 它加载插件并显示其 UI 元素。这使我能够发布具有更新界面的更新插件,而不必发布新控制器。
即使控制器和主机服务使用相同的插件,它们也不会互相干扰;事实上,他们甚至不知道另一个应用程序正在使用相同的插件。控制器和主机通过共享数据库相互通信,但您也可以使用另一种应用程序间机制,例如 MSMQ。在下一个版本中,主机将是一个 WCF 服务,在后端带有插件,并通过 Web 服务进行控制。
这有点啰嗦,但我想让您了解 MAF 的多功能性。它并不像乍看起来那么复杂,您可以用它构建坚如磐石的应用程序。
I've implemented something along these lines using the Managed Addin Framework (MAF) in the System.Addin namespace. With MAF you package your addins as separate DLLs, which your host app can discover and launch in its app domain, in a separate domain for all of the addins, or each addin in its own domain. With shadow copy and separate domains you can even update an addin without shutting down your hostapp.
Your host app and the addins communicate through contracts that you derive from MAF interfaces. You can send objects back and forth between the host and the addins. The cotnracts provide a black-box interface between addins and the host, allowing you to change an addin's implementation unbeknownst to the host.
Addins can even communicate between themselves if the host tells them about each other. In my case a logging addin is shared by the others. This lets me drop in different loggers without touching the other addins or the host.
For my app, the addin use simple supervisor classes that in launch worker classes on their own threads that do all of the processing. Workers catch their own exceptions, which they return to their supervisor through callback methods. Supervisors can restart workers or take other action. The host controls the supervisors through a command contract, which instructs them to start and stop workers and return data.
My host app is a Windows service. The worker threads have thrown exceptions for all the usual reasons (including bugs!), but the host app has never crashed in any of our installations. Since debugging services is inconvenient, addins allow me to build test apps that use the same contracts, with added assurance that I'm testing what I deploy.
Addins can expose UI elements, too. This is very helpful to me as I need to deploy a controller app with the host service, since services do not have UIs. Each plugin includes its own controller interface. The controller app itself is very simple - it loads the addins and displays their UI elements. This allows me to ship an updated addin with an updated interface and not have to ship a new controller.
Even though the controller and the host service use the same addins, they don't step on each other; in fact, they don't even know that another app is using the same addins. The controller and the host talk to each other through a shared database, but you could also use another inter-app mechanism like MSMQ. In the next version the host will be a WCF service with addins on the backend and web services for control.
This is a bit long-winded but I wanted to give you an idea of how versatile MAF is. It's not as complex as it might first look, and you can build rock-solid apps with it.
这取决于您希望允许扩展的信任程度。我正在开发一个类似的应用程序,并且我选择主要信任扩展代码,因为这大大简化了事情。我从公共线程调用代码(在我的例子中,扩展实际上并不在任何连续循环中“运行”,而是执行主应用程序想要执行的某些任务)并捕获该线程中的异常,因此提供有关加载的扩展行为不当的有用警告。
目前,没有什么可以阻止这些扩展启动自己的线程,这些线程可能会引发整个应用程序并使其崩溃,但我必须在安全性和复杂性之间进行权衡。我的应用程序不是关键任务(不像 Web 服务器或数据库服务器),因此我认为有缺陷的扩展可能会导致我的应用程序崩溃,这是一个可以接受的风险。我提供了保护措施,以更礼貌地涵盖最常见的故障情况,并将其留给插件开发人员(无论如何,他们现在主要是内部人员)来清理他们的错误。
关于卸载,是的,如果将程序集放置在 AppDomain 中,则只能卸载程序集的代码和元数据。也就是说,除非您希望在程序的生命周期中频繁加载和卸载,否则将代码保留在内存中所带来的开销不一定是问题。当您停止“使用”它时,使用程序集中类型的任何实际实例或资源仍将被 GC 清理,因此它仍在内存中并不意味着内存泄漏。
如果您的主要用例是一系列插件,您在启动时定位一次,然后提供在应用程序运行时实例化的选项,我建议调查与在启动时加载所有插件并保持加载相关的实际内存占用量。如果您使用 AppDomains,也会有额外的开销(例如,代理对象的内存和支持 AppDomain 封送的加载/JIT 代码)。还存在与编组和伴随序列化相关的 CPU 开销。
简而言之,如果满足以下条件之一,我只会使用 AppDomains:
出于代码安全的目的,我想获得真正的隔离(即我需要以隔离的方式运行不受信任的代码)
我的应用程序是关键任务,我绝对需要确保如果插件失败,它不会带来关闭我的核心应用程序。
我需要重复加载和卸载相同的插件,以支持对DLL的动态更改。这主要是如果我的应用程序无法停止运行,但我想在其仍在运行时热修补插件。
我不喜欢 AppDomains 的唯一目的是通过允许卸载来减少可能的内存占用。
It depends on how much trust you wish to allow the extensions. I'm working on a similar application and I've chosen to mostly trust the extension code, as this greatly simplifies things. I call into the code from a common thread (in my case, the extensions don't really 'run' in any continuous loop, but rather execute certain tasks that the main application wants to do) and catch exceptions in this thread, so as to provide helpful warnings that loaded extensions are misbehaving.
Currently there's nothing keeping these extensions from launching their own threads that could throw and crash the whole app, but this where I've had to make the trade-off between safety and complexity. My application is not mission-critical (not like a web server or database server), so I consider it an acceptable risk that a buggy extension could bring down my application. I provide safeguards to more politely cover the most common failure cases and leave it to the plugin developers (who will mostly be in-house people for now anyway) to clean up their bugs.
In regards to Unloading, yes, you can only unload the code and metadata for an assembly if you place it in an AppDomain. That said, unless you want to be loading and unloading frequently over the life of your program, the overhead associated with keeping the code in memory is not necessarily an issue. Any actual instances or resources using types from the assembly will still be cleaned up by the GC when you stop 'using' it, so the fact that it's still in memory doesn't imply a memory leak.
If your main use case is a series of plugins that you locate once at startup and then provide an option to instantiate while your app is running, I suggest investigating the real memory footprint associated with loading all of them at start-up and keeping them loaded. If you use AppDomains, there will be additional overhead there as well (for instance, memory for the proxy objects and loaded/JITed code to support AppDomain marshaling). There will also be CPU overhead associated with the marshaling and attendant serialization.
In short, I would only use AppDomains if one of the following were true:
I want to get true isolation for the purposes of code security (i.e. I need to run untrusted code in an isolated way)
My app is mission-critical and I absolutely need to make sure that if a plugin fails, it can't bring down my core app.
I need to load and unload the same plugin repeatedly, in order to support dynamic changes to the DLL. This is mainly if my app can't stop running, but I want to hot-patch plugins while it's still running.
I would not prefer AppDomains for the sole purpose of reducing possible memory footprint by allowing Unload.
这是一个有趣的问题。
我的第一个想法是简单地在插件应用程序中实现主机应用程序的接口,以允许它们通过反射进行通信,但这只会允许通信,而不会带来真正的“类似沙箱”的架构。
我的第二个想法是设计一个面向服务的平台。主机应用程序将是一种“插件广播器”,它将在不同线程的 ServiceHost 中发布您的插件。由于这需要真正响应并且“无需配置”,主机应用程序可以通过命名管道通道(WCF 的 NetNamedPipesBinding)与插件通信,这意味着仅与本地主机管道通信,根本不需要任何网络配置或知识。我认为这可以很好地解决您的问题。
问候。
This is an interisting question.
My first idea was to simply implement interfaces from your host application in your plugin applications to allow them to communicate through Reflection, but this would only allow communication and would not bring a real "sandbox-like" architecture.
My second thought was to design a service-oriented platform. The host application would be a kind of "plugin broadcaster" which would publish your plugins in a ServiceHost on a different thread. As this need to be really responsive and "no brainer configurated", the host application could communicate with the plugin through named pipes channel (NetNamedPipesBinding for WCF) which means is only communicating with localhost pipes and does not need any network configuration or knowledge at all. I think this could be a good solution to your problem.
Regards.