好的,所以我有一个使用插件的.NET项目。插件实现为类库(DLL)项目,每个项目都在自己的文件夹中构建。主项目不需要运行插件DLL,但如果它们可用,它们将用于各种可选功能。插件中的类由Type.GetType()
加载。
但是,出于我自己的目的,在测试软件时,我希望能够同时开发插件和主应用程序。我创建了一个引用所有项目的“主”解决方案文件,因此我可以设置断点并跨越程序集边界。但是,问题是插件构建到他们自己的目录中,因此我必须以某种方式将插件DLL文件放在Type.GetType()
可以找到它们的地方。
编辑:为了澄清,我的代码已经枚举了与.exe相同的目录中的DLL文件,并找到了与给定界面匹配的类(使用Assembly.LoadFile()
,Assembly.GetExportedTypes()
,和Type.IsAssignableFrom()
)。然后用户选择要启用的插件,然后使用Type.AssemblyQualifiedName
将这些选项保存到数据库中。最后,当我需要使用插件提供的功能时,我使用Type.GetType()
来加载插件类。所有这些都与预期完全一致。只是当我构建应用程序时,我得到了.exe的副本,没有任何插件。我正在寻找一种方法来构建项目和解决方案文件,以便我可以将主应用程序和插件项目分开,同时仍然可以在Visual Studio中一起调试它们。这有意义吗?
到目前为止,这些是我的想法:
从主项目中添加对插件项目的引用 这样做的好处是它总能在我的调试环境中按预期工作,但是当我的应用程序在没有插件的情况下部署时,这会导致问题吗?我使用Dependency Walker进行了检查,似乎没有任何项目引用创建的直接DLL依赖项。这里有任何隐藏的问题需要了解吗?
将所有插件项目更改为构建到同一目标目录 这似乎运行得很好,但它也使我的构建树变得混乱,如果没有检查其他项目就会更难开发一个项目(现在它们都是Subversion中的兄弟文件夹)。
添加后期构建脚本以将插件复制到另一个目录 这实际上我现在正在做的事情,它运作良好,但它似乎非常hackish和脆弱。我想转而采用另一种方法。
找到Type.GetType()
搜索其他目录以查找DLL的方法
这类似于我在Unix系统上使用LD_LIBRARY_PATH
的方式。显然,我只想为Debug版本启用它,因为在Release模式下,这可能会在用户的系统上造成许多微妙的问题。但这有可能吗?如果是这样,怎么样?
有趣的是,在关于这个主题的this tutorial中,它说:
要做的第一件事是引用我们刚刚创建的类库,并将构建输出设置为同一目录。
这对我来说似乎不是最理想的。还有更好的方法吗?
答案 0 :(得分:1)
对于插件模型,我会查看Activator.CreateInstance
和Activator.CreateInstanceFrom
方法,甚至可以查看Assembly.LoadFrom
。这些方法允许您从“传统”输出目录(bin, debug, release
)以外的目录中的程序集加载类型。
强制Type.GetType
找到“插件”程序集实际上取决于您如何修改加载程序搜索程序集的策略。例如,您可以设置AppDomain.PrivateBinPath
属性以更改加载器将查找引用程序集的子目录。
关于第一点“添加对插件项目的引用”。那么,这将使一个人陷入困境,阻碍未来的可扩展性。如果我想要制作新插件怎么办?哎呀,现在你没有对它的引用,所以我想我的插件将不起作用呢?这就是为什么需要另一种方法。我们将“探测”插件程序集。
注意
在插件模型中,所有插件都应该针对公共接口(或抽象类)工作,并且它实际上是 实现,您正在寻找任何新加载的插件。例如:
private void LoadAllPlugins(string path)
{
foreach(string file in Directory.GetFiles(path, "*.dll"))
{
Assembly a = Assembly.LoadFrom(file);
foreach(Type t in a.GetTypes())
{
if (t.GetInterface("IMyPluginInterface", true))
{
// we now have a potential plugin type that implements required functionality
}
}
}
}
最后,.NET 3.5支持插件模型(System.AddIn),还查看了Microsoft的Managed Extensibility Framework版本,该版本允许开发人员构建简单的可组合插件({{3} })
答案 1 :(得分:1)
只需在您的解决方案中包含插件项目,但只有插件依赖于“应用程序”项目。通过保留重复的解决方案来实现这一点,但只使用应用程序项目并确保构建干净。您可能还需要考虑一个构建后步骤,尝试单独构建每个插件项目,而不需要其他项目。
另一项检查是您是否要强制执行向后兼容性,或者至少知道何时发生了明显的中断。如果您有一个测试项目通过dll引用而不是项目引用(测试项目中的一个小烦恼)引用插件,这是最简单的。然后通过修改项目设置中的引用路径,您可以指向旧版本的插件。作为脚本测试的一部分正确地执行此操作也相当简单。
我会提出使用MEF系统的建议你的插件在编译时是有效的,只是没有你计划加载的dll的特定版本。
答案 2 :(得分:1)
前段时间我用反射做了plugin system。该机制类似于drupal中使用的机制,我觉得非常灵活。
我有两篇文章解释它:Sistema de plugins con C#. Parte I. Conceptos和Sistema de plugins con C#. Parte II. El código explicado但我没有时间翻译它们(因此它们是西班牙语)。尽管如此,该代码仍以英文评论(如果我没记错的话)。
我会尝试在这里解释我的方法。
基本上你有插件,因为它们通常被定义,实现简单接口的类。该界面描述了加载,卸载,安装,卸载和配置插件的方法。
然后你有两种方面的插件。
首先,插件可以通过实现公共(已知)接口并使用“ServiceAttribute”将自身标记为服务来向其他类提供服务。然后,插件系统将注册该服务,并将其公开提供给任何有兴趣使用它的人。例如:
ICrypt crypto = Plugins.Service["Crypt"] as ICrypt;
if (crypto != null)
crypto.Crypt(data, key, etc)
另一方面,你有钩子。钩子是插件事件。您有两个属性HookAttribute和HookableAttribute。
当您将方法声明为Hookable时,您说您希望它被其他插件拦截。例如
[Hookable]
public event TextChanged OnTextChanged;
//.....
if (OnTextChanged != null)
OnTextChanged(ref myText);
和其他插件
[Hooks("OnTextChanged")]
public bool MyTextChanged(ref myText)
{
myText = "Hello from the plugin";
return true;
}
只要签名匹配,钩子签名(委托)就可以是未知的。插件系统再次处理匹配并将其提升为聚合。
它还包含一些属性来表示插件之间的依赖关系以及自动检测和加载以及这类事物。该项目本身就是LGPL,所以你可能会在其中找到一些想法甚至更好......贡献:P
顺便说一句,我使用插件子文件夹放置插件。只要插件项目是我正在处理的解决方案的一部分,我就可以从VS调试它们没有问题。我不需要引用它们或任何其他东西,如果插件是解决方案的一部分,那么我就可以调试它。
答案 3 :(得分:1)
设置项目,以便将Executable编译到1位置,并将“插件”编译到同一位置。您可以使用Junctions使“bin”成为最终位置的路径。
同样在您的插件项目中,将可执行文件设置为调试主机。
如果您还没有这样做,请确保您的界面没有通过每个Exe的编译重新编译(如果它的名称很强),因为这会迫使您每次都必须重新编译每个插件你重新编译你的主机exe。