6

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

基本上我想testLibraryDomain.CreateInstance()在 GLPlugin 命名空间中创建一个我的 Draw 类。

        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,我必须了解serializable哪个我会再推迟一天。(我想您将能够从我的示例中弄清楚我正在尝试做什么。)

4

1 回答 1

11

好的,我们必须澄清一些事情。首先,如果您希望能够在不锁定文件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 中加载程序集 - 一个正在调用,另一个是您从中调用。当您在 AppDomains 中加载了两个不同的 dll 时,这可能会结束。

包含解包类的程序集必须加载到两个应用程序域中,但它可以加载仅存在于新应用程序域中的其他程序集。

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

对于您的插件架构,您可能需要阅读这篇博文。关于如何处理动态插件使用 AppDomain 类加载和卸载代码

编辑

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

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

在这里,我们将有另一个带有我们的虚拟插件的程序集,称为 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();
    }
}

}

影印似乎很方便。

于 2012-11-19T21:47:27.523 回答