1
votes

I'm creating a plugin framework with C#. The main requirements of the framework is to load, unload and update plugins at runtime.

To accomplish this I've been creating AppDomains and loading the plugin assemblies into the AppDomains.

Everything works fine on Microsoft .NET on windows but the plugins do not work on mono running on mac or linux.

When trying to start a plugin I get an exception like this:

Cannot cast argument 0 of type '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' to type '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'

This is caused because each plugin has it's own copy of the API.dll assembly, and though the assembly is an identical copy, mono doesn't see the Types as being the same.

How can I get the plugin to load the API.dll from the main application's directory? Or, alternatively, how can I get mono to see the Types as being the same?

1

1 Answers

1
votes

Well to find answer to your question I created a simple plugin system and tested it on mono 3.2.3 under the Windows successfully (unfortunately I can't make test on Linux now, maybe tomorrow). My code:

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;
        }

    }
}

Plugin.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;
        }
    }
}

Hosting program

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);
        }
    }
}

App.config

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

Some explanation to code. I created SDK dll with plugin interface. All plugins and host app must reference to it. Plugins must be provided without SDK dll because host app already contains it. They puts into the Plugins directory in the host application directory (ie if app path = c:\MyApp the plugins are in c:\MyApp\Plugins) so to provide CLR (or mono) opportunity to find plugin assemplies I also created App.config file with probing element.

Hope this helps.