简体   繁体   English

C#动态加载/卸载DLL Redux(当然使用AppDomain)

[英]C# Dynamic Loading/Unloading of DLLs Redux (using AppDomain, of course)

I've read as many different version of this question as are on Stack Overflow, as well as every blue link on the front page of 3 different Google searches for tutorials, as well as the MSDN (which is kind of shallow beyond executing assemblies). 我已经阅读了Stack Overflow上这个问题的不同版本,以及3个不同Google搜索教程的首页上的每个蓝色链接,以及MSDN(除了执行程序集之外还有一点浅薄) 。 I can only think of my efforts to get Tao to work as a good test case, but believe me, I've tried with a simple string return, a double, a function with parameters, too. 我只能想到我努力让Tao作为一个好的测试用例,但是相信我,我尝试过一个简单的字符串返回,一个double,一个带参数的函数。 Whatever my problem is, it isn't Tao. 无论我的问题是什么,都不是道。

Basically I want to create a testLibraryDomain.CreateInstance() of my Draw class in the GLPlugin namespace. 基本上我想在GLPlugin命名空间中创建Draw类的testLibraryDomain.CreateInstance()

        if( usePlugin )
        {
                AppDomain testLibraryDomain = AppDomain.CreateDomain( "TestGLDomain2" );

                //What the heck goes here so that I can simply call
                //the default constructor and maybe a function or two?

                AppDomain.Unload( testLibraryDomain );
        }
        Gl.glBegin( Gl.GL_TRIANGLES );

I know for a fact that: 我知道一个事实:

namespace GLPlugin
{
    public class DrawingControl : MarshalByRefObject
    {
        public DrawingControl()
        {
            Gl.glColor3f( 1.0f , 0.0f , 0.0f );

            //this is a test to make sure it passes
            //to the GL Rendering context... success
        }
    }
}

indeed changes the pen color. 确实改变了笔的颜色。 It works when I give it a static void Main( string args[] ) entry point and I call testLibraryDomain.ExecuteAssembly( thePluginFilePath ) Whether or not a direct ExecuteAssembly would work had concerned me, as I was not certain the GL Calls would make it into the "top level" AppDomain's OpenGL context. 当我给它一个static void Main( string args[] )入口点并且我调用testLibraryDomain.ExecuteAssembly( thePluginFilePath )是否正常工作直接ExecuteAssembly是否可以工作关注我,因为我不确定GL调用是否会使它进入“顶级”AppDomain的OpenGL上下文。 It even lets me overwrite the assembly and change the Pen Color a second time. 它甚至可以让我覆盖组件并再次更换笔颜色。 Unfortunately giving it an executable entry point means that a popup console interrupts me then goes away. 不幸的是,给它一个可执行的入口点意味着弹出控制台会打断我然后消失。 It also works when I simply give it a reference in the Project and create a regular GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl() , or even creating a someAssembly = Assembly.LoadFrom( thePluginFilePath ) (which of course and unfortunately, locks the assembly, preventing replacement/recompilation). 当我在项目中简单地给它一个引用并创建一个常规的GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl() ,甚至创建一个someAssembly = Assembly.LoadFrom( thePluginFilePath ) ,它也有效(当然,不幸的是,它锁定了装配,防止更换/重新编译)。

When using any of the various methods I've tried, I always get "the given assembly name or its code base is invalid." 当我使用我尝试的各种方法时,我总是得到“给定的程序集名称或其代码库无效”。 I promise, it is valid. 我保证,这是有效的。 Something in the way I'm trying to load it is not. 我试图加载它的方式不是。

One thing I know I'm lacking is a correct setup for the testLibraryDomain.CreateInstance( string assemblyName , string typeName); 我知道我缺少的一件事是testLibraryDomain.CreateInstance( string assemblyName , string typeName);的正确设置testLibraryDomain.CreateInstance( string assemblyName , string typeName);

As far as I can tell, the assemblyName argument is not the filepath to the assembly file. 据我所知,assemblyName参数不是程序集文件的文件路径。 Is it the namespace, or even just the assembly name, ie: GLPlugin ? 它是命名空间,甚至只是程序集名称,即: GLPlugin If so, where do I reference the actual file? 如果是这样,我在哪里引用实际文件? There is no someAppDomain.LoadFrom( someFilename ), though it would be dang handy if there were. 没有someAppDomain.LoadFrom(someFilename),虽然如果有的话会很方便。 Additionally, what the heck is the Type, and string typeName at that? 另外,什么是Type,以及字符串typeName呢? I don't want to put in "Object" here do I, since is not creating type other than an instance of an object? 我不想在这里输入"Object" ,因为除了对象的实例之外不创建类型吗? I've also tried CreateInstanceAndUnwrap( ... , ... ) with the same lack of a fundamental understanding of AppDomain. 我也试过CreateInstanceAndUnwrap( ... , ... )同样缺乏对AppDomain的基本理解。 Usually I can muddle through tutorials and get things to work, even though I often don't understand the "Why?"... not so here. 通常我可以混淆教程并让事情发挥作用,即使我经常不理解“为什么?”......在这里不是这样。 Usually it is helpful for me to look up six different tutorials... not so here again, but because every one takes a fundamentally (or what appears to be so) approach. 通常,对我来说,查找六个不同的教程是有帮助的......在这里不再如此,但因为每个教程都采用了一种基本的(或似乎是这样的)方法。

So please ELI5... I want to load an instance of a class from a dll in a separate AppDomain, maybe run a few functions, and unload it. 所以请ELI5 ...我想在一个单独的AppDomain中从一个DLL加载一个类的实例,可能运行一些函数,然后卸载它。 Eventually create a list of these functions as List, removing/updating as necessary... I'd love to be able to pass arguments to them as well, but that will be step 2. According to StackOverflow, I have to learn about serializable which I will put off for another day. 最终创建一个列表,列出这些函数作为List,删除/更新必要......我也希望能够将参数传递给它们,但这将是第2步。根据StackOverflow,我必须学习可serializable我将推迟一天。 (I imagine you'll be able to figure from my example what I'm trying to do.) (我想你可以从我的例子中找到我想要做的事情。)

Ok, we have to clarify few things. 好的,我们必须澄清一些事情。 First, if you want to be able to load and unload dlls to different AppDomain without locking the file iteslf, maybe you can use approach like this: 首先,如果你想能够在不锁定文件iteslf的情况下将dll加载和卸载到不同的AppDomain,也许你可以使用这样的方法:

AppDomain apd = AppDomain.CreateDomain("newdomain");
using(var fs = new FileStream("myDll.dll", FileMode.Open))
{
    var bytes = new byte[fs.Length];
    fs.Read(bytes, 0, bytes .Length);
    Assembly loadedAssembly = apd.Load(bytes);
} 

This way, you won't be locking the file, and you should be able to later, unload domain, recompile the file and load it with newer version later. 这样,您就不会锁定该文件,您应该可以稍后卸载域,重新编译该文件并在以后使用较新版本加载它。 But i'm not 100% sure if this won't break your application. 但我不能100%确定这是否会破坏您的申请。

And that's because of second thing. 这是因为第二件事。 If you will be using CreateInstanceAndUnwrap method, according to MSDN, you have to load the assembly in both appdomains - the one that is calling, and the one from which you are calling. 如果您将使用CreateInstanceAndUnwrap方法,则根据MSDN,您必须在两个appdomains中加载程序集 - 正在调用的程序集和您要调用的程序集。 And this may end in a situation, when you have two different dlls loaded in AppDomains. 当您在AppDomains中加载两个不同的dll时,这可能会在某种情况下结束。

The assembly that contains unwrapped class must be loaded into both application domains, but it can load other assemblies that exist only in the new application domain. 必须将包含展开类的程序集加载到两个应用程序域中,但它可以加载仅存在于新应用程序域中的其他程序集。

I don't remember right now, but i think behavior of object creation in both app domains will be different when you will call CreateInstanceAndUnwrap , but i don't remember the details. 我现在不记得了,但我认为当你调用CreateInstanceAndUnwrap时,两个应用程序域中的对象创建行为都会有所不同,但我不记得细节。

For your plugin architecture, you may want to read this blog post. 对于您的插件架构,您可能想阅读此博文。 About how to handle Dynamic Plugins using the AppDomain Class to Load and Unload Code 关于如何使用AppDomain类加载和卸载代码来处理动态插件

EDIT 编辑

I forgot how this AppDomains works and i might introduce some confusion. 我忘了这个AppDomains是如何工作的,我可能会引入一些混乱。 I prepared short example how 'plugin' architecture might work. 我准备了“插件”架构如何工作的简短示例。 It's quite similar to what was described in blog i put earlier, and here is my sample which uses Shadow Copying. 它与我之前提到的博客中描述的内容非常相似,这里是我使用Shadow Copying的示例。 If for some reasons you don't want to use it, it can be quite easly changed to use AppDomain.Load(byte[] bytes) 如果由于某些原因你不想使用它,可以很容易地改为使用AppDomain.Load(byte[] bytes)

We have 3 assemblies, first one is base plugin assembly, which will work as a proxy, and will be loaded in all AppDomains (in our case - in main app domain and in plugin app domain). 我们有3个程序集,第一个是基本插件程序集,它将作为代理,并将加载到所有AppDomains(在我们的例子中 - 在主应用程序域和插件应用程序域中)。

namespace PluginBaseLib
{
    //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 
    {
        protected MyPluginBase ()
        { }

        public abstract void DrawingControl();
    }

    //Helper class which instance will exist in destination AppDomain, and which 
    //TransparentProxy object will be used in home AppDomain
    public class MyPluginFactory : MarshalByRefObject
    {
        //This method will be executed in destination AppDomain and proxy object
        //will be returned to home AppDomain.
        public MyPluginBase CreatePlugin(string assembly, string typeName)
        {
            Console.WriteLine("Current domain: {0}", AppDomain.CurrentDomain.FriendlyName);
            return (MyPluginBase) Activator.CreateInstance(assembly, typeName).Unwrap();
        }
    }

    //Small helper class which will show how to call method in another AppDomain. 
    //But it can be easly deleted. 
    public class MyPluginsHelper
    {
        public static void LoadMyPlugins()
        {
            Console.WriteLine("----------------------");
            Console.WriteLine("Loading plugins in following app domain: {0}", AppDomain.CurrentDomain.FriendlyName);
            AppDomain.CurrentDomain.Load("SamplePlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
            Console.WriteLine("----------------------");
        }
    }
}

Here we will have another assembly with our dummy plugin, called SamplePlugin.dll and stored under "Plugins" folder. 在这里,我们将使用我们的虚拟插件另一个程序集,名为SamplePlugin.dll并存储在“Plugins”文件夹下。 It has PluginBaseLib.dll referenced 它引用了PluginBaseLib.dll

namespace SamplePlugin
{
    public class MySamplePlugin : MyPluginBase
    {
        public MySamplePlugin()
        { }

        public override void DrawingControl()
        {
            var color = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("----------------------");
            Console.WriteLine("This was called from app domian {0}", AppDomain.CurrentDomain.FriendlyName );
            Console.WriteLine("I have following assamblies loaded:");
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                Console.WriteLine("\t{0}", assembly.GetName().Name);
            }
            Console.WriteLine("----------------------");
            Console.ForegroundColor = color;
        }
    }
}

And last assembly (simple console app) which will reference only PluginBaseLib.dll and 最后一个程序集(简单的控制台应用程序)将只引用PluginBaseLib.dll和

namespace ConsoleApplication1
{
    //'Default implementation' which doesn't use any plugins. In this sample 
    //it just lists the assemblies loaded in AppDomain and AppDomain name itself.
    public static void DrawControlsDefault()
    {
        Console.WriteLine("----------------------");
        Console.WriteLine("No custom plugin, default app domain {0}", AppDomain.CurrentDomain.FriendlyName);
        Console.WriteLine("I have following assamblies loaded:");
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            Console.WriteLine("\t{0}", assembly.GetName().Name);
        }
        Console.WriteLine("----------------------");
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Showing that we don't have any additional plugins loaded in app domain. 
            DrawControlsDefault();

            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 = Path.Combine(appDir,"Plugins"),//Base path for new app domain - our plugins folder
                                     CachePath = "VSSCache"//Path, where we want to have our copied dlls store. 
                                 };
        var apd = AppDomain.CreateDomain("My new app domain", null, appDomainSetup);

        //Loading dlls in new appdomain - when using shadow copying it can be skipped,
        //in CreatePlugin method all required assemblies will be loaded internaly,  
        //Im using this just to show how method can be called in another app domain. 
        //but it has it limits - method cannot return any values and take any parameters.

        //apd.DoCallBack(new CrossAppDomainDelegate(MyPluginsHelper.LoadMyPlugins));

        //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();

        //if we would use here method (MyPluginBase) apd.CreateInstance("SamplePlugin", "SamplePlugin.MySamplePlugin").Unwrap();
        //we would have to load "SamplePlugin.dll" into our app domain. We may not want that, to not waste memory for example
        //with loading endless number of types.
        var instance = proxy.CreatePlugin("SamplePlugin", "SamplePlugin.MySamplePlugin");
        instance.DrawingControl();

        Console.WriteLine("Now we can recompile our SamplePlugin dll, replace it in Plugin directory and load in another AppDomain. Click Enter when you ready");
        Console.ReadKey();

        var apd2 = AppDomain.CreateDomain("My second domain", null, appDomainSetup);
        var proxy2 = (MyPluginFactory)apd2.CreateInstance("PluginBaseLib", "PluginBaseLib.MyPluginFactory").Unwrap();
        var instance2 = proxy2.CreatePlugin("SamplePlugin", "SamplePlugin.MySamplePlugin");
        instance2.DrawingControl();

        //Now we want to prove, that this additional assembly was not loaded to prmiary app domain. 
        DrawControlsDefault();

        //And that we still have the old assembly loaded in previous AppDomain.
        instance.DrawingControl();

        //App domain is unloaded so, we will get exception if we try to call any of this object method.
        AppDomain.Unload(apd);
        try
        {
            instance.DrawingControl();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }

        Console.ReadKey();
    }
}

} }

Shadow copying seems to be very convenient. 阴影复制似乎非常方便。

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

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