2009-03-18 12 views
96

Voglio caricare su un nuovo AppDomain un assembly che ha un albero di riferimenti complessi (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole .dll)Come caricare un assieme su AppDomain con tutti i riferimenti in modo ricorsivo?

Per quanto ho capito, quando un assembly viene caricato su AppDomain, i suoi riferimenti non verrebbero caricati automaticamente e devo caricarli manualmente. Così, quando lo faccio:

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

e ottenuto FileNotFoundException:

Impossibile caricare il file o l'assembly 'MyDll, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null' o uno dei suoi dipendenze. Il sistema non trova il file specificato.

Penso che la parte chiave sia una delle sue dipendenze.

Ok, devo fare prima domain.Load(AssemblyName.GetAssemblyName(path));

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

Ma got FileNotFoundException ancora una volta, su un altro di montaggio (riferimento).

Come caricare tutti i riferimenti in modo ricorsivo?

Devo creare un albero dei riferimenti prima di caricare l'assemblaggio della radice? Come ottenere i riferimenti di un assembly senza caricarlo?

+1

Ho caricato assiemi come questo molte volte prima, non ho mai dovuto caricare manualmente tutti i suoi riferimenti. Non sono sicuro che la premessa di questa domanda sia corretta. – Mick

risposta

55

È necessario richiamare CreateInstanceAndUnwrap prima il tuo proxy obj ect verrà eseguito nel dominio dell'applicazione esterna.

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

Inoltre, notare che se si utilizza LoadFrom è probabile ottenere un'eccezione FileNotFound perché il risolutore Assemblea tenterà di trovare il gruppo che si desidera caricare nel GAC o cartella bin dell'applicazione corrente.Utilizzare LoadFile per caricare invece un file di assembly arbitrario, ma si noti che se si esegue questa operazione è necessario caricare autonomamente qualsiasi dipendenza.

+14

Controlla il codice che ho scritto per risolvere questo problema: https://github.com/jduv/AppDomainToolkit. In particolare, guarda il metodo LoadAssemblyWithReferences in questa classe: https://github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolkit/AppDomainContext.cs – Jduv

+1

Ho scoperto che sebbene funzioni * la maggior parte * del tempo, in * alcuni casi * in realtà è ancora necessario associare un gestore all'evento 'AppDomain.CurrentDomain.AssemblyResolve' come descritto in [questa risposta MSDN] (http://social.msdn.microsoft.com/Forums/en-US/0a18ed66- 6995-4e7c-Baab-61c1e528fb82/why-fa-appdomaincreateinstanceandunwrap-lavoro-e-appdomaincreateinstancefromunwrap). Nel mio caso, stavo cercando di collegarmi alla distribuzione SpecRun in esecuzione con MSTest, ma penso che si applichi a molte situazioni in cui il codice potrebbe non essere eseguito dalle estensioni AppDomain "primarie" VS, MSTest, ecc. – Aaronaught

+0

Ah interessante. Approfondirò e vedrò se riesco a renderlo leggermente più semplice da utilizzare tramite ADT. Spiacente che il codice sia stato un po 'morto da un po' di tempo - tutti noi abbiamo un lavoro diurno :). – Jduv

5

È necessario gestire gli eventi AppDomain.AssemblyResolve o AppDomain.ReflectionOnlyAssemblyResolve (a seconda del carico che si sta eseguendo) nel caso in cui l'assembly di riferimento non si trovi nel GAC o nel percorso di verifica del CLR.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

+0

Quindi devo indicare manualmente il montaggio richiesto? Anche nel nuovo AppBase AppDomain? C'è un modo per non farlo? – abatishchev

10

Sul nuovo AppDomain, provare a impostare un gestore di eventi AssemblyResolve. Questo evento viene chiamato quando manca una dipendenza.

+0

Non è così. In realtà, ricevi un'eccezione sulla linea in cui stai registrando questo evento sul nuovo AppDomain. Devi registrare questo evento sull'AppDomain corrente. – user1004959

+0

Lo fa se la classe viene ereditata da MarshalByRefObject. Non funziona se la classe è contrassegnata solo con l'attributo [Serializable]. – user2126375

14

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

C# versione:

Creare una classe moderatore e ereditano da MarshalByRefObject:

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

chiamata dal sito client

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

In che modo questa soluzione viene inserita nel contesto della creazione di un nuovo AppDomain, qualcuno può spiegare? –

+20

Risolve il problema ma COME? –

+2

Un 'MarshalByRefObject' può essere passato attorno ad appdomain. Quindi suppongo che 'Assembly.LoadFrom' tenti di caricare l'assembly in un nuovo appdomain, ciò che è possibile solo se l'oggetto chiamante può essere passato tra quelle appdomain. Questo è anche chiamato remoting come descritto qui: http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject%28v=vs.80%29.aspx –

3

La chiave è l'evento AssemblyResolve generato dall'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

Una volta superato l'istanza di assemblaggio di nuovo al dominio del chiamante, il dominio del chiamante cercherà di caricarlo! Questo è il motivo per cui ottieni l'eccezione. Questo accade nella vostra ultima riga di codice:

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

Quindi, qualsiasi cosa si vuole fare con il montaggio, dovrebbe essere fatto in una classe proxy - una classe che erediti MarshalByRefObject.

Considerare che il dominio del chiamante e il nuovo dominio creato devono entrambi avere accesso all'assembly della classe del proxy. Se il problema non è troppo complicato, si consiglia di lasciare invariata la cartella ApplicationBase, quindi sarà uguale alla cartella del dominio chiamante (il nuovo dominio caricherà solo gli assembly necessari).

Nel codice semplice:

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

Se si ha bisogno di caricare le assemblee da una cartella che è diversa da quella cartella di dominio applicazione corrente si, creare il nuovo dominio applicazione con la cartella DLL specifiche del percorso di ricerca.

Ad esempio, la linea di creazione del dominio applicazione dal codice di cui sopra dovrebbe essere sostituito con:

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

In questo modo, tutte le DLL saranno automaically essere risolti da dllsSearchPath.

+0

Perché devo caricare l'assembly usando una classe proxy? Qual è la differenza rispetto al caricamento usando Assembly.LoadFrom (stringa) .Sono interessato ai dettagli tecnici, dal punto di vista del CLR. Sarei molto grato se potessi fornire una risposta –

+0

Si utilizza la classe proxy per evitare che il nuovo assembly venga caricato nel dominio del chiamante.Se si utilizzerà Assembly.LoadFrom (stringa), il dominio del chiamante proverà a caricare i nuovi riferimenti dell'assembly e non li troverà perché non è così cercare gli assembly in "[AsmPath]". (Https://msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.aspx) – Nir

3

Mi ci è voluto un po 'per capire la risposta di @user1996230 quindi ho deciso di fornire un esempio più esplicito. Nell'esempio seguente creo un proxy per un oggetto caricato in un altro AppDomain e chiamo un metodo su quell'oggetto da un altro dominio.

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