2017-11-10 87 views
0

我在.NET中搜索了很多關於在運行時重裝程序集的信息。我能找到的唯一方法是使用另一個AppDomain。但是這使事情變得非常複雜。在我的情況下幾乎是不可能的,因爲將在運行時加載的程序集中的類不會從MarshalByRefObject繼承。我看過Unity遊戲引擎。編輯器在運行時構建組件,並使用編譯後的程序集。這怎麼可能?.net在運行時重裝程序集

回答

2

一般而言,您不能在同一個AppDomain中裝配重新加載程序集。你可以動態地創建一個並加載它(它會永遠駐留在你的AppDomain中),你可以加載另一個幾乎不是完全相同的程序集副本,但是一旦程序集在AppDomain中,它就會被卡住。

想象一下,庫程序集定義了SomeType並且您的客戶端代碼剛剛創建了一個實例。如果卸載庫,該實例應該發生什麼?如果該庫位於另一個AppDomain中,則客戶端將使用具有定義良好的(在MarshalByRefObject中)行爲的代理(與域卸載並引發異常foreverafter)。支持任意類型的卸載會使運行時非常複雜,不可預知或者兩者兼而有之。

至於Unity,請參閱this discussion。 Quote:

一個「組裝reaload」聽起來像某種快速更新檢查,但實際上整個腳本環境重新加載。這將破壞管理土地上的一切。通過使用它的序列化系統,Unity可以從中恢復。 Unity在重裝之前序列化整個場景,然後重新創建所有場景並反序列化場景。當然,只有可以序列化的東西才能「存活」這個過程。

+0

好。我已經完成了你在第一段中所說的話。我使用一個字節數組加載程序集。我可以使用相同的信息加載另一個程序集。但問題是另一個程序集仍在內存中。需要多少內存?如果不可能,團結如何做到這一點?它是否將舊程序集保存在內存中? – KooKoo

+0

請參閱上面 - Unity不會這樣做,它只是假裝:) – archnae

+0

[您可以從一個單獨的域加載/卸載程序集](https://stackoverflow.com/questions/658498/how-to-load-an - 裝配到應用程序域與 - 所有引用遞歸)。 –

3

我已經完成了這個使用MEF。我不確定它是否適合您,但它運作良好。但是即使是MEF,它也有些複雜。

在我的情況下,我從一個特定的文件夾加載所有的DLL。

這些是設置類。

public static class SandBox 
{ 
    public static AppDomain CreateSandboxDomain(string name, string path, SecurityZone zone) 
    { 
     string fullDirectory = Path.GetFullPath(path); 
     string cachePath = Path.Combine(fullDirectory, "ShadowCopyCache"); 
     string pluginPath = Path.Combine(fullDirectory, "Plugins"); 

     if (!Directory.Exists(cachePath)) 
      Directory.CreateDirectory(cachePath); 

     if (!Directory.Exists(pluginPath)) 
      Directory.CreateDirectory(pluginPath); 


     AppDomainSetup setup = new AppDomainSetup 
     { 
      ApplicationBase = fullDirectory, 
      CachePath = cachePath, 
      ShadowCopyDirectories = pluginPath, 
      ShadowCopyFiles = "true" 
     }; 

     Evidence evidence = new Evidence(); 
     evidence.AddHostEvidence(new Zone(zone)); 
     PermissionSet permissions = SecurityManager.GetStandardSandbox(evidence); 

     return AppDomain.CreateDomain(name, evidence, setup, permissions); 
    } 
} 

public class Runner : MarshalByRefObject 
{ 
    private CompositionContainer _container; 
    private DirectoryCatalog _directoryCatalog; 
    private readonly AggregateCatalog _catalog = new AggregateCatalog(); 


    public bool CanExport<T>() 
    { 
     T result = _container.GetExportedValueOrDefault<T>(); 
     return result != null; 
    } 

    public void Recompose() 
    { 
     _directoryCatalog.Refresh(); 
     _container.ComposeParts(_directoryCatalog.Parts); 
    } 

    public void RunAction(Action codeToExecute) 
    { 
     MefBase.Container = _container; 
     codeToExecute.Invoke(); 
    } 

    public void CreateMefContainer() 
    { 
     RegistrationBuilder regBuilder = new RegistrationBuilder(); 
     string pluginPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; 
     _directoryCatalog = new DirectoryCatalog(pluginPath, regBuilder); 
     _catalog.Catalogs.Add(_directoryCatalog); 

     _container = new CompositionContainer(_catalog, true); 
     _container.ComposeExportedValue(_container); 
     Console.WriteLine("exports in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName); 
    } 
} 

這裏是實際的代碼。

AppDomain domain = SandBox.CreateSandboxDomain($"Sandbox Domain_{currentCount}", directoryName, SecurityZone.MyComputer); 
      foreach (FileInfo dll in currentDlls) 
      { 
       string path = Path.GetFullPath(Path.Combine(directoryName, dll.Name)); 
       if (!File.Exists(path)) 
        File.Copy(dll.FullName, Path.Combine(directoryName, dll.Name), true); 

       domain.Load(typeof(Runner).Assembly.FullName); 
      } 

您可以通過這樣做來取回域名。

Runner runner = (Runner) domain.CreateInstanceAndUnwrap(typeof(Runner).Assembly.FullName, typeof(Runner).FullName); 
runner.CreateMefContainer(); // or runner.Recompose(); 

您需要像這樣調用您的代碼。

runner.RunAction(() => 
       { 
        IRepository export = MefBase.Resolve<IRepository>(); 
        export?.Get("123"); 
        Console.WriteLine("Executing {0}", export); 
       }); 
+0

不知道這是否會在Unity中工作,但看起來很有前途! – PassetCronUs