0

これらの要件を持つプラグイン フレームワークをまとめています。

  • 自由にプラグインをロード/アンロード
  • 読み込まれたプラグインのメソッドを呼び出す
  • プラグインから所有者へのコールバック イベントを発生させる

これを行うには、新しい AppDomain を作成し、プラグイン アセンブリをこれにロードします。

これまでの実装はある程度機能していますが、ローカルの appDomain と新しい AppDomain にプラグインのインスタンスを作成していると思います。

最初にロードすると、コールバック メッセージが重複して表示されます。複数回ロード/アンロードすると、複数のコールバック メッセージがリストに追加されます。これは、リモートだけでなくローカルでもプラグイン アセンブリをロードしているため、「アンロード」メカニズムが期待どおりに動作していないことを示しています。誰かが私が間違っている場所を教えてくれたらありがたいです。

また、プラグインの「寿命」を考慮する必要があることも理解していますが、これを実装する場所がわかりません。

ありがとう。

(1) プラグイン インターフェイスがあります

public interface IPlugin
{
    string Name();
    string Version();
    string RunProcess();

    // custom event handler to be implemented, event arguments defined in child class
    event EventHandler<PluginEventArgs> CallbackEvent;
    //event EventHandler<EventArgs> CallbackEvent;
    void OnProcessStart(PluginEventArgs data);
    void OnProcessEnd(PluginEventArgs data);
}

(2) カスタムイベント引数

[Serializable]
public class PluginEventArgs : EventArgs
{
    public string ResultMessage;
    public string executingDomain;

    public PluginEventArgs(string resultMessage = "")
    {
        // default empty values allows us to send back default event response
        this.ResultMessage = resultMessage;
        this.executingDomain = AppDomain.CurrentDomain.FriendlyName;
    }
}

(3) プラグインクラスの実装例

 [Serializable]
    public class Plugin_1 : IPlugin
    {
        System.Timers.Timer counter;
        int TimerInterval;
        string PluginName = "My plugin";

        public string Name()
        {
            return "CMD";
        }

        public bool Start()
        {
            OnStart(new PluginEventArgs());
            RunProcess();
            return true;
        }

        // OnTimer event, process start raised, sleep to simulate doing some work, then process end raised
        public void OnCounterElapsed(Object sender, EventArgs e)
        {
            OnProcessStart(new PluginEventArgs());
            OnProcessEnd(new PluginEventArgs());
            Stop();
        }

        public bool Stop()
        {
            // simulate waiting for process to finish whatever its doing....
            if (counter != null)
            {
                counter.Stop();
                OnStop(new PluginEventArgs());
            }
            return true;
        }

        public string RunProcess()
        {
            TimerInterval = 2000;
            if (counter == null){ 
                counter = new System.Timers.Timer(TimerInterval); 
                }
            else { 
                counter.Stop();
                counter.Interval = TimerInterval;
                }

            counter.Elapsed += OnCounterElapsed;
            counter.Start();
            return "";
        }

        public event EventHandler<PluginEventArgs> CallbackEvent;

        void OnCallback(PluginEventArgs e)
        {
            if (CallbackEvent != null)
            {
                CallbackEvent(this, e);
            }                    
        }


        public void OnProcessStart(PluginEventArgs Data)
        {
            OnCallback(new PluginEventArgs(Data.executingDomain + " - " + PluginName + " started"));   
        }

        public void OnProcessEnd(PluginEventArgs Data)
        {
            OnCallback(new PluginEventArgs(Data.executingDomain + " - " + PluginName + " ended"));   
        }

(4) ロード/アンロードするプラグイン マネージャがあります

public bool LoadPlugin() { try { Domain_Command = AppDomain.CreateDomain("Second_domain"); command_loader = (ProxyLoader)Domain_Command.CreateInstanceAndUnwrap("PluginMgr", "PluginMgr.Method"); Plugins.AddPlugin(command_loader.LoadAndExecute("APluginName", Plugins.ProxyLoader_RaiseCallbackEvent), SomePluginType, false);
true を返します。
} catch (Exception ex) { 文字列メッセージ = ex.Message; false を返します。} }

(5) プラグインを別の AppDomain にロードするための「ProxyLoader」

public class ProxyLoader : MarshalByRefObject
{
    public AssemblyInstanceInfo LoadAndExecute(string assemblyName, EventHandler<PluginContract.PluginEventArgs> proxyLoader_RaiseCallbackEvent)
    {
        AssemblyInstanceInfo AInfo = new AssemblyInstanceInfo();
        //nb: this AppDomain.CurrentDomain is in its own context / different from the caller app domain?
        Assembly pluginAssembly = AppDomain.CurrentDomain.Load(assemblyName);
        foreach (Type type in pluginAssembly.GetTypes())
        {
            if (type.GetInterface("IPlugin") != null)
            {
                object instance = Activator.CreateInstance(type, null, null);
                AInfo.ObjectInstance = instance;
                string s = ((PluginContract.IPlugin)instance).RunProcess(); // main procedure
                AInfo.ASM = pluginAssembly;
                ((PluginContract.IPlugin)instance).CallbackEvent += proxyLoader_RaiseCallbackEvent;
                ((PluginContract.IPlugin)instance).Start();
                instance = null;
            }
        }
        return AInfo;
    }
}

(6) これがプラグインするコールバックがあります

public event EventHandler<PluginContract.PluginEventArgs> Callback;

void OnCallback(PluginContract.PluginEventArgs e)
        {
            if (Callback != null)
            {
                Callback(this, e);
            }
        }

(7) によって呼び出されます (アセンブリをロードするときに ProxyLoader で参照されます)

public void ProxyLoader_RaiseCallbackEvent(object source, PluginContract.PluginEventArgs e)
{
    OnCallback(new PluginContract.PluginEventArgs(str));
}
4

0 に答える 0