2009-03-18 15 views
96

Chcę załadować do nowego zestawu AppDomain, który ma złożone drzewo odniesień (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole .dll)Jak załadować zespół do aplikacji AppDomain z rekurencyjnie wszystkimi odwołaniami?

Z tego co zrozumiałem, gdy zespół jest ładowany do AppDomain, jego referencje nie zostaną załadowane automatycznie i muszę je załadować ręcznie. Więc kiedy zrobić:

string dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory 
string path = System.IO.Path.Combine(dir, "MyDll.dll"); 

AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation; 
setup.ApplicationBase = dir; 
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup); 

domain.Load(AssemblyName.GetAssemblyName(path)); 

i dostał FileNotFoundException:

Nie można załadować pliku lub zestawu 'MyDll, Version = 1.0.0.0, Culture = neutral, TokenKluczaPublicznego = null' lub jeden z jego zależności. System nie może odnaleźć określonego pliku.

Myślę, że kluczową częścią jest jedna z jego zależności.

Ok, zrobię następny przed domain.Load(AssemblyName.GetAssemblyName(path));

foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies()) 
{ 
    domain.Load(refAsmName); 
} 

Ale dostał FileNotFoundException znowu na innym (odwołanie) montażu.

Jak ładować rekurencyjnie wszystkie odwołania?

Czy przed załadowaniem zespołu głównego trzeba utworzyć drzewo referencyjne? Jak uzyskać referencje zespołu bez jego ładowania?

+1

Załadowałem już takie zestawy wiele razy wcześniej, nigdy nie musiałem ręcznie ładować wszystkich jego odniesień. Nie jestem pewien, czy przesłanka tego pytania jest prawidłowa. – Mick

Odpowiedz

55

Trzeba powołać CreateInstanceAndUnwrap przed twój serwer proxy ect zostanie wykonany w domenie aplikacji zagranicznej.

class Program 
{ 
    static void Main(string[] args) 
    { 
     AppDomainSetup domaininfo = new AppDomainSetup(); 
     domaininfo.ApplicationBase = System.Environment.CurrentDirectory; 
     Evidence adevidence = AppDomain.CurrentDomain.Evidence; 
     AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo); 

     Type type = typeof(Proxy); 
     var value = (Proxy)domain.CreateInstanceAndUnwrap(
      type.Assembly.FullName, 
      type.FullName); 

     var assembly = value.GetAssembly(args[0]); 
     // AppDomain.Unload(domain); 
    } 
} 

public class Proxy : MarshalByRefObject 
{ 
    public Assembly GetAssembly(string assemblyPath) 
    { 
     try 
     { 
      return Assembly.LoadFile(assemblyPath); 
     } 
     catch (Exception) 
     { 
      return null; 
      // throw new InvalidOperationException(ex); 
     } 
    } 
} 

Należy również pamiętać, że jeśli używasz LoadFrom będziesz prawdopodobnie uzyskać FileNotFound wyjątek ponieważ rezolwer Zgromadzenie będzie próbował znaleźć zespół jesteś wkładanego GAC lub folderu bin bieżącej aplikacji.Zamiast tego należy załadować dowolny plik zespołu, ale pamiętaj, że jeśli to zrobisz, musisz samodzielnie załadować wszystkie zależności.

+14

Sprawdź kod, który napisałem, aby rozwiązać ten problem: https://github.com/jduv/AppDomainToolkit. W szczególności spójrz na metodę LoadAssemblyWithReferences w tej klasie: https://github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolkit/AppDomainContext.cs – Jduv

+1

Znalazłem, że chociaż to działa * najczęściej * w tym czasie, * niektóre * przypadki nadal wymagają dołączenia programu obsługi do zdarzenia 'AppDomain.CurrentDomain.AssemblyResolve' zgodnie z opisem w [tej odpowiedzi MSDN] (http://social.msdn.microsoft.com/Forums/en-US/0a18ed66- 6995-4e7c-baab-61c1e528fb82/why-does-appdomaincreateinstanceandunwrap-work-and-appdomaincreateinstancefromunwrap). W moim przypadku próbowałem podłączyć się do wdrożenia SpecRun działającego pod MSTest, ale myślę, że dotyczy to wielu sytuacji, w których twój kod może nie działać z "podstawowej" AppDomain - rozszerzeń VS, MSTest, itp. – Aaronaught

+0

Ah interesujące. Przyjrzę się temu i zobaczę, czy dzięki ADT mogę nieco ułatwić to zadanie. Przykro nam, że kod od pewnego czasu jest nieco martwy - wszyscy mamy prace na dzień :). – Jduv

5

Musisz obsługiwać zdarzenia AppDomain.AssemblyResolve lub AppDomain.ReflectionOnlyAssemblyResolve (w zależności od tego, jaki ładunek robisz) w przypadku, gdy odwołanie zespołu nie znajduje się w GAC lub na ścieżce sondowania CLR.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

+0

Muszę więc ręcznie zaznaczyć żądany zestaw? Nawet w nowej AppBoan AppDomain? Czy istnieje sposób, aby tego nie robić? – abatishchev

10

Na nowym AppDomain, spróbuj ustawić moduł obsługi AssemblyResolve zdarzeń. To zdarzenie zostanie wywołane, gdy brakuje zależności.

+0

Nie ma. W rzeczywistości otrzymujesz wyjątek od linii, w której rejestrujesz to wydarzenie w nowej AppDomain. Musisz zarejestrować to zdarzenie w bieżącym AppDomain. – user1004959

+0

Dzieje się tak, jeśli klasa jest dziedziczona z MarshalByRefObject. Nie dzieje się tak, jeśli klasa jest oznaczona tylko atrybutem [Serializable]. – user2126375

14

http://support.microsoft.com/kb/837908/en-us

C# wersja:

Utwórz klasę moderator i dziedziczą ją z MarshalByRefObject:

class ProxyDomain : MarshalByRefObject 
{ 
    public Assembly GetAssembly(string assemblyPath) 
    { 
     try 
     { 
      return Assembly.LoadFrom(assemblyPath); 
     } 
     catch (Exception ex) 
     { 
      throw new InvalidOperationException(ex.Message); 
     } 
    } 
} 

telefon od strony klienta

ProxyDomain pd = new ProxyDomain(); 
Assembly assembly = pd.GetAssembly(assemblyFilePath); 
+4

W jaki sposób to rozwiązanie zostanie umieszczone w kontekście tworzenia nowej AppDomain, czy ktoś może wyjaśnić? –

+20

Rozwiązuje problem, ale JAK? –

+2

'MarshalByRefObject' może być przekazywany wokół appdomains. Tak więc przypuszczam, że 'Assembly.LoadFrom' próbuje załadować złożenie w nowej domenie aplikacji, co jest tylko możliwe, jeśli obiekt wywołujący może zostać przekazany między tymi aplikacjami. Jest to również nazywane działaniem zdalnym, jak opisano tutaj: http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject%28v=vs.80%29.aspx –

3

Klucz jest zdarzeniem AssemblyResolve wywołanym przez AppDomain.

[STAThread] 
static void Main(string[] args) 
{ 
    fileDialog.ShowDialog(); 
    string fileName = fileDialog.FileName; 
    if (string.IsNullOrEmpty(fileName) == false) 
    { 
     AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; 
     if (Directory.Exists(@"c:\Provisioning\") == false) 
      Directory.CreateDirectory(@"c:\Provisioning\"); 

     assemblyDirectory = Path.GetDirectoryName(fileName); 
     Assembly loadedAssembly = Assembly.LoadFile(fileName); 

     List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>(); 

     foreach (var type in assemblyTypes) 
     { 
      if (type.IsInterface == false) 
      { 
       StreamWriter jsonFile = File.CreateText(string.Format(@"c:\Provisioning\{0}.json", type.Name)); 
       JavaScriptSerializer serializer = new JavaScriptSerializer(); 
       jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type))); 
       jsonFile.Close(); 
      } 
     } 
    } 
} 

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 
{ 
    string[] tokens = args.Name.Split(",".ToCharArray()); 
    System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name); 
    return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"})); 
} 
9

Po przejściu instancji zespołu z powrotem do domeny dzwoniącego, domena dzwoniącego spróbuje ją załadować! Właśnie dlatego otrzymujesz wyjątek. Dzieje się tak w ostatniej linii kodu:

domain.Load(AssemblyName.GetAssemblyName(path)); 

Zatem, co chcesz zrobić z montażem, powinny być wykonane w klasie proxy - klasy, które dziedziczą MarshalByRefObject.

Weź pod uwagę, że zarówno domena dzwoniącego, jak i nowo utworzona domena powinny mieć dostęp do zespołu klasy proxy. Jeśli problem nie jest zbyt skomplikowany, rozważ pozostawienie folderu ApplicationBase bez zmian, aby był taki sam jak folder domeny wywołującej (nowa domena będzie ładować tylko wymagane zestawy).

W prostego kodu:

public void DoStuffInOtherDomain() 
{ 
    const string assemblyPath = @"[AsmPath]"; 
    var newDomain = AppDomain.CreateDomain("newDomain"); 
    var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName); 

    asmLoaderProxy.GetAssembly(assemblyPath); 
} 

class ProxyDomain : MarshalByRefObject 
{ 
    public void GetAssembly(string AssemblyPath) 
    { 
     try 
     { 
      Assembly.LoadFrom(AssemblyPath); 
      //If you want to do anything further to that assembly, you need to do it here. 
     } 
     catch (Exception ex) 
     { 
      throw new InvalidOperationException(ex.Message, ex); 
     } 
    } 
} 

Jeśli trzeba załadować zespoły z folderu, który jest inny niż ty bieżącym folderze app domeny utworzyć nową domenę aplikacji z folderu szczególne DLL wyszukiwania ścieżki.

Na przykład, linia tworzenie aplikacji domeny z powyższego kodu należy zastąpić:

var dllsSearchPath = @"[dlls search path for new app domain]"; 
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true); 

ten sposób wszystkie pliki DLL będą automaically być rozwiązane z dllsSearchPath.

+0

Dlaczego czy muszę załadować zespół przy użyciu klasy proxy? Jaka jest różnica w porównaniu do ładowania go za pomocą Assembly.LoadFrom (ciąg).Zainteresowane są szczegóły techniczne, z punktu widzenia CLR. Byłbym bardzo wdzięczny, gdyby można udziel odpowiedzi: –

+0

Używasz klasy proxy, aby uniknąć ładowania nowego zestawu do domeny wywołującej .Jeśli użyjesz Assembly.LoadFrom (ciąg), domena wywołująca spróbuje wczytać nowe odwołania do zestawu i nie znajdą ich, ponieważ tak nie jest szukaj złożeń w "[AsmPath]". (https://msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.aspx) – Nir

3

Zajęło mi trochę czasu, aby zrozumieć odpowiedź @ user1996230, więc postanowiłem podać bardziej wyraźny przykład. W poniższym przykładzie wykonuję proxy dla obiektu załadowanego w innym AppDomain i wywołuje metodę na tym obiekcie z innej domeny.

class ProxyObject : MarshalByRefObject 
{ 
    private Type _type; 
    private Object _object; 

    public void InstantiateObject(string AssemblyPath, string typeName, object[] args) 
    { 
     assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory 
     _type = assembly.GetType(typeName); 
     _object = Activator.CreateInstance(_type, args); ; 
    } 

    public void InvokeMethod(string methodName, object[] args) 
    { 
     var methodinfo = _type.GetMethod(methodName); 
     methodinfo.Invoke(_object, args); 
    } 
} 

static void Main(string[] args) 
{ 
    AppDomainSetup setup = new AppDomainSetup(); 
    setup.ApplicationBase = @"SomePathWithDLLs"; 
    AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup); 
    ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject"); 
    proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs}); 
    proxyObject.InvokeMethod("foo",new object[] { "bar"}); 
}