簡體   English   中英

C#動態加載/卸載DLL Redux(當然使用AppDomain)

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

我已經閱讀了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 ) ,它也有效(當然,不幸的是,它鎖定了裝配,防止更換/重新編譯)。

當我使用我嘗試的各種方法時,我總是得到“給定的程序集名稱或其代碼庫無效”。 我保證,這是有效的。 我試圖加載它的方式不是。

我知道我缺少的一件事是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();
    }
}

}

陰影復制似乎非常方便。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM