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

我已经阅读了Stack Overflow上这个问题的许多不同版本,以及3个不同Google搜索教程的首页上的每个蓝色链接,以及MSDN(除了执行程序集之外还有点浅薄) 。 我只能想到我努力让Tao作为一个好的测试用例,但请相信我,我尝试过一个简单的字符串返回,一个double,一个带参数的函数。 无论我的问题是什么,都不是道。

基本上我想在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 ) (当然,不幸的是,它锁定了assembly,防止更换/重新编译)。

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

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

据我所知,assemblyName参数不是程序集文件的文件路径。 它是命名空间,甚至只是程序集名称,即: GLPlugin ? 如果是这样,我在哪里引用实际文件? 没有someAppDomain.LoadFrom(someFilename),但如果有的话,它会很方便。 另外,什么是Type,以及字符串typeName? 我不想在这里输入"Object" ,因为除了对象的实例之外不创建类型吗? 我也尝试过CreateInstanceAndUnwrap( ... , ... )同样缺乏对AppDomain的基本理解。 通常我可以混淆教程并开始工作,即使我经常不理解“为什么?”……在这里不是这样。 通常,我查找六个不同的教程是有帮助的……在这里不再如此,但是因为每个教程都采用了一种基本的(或似乎是这样的)方法。

所以请ELI5 …我想在一个单独的AppDomain中从一个DLL加载一个类的实例,可能运行一些函数,然后卸载它。 最终创建这些函数的列表作为List,根据需要删除/更新…我也希望能够将参数传递给它们,但这将是第2步。根据StackOverflow,我必须学习可serializable我将推迟一天。 (我想你可以从我的例子中找出我想要做的事情。)

好的,我们必须澄清一些事情。 首先,如果您希望能够在不锁定文件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是如何工作的,我可能会引入一些混乱。 我准备了“插件”架构如何工作的简短示例。 它与我之前提到的博客中描述的内容非常相似,这是我使用Shadow Copying的示例。 如果由于某些原因你不想使用它,可以很容易地改为使用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(); } } 

}

阴影复制似乎非常方便。