将不同版本的程序集加载到单独的AppDomain中

时间:2014-02-21 17:44:14

标签: c# .net clr appdomain

我正在实现支持插件的应用程序。目前,当我尝试加载主机应用程序和插件使用的公共程序集时出现问题:主机应用程序应该使用该程序集的一个版本,而插件使用另一个版本。这是由应用程序升级决定的process - 插件可以与主机应用程序分开更新。

每个程序集都已签名,因此我使用强名称来加载程序集。

我创建了一个演示问题的测试应用程序。插件程序集位于主机应用程序的子文件夹“插件”中。插件文件夹包含插件实现DLL,插件声明接口DLL和CommonLib DLL。主机应用程序还使用插件声明DLL和CommonLib DLL(在文件夹树中位于1级)。

以下是插件加载的源代码:

static void Main(string[] args)
{
    Console.WriteLine("Host   says: {0}", GreetingManager.SayHello("Sasha"));
    Console.WriteLine("Host   says: {0}", GreetingManager.SayGoodBye("Sasha"));
    var pluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugin");
    var pluginFile = Directory.GetFiles(pluginDir).First(f => f.EndsWith("Plugin.dll"));

    Assembly asmr = Assembly.ReflectionOnlyLoadFrom(pluginFile);

    AppDomain pluginDomain = AppDomain.CreateDomain("Plugin", null, pluginDir, "", false);

    pluginDomain.Load(asmr.FullName);

    Assembly asm = pluginDomain.GetAssemblies().First(a => a.GetName().Name == "Plugin");
    Type p = asm.GetTypes().First(t => t.GetInterfaces().Contains(typeof(IPlugin)));

    var pluginInstance = (IPlugin)pluginDomain.CreateInstanceAndUnwrap(asm.FullName, p.FullName);
    Console.WriteLine("Plugin says: {0}", pluginInstance.TransformString("Sasha"));
}

抛出异常:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'CommonLib, Version=1.0.9.0, Culture=neutral, PublicKeyToken=73c7c163a33b622c' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
   at Plugin.MyPlugin.TransformString(String str)
   at PluginBackwardCompatibilityTest.Program.Main(String[] args) in D:\Projects\Test\PluginBackwardCompatibilityTest\PluginBackwardCompatibilityTest\Program.cs:line 37

正如我所说,插件仍尝试在主机应用程序文件夹中找到CommonLib程序集(忽略我在创建AppDomain时提供的appBasePath参数),但在那里找到旧版本1.0.8并生成此错误。 如何强制插件从插件文件夹加载引用的程序集 - 在使用强名称时无法指定要加载的完整程序集路径。

2 个答案:

答案 0 :(得分:4)

我认为您的问题如下:

Assembly asm = pluginDomain.GetAssemblies().First(a => a.GetName().Name == "Plugin");
Type p = asm.GetTypes().First(t => t.GetInterfaces().Contains(typeof(IPlugin)));
var pluginInstance = (IPlugin)pluginDomain.CreateInstanceAndUnwrap(asm.FullName, p.FullName);

这样你就可以将更新/过时的类型引用加载到主AppDomain中(它已经在不同版本中加载了程序集)。

我建议您使用以下方法:

  1. 创建一个包含合同/接口的单独程序集。此程序集已修复并始终保持特定版本,并且永远不会过时,并且每个版本的插件都可以引用它。
  2. 编写一个汇编加载程序,它是主机应用程序的一部分,但也可以单独组装。此程序集加载程序程序集必须没有对应用程序其余部分的任何引用,因此您可以在新的应用程序域中进行设置。
  3. 设置新的appdomain后,加载AssemblyLoader的一个实例。这个加载插件程序集并与之交互。
  4. 您的应用程序不与程序集本身交互。您的应用程序通过appdomain边界调用AssemblyLoader,AssemblyLoader调用Plugin
  5. 我不能说这是否是最好的版本,但是在每个插件可以加载任何版本的任何程序集的环境中,这个版本都适用。使用此设置,您几乎可以抵抗更改版本或更新,并且每个插件都可以使用它想要的版本。

    让我知道它是否适合你。

答案 1 :(得分:0)

最有可能是您自己的代码首先加载最新版本 - 请在调用CommonLib之前检查Load是否已加载到新的应用领域。

如果是这种情况,您需要非常小心地将主代码链接到CommonLib,以避免过早加载最新版本。您甚至可能需要使用后期绑定/反射。