【问题标题】:C# Dynamic Loading/Unloading of DLLs Redux (using AppDomain, of course)C# 动态加载/卸载 DLL Redux(当然使用 AppDomain)
【发布时间】:2012-11-07 21:21:25
【问题描述】:

我已经阅读了与 Stack Overflow 上一样多的这个问题的不同版本,以及 3 个不同的 Google 搜索教程首页上的每个蓝色链接,以及 MSDN(这有点超出执行程序集)。我只能将我为使 Tao 工作的努力视为一个好的测试用例,但请相信我,我也尝试过使用简单的字符串返回、双精度数和带参数的函数。不管我的问题是什么,都不是道。

基本上我想在 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 );

我知道一个事实:

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

确实改变了笔的颜色。当我给它一个static void Main( string args[] ) 入口点并调用testLibraryDomain.ExecuteAssembly( thePluginFilePath ) 时,它可以工作,直接的 ExecuteAssembly 是否可以工作让我很担心,因为我不确定 GL 调用是否会使其进入“顶级”AppDomain 的 OpenGL 上下文.它甚至可以让我再次覆盖程序集并更改笔颜色。不幸的是,给它一个可执行的入口点意味着弹出控制台会打断我然后消失。当我在项目中简单地给它一个引用并创建一个常规的GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl(),甚至创建一个someAssembly = Assembly.LoadFrom( thePluginFilePath )(当然,不幸的是,这会锁定程序集,防止替换/重新编译)时,它也可以工作。

使用我尝试过的各种方法时,我总是得到“给定的程序集名称或其代码库无效”。我保证,它是有效的。我试图加载它的方式不是。

我知道我缺少的一件事是testLibraryDomain.CreateInstance( string assemblyName , string typeName); 的正确设置

据我所知,assemblyName 参数不是程序集文件的文件路径。它是命名空间,还是只是程序集名称,即:GLPlugin?如果是这样,我在哪里引用实际文件?没有 someAppDomain.LoadFrom( someFilename ),但如果有的话会很方便。此外,Type 和字符串 typeName 到底是什么?我不想在这里输入"Object",因为不是创建对象实例以外的类型吗?我也尝试过CreateInstanceAndUnwrap( ... , ... ),但同样缺乏对 AppDomain 的基本了解。通常我可以应付教程并让事情发挥作用,即使我经常不明白“为什么?”......在这里不是这样。通常,查找六个不同的教程对我很有帮助……这里不再是这样,而是因为每个人都从根本上(或看起来如此)采取了一种方法。

所以请 ELI5... 我想从一个单独的 AppDomain 中的 dll 加载一个类的实例,也许运行一些函数,然后卸载它。最终将这些函数的列表创建为 List,根据需要删除/更新......我也希望能够将参数传递给它们,但这将是第 2 步。根据 StackOverflow,我必须了解 @ 987654332@ 我将推迟一天。 (我想你可以从我的例子中看出我想要做什么。)

【问题讨论】:

    标签: c# .net dynamic dll assemblies


    【解决方案1】:

    好的,我们必须澄清一些事情。首先,如果您希望能够在不锁定文件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);
    } 
    

    这样,您将不会锁定文件,并且您应该能够稍后卸载域,重新编译文件并在以后使用更新的版本加载它。但我不能 100% 确定这是否会破坏您的应用程序。

    那是因为第二件事。如果您将使用 CreateInstanceAndUnwrap 方法,根据 MSDN,您必须在两个应用程序域中加载程序集 - 正在调用的应用程序域和您正在调用的应用程序域。当您在 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.

    我现在不记得了,但我认为当您调用CreateInstanceAndUnwrap 时,两个应用程序域中的对象创建行为会有所不同,但我不记得细节了。

    对于您的插件架构,您可能需要阅读这篇博文。 About how to handle Dynamic Plugins using the AppDomain Class to Load and Unload Code

    编辑

    我忘记了这个 AppDomains 是如何工作的,我可能会引起一些混乱。我准备了一个简短的例子,'插件'架构如何工作。它与我之前在博客中描述的非常相似,这是我使用影子复制的示例。如果由于某些原因不想使用它,可以很容易地更改为使用AppDomain.Load(byte[] bytes)

    我们有 3 个程序集,第一个是基本插件程序集,它将作为代理工作,并将加载到所有 AppDomain(在我们的例子中 - 在主应用程序域和插件应用程序域中)。

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

    在这里,我们将有另一个带有我们的虚拟插件的程序集,称为 SamplePlugin.dll 并存储在“Plugins”文件夹下。它引用了 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;
            }
        }
    }
    

    最后一个程序集(简单的控制台应用程序)将仅引用 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();
        }
    }
    

    }

    影印似乎很方便。

    【讨论】:

    • 要在不锁定的情况下加载程序集,我认为最好使用影子复制,请参阅msdn.microsoft.com/en-us/library/ms404279.aspx
    • 我对你的例子很好奇,可能还是因为缺乏基础知识......它将新的loadedAssembly“应用”到apd AppDomain 而不是默认的顶部等级?它只是隐含在订单中,就像您创建了一个 AppDomain 一样,那么它下面的所有内容都是它的一部分,直到Unload()?另外,很棒的文章,虽然有点高高在上。我阅读了其中一部分被盗版并放在广告网站上的内容,但很高兴拥有完整的文章。我将尝试通过今天的课程真正从中学习。有点希望它有一个功能性的源文件。
    • 我不确定我是否理解你的问题,但我会在今天晚些时候尝试使用我的旧应用程序中的一些示例和 cmets。
    • 我在玩影子复制,我必须说,它非常方便。我还添加了一个允许“插件”的简单示例应用程序
    • 是否可以让加载的 DLL 生成事件?我尝试从 SamplePlugin 添加发送事件,但尝试在 ConsoleApplication1 中附加接收器时出现错误。如果这应该是可能的,你能不能说明如何?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    • 1970-01-01
    相关资源
    最近更新 更多