简体   繁体   English

在加载的appdomain中订阅事件

[英]Subscribing to event in loaded appdomain

I have 2 problems handling events across appdomains. 我在处理appdomains上的事件时有2个问题。 I have my main appdomain and a new appdomain that loads a plugin, in total 1 exe and 2 dll's. 我有我的主appdomain和一个新的appdomain加载一个插件,总共1个exe和2个dll。 Calling methods in the loaded plugin from the main domain works, but I am not able to receive an event generated in the plugin into the main appdomain. 从主域调用加载的插件中的方法可以正常工作,但是我无法在插件中接收到主appdomain中生成的事件。 My event receiving method is however being in the context of the new appdomain (that must have loaded it I guess... I am confused here). 我的事件接收方法是在新appdomain的上下文中(必须加载它我猜...我在这里很困惑)。 I have worked based on the solution provided here C# Dynamic Loading/Unloading of DLLs Redux (using AppDomain, of course) as my final program is to be loading and unloading plugins on the fly. 我基于这里提供的解决方案工作C#动态加载/卸载DLL Redux(当然使用AppDomain),因为我的最终程序是即时加载和卸载插件。 Here is the code so far: 这是迄今为止的代码:

This is DynamicLoadText.exe: 这是DynamicLoadText.exe:

namespace DynamicLoadTest
{
    class Program
    {
        static void EventReceiver(object sender, EventArgs e)
        {
            Console.WriteLine("[{1}] Received an event from {0}", sender.ToString(), AppDomain.CurrentDomain.FriendlyName);
        }

        static void Main(string[] args)
        {
            var appDir = AppDomain.CurrentDomain.BaseDirectory;

            var appDomainSetup = new AppDomainSetup
                                 {
                                     ApplicationName = "",
                                     ShadowCopyFiles = "true",
                                     ApplicationBase = Path.Combine(appDir, "Plugins"),
                                     CachePath = "VSSCache"
                                 };

            var apd = AppDomain.CreateDomain("My new app domain", null, appDomainSetup);
            var proxy = (MyPluginFactory)apd.CreateInstance("PluginBaseLib", "PluginBaseLib.MyPluginFactory").Unwrap();
            var instance = proxy.CreatePlugin("SamplePlugin", "SamplePlugin.MySamplePlugin");

            instance.MyEvent += EventReceiver;
            instance.TestEvent();
            instance.MyEvent -= EventReceiver;

            AppDomain.Unload(apd);
        }
    }
}

This is PluginBaseLib.dll: 这是PluginBaseLib.dll:

namespace PluginBaseLib
{
    public abstract class MyPluginBase : MarshalByRefObject
    {
        public abstract event EventHandler MyEvent;

        protected MyPluginBase()
        { }

        public abstract void TestEvent();
    }

    public class MyPluginFactory : MarshalByRefObject
    {
        public MyPluginBase CreatePlugin(string assemblyName, string typeName)
        {
            return (MyPluginBase)Activator.CreateInstance(assemblyName, typeName).Unwrap();
        }
    }
}

And this is SamplePlugin.dll: 这是SamplePlugin.dll:

namespace SamplePlugin
{
    public class MySamplePlugin : MyPluginBase
    {
        public override event EventHandler MyEvent;

        public MySamplePlugin()
        { }

        public override void TestEvent()
        {
            if (MyEvent != null)
                MyEvent(this, new EventArgs());
        }
    }
}

The file-placement prior to execution of the DynamicLoadTest.exe is this: 执行DynamicLoadTest.exe之前的文件放置是这样的:

dir\DynamicLoadText.exe
dir\PluginBaseLib.dll
dir\Plugins\PluginBaseLib.dll
dir\Plugins\SamplePlugin.dll

PluginBaseLib is present twice as both DynamicLoadText.exe and SamplePlugin.dll depend on it. PluginBaseLib存在两次,因为DynamicLoadText.exe和SamplePlugin.dll都依赖于它。

When I start the exe, all goes well until it hits the "+=" at which point I get a TargetInvocationException (InnerException: FileNotFoundException): 当我启动exe时,一切顺利,直到它达到“+ =”,此时我得到一个TargetInvocationException(InnerException:FileNotFoundException):

{"Could not load file or assembly 'DynamicLoadTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.":"DynamicLoadTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"} {“无法加载文件或程序集'DynamicLoadTest,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'或其依赖项之一。系统找不到指定的文件。”:“DynamicLoadTest,Version = 1.0.0.0, Culture = neutral,PublicKeyToken = null“}

I tried to move SamplePlugin.dll to the same directory as DynamicLoadTest.exe and changed the AppDomainSetup.ApplicationBase to point at the base directory and run again. 我尝试将SamplePlugin.dll移动到与DynamicLoadTest.exe相同的目录,并将AppDomainSetup.ApplicationBase更改为指向基目录并再次运行。 This made it all run without throwing exceptions and I even got en event: 这使得它全部运行而没有抛出异常,我甚至得到了事件:

[My new app domain] Received an event from SamplePlugin.MySamplePlugin [我的新应用程序域]从SamplePlugin.MySamplePlugin收到一个事件

I am now concerned that my EventReceiver that received the event isn't the one in the main appdomain, but is a clone running in the new appdomain (the one that holds the plugin). 我现在担心收到事件的EventReceiver不是主appdomain中的那个,而是在新appdomain(持有插件的那个)中运行的克隆。

So, how to I make this just right, ie having the plugin dll's be in the Plugins directory and receiving the event in the main appdomain's EventReceiver method? 那么,我如何才能做到这一点,即让插件dll位于Plugins目录中并在主appdomain的EventReceiver方法中接收事件?

Edit: 编辑:

I have tried to get more insight into this but failed. 我试图更深入地了解这一点,但失败了。 The only way I can subscribe to the event in the secondary appdomain is when the secondary appdomain loads the primary appdomain (which then exists twice). 我可以在辅助appdomain中订阅事件的唯一方法是辅助appdomain加载主appdomain(然后存在两次)。 I have checked it using a statically assigned key in the primary appdomain which I change during runtime. 我在主appdomain中使用静态分配的密钥检查了它,我在运行时更改了该密钥。 The change is only seen from the primary appdomain. 只能从主应用程序域中看到此更改。 The key is the statically assigned value when seen from the event handler when triggered by the secondary appdomain. 键是从辅助应用程序域触发时从事件处理程序中看到的静态分配值。 This is clearly not what I want. 这显然不是我想要的。

I have found no guide or explanation (I could understand) explaining if what I try is possible or not. 我没有找到任何指导或解释(我能理解),解释我尝试的是否可行。

I found a solution to the issue where my event handler (Program.EventReceiver) would be created in the secondary appdomain and handled events there. 我找到了一个问题的解决方案,我的事件处理程序(Program.EventReceiver)将在辅助appdomain中创建并在那里处理事件。 The issue is that the Program class isn't inheriting from MarshalByRefObject but seems to be serializable. 问题是Program类不是从MarshalByRefObject继承而是似乎是可序列化的。 I did not want Program to inherit MarshalByRefObject (actually don't know if it is allowed) so I moved Program.EventReceiver into its own class that inherited from MarshalByRefObject. 我不希望Program继承MarshalByRefObject(实际上不知道是否允许)所以我将Program.EventReceiver移动到自己的继承自MarshalByRefObject的类中。 I instantiated that class in Program and now EventReceiver is receiving events while existing in the primary appdomain. 我在Program中实例化了该类,现在EventReceiver正在接收主应用程序域中存在的事件。 Success all around. 周围的成功。

Without specifying MarshalByRefObject on Program, it was being serialized (duplicated) and this was not what I wanted. 没有在Program上指定MarshalByRefObject,它被序列化(重复),这不是我想要的。

This is what I ended up with. 这就是我最终的结果。 Please note I never got to put it into production and I think that the plugin may be unloaded automatically during garbage collection or object timeout handled not by my code. 请注意我从来没有把它投入生产,我认为插件可能会在垃圾收集或对象超时期间自动卸载,而不是由我的代码处理。 There is a workaround, but I cannot remember it. 有一种解决方法,但我不记得了。

DynamicLoadTest: DynamicLoadTest:

class Program
{
    public static string key = "I am in the WRONG assembly.";

    static void Main(string[] args)
    {
        key = "I am in the primary assembly";
        plap pop = new plap();

        var appDir = AppDomain.CurrentDomain.BaseDirectory;
        //We have to create AppDomain setup for shadow copying 
        var appDomainSetup = new AppDomainSetup
                             {
                                 ApplicationName = "", //with MSDN: If the ApplicationName property is not set, the CachePath property is ignored and the download cache is used. No exception is thrown.
                                 ShadowCopyFiles = "true",//Enabling ShadowCopy - yes, it's string value
                                 ApplicationBase = appDir,//Base path for new app domain - our plugins folder
                                 CachePath = "VSSCache",//Path, where we want to have our copied dlls store.                                      
                             };
        Console.WriteLine("Looking for plugins in {0}\\Plugins", appDir);
        var apd = AppDomain.CreateDomain("My new app domain", null, appDir, "Plugins", true);

        //We are creating our plugin proxy/factory which will exist in another app domain 
        //and will create for us objects and return their remote 'copies'. 
        var proxy = (MyPluginFactory)apd.CreateInstance("PluginBaseLib", "PluginBaseLib.MyPluginFactory").Unwrap();

        var instance = proxy.CreatePlugin("SamplePlugin", "SamplePlugin.MySamplePlugin");

        Console.WriteLine("[appdomain:{0}] Main: subscribing for event from remote appdomain.", AppDomain.CurrentDomain.FriendlyName);
        instance.MyEvent += pop.EventReceiver;

        instance.TestEvent();

        Console.WriteLine("[appdomain:{0}] Main: Waiting for event (press return to continue).", AppDomain.CurrentDomain.FriendlyName);
        Console.ReadKey();
        instance.MyEvent -= pop.EventReceiver;

        Console.WriteLine("[appdomain:{0}] Main: Unloading appdomain: {1}", AppDomain.CurrentDomain.FriendlyName, apd.FriendlyName);
        AppDomain.Unload(apd);
        Console.ReadKey();
    }
}

class plap : MarshalByRefObject
{
    public plap() { }

    public void EventReceiver(object sender, EventArgs e)
    {
        Console.WriteLine("[appdomain:{1}] Received an event from {0} [key: {2}]", sender.ToString(), AppDomain.CurrentDomain.FriendlyName, Program.key);
    }
}

MyPluginBase: MyPluginBase:

//Base class for plugins. It has to be delivered from MarshalByRefObject,
//cause we will want to get it's proxy in our main domain. 
public abstract class MyPluginBase : MarshalByRefObject
{
    public abstract event EventHandler MyEvent;

    protected MyPluginBase()
    { }

    public abstract void TestEvent();
}

//Helper class which instance will exist in destination AppDomain, and which 
//TransparentProxy object will be used in home AppDomain
public class MyPluginFactory : MarshalByRefObject
{
    //public event EventHandler MyEvent;

    //This method will be executed in destination AppDomain and proxy object
    //will be returned to home AppDomain.
    public MyPluginBase CreatePlugin(string assemblyName, string typeName)
    {
        Console.WriteLine("[appdomain:{0}] CreatePlugin {1} {2}", AppDomain.CurrentDomain.FriendlyName, assemblyName, typeName);
        return (MyPluginBase)Activator.CreateInstance(assemblyName, typeName).Unwrap();
    }
}

MySamplePlugin: MySamplePlugin:

public class MySamplePlugin : MyPluginBase
{
    public override event EventHandler MyEvent;

    public MySamplePlugin()
    { }

    public override void TestEvent()
    {
        Console.WriteLine("[appdomain:{0}] TestEvent: setting up delayed event.", AppDomain.CurrentDomain.FriendlyName);
        System.Threading.Timer timer = new System.Threading.Timer((x) => {
            Console.WriteLine("[appdomain:{0}] TestEvent: firing delayed event.", AppDomain.CurrentDomain.FriendlyName);
            if (MyEvent != null)
                MyEvent(this, new EventArgs());            
        }, null, 1000, System.Threading.Timeout.Infinite);
    }
}

I use these post-build commands: 我使用这些post-build命令:

DynamicLoadTest: DynamicLoadTest:

mkdir "$(TargetDir)Plugins" || cmd /c "exit /b 0"
mkdir "$(TargetDir)VSSCache" || cmd /c "exit /b 0"

SamplePlugin: SamplePlugin:

xcopy /i /e /s /y /f "$(TargetPath)" "$(SolutionDir)DynamicLoadTest\bin\$(ConfigurationName)\Plugins\"
xcopy /i /e /s /y /f "$(TargetDir)PluginBaseLib.dll" "$(SolutionDir)DynamicLoadTest\bin\$(ConfigurationName)\Plugins\"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM