1

我正在用 C# 创建一个插件框架。该框架的主要要求是在运行时加载、卸载和更新插件。

为此,我一直在创建 AppDomain 并将插件程序集加载到 AppDomain 中。

在 Windows 上的 Microsoft .NET 上一切正常,但插件不适用于在 mac 或 linux 上运行的单声道。

尝试启动插件时,出现如下异常:

无法转换类型为 'System.Func`1[[API.Network.NodeType, API, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] 的参数 0,mscorlib,Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089' 输入 'System.Func`1[[API.Network.NodeType, API, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

这是因为每个插件都有自己的 API.dll 程序集副本,尽管该程序集是相同的副本,但 mono 并不认为类型是相同的。

如何让插件从主应用程序目录加载 API.dll?或者,或者,我怎样才能让单声道看到类型相同?

4

1 回答 1

1

为了找到您问题的答案,我创建了一个简单的插件系统并在 Windows 下的 mono 3.2.3 上成功测试了它(不幸的是我现在无法在 Linux 上进行测试,也许明天)。我的代码:

SDK.dll

using System;

namespace SDK
{
    public interface IPlugin
    {
        void SomeMethod();

        SomeSDKType GetSDKType();

    }
}

using System;
using System.Collections.Generic;

namespace SDK
{
    [Serializable]
    public class StringEventArgs : EventArgs
    {

        public string Message { get; set; }

    }

    public class SomeSDKType : MarshalByRefObject
    {

        public event EventHandler<StringEventArgs> SDKEvent;

        public Action SDKDelegate;

        public void RiseSDKEvent(string message)
        {
            var handler = SDKEvent;
            if (handler != null) SDKEvent(this, new StringEventArgs { Message = message });
        }

        public Dictionary<int, string> GetDictionary()
        {
            var dict = new Dictionary<int, string> ();
            dict.Add(1, "One");
            dict.Add(2, "Two");
            return dict;
        }

    }
}

插件.dll

using System;
using SDK;

namespace Plugin
{
    public class Plugin : MarshalByRefObject, IPlugin
    {
        public Plugin()
        {
        }

        public void SomeMethod()
        {
            Console.WriteLine("SomeMethod");
        }

        public SomeSDKType GetSDKType()
        {
            var obj = new SomeSDKType();
            obj.SDKDelegate = () => Console.WriteLine("Delegate called from {0}", AppDomain.CurrentDomain.FriendlyName);
            return obj;
        }
    }
}

托管程序

using System;
using System.Reflection;
using System.IO;
using SDK;

namespace AppDomains
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            var domain = AppDomain.CreateDomain("Plugin domain"); // Domain for plugins
            domain.Load(typeof(IPlugin).Assembly.FullName); // Load assembly containing plugin interface to domain 

            var currentPath = Directory.GetCurrentDirectory();
            var pluginPath = Path.Combine(currentPath, "Plugins");
            var pluginFiles = Directory.GetFiles(pluginPath, "*.dll");
            foreach (var pluginFile in pluginFiles) // Foreach dll in Plugins directory
            {
                var asm = Assembly.LoadFrom(pluginFile);
                foreach (var exportedType in asm.GetExportedTypes())
                {
                    if (!typeof(IPlugin).IsAssignableFrom(exportedType)) continue; // Check if exportedType implement IPlugin interface
                    domain.Load(asm.FullName); // If so load this dll into domain
                    var plugin = (IPlugin)domain.CreateInstanceAndUnwrap(asm.FullName, exportedType.FullName); // Create plugin instance
                    plugin.SomeMethod(); // Call plugins methods
                    var obj = plugin.GetSDKType();
                    obj.SDKDelegate();
                    var dict = obj.GetDictionary();
                    foreach (var pair in dict)
                    {
                        Console.WriteLine("{0} - {1}", pair.Key, pair.Value);
                    }
                    obj.SDKEvent += obj_SDKEvent;
                    obj.RiseSDKEvent(string.Format("Argument from domain {0}", AppDomain.CurrentDomain.FriendlyName));
                }
            }
            Console.ReadLine();
        }

        static void obj_SDKEvent(object sender, StringEventArgs e)
        {
            Console.WriteLine("Received event in {0}", AppDomain.CurrentDomain.FriendlyName);
            Console.WriteLine(e.Message);
        }
    }
}

应用程序配置

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="Plugins"/>
    </assemblyBinding>
  </runtime>
</configuration>

对代码的一些解释。我用插件接口创建了 SDK dll。所有插件和宿主应用程序都必须引用它。必须在没有 SDK dll 的情况下提供插件,因为主机应用程序已经包含它。他们放入宿主应用程序目录中的Plugins目录(即,如果应用程序路径 = c:\MyApp插件位于c:\MyApp\Plugins中),以便为 CLR(或单声道)提供查找插件程序集的机会,我还创建了 App。带有探测元素的配置文件。

希望这可以帮助。

于 2013-10-24T17:56:27.150 回答