2012-11-19 40 views
5

Tôi đã đọc nhiều phiên bản khác nhau của câu hỏi này như trên Stack Overflow, cũng như mọi liên kết màu xanh trên trang đầu của 3 tìm kiếm khác nhau của Google cho hướng dẫn, cũng như MSDN (là loại nông cạn ngoài việc thực hiện các hội đồng). Tôi chỉ có thể nghĩ đến những nỗ lực của mình để giúp Tao làm việc như một trường hợp thử nghiệm tốt, nhưng hãy tin tôi, tôi đã thử với một chuỗi trả về đơn giản, một đôi, một hàm với các thông số. Dù vấn đề của tôi là gì, nó không phải là Tao.C# Dynamic Loading/Unloading DLLs Redux (sử dụng AppDomain, tất nhiên)

Về cơ bản tôi muốn tạo một lớp testLibraryDomain.CreateInstance() của lớp Vẽ trong không gian tên GLPlugin.

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

Tôi biết một thực tế rằng:

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

thực sự làm thay đổi màu bút. Nó hoạt động khi tôi cung cấp cho nó một điểm nhập static void Main(string args[]) và tôi gọi testLibraryDomain.ExecuteAssembly(thePluginFilePath) Có hay không một ExecuteAssembly trực tiếp sẽ làm việc có liên quan đến tôi, vì tôi đã không chắc chắn các cuộc gọi GL sẽ làm cho nó vào "cấp cao nhất" AppDomain của OpenGL bối cảnh. Nó thậm chí còn cho phép tôi ghi đè lên hội đồng và thay đổi Màu Bút lần thứ hai. Thật không may cho nó một điểm nhập thực thi có nghĩa là một giao diện điều khiển popup ngắt tôi sau đó biến mất. Nó cũng hoạt động khi tôi chỉ cần cung cấp cho nó một tham chiếu trong dự án và tạo ra một thường lệ GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl(), hoặc thậm chí tạo ra một someAssembly = Assembly.LoadFrom(thePluginFilePath) (mà tất nhiên và không may, khóa lắp ráp, ngăn chặn thay thế/biên dịch lại).

Khi sử dụng bất kỳ phương pháp nào khác nhau mà tôi đã thử, tôi luôn nhận được "tên lắp ráp đã cho hoặc mã cơ sở của nó không hợp lệ". Tôi hứa, nó hợp lệ. Một cái gì đó trong cách tôi đang cố gắng để tải nó không phải là.

Một điều tôi biết tôi đang thiếu là một thiết lập chính xác cho các testLibraryDomain.CreateInstance(string assemblyName , string typeName);

Theo như tôi có thể nói, lập luận AssemblyName không phải là filepath vào file lắp ráp. Là không gian tên, hoặc thậm chí chỉ là tên lắp ráp, ví dụ: GLPlugin? Nếu vậy, tôi sẽ tham khảo tệp thực sự ở đâu? Không có một sốAppDomain.LoadFrom (someFilename), mặc dù nó sẽ là tiện dụng nếu có. Ngoài ra, những gì heck là Type, và string typeName tại đó? Tôi không muốn để trong "Object" ở đây làm tôi, vì không phải là tạo ra loại khác hơn là một thể hiện của một đối tượng? Tôi cũng đã thử CreateInstanceAndUnwrap(... , ...) với sự thiếu hiểu biết cơ bản về AppDomain. Thông thường tôi có thể lộn xộn thông qua hướng dẫn và nhận được những thứ để làm việc, mặc dù tôi thường không hiểu "Tại sao?" ... không phải như vậy ở đây. Thông thường nó rất hữu ích cho tôi để tìm kiếm sáu hướng dẫn khác nhau ... không phải như vậy ở đây một lần nữa, nhưng bởi vì mỗi người có một cách tiếp cận cơ bản (hoặc những gì dường như được như vậy).

Vì vậy, xin vui lòng ELI5 ... Tôi muốn tải một thể hiện của một lớp từ một dll trong một AppDomain riêng biệt, có thể chạy một vài chức năng, và dỡ bỏ nó. Cuối cùng tạo ra một danh sách các chức năng như Danh sách, loại bỏ/cập nhật khi cần thiết ... Tôi muốn có thể vượt qua đối số cho họ là tốt, nhưng đó sẽ là bước 2. Theo StackOverflow, tôi phải tìm hiểu về serializable mà tôi sẽ cất cánh trong một ngày khác. (Tôi tưởng tượng bạn sẽ có thể hình dung từ ví dụ của tôi những gì tôi đang cố gắng làm.)

Trả lời

11

Ok, chúng ta phải làm rõ vài điều. Thứ nhất, nếu bạn muốn để có thể tải và dỡ bỏ dlls để AppDomain khác nhau mà không cần khóa các tập tin iteslf, có thể bạn có thể sử dụng cách tiếp cận như thế này:

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

Bằng cách này, bạn sẽ không được khóa các tập tin, và bạn sẽ có thể sau đó, dỡ miền, biên dịch lại tệp và tải tệp đó bằng phiên bản mới hơn sau đó. Nhưng tôi không chắc chắn 100% nếu điều này sẽ không phá vỡ ứng dụng của bạn.

Và đó là vì điều thứ hai. Nếu bạn sẽ sử dụng phương pháp CreateInstanceAndUnwrap, theo MSDN, bạn phải tải assembly trong cả hai appdomains - cái mà đang gọi, và cái mà bạn đang gọi. Và điều này có thể kết thúc trong một tình huống, khi bạn có hai dll khác nhau được tải trong AppDomains.

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.

Tôi không nhớ ngay bây giờ, nhưng tôi nghĩ rằng hành vi của đối tượng sáng tạo trong cả hai lĩnh vực ứng dụng sẽ khác nhau khi bạn sẽ gọi CreateInstanceAndUnwrap, nhưng tôi không nhớ các chi tiết.

Đối với kiến ​​trúc plugin của bạn, bạn có thể muốn đọc bài đăng trên blog này. About how to handle Dynamic Plugins using the AppDomain Class to Load and Unload Code

EDIT

Tôi quên thế nào điều này AppDomains công trình, tôi có thể giới thiệu một số nhầm lẫn. Tôi đã chuẩn bị ví dụ ngắn gọn về cách kiến ​​trúc 'plugin' có thể hoạt động. Nó khá giống với những gì đã được mô tả trong blog tôi đưa trước đó, và đây là mẫu của tôi sử dụng Shadow Copying. Nếu vì một số lý do bạn không muốn sử dụng nó, nó có thể được thay đổi khá dễ dàng để sử dụng AppDomain.Load(byte[] bytes)

Chúng tôi có 3 hội đồng, đầu tiên là lắp ráp plugin cơ sở, sẽ hoạt động như một proxy và sẽ được tải tất cả AppDomains (trong trường hợp của chúng tôi - trong miền ứng dụng chính và trong miền ứng dụng plugin).

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

Ở đây chúng tôi sẽ có một lắp ráp khác với plugin giả của chúng tôi, được gọi là SamplePlugin.dll và được lưu trữ trong thư mục "Plugin". Nó có PluginBaseLib.dll tham chiếu

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

Và lắp ráp cuối cùng (đơn giản giao diện điều khiển ứng dụng) sẽ tham khảo chỉ PluginBaseLib.dll và

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

}

bóng sao chép có vẻ là rất thuận tiện.

+1

Để tải các cụm không cần khóa Tôi nghĩ tốt hơn là nên sử dụng Sao chép bóng, hãy xem http://msdn.microsoft.com/en-us/library/ms404279.aspx. – Maarten

+0

Tôi tò mò về ví dụ của bạn, một lần nữa có thể là do thiếu kiến ​​thức cơ bản ... ở đâu "áp dụng" mới 'loadAssembly' vào' apd' AppDomain thay vì cấp cao nhất mặc định? Nó chỉ được ngụ ý theo thứ tự, như trong bạn đã tạo ra một AppDomain, sau đó tất cả mọi thứ bên dưới nó là một phần của nó cho đến khi 'Unload()'? Ngoài ra, bài viết tuyệt vời, một chút trên đầu tôi mặc dù. Tôi đọc một phần của nó đã được tách ra và đưa vào một trang web quảng cáo nhưng nó là tốt đẹp để có toàn bộ bài viết. Tôi sẽ cố gắng thực sự học hỏi từ nó thông qua quá trình của ngày hôm nay. Loại mong muốn nó có một tập tin nguồn chức năng. – Adam

+0

Tôi không chắc chắn nếu tôi hiểu câu hỏi của bạn, nhưng tôi sẽ cố gắng để chơi xung quanh với một số ví dụ và ý kiến ​​từ ứng dụng cũ của tôi sau ngày hôm nay. –