it-swarm.com.de

Wie lädt man eine Assembly in AppDomain mit allen Referenzen rekursiv?

Ich möchte eine neue AppDomain Assembly laden, die einen komplexen Verweisbaum enthält (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll )

Soweit ich es verstanden habe, werden beim Laden einer Assembly in AppDomain die Referenzen nicht automatisch geladen, und ich muss sie manuell laden. __ Wenn ich:

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

und bekam FileNotFoundException

Datei oder Assembly 'MyDll, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null' oder eine ihrer Abhängigkeiten konnte nicht geladen werden. Die angegebene Datei wurde vom System nicht gefunden.

Ich denke, der Schlüsselteil ist eine seiner Abhängigkeiten .

Ok, mache ich weiter vor domain.Load(AssemblyName.GetAssemblyName(path));

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

Bekam aber wieder FileNotFoundException auf einer anderen (referenzierten) Assembly.

Wie kann man alle Referenzen rekursiv laden?

Muss ich vor dem Laden der Root-Assembly eine Referenzstruktur erstellen? Wie bekomme ich die Referenzen einer Assembly, ohne sie zu laden?

102
abatishchev

Sie müssen CreateInstanceAndUnwrap aufrufen, bevor Ihr Proxy-Objekt in der fremden Anwendungsdomäne ausgeführt wird.

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

Beachten Sie außerdem, dass Sie bei Verwendung von LoadFrom wahrscheinlich eine FileNotFound-Ausnahme erhalten, da der Assembly-Resolver die Assembly, die Sie laden, im GAC oder im bin-Ordner der aktuellen Anwendung finden wird. Verwenden Sie LoadFile, um stattdessen eine beliebige Assembly-Datei zu laden. Beachten Sie jedoch, dass Sie dabei Abhängigkeiten selbst laden müssen.

61
Jduv

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

C # -Version:

Erstellen Sie eine Moderatorklasse und erben Sie sie von MarshalByRefObject:

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

anruf vom Client-Standort

ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
16
rockvista

Versuchen Sie, in Ihrer neuen AppDomain einen AssemblyResolve - Eventhandler festzulegen. Dieses Ereignis wird aufgerufen, wenn eine Abhängigkeit fehlt.

11
David

Wenn Sie die Assembly-Instanz zurück an die Anruferdomäne übergeben haben, versucht die Anruferdomäne, sie zu laden! Deshalb erhalten Sie die Ausnahme. Dies geschieht in Ihrer letzten Codezeile:

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

Was auch immer Sie mit der Assembly machen möchten, sollte also in einer Proxy-Klasse ausgeführt werden - einer Klasse, die MarshalByRefObject erbt.

Berücksichtigen Sie dabei, dass sowohl die Anruferdomäne als auch die neu erstellte Domäne Zugriff auf die Proxy-Klasse Assembly haben sollten. Wenn Ihr Problem nicht zu kompliziert ist, sollten Sie den ApplicationBase-Ordner unverändert lassen, sodass er mit dem Anrufer-Domänenordner identisch ist (die neue Domäne lädt nur die benötigten Assemblies).

In einfachem Code:

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

Wenn Sie die Assemblys aus einem Ordner laden müssen, der sich vom aktuellen App-Domänenordner unterscheidet, erstellen Sie die neue App-Domäne mit einem bestimmten DLL-Suchpfadordner.

Die Zeile zum Erstellen der App-Domain aus dem obigen Code sollte beispielsweise durch Folgendes ersetzt werden:

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

Auf diese Weise werden alle DLLs automatisch von dllsSearchPath aufgelöst.

10
Nir

Sie müssen die Ereignisse AppDomain.AssemblyResolve oder AppDomain.ReflectionOnlyAssemblyResolve behandeln (abhängig von der Last, die Sie ausführen), falls die referenzierte Assembly nicht im GAC oder im Prüfpfad der CLR ist.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

5
Dustin Campbell

Ich brauchte eine Weile, um die Antwort von @ user1996230 zu verstehen, also entschied ich mich, ein genaueres Beispiel anzuführen. Im folgenden Beispiel mache ich einen Proxy für ein Objekt, das in einer anderen AppDomain geladen ist, und rufe eine Methode für dieses Objekt aus einer anderen Domäne auf.

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

Der Schlüssel ist das AssemblyResolve-Ereignis, das von der AppDomain ausgelöst wird.

[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"}));
}
3
Leslie Marshall

Ich musste dies mehrmals tun und habe viele verschiedene Lösungen erforscht.

Die Lösung, die ich am elegantesten und einfachsten finde, kann als solche implementiert werden.

1. Erstellen Sie ein Projekt, mit dem Sie eine einfache Schnittstelle erstellen können

Die Benutzeroberfläche enthält Unterschriften aller Mitglieder, die Sie anrufen möchten.  

public interface IExampleProxy
{
    string HelloWorld( string name );
}

Es ist wichtig, dass dieses Projekt sauber und hell bleibt. Es ist ein Projekt, auf das beide AppDomains verweisen können und es uns ermöglichen wird, nicht auf das Assembly zu verweisen, das wir in einer separaten Domäne von unserem Kunden Assembly laden möchten.

2. Erstellen Sie nun ein Projekt, das den Code enthält, den Sie in separatem AppDomain laden möchten.

Dieses Projekt wird wie das Client-Projekt auf das Proxy-Projekt verweisen und Sie werden die Schnittstelle implementieren.  

public interface Example : MarshalByRefObject, IExampleProxy
{
    public string HelloWorld( string name )
    {
        return $"Hello '{ name }'";
    }
}

3. Laden Sie im Client-Projekt den Code in ein anderes AppDomain.

Also erstellen wir jetzt eine neue AppDomain. Kann den Basisort für Assemblyreferenzen angeben. Bei der Prüfung werden abhängige Assemblys in GAC und im aktuellen Verzeichnis sowie in der AppDomain-Basislok gesucht.

// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
    ApplicationBase = System.Environment.CurrentDirectory
};

Evidence adevidence = AppDomain.CurrentDomain.Evidence;

AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);

// Assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";

// Optional - get a reflection only Assembly type reference
var @type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName ); 

// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );

// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( @type.Assembly.Name, @type.Name );    

// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );

// unload the `AppDomain`
AppDomain.Unload( exampleDomain );

Wenn Sie möchten, gibt es unzählige Möglichkeiten, eine Assembly zu laden. Sie können diese Lösung auf andere Weise verwenden. Wenn Sie den Assembly-qualifizierten Namen haben, benutze ich gerne CreateInstanceAndUnwrap, da er die Assembly-Bytes lädt und dann Ihren Typ für Sie instanziiert und ein object zurückgibt, das Sie einfach in Ihren Proxy-Typ oder in stark typisierten Code umsetzen können Sie können die Laufzeit in dynamischer Sprache verwenden und das zurückgegebene Objekt einer Variablen vom Typ dynamic zuweisen, die dann direkt Mitglieder aufrufen.  

Hier hast du es.

Dies ermöglicht das Laden einer Assembly, auf die Ihr Client-Projekt keinen Verweis enthält, in einem separaten AppDomain-Element und das Aufrufen von Mitgliedern vom Client.

Zum Testen verwende ich gerne das Modulfenster in Visual Studio. Es zeigt Ihnen Ihre Client-Assembly-Domäne und alle in dieser Domäne geladenen Module sowie Ihre neue App-Domäne und welche Assemblys oder Module in dieser Domäne geladen werden.

Der Schlüssel ist, entweder sicherzustellen, dass der Code entweder MarshalByRefObject ableitet oder serialisierbar ist.

`MarshalByRefObject ermöglicht es Ihnen, die Lebensdauer der Domäne zu konfigurieren. Beispiel: Sie möchten, dass die Domäne gelöscht wird, wenn der Proxy nicht innerhalb von 20 Minuten aufgerufen wurde.

Ich hoffe das hilft.

0
SimperT